added gpr speed tracking

This commit is contained in:
Ayzen
2026-04-01 22:05:04 +03:00
parent 669205d8f8
commit 4b78c2808d
20 changed files with 1165 additions and 47 deletions
@@ -165,6 +165,8 @@ class AppWindowConfigMixin:
comp_power=0.2,
start_freq_mhz=3000.0,
stop_freq_mhz=6000.0,
speed_m_s=0.0,
look_angle_deg=0.0,
background_subtract_enabled=True,
background_mean_count=10,
),
@@ -217,6 +219,8 @@ class AppWindowConfigMixin:
comp_power=float(self._gpr_comp_power.value()),
start_freq_mhz=float(self._gpr_start_freq_mhz.value()),
stop_freq_mhz=float(self._gpr_stop_freq_mhz.value()),
speed_m_s=float(self._gpr_speed_m_s.value()),
look_angle_deg=float(self._gpr_look_angle_deg.value()),
background_subtract_enabled=bool(self._gpr_background_subtract_enabled.isChecked()),
background_mean_count=int(self._gpr_background_mean_count.value()),
),
@@ -315,7 +319,7 @@ class AppWindowConfigMixin:
details=self._capture_state_details(),
)
return
if self._supervisor.is_running() or self._supervisor.is_processor_running():
if self._supervisor.is_running():
self._show_error(
"Stop all pipeline processes before loading a config profile",
details=self._process_state_details(),
@@ -332,23 +336,26 @@ class AppWindowConfigMixin:
return
try:
self._load_config_profile(Path(selected_path))
normalized_path = self._normalize_profile_path(Path(selected_path))
profile = GuiProfileModel.load_from_path(normalized_path)
processor_running = self._supervisor.is_processor_running()
self._apply_loaded_profile(profile, normalized_path)
profile_kind = "legacy run config" if profile.gui is None else "full GUI profile"
message = (
f"Config profile loaded: path={normalized_path}, "
f"kind={profile_kind}, "
f"combos={len(self._defaults_config.combos)}, "
f"processing_mode={self._processing_mode.currentText()}"
)
if processor_running:
message += (
"; data_processor is still running, so live processing settings were applied immediately "
"and stable settings are now staged in the UI for the next Start"
)
self._log(message)
except Exception as exc: # noqa: BLE001
self._show_exception("Failed to load config profile", exc)
def _load_config_profile(self, profile_path: Path) -> None:
"""Load config profile from `profile_path` and atomically apply it to the UI."""
normalized_path = self._normalize_profile_path(profile_path)
profile = GuiProfileModel.load_from_path(normalized_path)
self._apply_loaded_profile(profile, normalized_path)
profile_kind = "legacy run config" if profile.gui is None else "full GUI profile"
self._log(
f"Config profile loaded: path={normalized_path}, "
f"kind={profile_kind}, "
f"combos={len(self._defaults_config.combos)}, "
f"processing_mode={self._processing_mode.currentText()}"
)
def _apply_loaded_profile(self, profile: GuiProfileModel, profile_path: Path) -> None:
"""Apply already parsed profile to GUI state without restarting the pipeline."""
config = profile.run_config.clone()
@@ -395,6 +402,8 @@ class AppWindowConfigMixin:
self._gpr_comp_power,
self._gpr_start_freq_mhz,
self._gpr_stop_freq_mhz,
self._gpr_speed_m_s,
self._gpr_look_angle_deg,
self._gpr_background_subtract_enabled,
self._gpr_background_mean_count,
self._save_count,
@@ -455,6 +464,8 @@ class AppWindowConfigMixin:
self._gpr_comp_power.setValue(float(gui_state.processing.gpr.comp_power))
self._gpr_start_freq_mhz.setValue(float(gui_state.processing.gpr.start_freq_mhz))
self._gpr_stop_freq_mhz.setValue(float(gui_state.processing.gpr.stop_freq_mhz))
self._gpr_speed_m_s.setValue(float(gui_state.processing.gpr.speed_m_s))
self._gpr_look_angle_deg.setValue(float(gui_state.processing.gpr.look_angle_deg))
self._gpr_background_subtract_enabled.setChecked(
bool(gui_state.processing.gpr.background_subtract_enabled)
)
@@ -567,6 +578,8 @@ class AppWindowConfigMixin:
gpr_comp_power=float(self._gpr_comp_power.value()),
gpr_start_freq_mhz=float(self._gpr_start_freq_mhz.value()),
gpr_stop_freq_mhz=float(self._gpr_stop_freq_mhz.value()),
gpr_speed_m_s=float(self._gpr_speed_m_s.value()),
gpr_look_angle_deg=float(self._gpr_look_angle_deg.value()),
gpr_background_subtract_enabled=bool(self._gpr_background_subtract_enabled.isChecked()),
gpr_background_mean_count=int(self._gpr_background_mean_count.value()),
history_command_seq=int(self._history_command_seq),
@@ -640,6 +653,8 @@ class AppWindowConfigMixin:
f"outputs={self._gpr_output_positions_input.text().strip() or '<all>'}, "
f"depth={self._gpr_min_depth_m.value():g}..{self._gpr_max_depth_m.value():g} m, "
f"freq={self._gpr_start_freq_mhz.value():g}..{self._gpr_stop_freq_mhz.value():g} MHz, "
f"speed={self._gpr_speed_m_s.value():g} m/s, "
f"look_angle={self._gpr_look_angle_deg.value():g} deg, "
f"background_subtract={self._gpr_background_subtract_enabled.isChecked()}, "
f"mean_count={self._gpr_background_mean_count.value()})"
)
@@ -39,10 +39,10 @@ class AppWindowPipelineMixin:
try:
processor_was_running = self._supervisor.is_processor_running()
config = self._build_config()
if not processor_was_running:
self._reset_runtime_history()
config = self._build_config()
self._validate_processing_mode_constraints(config)
run_signature = self._build_run_history_signature(config)
radar_key = self._radar_key(config)
@@ -191,6 +191,18 @@ def build_processing_group(owner) -> QGroupBox:
owner._gpr_stop_freq_mhz.setSingleStep(10.0)
owner._gpr_stop_freq_mhz.setValue(float(gpr_live_defaults.stop_freq_mhz))
owner._gpr_speed_m_s = QDoubleSpinBox()
owner._gpr_speed_m_s.setDecimals(3)
owner._gpr_speed_m_s.setRange(-100.0, 100.0)
owner._gpr_speed_m_s.setSingleStep(0.01)
owner._gpr_speed_m_s.setValue(float(gpr_live_defaults.speed_m_s))
owner._gpr_look_angle_deg = QDoubleSpinBox()
owner._gpr_look_angle_deg.setDecimals(2)
owner._gpr_look_angle_deg.setRange(-90.0, 90.0)
owner._gpr_look_angle_deg.setSingleStep(0.1)
owner._gpr_look_angle_deg.setValue(float(gpr_live_defaults.look_angle_deg))
owner._gpr_background_subtract_enabled = QCheckBox("Subtract mean of previous collections")
owner._gpr_background_subtract_enabled.setChecked(bool(gpr_live_defaults.background_subtract_enabled))
@@ -209,6 +221,8 @@ def build_processing_group(owner) -> QGroupBox:
gpr_form.addRow("Comp power", owner._gpr_comp_power)
gpr_form.addRow("Start MHz", owner._gpr_start_freq_mhz)
gpr_form.addRow("Stop MHz", owner._gpr_stop_freq_mhz)
gpr_form.addRow("Speed m/s", owner._gpr_speed_m_s)
gpr_form.addRow("Look angle deg", owner._gpr_look_angle_deg)
gpr_form.addRow(owner._gpr_background_subtract_enabled)
gpr_form.addRow("Mean count", owner._gpr_background_mean_count)
owner._processing_mode_pages.addWidget(gpr_page)
@@ -236,6 +250,8 @@ def build_processing_group(owner) -> QGroupBox:
owner._gpr_comp_power.valueChanged.connect(owner._on_processing_live_settings_changed)
owner._gpr_start_freq_mhz.valueChanged.connect(owner._on_processing_live_settings_changed)
owner._gpr_stop_freq_mhz.valueChanged.connect(owner._on_processing_live_settings_changed)
owner._gpr_speed_m_s.valueChanged.connect(owner._on_processing_live_settings_changed)
owner._gpr_look_angle_deg.valueChanged.connect(owner._on_processing_live_settings_changed)
owner._gpr_background_subtract_enabled.toggled.connect(owner._on_processing_live_settings_changed)
owner._gpr_background_mean_count.valueChanged.connect(owner._on_processing_live_settings_changed)
+2
View File
@@ -47,6 +47,8 @@ class SweepCollection:
collection_id: int
monotonic_ns: int
traces: list[TraceData] = field(default_factory=list)
capture_start_ns: int = 0
capture_end_ns: int = 0
@dataclass(slots=True)
+14 -1
View File
@@ -196,6 +196,18 @@ def gui_profile_from_dict(payload: dict[str, Any]) -> GuiProfileModel:
gui.processing.gpr.stop_freq_mhz,
"gui.processing.gpr",
),
speed_m_s=_optional_float(
gpr_object,
"speed_m_s",
gui.processing.gpr.speed_m_s,
"gui.processing.gpr",
),
look_angle_deg=_optional_float(
gpr_object,
"look_angle_deg",
gui.processing.gpr.look_angle_deg,
"gui.processing.gpr",
),
background_subtract_enabled=_optional_bool(
gpr_object,
"background_subtract_enabled",
@@ -288,6 +300,8 @@ def gui_profile_to_dict(model: GuiProfileModel) -> dict[str, Any]:
"comp_power": gui.processing.gpr.comp_power,
"start_freq_mhz": gui.processing.gpr.start_freq_mhz,
"stop_freq_mhz": gui.processing.gpr.stop_freq_mhz,
"speed_m_s": gui.processing.gpr.speed_m_s,
"look_angle_deg": gui.processing.gpr.look_angle_deg,
"background_subtract_enabled": gui.processing.gpr.background_subtract_enabled,
"background_mean_count": gui.processing.gpr.background_mean_count,
},
@@ -302,4 +316,3 @@ def gui_profile_to_dict(model: GuiProfileModel) -> dict[str, Any]:
},
}
return payload
+2
View File
@@ -55,6 +55,8 @@ class GuiGprStateModel:
comp_power: float = 0.2
start_freq_mhz: float = 3000.0
stop_freq_mhz: float = 6000.0
speed_m_s: float = 0.0
look_angle_deg: float = 0.0
background_subtract_enabled: bool = True
background_mean_count: int = 10
@@ -30,6 +30,8 @@ class ProcessingLiveConfig:
gpr_comp_power: float = 0.2
gpr_start_freq_mhz: float = 3000.0
gpr_stop_freq_mhz: float = 6000.0
gpr_speed_m_s: float = 0.0
gpr_look_angle_deg: float = 0.0
gpr_background_subtract_enabled: bool = True
gpr_background_mean_count: int = 10
history_command_seq: int = 0
@@ -68,6 +70,8 @@ class ProcessingLiveConfig:
"gpr_comp_power": float(self.gpr_comp_power),
"gpr_start_freq_mhz": float(self.gpr_start_freq_mhz),
"gpr_stop_freq_mhz": float(self.gpr_stop_freq_mhz),
"gpr_speed_m_s": float(self.gpr_speed_m_s),
"gpr_look_angle_deg": float(self.gpr_look_angle_deg),
"gpr_background_subtract_enabled": bool(self.gpr_background_subtract_enabled),
"gpr_background_mean_count": int(self.gpr_background_mean_count),
"history_command_seq": int(self.history_command_seq),
@@ -48,3 +48,7 @@ class ByteCursor:
data = self.payload[self.offset : self.offset + size]
self.offset += size
return data
def remaining_bytes(self) -> int:
"""Return unread byte count."""
return len(self.payload) - self.offset
+15 -1
View File
@@ -54,7 +54,21 @@ def decode_trace_collection(payload: bytes, expected_magic: int) -> SweepCollect
)
)
return SweepCollection(collection_id=collection_id, monotonic_ns=monotonic_ns, traces=traces)
capture_start_ns = 0
capture_end_ns = 0
if cursor.remaining_bytes() == 16:
capture_start_ns = cursor.read_u64()
capture_end_ns = cursor.read_u64()
elif cursor.remaining_bytes() != 0:
raise ValueError("Unexpected trailing bytes in trace collection")
return SweepCollection(
collection_id=collection_id,
monotonic_ns=monotonic_ns,
traces=traces,
capture_start_ns=capture_start_ns,
capture_end_ns=capture_end_ns,
)
def decode_result_collection(payload: bytes) -> ResultCollection:
+7
View File
@@ -41,6 +41,13 @@ def serialize_trace_collection(collection: SweepCollection, magic: int) -> bytes
_write_interleaved_complex(buffer, s11)
_write_interleaved_complex(buffer, s21)
buffer.extend(
struct.pack(
"<QQ",
int(collection.capture_start_ns),
int(collection.capture_end_ns),
)
)
return bytes(buffer)
+4
View File
@@ -117,6 +117,8 @@ def save_trace_history_binary(stage_dir: Path, history: list[SweepCollection], m
{
"collection_id": collection.collection_id,
"monotonic_ns": collection.monotonic_ns,
"capture_start_ns": int(collection.capture_start_ns),
"capture_end_ns": int(collection.capture_end_ns),
"trace_count": len(collection.traces),
},
indent=2,
@@ -178,6 +180,8 @@ def save_trace_history_numpy(stage_dir: Path, history: list[SweepCollection]) ->
{
"collection_id": int(collection.collection_id),
"monotonic_ns": int(collection.monotonic_ns),
"capture_start_ns": int(collection.capture_start_ns),
"capture_end_ns": int(collection.capture_end_ns),
"trace_count": len(collection.traces),
"traces": traces_meta,
},
+4
View File
@@ -64,6 +64,8 @@ class NpzStore(StoreApi):
meta = {
"collection_id": int(collection.collection_id),
"monotonic_ns": int(collection.monotonic_ns),
"capture_start_ns": int(collection.capture_start_ns),
"capture_end_ns": int(collection.capture_end_ns),
"combos": combo_records,
}
meta_path.write_text(json.dumps(meta, indent=2), encoding="utf-8")
@@ -98,6 +100,8 @@ class NpzStore(StoreApi):
collection_id=int(meta["collection_id"]),
monotonic_ns=int(meta["monotonic_ns"]),
traces=traces,
capture_start_ns=int(meta.get("capture_start_ns", 0)),
capture_end_ns=int(meta.get("capture_end_ns", 0)),
)
def list_sets(self, kind: str, radar_key: str) -> list[str]: