Update GPR and SAR3D processing
This commit is contained in:
@@ -10,7 +10,7 @@ from python_app.models.json_fields import finite_number, json_object
|
||||
from python_app.models.run_config_schema import RunConfigModel
|
||||
|
||||
|
||||
def _decode_state(model: object, payload: dict[str, Any], context: str, *, check_constraints: bool = True) -> None:
|
||||
def _decode_state(model: object, payload: dict[str, Any], context: str) -> None:
|
||||
"""Read known fields recursively; unknown fields do not survive re-encoding."""
|
||||
if not is_dataclass(model) or isinstance(model, type):
|
||||
raise TypeError("GUI state must be a dataclass instance")
|
||||
@@ -19,14 +19,14 @@ def _decode_state(model: object, payload: dict[str, Any], context: str, *, check
|
||||
value = payload.get(member.name, default)
|
||||
name = f"{context}.{member.name}"
|
||||
if is_dataclass(default):
|
||||
_decode_state(default, json_object(payload.get(member.name), name), name, check_constraints=check_constraints)
|
||||
_decode_state(default, json_object(payload.get(member.name), name), name)
|
||||
continue
|
||||
if isinstance(default, float):
|
||||
value = finite_number(value, name)
|
||||
elif type(value) is not type(default):
|
||||
raise ValueError(f"{name} must be a JSON {type(default).__name__}")
|
||||
spec = member.metadata.get("setting")
|
||||
if check_constraints and isinstance(spec, Setting):
|
||||
if isinstance(spec, Setting):
|
||||
if spec.choices and value not in spec.choices:
|
||||
raise ValueError(f"{name} must be one of: {', '.join(spec.choices)}")
|
||||
if spec.minimum is not None:
|
||||
@@ -39,33 +39,16 @@ def _decode_state(model: object, payload: dict[str, Any], context: str, *, check
|
||||
|
||||
|
||||
def gui_profile_from_dict(payload: dict[str, Any]) -> GuiProfileModel:
|
||||
"""Read the pipeline config and optional GUI state, preserving old GPR profiles."""
|
||||
"""Read the pipeline config and optional GUI state; ignore unknown fields."""
|
||||
profile = GuiProfileModel(run_config=RunConfigModel.from_dict(payload))
|
||||
if payload.get("gui") is None:
|
||||
return profile
|
||||
gui_object = json_object(payload["gui"], "gui")
|
||||
processing = json_object(gui_object.get("processing"), "gui.processing")
|
||||
old_gpr = json_object(processing.get("gpr"), "gui.processing.gpr")
|
||||
gui = GuiStateModel()
|
||||
|
||||
# Older profiles kept both algorithms under gpr. Root mode and the short-lived
|
||||
# algorithm selector choose the legacy page; explicit legacy values win.
|
||||
root_gpr = payload.get("gpr")
|
||||
root_mode = root_gpr.get("mode") if isinstance(root_gpr, dict) else None
|
||||
algorithm_mode = {"legacy_point": "point", "legacy_extended": "extended"}.get(
|
||||
str(old_gpr.get("algorithm", "")))
|
||||
if root_mode in ("point", "extended"):
|
||||
gui.processing.legacy_gpr.mode = root_mode
|
||||
if algorithm_mode is not None:
|
||||
gui.processing.legacy_gpr.mode = algorithm_mode
|
||||
_decode_state(gui.processing.legacy_gpr, old_gpr, "gui.processing.gpr", check_constraints=False)
|
||||
_decode_state(gui, gui_object, "gui")
|
||||
if gui.version != 1:
|
||||
raise ValueError(f"Unsupported gui.version: {gui.version}")
|
||||
if gui.processing.selected_mode == "gpr" and (
|
||||
algorithm_mode is not None or root_mode in ("point", "extended")
|
||||
):
|
||||
gui.processing.selected_mode = "legacy_gpr"
|
||||
gui.preprocess_dialog.median_sweep_count = max(1, gui.preprocess_dialog.median_sweep_count)
|
||||
profile.gui = gui
|
||||
return profile
|
||||
|
||||
@@ -121,7 +121,8 @@ class GuiGprStateModel:
|
||||
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')
|
||||
bp_ground_depth_m: float = setting(1.25, '_gpr_bp_ground_depth_m', live='gpr_bp_ground_depth_m', group='Geometry & depth', minimum=0)
|
||||
pair_depth_equalize: bool = setting(True, '_gpr_pair_depth_equalize', live='pair_depth_equalize', group='Imaging')
|
||||
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')
|
||||
@@ -129,48 +130,11 @@ class GuiGprStateModel:
|
||||
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 = 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)
|
||||
class GuiSar3dStateModel:
|
||||
"""UI-only defaults for the 3D SAR (sar3d) live settings.
|
||||
|
||||
Values are the reference notebook's (Multi_antenna_3D_clean_v1.ipynb), whose
|
||||
Values are the reference notebook's (Multi_antenna_demonstrate_depthnorm.ipynb), whose
|
||||
names the GUI shows: INPUT_IDX, F_START, MIN_DEPTH, LOOK_ANGLE_DEG, ...
|
||||
"""
|
||||
|
||||
@@ -189,6 +153,15 @@ class GuiSar3dStateModel:
|
||||
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')
|
||||
pair_depth_equalize: bool = setting(True, '_sar3d_pair_depth_equalize', live='pair_depth_equalize', group='3D SAR')
|
||||
background_subtract_enabled: bool = setting(True, '_sar3d_background_subtract_enabled', live='gpr_background_subtract_enabled', group='3D SAR')
|
||||
background_mean_count: int = setting(10, '_sar3d_background_mean_count', live='gpr_background_mean_count', group='3D SAR', minimum=0)
|
||||
x_size_m: float = setting(0.0, '_sar3d_x_size_m', live='sar3d_x_size_m', group='Scene', minimum=0)
|
||||
y_min_m: float = setting(-0.5, '_sar3d_y_min_m', live='sar3d_y_min_m', group='Scene')
|
||||
y_max_m: float = setting(2.0, '_sar3d_y_max_m', live='sar3d_y_max_m', group='Scene')
|
||||
z_size_m: float = setting(0.0, '_sar3d_z_size_m', live='sar3d_z_size_m', group='Scene', minimum=0)
|
||||
cloud_threshold: float = setting(0.65, '_sar3d_cloud_threshold', web='sar3d_cloud_threshold', group='Display', minimum=0, maximum=1)
|
||||
zoom_percent: int = setting(100, '_sar3d_zoom_percent', web='sar3d_zoom_percent', group='Display', minimum=100, maximum=1000)
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
@@ -196,12 +169,11 @@ class GuiProcessingStateModel:
|
||||
"""UI-only processing-section defaults."""
|
||||
|
||||
selected_mode: str = setting(
|
||||
"pass_through", '_processing_mode', live='processor_mode', group='Processor', choices=('pass_through', 'bscan', 'gpr', 'legacy_gpr', 'sar3d'),
|
||||
"pass_through", '_processing_mode', live='processor_mode', group='Processor', choices=('pass_through', 'bscan', 'gpr', 'sar3d'),
|
||||
)
|
||||
pass_through: GuiPassThroughStateModel = field(default_factory=GuiPassThroughStateModel)
|
||||
bscan: GuiBscanStateModel = field(default_factory=GuiBscanStateModel)
|
||||
gpr: GuiGprStateModel = field(default_factory=GuiGprStateModel)
|
||||
legacy_gpr: GuiLegacyGprStateModel = field(default_factory=GuiLegacyGprStateModel)
|
||||
sar3d: GuiSar3dStateModel = field(default_factory=GuiSar3dStateModel)
|
||||
|
||||
|
||||
|
||||
@@ -343,9 +343,6 @@ def run_config_from_dict(payload: object) -> RunConfigModel:
|
||||
if model.preprocess.notch.taper_type not in ("cosine", "hard"):
|
||||
raise ValueError("preprocess.notch.taper_type must be cosine or hard")
|
||||
|
||||
model.gpr.relative_permittivity = _read_float(
|
||||
gpr_payload, "relative_permittivity", model.gpr.relative_permittivity
|
||||
)
|
||||
model.gpr.tx_geometry = []
|
||||
for entry in _as_list(gpr_payload.get("tx_geometry"), "gpr.tx_geometry"):
|
||||
entry_payload = _as_dict(entry, "gpr.tx_geometry[]")
|
||||
|
||||
@@ -304,7 +304,6 @@ class GprRxGeometryModel:
|
||||
class GprModel:
|
||||
"""Stable GPR configuration saved in run_config.json."""
|
||||
|
||||
relative_permittivity: float = 1.0
|
||||
tx_geometry: list[GprTxGeometryModel] = field(default_factory=list)
|
||||
rx_geometry: list[GprRxGeometryModel] = field(default_factory=list)
|
||||
|
||||
|
||||
@@ -288,8 +288,7 @@ def validate_gpr_model(
|
||||
) -> None:
|
||||
"""Validate stable GPR config against current switch dimensions.
|
||||
|
||||
Checks that the relative permittivity is positive and that every tx/rx
|
||||
geometry entry indexes a valid, non-duplicate switch position. When ``sweep``
|
||||
Checks that every tx/rx geometry entry indexes a valid, non-duplicate switch position. When ``sweep``
|
||||
is supplied (the load and GUI/save paths share this chokepoint), its bounds
|
||||
are validated here too so sweep failures surface alongside the GPR checks
|
||||
instead of as a C++ boot crash.
|
||||
@@ -297,9 +296,6 @@ def validate_gpr_model(
|
||||
if sweep is not None:
|
||||
validate_sweep_model(sweep)
|
||||
|
||||
if not math.isfinite(gpr.relative_permittivity) or float(gpr.relative_permittivity) <= 0.0:
|
||||
raise ValueError("gpr.relative_permittivity must be > 0")
|
||||
|
||||
normalize_antenna_geometry(gpr.tx_geometry, "output_pos", output_switch_positions)
|
||||
normalize_antenna_geometry(gpr.rx_geometry, "input_pos", input_switch_positions)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user