//! Integration test for the *real* on-disk dataset loader ([`MmFiDataset`]). //! //! The deterministic training proof (`verify-training`) runs on the in-memory //! `SyntheticCsiDataset`, which never touches `.npy` files — by design (a //! reproducible source is the whole point of the proof). This test covers the //! path the proof bypasses: it writes synthetic CSI to `.npy` files in the //! directory layout [`MmFiDataset::discover`] expects, loads it back, and //! checks the resulting [`CsiSample`] — including the subcarrier-interpolation //! branch. use ndarray::{Array3, Array4}; use ndarray_npy::write_npy; use tempfile::TempDir; use wifi_densepose_train::dataset::{CsiDataset, MmFiDataset}; /// Write one deterministic `S01/A01` recording (no RNG) under `root`, with /// `n_t` frames, `[n_tx, n_rx]` antennas and `n_sc` subcarriers. fn write_recording(root: &std::path::Path, n_t: usize, n_tx: usize, n_rx: usize, n_sc: usize) { let dir = root.join("S01").join("A01"); std::fs::create_dir_all(&dir).expect("create S01/A01"); let amplitude = Array4::::from_shape_fn((n_t, n_tx, n_rx, n_sc), |(t, tx, rx, sc)| { 0.5 + 0.4 * (((t * 7 + tx * 3 + rx * 2 + sc) % 17) as f32 / 17.0) }); let phase = Array4::::from_shape_fn((n_t, n_tx, n_rx, n_sc), |(t, tx, rx, sc)| { ((t + tx + rx + sc) as f32 * 0.05).sin() }); let mut kp = Array3::::zeros((n_t, 17, 3)); for t in 0..n_t { for j in 0..17 { kp[[t, j, 0]] = ((j as f32 + 1.0) / 18.0).clamp(0.0, 1.0); // x kp[[t, j, 1]] = (((j * 3 + t) % 18) as f32 / 18.0).clamp(0.0, 1.0); // y kp[[t, j, 2]] = 2.0; // COCO "visible" } } write_npy(dir.join("wifi_csi.npy"), &litude).expect("write wifi_csi.npy"); write_npy(dir.join("wifi_csi_phase.npy"), &phase).expect("write wifi_csi_phase.npy"); write_npy(dir.join("gt_keypoints.npy"), &kp).expect("write gt_keypoints.npy"); } /// Round-trip: write `.npy`, discover, load — no interpolation (native == target). #[test] fn mmfi_loads_real_npy_without_interpolation() { let tmp = TempDir::new().expect("tempdir"); write_recording(tmp.path(), 8, 3, 3, 56); let ds = MmFiDataset::discover(tmp.path(), 8, 56, 17).expect("discover the recording"); assert!( ds.len() >= 1, "must discover at least one sample, got {}", ds.len() ); let sample = ds.get(0).expect("sample 0"); assert_eq!(sample.amplitude.shape(), &[8, 3, 3, 56], "amplitude shape"); assert_eq!(sample.phase.shape(), &[8, 3, 3, 56], "phase shape"); assert_eq!(sample.keypoints.shape(), &[17, 2], "keypoints shape"); assert_eq!( sample.keypoint_visibility.shape(), &[17], "visibility shape" ); assert!( sample.amplitude.iter().all(|v| v.is_finite()), "amplitude must be finite" ); assert!( sample.phase.iter().all(|v| v.is_finite()), "phase must be finite" ); assert!( sample.keypoints.iter().all(|v| v.is_finite()), "keypoints must be finite" ); } /// The loader resamples the subcarrier axis when the requested target differs /// from the dataset's native count. #[test] fn mmfi_resamples_subcarriers_on_load() { let tmp = TempDir::new().expect("tempdir"); write_recording(tmp.path(), 8, 3, 3, 56); // target (28) < native (56) — the loader must interpolate down. let ds = MmFiDataset::discover(tmp.path(), 8, 28, 17).expect("discover"); let sample = ds.get(0).expect("sample 0"); assert_eq!( sample.amplitude.shape(), &[8, 3, 3, 28], "amplitude must be resampled to the requested 28 subcarriers" ); assert_eq!( sample.phase.shape(), &[8, 3, 3, 28], "phase must be resampled too" ); assert!( sample.amplitude.iter().all(|v| v.is_finite()), "resampled amplitude must be finite" ); } /// An empty root directory yields an empty dataset (no panic, no spurious /// samples) — the same loader code path, just with nothing to discover. #[test] fn mmfi_empty_root_is_empty() { let tmp = TempDir::new().expect("tempdir"); let ds = MmFiDataset::discover(tmp.path(), 8, 56, 17).expect("discover empty root"); assert_eq!(ds.len(), 0, "empty root must produce an empty dataset"); }