Add object calibration
This commit is contained in:
@@ -16,6 +16,7 @@ from zipfile import BadZipFile
|
||||
import numpy as np
|
||||
|
||||
from python_app.models.dataset_model import ComboKey, ResultCollection, SweepCollection, TraceData
|
||||
from python_app.models.run_config_model import RunConfigModel
|
||||
from python_app.storage.npz.paths import radar_key_from_config, sanitize_path_component
|
||||
from python_app.storage.npz.serialize import PREPROC_MAGIC, RAW_MAGIC, serialize_trace_collection, validate_trace_collection
|
||||
from python_app.storage.npz.snapshot_numpy import (
|
||||
@@ -300,10 +301,34 @@ class NpzStore(StoreApi):
|
||||
set_dir = self._set_dir(kind, radar_key)
|
||||
if not set_dir.exists():
|
||||
return []
|
||||
if kind == "s21_object_calibration":
|
||||
return sorted(path.name for path in set_dir.iterdir()
|
||||
if not path.name.startswith(".") and (path / "cal_meta.json").is_file())
|
||||
return sorted(path.stem for path in set_dir.glob("*.npz"))
|
||||
|
||||
def save_object_calibration(self, radar_key: str, set_name: str, calibration: dict[str, Any]) -> None:
|
||||
"""Save an object calibration in its shared archive format without overwriting a set."""
|
||||
from python_app.workflows.mimo_calibration import save_calibration
|
||||
|
||||
self._validate_set_name(set_name)
|
||||
destination = self._set_dir("s21_object_calibration", radar_key) / set_name
|
||||
if destination.exists():
|
||||
raise FileExistsError(f"Object calibration already exists: {set_name}")
|
||||
save_calibration(calibration, destination)
|
||||
|
||||
def load_object_calibration(self, radar_key: str, set_name: str) -> dict[str, Any]:
|
||||
"""Load and validate a saved object calibration, retaining all estimation metadata."""
|
||||
from python_app.workflows.mimo_calibration import load_calibration
|
||||
|
||||
self._validate_set_name(set_name)
|
||||
return load_calibration(self._set_dir("s21_object_calibration", radar_key) / set_name)
|
||||
|
||||
def has_combo_coverage(self, kind: str, radar_key: str, set_name: str, combos: list[ComboKey]) -> bool:
|
||||
"""Validate that named set covers all required switch combinations."""
|
||||
if kind == "s21_object_calibration":
|
||||
calibration = self.load_object_calibration(radar_key, set_name)
|
||||
return all(combo.input in calibration["rx_input_pos"] and combo.output in calibration["tx_output_pos"]
|
||||
for combo in combos)
|
||||
collection = self.load_set(kind, radar_key, set_name)
|
||||
existing = {(trace.combo.input, trace.combo.output) for trace in collection.traces}
|
||||
required = {(combo.input, combo.output) for combo in combos}
|
||||
@@ -312,6 +337,36 @@ class NpzStore(StoreApi):
|
||||
def export_set_bundle(self, kind: str, radar_key: str, set_name: str, output_path: Path) -> Path:
|
||||
"""Export named set as binary collection bundle for C++ preprocessing stage."""
|
||||
collection = self.load_set(kind, radar_key, set_name)
|
||||
return self._export_collection_bundle(collection, output_path)
|
||||
|
||||
def export_object_calibration_bundle(
|
||||
self, radar_key: str, set_name: str, output_path: Path, config: RunConfigModel,
|
||||
) -> Path:
|
||||
"""Resample pair multipliers on the through-calibration grids used by the acquisition."""
|
||||
from python_app.workflows.mimo_calibration import calibration_on_grid, validate_for_config
|
||||
|
||||
calibration = self.load_object_calibration(radar_key, set_name)
|
||||
validate_for_config(calibration, config.to_dict())
|
||||
tx_indices = {int(port): index for index, port in enumerate(calibration["tx_output_pos"])}
|
||||
rx_indices = {int(port): index for index, port in enumerate(calibration["rx_input_pos"])}
|
||||
source = self.load_set("s21_calibration", radar_key, config.preprocess.s21.calibration.set_name)
|
||||
by_combo = {trace.combo: trace for trace in source.traces}
|
||||
grids: dict[bytes, np.ndarray] = {}
|
||||
traces: list[TraceData] = []
|
||||
for combo in config.combos:
|
||||
key = ComboKey(input=combo.input, output=combo.output)
|
||||
trace = by_combo[key]
|
||||
frequency = trace.frequency_hz
|
||||
grid_key = frequency.tobytes()
|
||||
if grid_key not in grids:
|
||||
grids[grid_key] = calibration_on_grid(calibration, frequency)
|
||||
correction = grids[grid_key][tx_indices[key.output], rx_indices[key.input]]
|
||||
traces.append(TraceData(key, frequency, np.zeros(frequency.size, np.complex64), correction))
|
||||
return self._export_collection_bundle(SweepCollection(0, 0, traces), output_path)
|
||||
|
||||
def _export_collection_bundle(self, collection: SweepCollection, output_path: Path) -> Path:
|
||||
"""Publish a complete binary trace bundle using one atomic replacement."""
|
||||
validate_trace_collection(collection, require_finite=True)
|
||||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
temporary = self._temporary_file(output_path)
|
||||
try:
|
||||
@@ -319,7 +374,7 @@ class NpzStore(StoreApi):
|
||||
temporary.replace(output_path)
|
||||
finally:
|
||||
temporary.unlink(missing_ok=True)
|
||||
logger.info("Exported set %s/%s/%s bundle to %s", kind, radar_key, set_name, output_path)
|
||||
logger.info("Exported %d traces to bundle %s", len(collection.traces), output_path)
|
||||
return output_path
|
||||
|
||||
def save_runtime_snapshot(
|
||||
|
||||
Reference in New Issue
Block a user