111 lines
4.3 KiB
Rust
111 lines
4.3 KiB
Rust
//! 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::<f32>::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::<f32>::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::<f32>::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");
|
|
}
|