|
|
|
@@ -16,10 +16,9 @@ from python_app.gui.runtime.constraints import validate_processing_mode_constrai
|
|
|
|
|
from python_app.orchestration.live_processing_config import ProcessingLiveConfig
|
|
|
|
|
|
|
|
|
|
_GPR_MODES = ("gpr", "legacy_gpr")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _is_legacy_gpr(window) -> bool:
|
|
|
|
|
return window._processing_mode.currentText() == "legacy_gpr"
|
|
|
|
|
# Modes that consume the shared gpr_* live-config fields (band, depth gate, antenna
|
|
|
|
|
# selection, motion): both GPR imagers plus the 3D SAR processor.
|
|
|
|
|
_GPR_LIKE_MODES = ("gpr", "legacy_gpr", "sar3d")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _attr(name: str):
|
|
|
|
@@ -27,9 +26,16 @@ def _attr(name: str):
|
|
|
|
|
return lambda window: getattr(window, name)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _dual(gpr_attr: str, legacy_attr: str):
|
|
|
|
|
"""Widget getter for a gpr_* field with separate gpr / legacy_gpr widgets."""
|
|
|
|
|
return lambda window: getattr(window, legacy_attr if _is_legacy_gpr(window) else gpr_attr)
|
|
|
|
|
def _dual(default_attr: str, mode_attrs: dict[str, str] | None = None):
|
|
|
|
|
"""Widget getter for a field with per-mode widgets (gpr / legacy_gpr / sar3d).
|
|
|
|
|
|
|
|
|
|
`mode_attrs` maps a processor mode to its dedicated widget attribute; any
|
|
|
|
|
other mode (including inactive ones) falls back to `default_attr`.
|
|
|
|
|
"""
|
|
|
|
|
attrs = dict(mode_attrs or {})
|
|
|
|
|
return lambda window: getattr(
|
|
|
|
|
window, attrs.get(window._processing_mode.currentText(), default_attr)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# The ONE authoritative field <-> widget map (inverse of `_live_processing_config`),
|
|
|
|
@@ -50,17 +56,17 @@ _WEB_LIVE_SCHEMA = [
|
|
|
|
|
("bscan_start_freq_mhz", "B-scan", ("bscan",), _attr("_bscan_start_freq_mhz")),
|
|
|
|
|
("bscan_stop_freq_mhz", "B-scan", ("bscan",), _attr("_bscan_stop_freq_mhz")),
|
|
|
|
|
("legacy_gpr_mode", "Mode", ("legacy_gpr",), _attr("_legacy_gpr_config_mode")),
|
|
|
|
|
("gpr_input_positions", "Geometry & depth", _GPR_MODES, _dual("_gpr_input_positions_input", "_legacy_gpr_input_positions_input")),
|
|
|
|
|
("gpr_output_positions", "Geometry & depth", _GPR_MODES, _dual("_gpr_output_positions_input", "_legacy_gpr_output_positions_input")),
|
|
|
|
|
("gpr_min_depth_m", "Geometry & depth", _GPR_MODES, _dual("_gpr_min_depth_m", "_legacy_gpr_min_depth_m")),
|
|
|
|
|
("gpr_max_depth_m", "Geometry & depth", _GPR_MODES, _dual("_gpr_max_depth_m", "_legacy_gpr_max_depth_m")),
|
|
|
|
|
("gpr_start_freq_mhz", "Geometry & depth", _GPR_MODES, _dual("_gpr_start_freq_mhz", "_legacy_gpr_start_freq_mhz")),
|
|
|
|
|
("gpr_stop_freq_mhz", "Geometry & depth", _GPR_MODES, _dual("_gpr_stop_freq_mhz", "_legacy_gpr_stop_freq_mhz")),
|
|
|
|
|
("gpr_input_positions", "Geometry & depth", _GPR_LIKE_MODES, _dual("_gpr_input_positions_input", {"legacy_gpr": "_legacy_gpr_input_positions_input", "sar3d": "_sar3d_input_positions_input"})),
|
|
|
|
|
("gpr_output_positions", "Geometry & depth", _GPR_LIKE_MODES, _dual("_gpr_output_positions_input", {"legacy_gpr": "_legacy_gpr_output_positions_input", "sar3d": "_sar3d_output_positions_input"})),
|
|
|
|
|
("gpr_min_depth_m", "Geometry & depth", _GPR_LIKE_MODES, _dual("_gpr_min_depth_m", {"legacy_gpr": "_legacy_gpr_min_depth_m", "sar3d": "_sar3d_min_depth_m"})),
|
|
|
|
|
("gpr_max_depth_m", "Geometry & depth", _GPR_LIKE_MODES, _dual("_gpr_max_depth_m", {"legacy_gpr": "_legacy_gpr_max_depth_m", "sar3d": "_sar3d_max_depth_m"})),
|
|
|
|
|
("gpr_start_freq_mhz", "Geometry & depth", _GPR_LIKE_MODES, _dual("_gpr_start_freq_mhz", {"legacy_gpr": "_legacy_gpr_start_freq_mhz", "sar3d": "_sar3d_start_freq_mhz"})),
|
|
|
|
|
("gpr_stop_freq_mhz", "Geometry & depth", _GPR_LIKE_MODES, _dual("_gpr_stop_freq_mhz", {"legacy_gpr": "_legacy_gpr_stop_freq_mhz", "sar3d": "_sar3d_stop_freq_mhz"})),
|
|
|
|
|
("gpr_imaging_plane_y_m", "Geometry & depth", ("gpr",), _attr("_gpr_imaging_plane_y_m")),
|
|
|
|
|
("gpr_range_comp_power", "Imaging", ("gpr",), _attr("_gpr_range_comp_power")),
|
|
|
|
|
("gpr_score_mode", "Imaging", ("gpr",), _attr("_gpr_score_mode")),
|
|
|
|
|
("gpr_background_subtract_enabled", "Imaging", _GPR_MODES, _dual("_gpr_background_subtract_enabled", "_legacy_gpr_background_subtract_enabled")),
|
|
|
|
|
("gpr_background_mean_count", "Imaging", _GPR_MODES, _dual("_gpr_background_mean_count", "_legacy_gpr_background_mean_count")),
|
|
|
|
|
("gpr_background_subtract_enabled", "Imaging", _GPR_MODES, _dual("_gpr_background_subtract_enabled", {"legacy_gpr": "_legacy_gpr_background_subtract_enabled"})),
|
|
|
|
|
("gpr_background_mean_count", "Imaging", _GPR_MODES, _dual("_gpr_background_mean_count", {"legacy_gpr": "_legacy_gpr_background_mean_count"})),
|
|
|
|
|
("gpr_remove_sidelobe_objects_enabled", "Imaging", ("gpr",), _attr("_gpr_remove_sidelobe_objects_enabled")),
|
|
|
|
|
("gpr_object_min_frac", "Detection", ("gpr",), _attr("_gpr_object_min_frac")),
|
|
|
|
|
("gpr_max_detected_objects_to_draw", "Detection", ("gpr",), _attr("_gpr_max_detected_objects_to_draw")),
|
|
|
|
@@ -70,17 +76,29 @@ _WEB_LIVE_SCHEMA = [
|
|
|
|
|
("gpr_snr_thresh", "Detection", ("legacy_gpr",), _attr("_legacy_gpr_snr_thresh")),
|
|
|
|
|
("gpr_snr_comp_max", "Detection", ("legacy_gpr",), _attr("_legacy_gpr_snr_comp_max")),
|
|
|
|
|
("legacy_gpr_min_visible_pair_count", "Detection", ("legacy_gpr",), _attr("_legacy_gpr_min_visible_pair_count")),
|
|
|
|
|
("gpr_visible_x_min_m", "Visible window", _GPR_MODES, _dual("_gpr_visible_x_min_m", "_legacy_gpr_visible_x_min_m")),
|
|
|
|
|
("gpr_visible_x_max_m", "Visible window", _GPR_MODES, _dual("_gpr_visible_x_max_m", "_legacy_gpr_visible_x_max_m")),
|
|
|
|
|
("gpr_visible_z_min_m", "Visible window", _GPR_MODES, _dual("_gpr_visible_z_min_m", "_legacy_gpr_visible_z_min_m")),
|
|
|
|
|
("gpr_visible_z_max_m", "Visible window", _GPR_MODES, _dual("_gpr_visible_z_max_m", "_legacy_gpr_visible_z_max_m")),
|
|
|
|
|
("gpr_visible_x_min_m", "Visible window", _GPR_MODES, _dual("_gpr_visible_x_min_m", {"legacy_gpr": "_legacy_gpr_visible_x_min_m"})),
|
|
|
|
|
("gpr_visible_x_max_m", "Visible window", _GPR_MODES, _dual("_gpr_visible_x_max_m", {"legacy_gpr": "_legacy_gpr_visible_x_max_m"})),
|
|
|
|
|
("gpr_visible_z_min_m", "Visible window", _GPR_MODES, _dual("_gpr_visible_z_min_m", {"legacy_gpr": "_legacy_gpr_visible_z_min_m"})),
|
|
|
|
|
("gpr_visible_z_max_m", "Visible window", _GPR_MODES, _dual("_gpr_visible_z_max_m", {"legacy_gpr": "_legacy_gpr_visible_z_max_m"})),
|
|
|
|
|
("gpr_motion_mode", "Motion", ("gpr",), _attr("_gpr_motion_mode")),
|
|
|
|
|
("gpr_look_angle_deg", "Motion", _GPR_MODES, _dual("_gpr_look_angle_deg", "_legacy_gpr_look_angle_deg")),
|
|
|
|
|
("gpr_direction_sign", "Motion", ("gpr",), _attr("_gpr_direction_sign")),
|
|
|
|
|
("gpr_apply_freq_phase_correction", "Motion", ("legacy_gpr",), _attr("_legacy_gpr_apply_freq_phase_correction")),
|
|
|
|
|
("gpr_look_angle_deg", "Motion", _GPR_LIKE_MODES, _dual("_gpr_look_angle_deg", {"legacy_gpr": "_legacy_gpr_look_angle_deg", "sar3d": "_sar3d_look_angle_deg"})),
|
|
|
|
|
("gpr_direction_sign", "Motion", ("gpr", "sar3d"), _dual("_gpr_direction_sign", {"sar3d": "_sar3d_direction_sign"})),
|
|
|
|
|
("gpr_apply_freq_phase_correction", "Motion", ("legacy_gpr", "sar3d"), _dual("_legacy_gpr_apply_freq_phase_correction", {"sar3d": "_sar3d_apply_freq_phase_correction"})),
|
|
|
|
|
("gpr_reference_mode", "Motion", ("legacy_gpr",), _attr("_legacy_gpr_reference_mode")),
|
|
|
|
|
("ignore_socket_speed", "Motion", _GPR_MODES, _dual("_gpr_ignore_socket_speed_enabled", "_legacy_gpr_ignore_socket_speed_enabled")),
|
|
|
|
|
("gpr_speed_m_s", "Motion", _GPR_MODES, _dual("_gpr_speed_m_s", "_legacy_gpr_speed_m_s")),
|
|
|
|
|
("ignore_socket_speed", "Motion", _GPR_LIKE_MODES, _dual("_gpr_ignore_socket_speed_enabled", {"legacy_gpr": "_legacy_gpr_ignore_socket_speed_enabled", "sar3d": "_sar3d_ignore_socket_speed_enabled"})),
|
|
|
|
|
("gpr_speed_m_s", "Motion", _GPR_LIKE_MODES, _dual("_gpr_speed_m_s", {"legacy_gpr": "_legacy_gpr_speed_m_s", "sar3d": "_sar3d_speed_m_s"})),
|
|
|
|
|
("sar3d_frame_radius", "3D SAR", ("sar3d",), _attr("_sar3d_frame_radius")),
|
|
|
|
|
("sar3d_edge_frame_weight", "3D SAR", ("sar3d",), _attr("_sar3d_edge_frame_weight")),
|
|
|
|
|
("sar3d_pitch_deg", "3D SAR", ("sar3d",), _attr("_sar3d_pitch_deg")),
|
|
|
|
|
("sar3d_center_depth_m", "3D SAR", ("sar3d",), _attr("_sar3d_center_depth_m")),
|
|
|
|
|
("sar3d_half_width_x_m", "3D SAR", ("sar3d",), _attr("_sar3d_half_width_x_m")),
|
|
|
|
|
("sar3d_half_width_y_m", "3D SAR", ("sar3d",), _attr("_sar3d_half_width_y_m")),
|
|
|
|
|
("sar3d_half_width_z_m", "3D SAR", ("sar3d",), _attr("_sar3d_half_width_z_m")),
|
|
|
|
|
("sar3d_grid_nx", "3D SAR", ("sar3d",), _attr("_sar3d_grid_nx")),
|
|
|
|
|
("sar3d_grid_ny", "3D SAR", ("sar3d",), _attr("_sar3d_grid_ny")),
|
|
|
|
|
("sar3d_grid_nz", "3D SAR", ("sar3d",), _attr("_sar3d_grid_nz")),
|
|
|
|
|
("sar3d_use_compensation", "3D SAR", ("sar3d",), _attr("_sar3d_use_compensation")),
|
|
|
|
|
("sar3d_normalize_pairs", "3D SAR", ("sar3d",), _attr("_sar3d_normalize_pairs")),
|
|
|
|
|
]
|
|
|
|
|
_WEB_LIVE_GETTERS = {field: getter for field, _group, _modes, getter in _WEB_LIVE_SCHEMA}
|
|
|
|
|
|
|
|
|
@@ -101,7 +119,7 @@ _NON_WIDGET_LIVE_FIELDS = frozenset({
|
|
|
|
|
# config). They apply immediately through the same redraw as a live edit but write
|
|
|
|
|
# nothing to processing_live.json.
|
|
|
|
|
_WEB_DISPLAY_SCHEMA = [
|
|
|
|
|
("render_mode", "Display", _GPR_MODES, _dual("_gpr_render_mode", "_legacy_gpr_render_mode")),
|
|
|
|
|
("render_mode", "Display", _GPR_MODES, _dual("_gpr_render_mode", {"legacy_gpr": "_legacy_gpr_render_mode"})),
|
|
|
|
|
("subtract_mean_ascan", "Display", ("bscan",), _attr("_bscan_subtract_mean_ascan")),
|
|
|
|
|
("combo_filter", "Display", ("pass_through",), _attr("_pass_through_combo_filter_input")),
|
|
|
|
|
]
|
|
|
|
@@ -113,8 +131,8 @@ _WEB_STABLE_SCHEMA = [
|
|
|
|
|
# Coherent BP fixes the medium to eps_r = 1 (Horns_motion_3libre.py), so relative
|
|
|
|
|
# permittivity is a legacy-GPR-only knob; BP ignores it.
|
|
|
|
|
("relative_permittivity", "Geometry & medium", ("legacy_gpr",), _attr("_gpr_relative_permittivity")),
|
|
|
|
|
("tx_geometry", "Geometry & medium", _GPR_MODES, _attr("_gpr_tx_geometry_input")),
|
|
|
|
|
("rx_geometry", "Geometry & medium", _GPR_MODES, _attr("_gpr_rx_geometry_input")),
|
|
|
|
|
("tx_geometry", "Geometry & medium", _GPR_LIKE_MODES, _attr("_gpr_tx_geometry_input")),
|
|
|
|
|
("rx_geometry", "Geometry & medium", _GPR_LIKE_MODES, _attr("_gpr_rx_geometry_input")),
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
# Every field the web form renders and may write back, across all three categories.
|
|
|
|
@@ -222,6 +240,17 @@ class AppWindowLiveProcessingMixin:
|
|
|
|
|
"""Return whether a mode uses GPR result payloads and plot surface."""
|
|
|
|
|
return mode in {"gpr", "legacy_gpr"}
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def _is_gpr_like_processing_mode(mode: str) -> bool:
|
|
|
|
|
"""Return whether a mode consumes the shared gpr_* fields and Tx/Rx geometry.
|
|
|
|
|
|
|
|
|
|
Covers both GPR imagers and the 3D SAR processor. The latter renders on its
|
|
|
|
|
own plot surface, so GPR-object/locator branches must keep using
|
|
|
|
|
:meth:`_is_gpr_processing_mode`; this predicate gates only the shared
|
|
|
|
|
behaviors (result drain on settings changes, geometry page visibility).
|
|
|
|
|
"""
|
|
|
|
|
return mode in set(_GPR_LIKE_MODES)
|
|
|
|
|
|
|
|
|
|
def _validate_processing_mode_selection(self, mode: str) -> None:
|
|
|
|
|
"""Validate requested processing mode against current stable/live GUI state."""
|
|
|
|
|
validate_processing_mode_constraints(
|
|
|
|
@@ -352,7 +381,7 @@ class AppWindowLiveProcessingMixin:
|
|
|
|
|
return
|
|
|
|
|
try:
|
|
|
|
|
current_mode = self._processing_mode.currentText()
|
|
|
|
|
if self._is_gpr_processing_mode(current_mode):
|
|
|
|
|
if self._is_gpr_like_processing_mode(current_mode):
|
|
|
|
|
self._drain_results_until_quiet(timeout_s=0.05, poll_s=0.005)
|
|
|
|
|
|
|
|
|
|
self._write_live_processing_config()
|
|
|
|
@@ -364,16 +393,16 @@ class AppWindowLiveProcessingMixin:
|
|
|
|
|
# recompute of everything on screen. Debounced, so dragging a spin box
|
|
|
|
|
# sends one burst instead of one per step.
|
|
|
|
|
self._schedule_bscan_history_reprocess()
|
|
|
|
|
elif self._is_gpr_processing_mode(current_mode):
|
|
|
|
|
elif self._is_gpr_like_processing_mode(current_mode):
|
|
|
|
|
latest = self._drain_results_until_quiet(timeout_s=0.8, poll_s=0.02)
|
|
|
|
|
collection = latest
|
|
|
|
|
if collection is None and self._result_history:
|
|
|
|
|
collection = self._result_history[-1]
|
|
|
|
|
if collection is not None:
|
|
|
|
|
if not self._draw_results(collection):
|
|
|
|
|
if collection is None or not self._draw_results(collection):
|
|
|
|
|
if current_mode == "sar3d":
|
|
|
|
|
self._clear_sar3d_plot()
|
|
|
|
|
else:
|
|
|
|
|
self._clear_gpr_plot()
|
|
|
|
|
else:
|
|
|
|
|
self._clear_gpr_plot()
|
|
|
|
|
elif self._result_history:
|
|
|
|
|
self._draw_results(self._result_history[-1])
|
|
|
|
|
else:
|
|
|
|
@@ -427,9 +456,10 @@ class AppWindowLiveProcessingMixin:
|
|
|
|
|
"bscan": 1,
|
|
|
|
|
"gpr": 2,
|
|
|
|
|
"legacy_gpr": 3,
|
|
|
|
|
"sar3d": 4,
|
|
|
|
|
}
|
|
|
|
|
self._processing_mode_pages.setCurrentIndex(mode_to_page.get(mode, 0))
|
|
|
|
|
self._gpr_common_page.setVisible(self._is_gpr_processing_mode(mode))
|
|
|
|
|
self._gpr_common_page.setVisible(self._is_gpr_like_processing_mode(mode))
|
|
|
|
|
current_page = self._processing_mode_pages.currentWidget()
|
|
|
|
|
if current_page is not None:
|
|
|
|
|
self._processing_mode_pages.setFixedHeight(current_page.sizeHint().height())
|
|
|
|
@@ -509,9 +539,21 @@ class AppWindowLiveProcessingMixin:
|
|
|
|
|
f"render_mode={self._legacy_gpr_render_mode.currentText()}, "
|
|
|
|
|
f"min_pairs={self._legacy_gpr_min_visible_pair_count.value()})"
|
|
|
|
|
)
|
|
|
|
|
elif mode == "sar3d":
|
|
|
|
|
self._log(
|
|
|
|
|
"Processing mode selected: sar3d "
|
|
|
|
|
f"(inputs={self._sar3d_input_positions_input.text().strip() or '<all>'}, "
|
|
|
|
|
f"outputs={self._sar3d_output_positions_input.text().strip() or '<all>'}, "
|
|
|
|
|
f"depth={self._sar3d_min_depth_m.value():g}..{self._sar3d_max_depth_m.value():g} m, "
|
|
|
|
|
f"freq={self._sar3d_start_freq_mhz.value():g}..{self._sar3d_stop_freq_mhz.value():g} MHz, "
|
|
|
|
|
f"frame_radius={self._sar3d_frame_radius.value()}, "
|
|
|
|
|
f"grid={self._sar3d_grid_nx.value()}x{self._sar3d_grid_ny.value()}x{self._sar3d_grid_nz.value()}, "
|
|
|
|
|
f"center_depth={self._sar3d_center_depth_m.value():g} m, "
|
|
|
|
|
f"pitch={self._sar3d_pitch_deg.value():g} deg)"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def _clear_history_mode_caches(self) -> None:
|
|
|
|
|
"""Drop cached render state for pass-through, B-scan, and GPR views."""
|
|
|
|
|
"""Drop cached render state for pass-through, B-scan, GPR, and 3D SAR views."""
|
|
|
|
|
self._clear_bscan_plot_history()
|
|
|
|
|
if hasattr(self, "_bscan_plot"):
|
|
|
|
|
self._bscan_plot.clear()
|
|
|
|
@@ -519,6 +561,8 @@ class AppWindowLiveProcessingMixin:
|
|
|
|
|
self._clear_trace_plots()
|
|
|
|
|
if hasattr(self, "_gpr_plot"):
|
|
|
|
|
self._clear_gpr_plot()
|
|
|
|
|
if hasattr(self, "_sar3d_xz_plot"):
|
|
|
|
|
self._clear_sar3d_plot()
|
|
|
|
|
|
|
|
|
|
def _redraw_after_history_deletion(self) -> None:
|
|
|
|
|
"""Refresh plot immediately after destructive history deletion."""
|
|
|
|
@@ -534,6 +578,11 @@ class AppWindowLiveProcessingMixin:
|
|
|
|
|
return
|
|
|
|
|
self._clear_gpr_plot()
|
|
|
|
|
return
|
|
|
|
|
if self._processing_mode.currentText() == "sar3d":
|
|
|
|
|
if self._result_history and self._draw_results(self._result_history[-1]):
|
|
|
|
|
return
|
|
|
|
|
self._clear_sar3d_plot()
|
|
|
|
|
return
|
|
|
|
|
if self._result_history:
|
|
|
|
|
self._draw_results(self._result_history[-1])
|
|
|
|
|
return
|
|
|
|
|