"""CLI to create neutral S21 preprocess sets for a profile (headless). Mirrors the GUI's "Create neutral sets" button: builds calibration = 1+0j and reference = 0 collections for every combo of the profile and saves them under the profile's radar key, so the data_preprocessor becomes a bit-exact pass-through. Needed for replay profiles whose recorded data is already preprocessed (see run_config_examples/run_config_replay_vna.example.json). Usage: python -m python_app.scripts.create_neutral_preprocess_sets \ --config run_config_examples/run_config_replay_vna.example.json \ [--set-name neutral_replay] [--force] """ from __future__ import annotations import argparse import logging from pathlib import Path from python_app.models.run_config_model import RunConfigModel from python_app.storage.npz_store import NpzStore, radar_key_from_config from python_app.workflows.kamil_adc_neutral_preprocess import ( build_neutral_s21_sets, supports_neutral_preprocess_sets, ) logger = logging.getLogger(__name__) PROJECT_ROOT = Path(__file__).resolve().parents[2] def main() -> int: """Create and save the neutral S21 calibration/reference pair.""" parser = argparse.ArgumentParser(description="Create neutral S21 preprocess sets") parser.add_argument("--config", required=True, type=Path, help="Path to a run_config profile") parser.add_argument("--set-name", default="neutral_replay", help="Name for both saved sets") parser.add_argument( "--force", action="store_true", help="Overwrite the sets if they already exist" ) args = parser.parse_args() logging.basicConfig(level=logging.INFO, format="%(levelname)s %(name)s: %(message)s") config = RunConfigModel.load_from_path(args.config) config.ensure_combos() if not supports_neutral_preprocess_sets(config): raise RuntimeError( "Neutral S21 sets are available only for kamil_adc and librevna_multi" ) radar_key = radar_key_from_config( model_name=config.radar.model, serial=config.radar.serial, sweep_start_hz=config.radar.sweep.start_hz, sweep_stop_hz=config.radar.sweep.stop_hz, sweep_points=config.radar.sweep.points, ifbw_hz=config.radar.sweep.if_bandwidth_hz, power_dbm=config.radar.sweep.power_dbm, extra_serials=config.radar_key_extra_parts() or None, ) store = NpzStore(PROJECT_ROOT / "python_app/data") existing = [ kind for kind in ("s21_calibration", "s21_reference") if args.set_name in store.list_sets(kind, radar_key) ] if existing and not args.force: logger.info( "Sets '%s' already exist for %s (radar_key=%s); nothing to do (use --force to overwrite).", args.set_name, ", ".join(existing), radar_key, ) return 0 calibration, reference = build_neutral_s21_sets(config) store.save_set("s21_calibration", radar_key, args.set_name, calibration) store.save_set("s21_reference", radar_key, args.set_name, reference) logger.info( "Neutral S21 sets saved: set=%s, radar_key=%s, combos=%d, points=%d", args.set_name, radar_key, len(calibration.traces), int(calibration.traces[0].frequency_hz.size), ) return 0 if __name__ == "__main__": raise SystemExit(main())