"""Neutral preprocessing-set helpers for Kamil ADC acquisition.""" from __future__ import annotations import logging import time import numpy as np from python_app.hardware_full.kamil_adc import ( KamilAdcProcessingParams, KamilAdcSweepProcessor, ) from python_app.models.dataset_model import ComboKey, SweepCollection, TraceData from python_app.models.run_config_model import ComboModel, RunConfigModel logger = logging.getLogger(__name__) def build_kamil_adc_neutral_s21_sets( config: RunConfigModel, ) -> tuple[SweepCollection, SweepCollection]: """Build neutral S21 calibration/reference collections for the Kamil ADC radar. The calibration uses unit S21 (1+0j) and the reference uses zero S21 across every configured combo, so applying them in the preprocessing pipeline leaves the input S21 unchanged. The frequency axis is the exact acquisition grid (``radar.kamil_adc.band``), so neutral sets line up sample-for-sample with live sweeps. Returns the ``(calibration, reference)`` collections. """ if not config.is_kamil_adc: raise ValueError("Neutral Kamil ADC sets require radar.model='kamil_adc'") combos = list(config.combos) if not combos: combos = RunConfigModel.build_full_combos( config.input_switch.positions, config.output_switch.positions ) if not combos: raise ValueError("Kamil ADC neutral sets require at least one switch combo") # Single source of truth for the axis: the same grid the processor emits. processor = KamilAdcSweepProcessor(KamilAdcProcessingParams.from_kamil_model(config.radar.kamil_adc)) frequency_hz = processor.grid_hz now_ns = time.monotonic_ns() calibration = _neutral_collection( combos=combos, frequency_hz=frequency_hz, s21_value=np.complex64(1.0 + 0.0j), monotonic_ns=now_ns, ) reference = _neutral_collection( combos=combos, frequency_hz=frequency_hz, s21_value=np.complex64(0.0 + 0.0j), monotonic_ns=now_ns, ) logger.info( "Built neutral Kamil ADC S21 sets: combos=%d points=%d", len(combos), int(frequency_hz.size) ) return calibration, reference def _neutral_collection( *, combos: list[ComboModel], frequency_hz: np.ndarray, s21_value: np.complex64, monotonic_ns: int, ) -> SweepCollection: point_count = int(frequency_hz.size) traces = [ TraceData( combo=ComboKey(input=int(combo.input), output=int(combo.output)), frequency_hz=frequency_hz.copy(), s11=np.zeros(point_count, dtype=np.complex64), s21=np.full(point_count, s21_value, dtype=np.complex64), ) for combo in combos ] return SweepCollection( collection_id=1, monotonic_ns=monotonic_ns, traces=traces, capture_start_ns=monotonic_ns, capture_end_ns=monotonic_ns, )