Rework radar system
This commit is contained in:
@@ -7,120 +7,163 @@ from dataclasses import dataclass, field
|
||||
import json
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from typing import Protocol
|
||||
from typing import Any, TypeVar
|
||||
|
||||
from python_app.models.run_config_model import RunConfigModel
|
||||
from python_app.models.run_config_schema import RunConfigModel
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
Scalar = str | int | float | bool
|
||||
_ScalarT = TypeVar("_ScalarT", str, int, float, bool)
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class Setting:
|
||||
"""Qt-free binding and validation metadata for one persistent GUI field."""
|
||||
|
||||
widget: str = ""
|
||||
live: str = ""
|
||||
web: str = ""
|
||||
group: str = ""
|
||||
choices: tuple[str, ...] = ()
|
||||
minimum: float | None = None
|
||||
maximum: float | None = None
|
||||
exclusive_minimum: bool = False
|
||||
|
||||
|
||||
def setting(
|
||||
default: _ScalarT, widget: str = "", *, live: str = "", web: str = "",
|
||||
group: str = "", choices: tuple[str, ...] = (), minimum: float | None = None,
|
||||
maximum: float | None = None, exclusive_minimum: bool = False,
|
||||
) -> _ScalarT:
|
||||
"""Declare a typed dataclass field and its single profile/live/web binding."""
|
||||
return field(default=default, metadata={"setting": Setting(
|
||||
widget, live, web or live, group, choices, minimum, maximum, exclusive_minimum,
|
||||
)})
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class GuiSwitchStateModel:
|
||||
"""UI-only state for switch selection controls."""
|
||||
|
||||
combo_mode: str = "text"
|
||||
combos_text: str = ""
|
||||
single_input: str = "0"
|
||||
single_output: str = "0"
|
||||
combo_mode: str = setting("text", choices=('text', 'single'))
|
||||
combos_text: str = setting("", '_combos_text')
|
||||
single_input: str = setting("0", '_single_combo_input')
|
||||
single_output: str = setting("0", '_single_combo_output')
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class GuiPassThroughStateModel:
|
||||
"""UI-only defaults for pass-through rendering."""
|
||||
|
||||
show_magnitude: bool = True
|
||||
show_phase: bool = True
|
||||
unwrap_phase: bool = False
|
||||
combo_filter: str = ""
|
||||
fixed_y_enabled: bool = False
|
||||
y_min_db: float = -100.0
|
||||
y_max_db: float = 0.0
|
||||
show_magnitude: bool = setting(True, '_show_magnitude_checkbox')
|
||||
show_phase: bool = setting(True, '_show_phase_checkbox')
|
||||
unwrap_phase: bool = setting(False, '_unwrap_phase_checkbox')
|
||||
combo_filter: str = setting("", '_pass_through_combo_filter_input', web='combo_filter', group='Display')
|
||||
fixed_y_enabled: bool = setting(False, '_pass_through_fixed_y_enabled', web='pass_through_fixed_y_enabled', group='Pass-through')
|
||||
y_min_db: float = setting(-100.0, '_pass_through_y_min_db', web='pass_through_y_min_db', group='Pass-through')
|
||||
y_max_db: float = setting(0.0, '_pass_through_y_max_db', web='pass_through_y_max_db', group='Pass-through')
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class GuiBscanStateModel:
|
||||
"""UI-only defaults for B-scan live settings."""
|
||||
|
||||
axis: str = "abs"
|
||||
cut_m: float = 0.0
|
||||
max_depth_m: float = 8.0
|
||||
gain: float = 1.0
|
||||
start_freq_mhz: float = 100.0
|
||||
stop_freq_mhz: float = 8800.0
|
||||
subtract_mean_ascan: bool = False
|
||||
axis: str = setting("abs", '_bscan_axis', live='bscan_axis', group='B-scan', choices=('abs', 'real', 'phase'))
|
||||
cut_m: float = setting(0.824, '_bscan_cut_m', live='bscan_cut_m', group='B-scan')
|
||||
max_depth_m: float = setting(1.0, '_bscan_max_depth_m', live='bscan_max_depth_m', group='B-scan')
|
||||
gain: float = setting(1.0, '_bscan_gain', live='bscan_gain', group='B-scan')
|
||||
start_freq_mhz: float = setting(100.0, '_bscan_start_freq_mhz', live='bscan_start_freq_mhz', group='B-scan')
|
||||
stop_freq_mhz: float = setting(8800.0, '_bscan_stop_freq_mhz', live='bscan_stop_freq_mhz', group='B-scan')
|
||||
subtract_mean_ascan: bool = setting(False, '_bscan_subtract_mean_ascan', web='subtract_mean_ascan', group='Display')
|
||||
# How many past sweeps the B-scan heatmap renders once acquisition is stopped.
|
||||
# While running the window stays at the C++ replay window (see
|
||||
# `_cpp_bscan_replay_window_for_config`); this only widens the stopped-mode view.
|
||||
history_window_scans: int = 50
|
||||
history_window_scans: int = setting(50, '_bscan_history_window')
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class GuiGprStateModel:
|
||||
"""UI-only defaults for coherent backprojection GPR live settings."""
|
||||
|
||||
input_positions: str = ""
|
||||
output_positions: str = ""
|
||||
min_depth_m: float = 2.0
|
||||
max_depth_m: float = 14.0
|
||||
range_comp_power: float = 0.1
|
||||
# BP object-detection stop level, fraction of the global peak (Horns_motion_3libre.py
|
||||
# BP_OBJECT_MIN_FRAC). Angle compensation and permittivity are fixed for coherent BP
|
||||
# (Python 0.3 block), so they are not exposed here.
|
||||
object_min_frac: float = 0.7
|
||||
score_mode: str = "combined"
|
||||
motion_mode: str = "int_minus"
|
||||
input_positions: str = setting("", '_gpr_input_positions_input', live='gpr_input_positions', group='Geometry & depth')
|
||||
output_positions: str = setting("", '_gpr_output_positions_input', live='gpr_output_positions', group='Geometry & depth')
|
||||
min_depth_m: float = setting(2.0, '_gpr_min_depth_m', live='gpr_min_depth_m', group='Geometry & depth')
|
||||
max_depth_m: float = setting(14.0, '_gpr_max_depth_m', live='gpr_max_depth_m', group='Geometry & depth')
|
||||
range_comp_power: float = setting(0.1, '_gpr_range_comp_power', live='gpr_range_comp_power', group='Imaging', minimum=0)
|
||||
# BP object-detection stop level, fraction of the global peak. Angle compensation and
|
||||
# permittivity are fixed for coherent BP, so they are not exposed here.
|
||||
object_min_frac: float = setting(0.7, '_gpr_object_min_frac', live='gpr_object_min_frac', group='Detection', minimum=0, maximum=1)
|
||||
score_mode: str = setting("combined", '_gpr_score_mode', live='gpr_score_mode', group='Imaging', choices=('peak', 'combined'))
|
||||
motion_mode: str = setting("int_minus", '_gpr_motion_mode', live='gpr_motion_mode', group='Motion', choices=('int_minus', 'int_focus'))
|
||||
# Intra-sweep motion-correction inputs. Sweep time is derived from acquisition
|
||||
# metadata; speed comes from the socket unless `ignore_socket_speed_enabled`,
|
||||
# in which case `speed_m_s` from here is used.
|
||||
look_angle_deg: float = 0.0
|
||||
direction_sign: float = 1.0
|
||||
speed_m_s: float = 0.0
|
||||
ignore_socket_speed_enabled: bool = False
|
||||
max_detected_objects_to_draw: int = 5
|
||||
draw_top_m_objects: int = 2
|
||||
look_angle_deg: float = setting(0.0, '_gpr_look_angle_deg', live='gpr_look_angle_deg', group='Motion')
|
||||
direction_sign: float = setting(1.0, '_gpr_direction_sign', live='gpr_direction_sign', group='Motion')
|
||||
speed_m_s: float = setting(0.0, '_gpr_speed_m_s', live='gpr_speed_m_s', group='Motion')
|
||||
ignore_socket_speed_enabled: bool = setting(False, '_gpr_ignore_socket_speed_enabled', live='ignore_socket_speed', group='Motion')
|
||||
max_detected_objects_to_draw: int = setting(
|
||||
5, '_gpr_max_detected_objects_to_draw', live='gpr_max_detected_objects_to_draw', group='Detection', minimum=0,
|
||||
)
|
||||
draw_top_m_objects: int = setting(2, '_gpr_draw_top_m_objects', live='gpr_draw_top_m_objects', group='Detection', minimum=0)
|
||||
# Cross-frame approach filter: show an object only after it persists as a
|
||||
# motion-consistent track this many consecutive frames (<= 1 disables it).
|
||||
object_approach_min_frames: int = 3
|
||||
start_freq_mhz: float = 3000.0
|
||||
stop_freq_mhz: float = 6000.0
|
||||
background_subtract_enabled: bool = True
|
||||
background_mean_count: int = 10
|
||||
remove_sidelobe_objects_enabled: bool = True
|
||||
imaging_plane_y_m: float = 0.0
|
||||
render_mode: str = "heatmap"
|
||||
visible_x_min_m: float = -2.0
|
||||
visible_x_max_m: float = 2.0
|
||||
visible_z_min_m: float = 0.0
|
||||
visible_z_max_m: float = 14.0
|
||||
object_approach_min_frames: int = setting(
|
||||
3, '_gpr_object_approach_min_frames', live='gpr_object_approach_min_frames', group='Detection', minimum=1,
|
||||
)
|
||||
start_freq_mhz: float = setting(3000.0, '_gpr_start_freq_mhz', live='gpr_start_freq_mhz', group='Geometry & depth')
|
||||
stop_freq_mhz: float = setting(6000.0, '_gpr_stop_freq_mhz', live='gpr_stop_freq_mhz', group='Geometry & depth')
|
||||
background_subtract_enabled: bool = setting(True, '_gpr_background_subtract_enabled', live='gpr_background_subtract_enabled', group='Imaging')
|
||||
background_mean_count: int = setting(10, '_gpr_background_mean_count', live='gpr_background_mean_count', group='Imaging')
|
||||
remove_sidelobe_objects_enabled: bool = setting(
|
||||
True, '_gpr_remove_sidelobe_objects_enabled', live='gpr_remove_sidelobe_objects_enabled', group='Imaging',
|
||||
)
|
||||
imaging_plane_y_m: float = setting(0.0, '_gpr_imaging_plane_y_m', live='gpr_imaging_plane_y_m', group='Geometry & depth')
|
||||
render_mode: str = setting("heatmap", '_gpr_render_mode', web='render_mode', group='Display', choices=('heatmap', 'objects_only'))
|
||||
visible_x_min_m: float = setting(-2.0, '_gpr_visible_x_min_m', live='gpr_visible_x_min_m', group='Visible window')
|
||||
visible_x_max_m: float = setting(2.0, '_gpr_visible_x_max_m', live='gpr_visible_x_max_m', group='Visible window')
|
||||
visible_z_min_m: float = setting(0.0, '_gpr_visible_z_min_m', live='gpr_visible_z_min_m', group='Visible window')
|
||||
visible_z_max_m: float = setting(14.0, '_gpr_visible_z_max_m', live='gpr_visible_z_max_m', group='Visible window')
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class GuiLegacyGprStateModel:
|
||||
"""UI-only defaults for legacy point/extended GPR live settings."""
|
||||
|
||||
mode: str = "point"
|
||||
input_positions: str = ""
|
||||
output_positions: str = ""
|
||||
min_depth_m: float = 2.0
|
||||
max_depth_m: float = 14.0
|
||||
comp_power: float = 0.2
|
||||
start_freq_mhz: float = 3000.0
|
||||
stop_freq_mhz: float = 6000.0
|
||||
speed_m_s: float = 0.0
|
||||
ignore_socket_speed_enabled: bool = False
|
||||
look_angle_deg: float = 0.0
|
||||
apply_freq_phase_correction: bool = True
|
||||
reference_mode: str = "frame_center"
|
||||
snr_thresh: float = 4.5
|
||||
snr_comp_max: float = 25.0
|
||||
background_subtract_enabled: bool = True
|
||||
background_mean_count: int = 10
|
||||
render_mode: str = "heatmap"
|
||||
min_visible_pair_count: int = 1
|
||||
visible_x_min_m: float = -2.0
|
||||
visible_x_max_m: float = 2.0
|
||||
visible_z_min_m: float = 0.0
|
||||
visible_z_max_m: float = 14.0
|
||||
mode: str = setting("point", '_legacy_gpr_config_mode', live='legacy_gpr_mode', group='Mode', choices=('point', 'extended'))
|
||||
input_positions: str = setting("", '_legacy_gpr_input_positions_input', live='gpr_input_positions', group='Geometry & depth')
|
||||
output_positions: str = setting("", '_legacy_gpr_output_positions_input', live='gpr_output_positions', group='Geometry & depth')
|
||||
min_depth_m: float = setting(2.0, '_legacy_gpr_min_depth_m', live='gpr_min_depth_m', group='Geometry & depth')
|
||||
max_depth_m: float = setting(14.0, '_legacy_gpr_max_depth_m', live='gpr_max_depth_m', group='Geometry & depth')
|
||||
comp_power: float = setting(0.2, '_legacy_gpr_comp_power', live='gpr_comp_power', group='Detection', minimum=0)
|
||||
start_freq_mhz: float = setting(3000.0, '_legacy_gpr_start_freq_mhz', live='gpr_start_freq_mhz', group='Geometry & depth')
|
||||
stop_freq_mhz: float = setting(6000.0, '_legacy_gpr_stop_freq_mhz', live='gpr_stop_freq_mhz', group='Geometry & depth')
|
||||
speed_m_s: float = setting(0.0, '_legacy_gpr_speed_m_s', live='gpr_speed_m_s', group='Motion')
|
||||
ignore_socket_speed_enabled: bool = setting(False, '_legacy_gpr_ignore_socket_speed_enabled', live='ignore_socket_speed', group='Motion')
|
||||
look_angle_deg: float = setting(0.0, '_legacy_gpr_look_angle_deg', live='gpr_look_angle_deg', group='Motion')
|
||||
apply_freq_phase_correction: bool = setting(
|
||||
True, '_legacy_gpr_apply_freq_phase_correction', live='gpr_apply_freq_phase_correction', group='Motion',
|
||||
)
|
||||
reference_mode: str = setting(
|
||||
"frame_center", '_legacy_gpr_reference_mode', live='gpr_reference_mode', group='Motion', choices=('frame_center', 'first_tx_event'),
|
||||
)
|
||||
snr_thresh: float = setting(4.5, '_legacy_gpr_snr_thresh', live='gpr_snr_thresh', group='Detection', minimum=0)
|
||||
snr_comp_max: float = setting(25.0, '_legacy_gpr_snr_comp_max', live='gpr_snr_comp_max', group='Detection', minimum=0)
|
||||
background_subtract_enabled: bool = setting(
|
||||
True, '_legacy_gpr_background_subtract_enabled', live='gpr_background_subtract_enabled', group='Imaging',
|
||||
)
|
||||
background_mean_count: int = setting(10, '_legacy_gpr_background_mean_count', live='gpr_background_mean_count', group='Imaging')
|
||||
render_mode: str = setting("heatmap", '_legacy_gpr_render_mode', web='render_mode', group='Display', choices=('heatmap', 'objects_only'))
|
||||
min_visible_pair_count: int = setting(
|
||||
1, '_legacy_gpr_min_visible_pair_count', live='legacy_gpr_min_visible_pair_count', group='Detection', minimum=1,
|
||||
)
|
||||
visible_x_min_m: float = setting(-2.0, '_legacy_gpr_visible_x_min_m', live='gpr_visible_x_min_m', group='Visible window')
|
||||
visible_x_max_m: float = setting(2.0, '_legacy_gpr_visible_x_max_m', live='gpr_visible_x_max_m', group='Visible window')
|
||||
visible_z_min_m: float = setting(0.0, '_legacy_gpr_visible_z_min_m', live='gpr_visible_z_min_m', group='Visible window')
|
||||
visible_z_max_m: float = setting(14.0, '_legacy_gpr_visible_z_max_m', live='gpr_visible_z_max_m', group='Visible window')
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
@@ -131,28 +174,30 @@ class GuiSar3dStateModel:
|
||||
names the GUI shows: INPUT_IDX, F_START, MIN_DEPTH, LOOK_ANGLE_DEG, ...
|
||||
"""
|
||||
|
||||
input_positions: str = ""
|
||||
output_positions: str = ""
|
||||
min_depth_m: float = 2.0
|
||||
max_depth_m: float = 8.0
|
||||
start_freq_mhz: float = 1500.0
|
||||
stop_freq_mhz: float = 6000.0
|
||||
speed_m_s: float = 0.0
|
||||
ignore_socket_speed_enabled: bool = False
|
||||
look_angle_deg: float = 19.5
|
||||
frame_radius: int = 0
|
||||
voxel_m: float = 0.02
|
||||
bp_ground_depth_m: float = 1.25
|
||||
comp_range_power: float = 0.3
|
||||
use_compensation: bool = True
|
||||
normalize_each_frame: bool = True
|
||||
input_positions: str = setting("", '_sar3d_input_positions_input', live='gpr_input_positions', group='Geometry & depth')
|
||||
output_positions: str = setting("", '_sar3d_output_positions_input', live='gpr_output_positions', group='Geometry & depth')
|
||||
min_depth_m: float = setting(2.0, '_sar3d_min_depth_m', live='gpr_min_depth_m', group='Geometry & depth')
|
||||
max_depth_m: float = setting(8.0, '_sar3d_max_depth_m', live='gpr_max_depth_m', group='Geometry & depth')
|
||||
start_freq_mhz: float = setting(1500.0, '_sar3d_start_freq_mhz', live='gpr_start_freq_mhz', group='Geometry & depth')
|
||||
stop_freq_mhz: float = setting(6000.0, '_sar3d_stop_freq_mhz', live='gpr_stop_freq_mhz', group='Geometry & depth')
|
||||
speed_m_s: float = setting(0.0, '_sar3d_speed_m_s', live='gpr_speed_m_s', group='Motion')
|
||||
ignore_socket_speed_enabled: bool = setting(False, '_sar3d_ignore_socket_speed_enabled', live='ignore_socket_speed', group='Motion')
|
||||
look_angle_deg: float = setting(19.5, '_sar3d_look_angle_deg', live='gpr_look_angle_deg', group='Motion')
|
||||
frame_radius: int = setting(0, '_sar3d_frame_radius', live='sar3d_frame_radius', group='3D SAR', minimum=0)
|
||||
voxel_m: float = setting(0.02, '_sar3d_voxel_m', live='sar3d_voxel_m', group='3D SAR', minimum=0, exclusive_minimum=True)
|
||||
bp_ground_depth_m: float = setting(1.25, '_sar3d_bp_ground_depth_m', live='sar3d_bp_ground_depth_m', group='3D SAR')
|
||||
comp_range_power: float = setting(0.3, '_sar3d_comp_range_power', live='sar3d_comp_range_power', group='3D SAR')
|
||||
use_compensation: bool = setting(True, '_sar3d_use_compensation', live='sar3d_use_compensation', group='3D SAR')
|
||||
normalize_each_frame: bool = setting(True, '_sar3d_normalize_each_frame', live='sar3d_normalize_each_frame', group='3D SAR')
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class GuiProcessingStateModel:
|
||||
"""UI-only processing-section defaults."""
|
||||
|
||||
selected_mode: str = "pass_through"
|
||||
selected_mode: str = setting(
|
||||
"pass_through", '_processing_mode', live='processor_mode', group='Processor', choices=('pass_through', 'bscan', 'gpr', 'legacy_gpr', 'sar3d'),
|
||||
)
|
||||
pass_through: GuiPassThroughStateModel = field(default_factory=GuiPassThroughStateModel)
|
||||
bscan: GuiBscanStateModel = field(default_factory=GuiBscanStateModel)
|
||||
gpr: GuiGprStateModel = field(default_factory=GuiGprStateModel)
|
||||
@@ -164,21 +209,21 @@ class GuiProcessingStateModel:
|
||||
class GuiDataActionsStateModel:
|
||||
"""UI-only defaults for snapshot/export controls."""
|
||||
|
||||
save_path: str = ""
|
||||
save_name: str = "snapshot_manual"
|
||||
save_path: str = setting("", '_save_path_input')
|
||||
save_name: str = setting("snapshot_manual", '_save_name_input')
|
||||
# How many freshly acquired measurements the "Start + Record" action writes to
|
||||
# disk before it stops recording (acquisition keeps running).
|
||||
record_count: int = 100
|
||||
record_count: int = setting(100, '_record_count')
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class GuiPreprocessDialogStateModel:
|
||||
"""UI-only defaults for preprocessing dialog controls."""
|
||||
|
||||
set_name: str = "set_001"
|
||||
radar_config_dir: str = ""
|
||||
use_all_radar_configs: bool = False
|
||||
median_sweep_count: int = 5
|
||||
set_name: str = setting("set_001")
|
||||
radar_config_dir: str = setting("")
|
||||
use_all_radar_configs: bool = setting(False)
|
||||
median_sweep_count: int = setting(5)
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
@@ -227,3 +272,10 @@ class GuiProfileModel:
|
||||
from python_app.models.gui_profile_codec import gui_profile_to_dict
|
||||
|
||||
return gui_profile_to_dict(self)
|
||||
|
||||
|
||||
class ProfileWriter(Protocol):
|
||||
"""Publish the current profile, optionally using the acquisition provenance."""
|
||||
|
||||
def __call__(self, path: Path, /, *, allow_overwrite: bool = True,
|
||||
dataset: bool = False) -> GuiProfileModel: ...
|
||||
|
||||
Reference in New Issue
Block a user