"""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 not supported for this radar configuration") 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") kinds = ("s21_calibration", "s21_reference") existing = [ kind for kind in kinds if args.set_name in store.list_sets(kind, radar_key) ] if existing and not args.force: if len(existing) != len(kinds): missing = ", ".join(kind for kind in kinds if kind not in existing) raise RuntimeError( f"Neutral set pair '{args.set_name}' is incomplete: missing {missing}; " "use --force to replace both sets." ) 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_sets(radar_key, args.set_name, dict(zip(kinds, (calibration, 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())