added GPR
This commit is contained in:
@@ -7,6 +7,21 @@ from dataclasses import dataclass, field
|
||||
import numpy as np
|
||||
|
||||
|
||||
def _empty_f32_array() -> np.ndarray:
|
||||
"""Return empty float32 array used by payload defaults."""
|
||||
return np.array([], dtype=np.float32)
|
||||
|
||||
|
||||
def _empty_c64_array() -> np.ndarray:
|
||||
"""Return empty complex64 array used by payload defaults."""
|
||||
return np.array([], dtype=np.complex64)
|
||||
|
||||
|
||||
def _empty_f32_matrix() -> np.ndarray:
|
||||
"""Return empty 2D float32 matrix used by payload defaults."""
|
||||
return np.zeros((0, 0), dtype=np.float32)
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class ComboKey:
|
||||
"""Switch combination key: input position + output position."""
|
||||
@@ -39,9 +54,13 @@ class ResultPayload:
|
||||
|
||||
processing_name: str
|
||||
kind: int
|
||||
frequency_hz: np.ndarray
|
||||
trace: np.ndarray
|
||||
frequency_hz: np.ndarray = field(default_factory=_empty_f32_array)
|
||||
trace: np.ndarray = field(default_factory=_empty_c64_array)
|
||||
scalar_value: float = 0.0
|
||||
image_x_axis: np.ndarray = field(default_factory=_empty_f32_array)
|
||||
image_y_axis: np.ndarray = field(default_factory=_empty_f32_array)
|
||||
image: np.ndarray = field(default_factory=_empty_f32_matrix)
|
||||
table: np.ndarray = field(default_factory=_empty_f32_matrix)
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
@@ -58,4 +77,5 @@ class ResultCollection:
|
||||
|
||||
collection_id: int
|
||||
monotonic_ns: int
|
||||
collection_payloads: list[ResultPayload] = field(default_factory=list)
|
||||
blocks: list[ResultBlock] = field(default_factory=list)
|
||||
|
||||
@@ -4,8 +4,13 @@ from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from python_app.models.run_config_schema import ComboModel, RunConfigModel
|
||||
from python_app.models.run_config_validation import load_ring_payload, load_switch_payload
|
||||
from python_app.models.run_config_schema import (
|
||||
ComboModel,
|
||||
GprRxGeometryModel,
|
||||
GprTxGeometryModel,
|
||||
RunConfigModel,
|
||||
)
|
||||
from python_app.models.run_config_validation import load_ring_payload, load_switch_payload, validate_gpr_model
|
||||
|
||||
|
||||
def _as_dict(value: Any, context: str) -> dict[str, Any]:
|
||||
@@ -29,6 +34,7 @@ def run_config_from_dict(payload: dict[str, Any]) -> RunConfigModel:
|
||||
port2_payload = _as_dict(switches_payload.get("port2"), "switches.port2")
|
||||
run_payload = _as_dict(payload.get("run"), "run")
|
||||
preprocess_payload = _as_dict(payload.get("preprocess"), "preprocess")
|
||||
gpr_payload = _as_dict(payload.get("gpr"), "gpr")
|
||||
rings_payload = _as_dict(payload.get("rings"), "rings")
|
||||
raw_ring_payload = _as_dict(rings_payload.get("raw"), "rings.raw")
|
||||
raw_tap_ring_payload = _as_dict(rings_payload.get("raw_tap"), "rings.raw_tap")
|
||||
@@ -68,6 +74,38 @@ def run_config_from_dict(payload: dict[str, Any]) -> RunConfigModel:
|
||||
preprocess_payload.get("reference_bundle_path", model.preprocess.reference_bundle_path)
|
||||
)
|
||||
|
||||
model.gpr.mode = str(gpr_payload.get("mode", model.gpr.mode))
|
||||
model.gpr.relative_permittivity = float(
|
||||
gpr_payload.get("relative_permittivity", model.gpr.relative_permittivity)
|
||||
)
|
||||
model.gpr.tx_geometry = []
|
||||
tx_geometry_payload = gpr_payload.get("tx_geometry", [])
|
||||
if isinstance(tx_geometry_payload, list):
|
||||
for entry in tx_geometry_payload:
|
||||
entry_payload = _as_dict(entry, "gpr.tx_geometry[]")
|
||||
model.gpr.tx_geometry.append(
|
||||
GprTxGeometryModel(
|
||||
output_pos=int(entry_payload.get("output_pos", 0)),
|
||||
x_m=float(entry_payload.get("x_m", 0.0)),
|
||||
)
|
||||
)
|
||||
model.gpr.rx_geometry = []
|
||||
rx_geometry_payload = gpr_payload.get("rx_geometry", [])
|
||||
if isinstance(rx_geometry_payload, list):
|
||||
for entry in rx_geometry_payload:
|
||||
entry_payload = _as_dict(entry, "gpr.rx_geometry[]")
|
||||
model.gpr.rx_geometry.append(
|
||||
GprRxGeometryModel(
|
||||
input_pos=int(entry_payload.get("input_pos", 0)),
|
||||
x_m=float(entry_payload.get("x_m", 0.0)),
|
||||
)
|
||||
)
|
||||
validate_gpr_model(
|
||||
model.gpr,
|
||||
input_switch_positions=model.input_switch.positions,
|
||||
output_switch_positions=model.output_switch.positions,
|
||||
)
|
||||
|
||||
load_ring_payload(raw_ring_payload, model.rings.raw)
|
||||
load_ring_payload(raw_tap_ring_payload, model.rings.raw_tap)
|
||||
load_ring_payload(pre_ring_payload, model.rings.preprocessed)
|
||||
@@ -145,6 +183,24 @@ def run_config_to_dict(model: RunConfigModel) -> dict[str, Any]:
|
||||
"calibration_bundle_path": model.preprocess.calibration_bundle_path,
|
||||
"reference_bundle_path": model.preprocess.reference_bundle_path,
|
||||
},
|
||||
"gpr": {
|
||||
"mode": model.gpr.mode,
|
||||
"relative_permittivity": model.gpr.relative_permittivity,
|
||||
"tx_geometry": [
|
||||
{
|
||||
"output_pos": entry.output_pos,
|
||||
"x_m": entry.x_m,
|
||||
}
|
||||
for entry in model.gpr.tx_geometry
|
||||
],
|
||||
"rx_geometry": [
|
||||
{
|
||||
"input_pos": entry.input_pos,
|
||||
"x_m": entry.x_m,
|
||||
}
|
||||
for entry in model.gpr.rx_geometry
|
||||
],
|
||||
},
|
||||
"rings": {
|
||||
"raw": {
|
||||
"name": model.rings.raw.name,
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
from python_app.models.run_config_codec import run_config_from_dict, run_config_to_dict
|
||||
from python_app.models.run_config_schema import (
|
||||
ComboModel,
|
||||
GprModel,
|
||||
GprRxGeometryModel,
|
||||
GprTxGeometryModel,
|
||||
PreprocessModel,
|
||||
RadarModel,
|
||||
RadarSweepModel,
|
||||
@@ -20,6 +23,9 @@ from python_app.models.run_config_validation import (
|
||||
|
||||
__all__ = [
|
||||
"ComboModel",
|
||||
"GprModel",
|
||||
"GprRxGeometryModel",
|
||||
"GprTxGeometryModel",
|
||||
"PreprocessModel",
|
||||
"RadarModel",
|
||||
"RadarSweepModel",
|
||||
|
||||
@@ -95,6 +95,32 @@ class PreprocessModel:
|
||||
reference_bundle_path: str = ""
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class GprTxGeometryModel:
|
||||
"""One transmitter geometry record keyed by output switch position."""
|
||||
|
||||
output_pos: int = 0
|
||||
x_m: float = 0.0
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class GprRxGeometryModel:
|
||||
"""One receiver geometry record keyed by input switch position."""
|
||||
|
||||
input_pos: int = 0
|
||||
x_m: float = 0.0
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class GprModel:
|
||||
"""Stable GPR configuration saved in run_config.json."""
|
||||
|
||||
mode: str = "point"
|
||||
relative_permittivity: float = 1.0
|
||||
tx_geometry: list[GprTxGeometryModel] = field(default_factory=list)
|
||||
rx_geometry: list[GprRxGeometryModel] = field(default_factory=list)
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class RunConfigModel:
|
||||
"""Top-level runtime config model consumed by C++ processes and GUI."""
|
||||
@@ -105,6 +131,7 @@ class RunConfigModel:
|
||||
rings: RingsModel = field(default_factory=RingsModel)
|
||||
runtime: RuntimeModel = field(default_factory=RuntimeModel)
|
||||
preprocess: PreprocessModel = field(default_factory=PreprocessModel)
|
||||
gpr: GprModel = field(default_factory=GprModel)
|
||||
combos: list[ComboModel] = field(default_factory=list)
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -4,7 +4,7 @@ from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from python_app.models.run_config_schema import ComboModel, RingEndpointModel, SwitchModel
|
||||
from python_app.models.run_config_schema import ComboModel, GprModel, RingEndpointModel, SwitchModel
|
||||
|
||||
def load_switch_payload(
|
||||
payload: dict[str, Any],
|
||||
@@ -30,6 +30,37 @@ def load_ring_payload(payload: dict[str, Any], target: RingEndpointModel) -> Non
|
||||
target.slot_size_bytes = int(payload.get("slot_size_bytes", target.slot_size_bytes))
|
||||
|
||||
|
||||
def validate_gpr_model(
|
||||
gpr: GprModel,
|
||||
*,
|
||||
input_switch_positions: int,
|
||||
output_switch_positions: int,
|
||||
) -> None:
|
||||
"""Validate stable GPR config against current switch dimensions."""
|
||||
if gpr.mode not in {"point", "extended"}:
|
||||
raise ValueError("gpr.mode must be either 'point' or 'extended'")
|
||||
if float(gpr.relative_permittivity) <= 0.0:
|
||||
raise ValueError("gpr.relative_permittivity must be > 0")
|
||||
|
||||
seen_output_positions: set[int] = set()
|
||||
for entry in gpr.tx_geometry:
|
||||
output_pos = int(entry.output_pos)
|
||||
if output_pos < 0 or output_pos >= int(output_switch_positions):
|
||||
raise ValueError("gpr.tx_geometry output_pos is out of range")
|
||||
if output_pos in seen_output_positions:
|
||||
raise ValueError("gpr.tx_geometry contains duplicate output_pos")
|
||||
seen_output_positions.add(output_pos)
|
||||
|
||||
seen_input_positions: set[int] = set()
|
||||
for entry in gpr.rx_geometry:
|
||||
input_pos = int(entry.input_pos)
|
||||
if input_pos < 0 or input_pos >= int(input_switch_positions):
|
||||
raise ValueError("gpr.rx_geometry input_pos is out of range")
|
||||
if input_pos in seen_input_positions:
|
||||
raise ValueError("gpr.rx_geometry contains duplicate input_pos")
|
||||
seen_input_positions.add(input_pos)
|
||||
|
||||
|
||||
def parse_combos_from_text(text: str) -> list[ComboModel]:
|
||||
"""Parse UI combos string in `input:output,input:output` format."""
|
||||
cleaned = text.strip()
|
||||
|
||||
Reference in New Issue
Block a user