sar3d: python plumbing for the new parameters and real replay timing

- replay_raw_producer carries the RECORDED per-trace capture windows
  from each snapshot's meta.json (re-anchored by the same shift as the
  frame time) instead of one synthetic window for all traces — without
  this the segmented timing model degenerates in replay;
- GUI/profile/orchestration swap the removed window knobs
  (center_depth, half_width_*, grid_n*) for the notebook parameters
  (ground depth, pitch override, voxel pitch, Y range, X margin, norm
  edge margin, smooth sigma, comp power, tx per segment).

Headless replay smoke on the 20260922_mine_fast_true dataset passes:
51 sar3d result collections, transparent preprocessor.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
awe
2026-09-25 16:42:14 +03:00
co-authored by Claude Fable 5
parent b8cb572bc4
commit e1dfbec712
9 changed files with 345 additions and 149 deletions
@@ -90,14 +90,19 @@ _WEB_LIVE_SCHEMA = [
("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"})), ("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_frame_radius", "3D SAR", ("sar3d",), _attr("_sar3d_frame_radius")),
("sar3d_edge_frame_weight", "3D SAR", ("sar3d",), _attr("_sar3d_edge_frame_weight")), ("sar3d_edge_frame_weight", "3D SAR", ("sar3d",), _attr("_sar3d_edge_frame_weight")),
("sar3d_pitch_override_enabled", "3D SAR", ("sar3d",), _attr("_sar3d_pitch_override_enabled")),
("sar3d_pitch_deg", "3D SAR", ("sar3d",), _attr("_sar3d_pitch_deg")), ("sar3d_pitch_deg", "3D SAR", ("sar3d",), _attr("_sar3d_pitch_deg")),
("sar3d_center_depth_m", "3D SAR", ("sar3d",), _attr("_sar3d_center_depth_m")), ("sar3d_ground_depth_m", "3D SAR", ("sar3d",), _attr("_sar3d_ground_depth_m")),
("sar3d_half_width_x_m", "3D SAR", ("sar3d",), _attr("_sar3d_half_width_x_m")), ("sar3d_x_margin_m", "3D SAR", ("sar3d",), _attr("_sar3d_x_margin_m")),
("sar3d_half_width_y_m", "3D SAR", ("sar3d",), _attr("_sar3d_half_width_y_m")), ("sar3d_y_min_m", "3D SAR", ("sar3d",), _attr("_sar3d_y_min_m")),
("sar3d_half_width_z_m", "3D SAR", ("sar3d",), _attr("_sar3d_half_width_z_m")), ("sar3d_y_max_m", "3D SAR", ("sar3d",), _attr("_sar3d_y_max_m")),
("sar3d_grid_nx", "3D SAR", ("sar3d",), _attr("_sar3d_grid_nx")), ("sar3d_voxel_x_m", "3D SAR", ("sar3d",), _attr("_sar3d_voxel_x_m")),
("sar3d_grid_ny", "3D SAR", ("sar3d",), _attr("_sar3d_grid_ny")), ("sar3d_voxel_y_m", "3D SAR", ("sar3d",), _attr("_sar3d_voxel_y_m")),
("sar3d_grid_nz", "3D SAR", ("sar3d",), _attr("_sar3d_grid_nz")), ("sar3d_voxel_z_m", "3D SAR", ("sar3d",), _attr("_sar3d_voxel_z_m")),
("sar3d_norm_edge_margin_m", "3D SAR", ("sar3d",), _attr("_sar3d_norm_edge_margin_m")),
("sar3d_smooth_sigma", "3D SAR", ("sar3d",), _attr("_sar3d_smooth_sigma")),
("sar3d_comp_range_power", "3D SAR", ("sar3d",), _attr("_sar3d_comp_range_power")),
("sar3d_tx_per_segment", "3D SAR", ("sar3d",), _attr("_sar3d_tx_per_segment")),
("sar3d_use_compensation", "3D SAR", ("sar3d",), _attr("_sar3d_use_compensation")), ("sar3d_use_compensation", "3D SAR", ("sar3d",), _attr("_sar3d_use_compensation")),
("sar3d_normalize_pairs", "3D SAR", ("sar3d",), _attr("_sar3d_normalize_pairs")), ("sar3d_normalize_pairs", "3D SAR", ("sar3d",), _attr("_sar3d_normalize_pairs")),
("sar3d_bottom_view_deg", "3D SAR", ("sar3d",), _attr("_sar3d_bottom_view")), ("sar3d_bottom_view_deg", "3D SAR", ("sar3d",), _attr("_sar3d_bottom_view")),
@@ -553,9 +558,8 @@ class AppWindowLiveProcessingMixin:
f"depth={self._sar3d_min_depth_m.value():g}..{self._sar3d_max_depth_m.value():g} m, " 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"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"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"voxel={self._sar3d_voxel_x_m.value():g}x{self._sar3d_voxel_y_m.value():g}x{self._sar3d_voxel_z_m.value():g} m, "
f"center_depth={self._sar3d_center_depth_m.value():g} m, " f"ground_depth={self._sar3d_ground_depth_m.value():g} m)"
f"pitch={self._sar3d_pitch_deg.value():g} deg)"
) )
def _clear_history_mode_caches(self) -> None: def _clear_history_mode_caches(self) -> None:
@@ -350,14 +350,19 @@ class AppWindowConfigProfileIOMixin:
self._sar3d_apply_freq_phase_correction, self._sar3d_apply_freq_phase_correction,
self._sar3d_frame_radius, self._sar3d_frame_radius,
self._sar3d_edge_frame_weight, self._sar3d_edge_frame_weight,
self._sar3d_pitch_override_enabled,
self._sar3d_pitch_deg, self._sar3d_pitch_deg,
self._sar3d_center_depth_m, self._sar3d_ground_depth_m,
self._sar3d_half_width_x_m, self._sar3d_x_margin_m,
self._sar3d_half_width_y_m, self._sar3d_y_min_m,
self._sar3d_half_width_z_m, self._sar3d_y_max_m,
self._sar3d_grid_nx, self._sar3d_voxel_x_m,
self._sar3d_grid_ny, self._sar3d_voxel_y_m,
self._sar3d_grid_nz, self._sar3d_voxel_z_m,
self._sar3d_norm_edge_margin_m,
self._sar3d_smooth_sigma,
self._sar3d_comp_range_power,
self._sar3d_tx_per_segment,
self._sar3d_use_compensation, self._sar3d_use_compensation,
self._sar3d_normalize_pairs, self._sar3d_normalize_pairs,
self._save_count, self._save_count,
@@ -562,14 +567,19 @@ class AppWindowConfigProfileIOMixin:
self._sar3d_apply_freq_phase_correction.setChecked(bool(sar3d_state.apply_freq_phase_correction)) self._sar3d_apply_freq_phase_correction.setChecked(bool(sar3d_state.apply_freq_phase_correction))
self._sar3d_frame_radius.setValue(int(sar3d_state.frame_radius)) self._sar3d_frame_radius.setValue(int(sar3d_state.frame_radius))
self._sar3d_edge_frame_weight.setValue(float(sar3d_state.edge_frame_weight)) self._sar3d_edge_frame_weight.setValue(float(sar3d_state.edge_frame_weight))
self._sar3d_pitch_override_enabled.setChecked(bool(sar3d_state.pitch_override_enabled))
self._sar3d_pitch_deg.setValue(float(sar3d_state.pitch_deg)) self._sar3d_pitch_deg.setValue(float(sar3d_state.pitch_deg))
self._sar3d_center_depth_m.setValue(float(sar3d_state.center_depth_m)) self._sar3d_ground_depth_m.setValue(float(sar3d_state.ground_depth_m))
self._sar3d_half_width_x_m.setValue(float(sar3d_state.half_width_x_m)) self._sar3d_x_margin_m.setValue(float(sar3d_state.x_margin_m))
self._sar3d_half_width_y_m.setValue(float(sar3d_state.half_width_y_m)) self._sar3d_y_min_m.setValue(float(sar3d_state.y_min_m))
self._sar3d_half_width_z_m.setValue(float(sar3d_state.half_width_z_m)) self._sar3d_y_max_m.setValue(float(sar3d_state.y_max_m))
self._sar3d_grid_nx.setValue(int(sar3d_state.grid_nx)) self._sar3d_voxel_x_m.setValue(float(sar3d_state.voxel_x_m))
self._sar3d_grid_ny.setValue(int(sar3d_state.grid_ny)) self._sar3d_voxel_y_m.setValue(float(sar3d_state.voxel_y_m))
self._sar3d_grid_nz.setValue(int(sar3d_state.grid_nz)) self._sar3d_voxel_z_m.setValue(float(sar3d_state.voxel_z_m))
self._sar3d_norm_edge_margin_m.setValue(float(sar3d_state.norm_edge_margin_m))
self._sar3d_smooth_sigma.setValue(float(sar3d_state.smooth_sigma))
self._sar3d_comp_range_power.setValue(float(sar3d_state.comp_range_power))
self._sar3d_tx_per_segment.setValue(int(sar3d_state.tx_per_segment))
self._sar3d_use_compensation.setChecked(bool(sar3d_state.use_compensation)) self._sar3d_use_compensation.setChecked(bool(sar3d_state.use_compensation))
self._sar3d_normalize_pairs.setChecked(bool(sar3d_state.normalize_pairs)) self._sar3d_normalize_pairs.setChecked(bool(sar3d_state.normalize_pairs))
self._set_combo_current_text( self._set_combo_current_text(
@@ -438,14 +438,19 @@ class AppWindowConfigStateBuildersMixin:
apply_freq_phase_correction=bool(self._sar3d_apply_freq_phase_correction.isChecked()), apply_freq_phase_correction=bool(self._sar3d_apply_freq_phase_correction.isChecked()),
frame_radius=int(self._sar3d_frame_radius.value()), frame_radius=int(self._sar3d_frame_radius.value()),
edge_frame_weight=float(self._sar3d_edge_frame_weight.value()), edge_frame_weight=float(self._sar3d_edge_frame_weight.value()),
pitch_override_enabled=bool(self._sar3d_pitch_override_enabled.isChecked()),
pitch_deg=float(self._sar3d_pitch_deg.value()), pitch_deg=float(self._sar3d_pitch_deg.value()),
center_depth_m=float(self._sar3d_center_depth_m.value()), ground_depth_m=float(self._sar3d_ground_depth_m.value()),
half_width_x_m=float(self._sar3d_half_width_x_m.value()), x_margin_m=float(self._sar3d_x_margin_m.value()),
half_width_y_m=float(self._sar3d_half_width_y_m.value()), y_min_m=float(self._sar3d_y_min_m.value()),
half_width_z_m=float(self._sar3d_half_width_z_m.value()), y_max_m=float(self._sar3d_y_max_m.value()),
grid_nx=int(self._sar3d_grid_nx.value()), voxel_x_m=float(self._sar3d_voxel_x_m.value()),
grid_ny=int(self._sar3d_grid_ny.value()), voxel_y_m=float(self._sar3d_voxel_y_m.value()),
grid_nz=int(self._sar3d_grid_nz.value()), voxel_z_m=float(self._sar3d_voxel_z_m.value()),
norm_edge_margin_m=float(self._sar3d_norm_edge_margin_m.value()),
smooth_sigma=float(self._sar3d_smooth_sigma.value()),
comp_range_power=float(self._sar3d_comp_range_power.value()),
tx_per_segment=int(self._sar3d_tx_per_segment.value()),
use_compensation=bool(self._sar3d_use_compensation.isChecked()), use_compensation=bool(self._sar3d_use_compensation.isChecked()),
normalize_pairs=bool(self._sar3d_normalize_pairs.isChecked()), normalize_pairs=bool(self._sar3d_normalize_pairs.isChecked()),
bottom_view_deg=sar3d_bottom_view_deg_from_label( bottom_view_deg=sar3d_bottom_view_deg_from_label(
@@ -660,49 +660,97 @@ def build_processing_group(owner) -> QGroupBox:
"Weight of the outermost aperture frames (1 = flat window, 0 = strong taper)." "Weight of the outermost aperture frames (1 = flat window, 0 = strong taper)."
) )
owner._sar3d_pitch_override_enabled = QCheckBox("Override pitch")
owner._sar3d_pitch_override_enabled.setChecked(bool(sar3d_defaults.pitch_override_enabled))
owner._sar3d_pitch_override_enabled.setToolTip(
"When unchecked, the frame pitch follows the look angle (notebook "
"BP_GROUND_PITCH_DEG = None); check to set it separately below."
)
owner._sar3d_pitch_deg = QDoubleSpinBox() owner._sar3d_pitch_deg = QDoubleSpinBox()
owner._sar3d_pitch_deg.setDecimals(2) owner._sar3d_pitch_deg.setDecimals(2)
owner._sar3d_pitch_deg.setRange(-90.0, 90.0) owner._sar3d_pitch_deg.setRange(-90.0, 90.0)
owner._sar3d_pitch_deg.setSingleStep(0.5) owner._sar3d_pitch_deg.setSingleStep(0.5)
owner._sar3d_pitch_deg.setValue(float(sar3d_defaults.pitch_deg)) owner._sar3d_pitch_deg.setValue(float(sar3d_defaults.pitch_deg))
owner._sar3d_pitch_deg.setToolTip("Antenna pitch above the horizon (deg); tilts the imaged volume.") owner._sar3d_pitch_deg.setToolTip(
"Frame pitch (deg) used for ground coordinates and aperture offsets when the override is on."
)
owner._sar3d_center_depth_m = QDoubleSpinBox() owner._sar3d_ground_depth_m = QDoubleSpinBox()
owner._sar3d_center_depth_m.setDecimals(2) owner._sar3d_ground_depth_m.setDecimals(2)
owner._sar3d_center_depth_m.setRange(0.0, 50.0) owner._sar3d_ground_depth_m.setRange(0.0, 10.0)
owner._sar3d_center_depth_m.setSingleStep(0.1) owner._sar3d_ground_depth_m.setSingleStep(0.05)
owner._sar3d_center_depth_m.setValue(float(sar3d_defaults.center_depth_m)) owner._sar3d_ground_depth_m.setValue(float(sar3d_defaults.ground_depth_m))
owner._sar3d_center_depth_m.setToolTip("Range distance to the center of the imaged volume (m).") owner._sar3d_ground_depth_m.setToolTip(
"Height of the lower antenna row above the ground (m); scene Y=0 is the ground."
)
owner._sar3d_half_width_x_m = QDoubleSpinBox() owner._sar3d_x_margin_m = QDoubleSpinBox()
owner._sar3d_half_width_x_m.setDecimals(2) owner._sar3d_x_margin_m.setDecimals(2)
owner._sar3d_half_width_x_m.setRange(0.01, 50.0) owner._sar3d_x_margin_m.setRange(0.0, 20.0)
owner._sar3d_half_width_x_m.setSingleStep(0.1) owner._sar3d_x_margin_m.setSingleStep(0.1)
owner._sar3d_half_width_x_m.setValue(float(sar3d_defaults.half_width_x_m)) owner._sar3d_x_margin_m.setValue(float(sar3d_defaults.x_margin_m))
owner._sar3d_x_margin_m.setToolTip("Grid X margin beyond the antenna span (m).")
owner._sar3d_half_width_y_m = QDoubleSpinBox() owner._sar3d_y_min_m = QDoubleSpinBox()
owner._sar3d_half_width_y_m.setDecimals(2) owner._sar3d_y_min_m.setDecimals(2)
owner._sar3d_half_width_y_m.setRange(0.01, 50.0) owner._sar3d_y_min_m.setRange(-10.0, 10.0)
owner._sar3d_half_width_y_m.setSingleStep(0.1) owner._sar3d_y_min_m.setSingleStep(0.1)
owner._sar3d_half_width_y_m.setValue(float(sar3d_defaults.half_width_y_m)) owner._sar3d_y_min_m.setValue(float(sar3d_defaults.y_min_m))
owner._sar3d_half_width_z_m = QDoubleSpinBox() owner._sar3d_y_max_m = QDoubleSpinBox()
owner._sar3d_half_width_z_m.setDecimals(2) owner._sar3d_y_max_m.setDecimals(2)
owner._sar3d_half_width_z_m.setRange(0.01, 50.0) owner._sar3d_y_max_m.setRange(-10.0, 10.0)
owner._sar3d_half_width_z_m.setSingleStep(0.1) owner._sar3d_y_max_m.setSingleStep(0.1)
owner._sar3d_half_width_z_m.setValue(float(sar3d_defaults.half_width_z_m)) owner._sar3d_y_max_m.setValue(float(sar3d_defaults.y_max_m))
owner._sar3d_grid_nx = QSpinBox() owner._sar3d_voxel_x_m = QDoubleSpinBox()
owner._sar3d_grid_nx.setRange(2, 1024) owner._sar3d_voxel_x_m.setDecimals(3)
owner._sar3d_grid_nx.setValue(int(sar3d_defaults.grid_nx)) owner._sar3d_voxel_x_m.setRange(0.005, 1.0)
owner._sar3d_voxel_x_m.setSingleStep(0.005)
owner._sar3d_voxel_x_m.setValue(float(sar3d_defaults.voxel_x_m))
owner._sar3d_grid_ny = QSpinBox() owner._sar3d_voxel_y_m = QDoubleSpinBox()
owner._sar3d_grid_ny.setRange(2, 1024) owner._sar3d_voxel_y_m.setDecimals(3)
owner._sar3d_grid_ny.setValue(int(sar3d_defaults.grid_ny)) owner._sar3d_voxel_y_m.setRange(0.005, 1.0)
owner._sar3d_voxel_y_m.setSingleStep(0.005)
owner._sar3d_voxel_y_m.setValue(float(sar3d_defaults.voxel_y_m))
owner._sar3d_grid_nz = QSpinBox() owner._sar3d_voxel_z_m = QDoubleSpinBox()
owner._sar3d_grid_nz.setRange(2, 1024) owner._sar3d_voxel_z_m.setDecimals(3)
owner._sar3d_grid_nz.setValue(int(sar3d_defaults.grid_nz)) owner._sar3d_voxel_z_m.setRange(0.005, 1.0)
owner._sar3d_voxel_z_m.setSingleStep(0.005)
owner._sar3d_voxel_z_m.setValue(float(sar3d_defaults.voxel_z_m))
owner._sar3d_norm_edge_margin_m = QDoubleSpinBox()
owner._sar3d_norm_edge_margin_m.setDecimals(2)
owner._sar3d_norm_edge_margin_m.setRange(0.0, 5.0)
owner._sar3d_norm_edge_margin_m.setSingleStep(0.05)
owner._sar3d_norm_edge_margin_m.setValue(float(sar3d_defaults.norm_edge_margin_m))
owner._sar3d_norm_edge_margin_m.setToolTip(
"Grid Y edges excluded from volume normalization (constant-range ridge)."
)
owner._sar3d_smooth_sigma = QDoubleSpinBox()
owner._sar3d_smooth_sigma.setDecimals(2)
owner._sar3d_smooth_sigma.setRange(0.0, 10.0)
owner._sar3d_smooth_sigma.setSingleStep(0.25)
owner._sar3d_smooth_sigma.setValue(float(sar3d_defaults.smooth_sigma))
owner._sar3d_smooth_sigma.setToolTip("Gaussian smoothing sigma over (Z, X) planes, voxels.")
owner._sar3d_comp_range_power = QDoubleSpinBox()
owner._sar3d_comp_range_power.setDecimals(2)
owner._sar3d_comp_range_power.setRange(0.0, 2.0)
owner._sar3d_comp_range_power.setSingleStep(0.05)
owner._sar3d_comp_range_power.setValue(float(sar3d_defaults.comp_range_power))
owner._sar3d_comp_range_power.setToolTip("Geometric spreading compensation power.")
owner._sar3d_tx_per_segment = QSpinBox()
owner._sar3d_tx_per_segment.setRange(1, 64)
owner._sar3d_tx_per_segment.setValue(int(sar3d_defaults.tx_per_segment))
owner._sar3d_tx_per_segment.setToolTip(
"Fallback Tx-per-segment split of a frame when per-trace capture stamps are absent."
)
owner._sar3d_use_compensation = QCheckBox("Use per-voxel compensation") owner._sar3d_use_compensation = QCheckBox("Use per-voxel compensation")
owner._sar3d_use_compensation.setChecked(bool(sar3d_defaults.use_compensation)) owner._sar3d_use_compensation.setChecked(bool(sar3d_defaults.use_compensation))
@@ -738,19 +786,24 @@ def build_processing_group(owner) -> QGroupBox:
owner._sar3d_apply_freq_phase_correction, owner._sar3d_apply_freq_phase_correction,
("Frame radius", owner._sar3d_frame_radius), ("Frame radius", owner._sar3d_frame_radius),
("Edge frame weight", owner._sar3d_edge_frame_weight), ("Edge frame weight", owner._sar3d_edge_frame_weight),
owner._sar3d_pitch_override_enabled,
("Pitch deg", owner._sar3d_pitch_deg), ("Pitch deg", owner._sar3d_pitch_deg),
("Center depth m", owner._sar3d_center_depth_m), ("Ground depth m", owner._sar3d_ground_depth_m),
("Half width X m", owner._sar3d_half_width_x_m), ("X margin m", owner._sar3d_x_margin_m),
("Half width Y m", owner._sar3d_half_width_y_m), ("Y min m", owner._sar3d_y_min_m),
("Half width Z m", owner._sar3d_half_width_z_m), ("Y max m", owner._sar3d_y_max_m),
("Grid Nx", owner._sar3d_grid_nx), ("Voxel X m", owner._sar3d_voxel_x_m),
("Grid Ny", owner._sar3d_grid_ny), ("Voxel Y m", owner._sar3d_voxel_y_m),
("Grid Nz", owner._sar3d_grid_nz), ("Voxel Z m", owner._sar3d_voxel_z_m),
("Norm edge margin m", owner._sar3d_norm_edge_margin_m),
("Smooth sigma", owner._sar3d_smooth_sigma),
("Comp range power", owner._sar3d_comp_range_power),
("Tx per segment", owner._sar3d_tx_per_segment),
("Bottom view", owner._sar3d_bottom_view), ("Bottom view", owner._sar3d_bottom_view),
owner._sar3d_use_compensation, owner._sar3d_use_compensation,
owner._sar3d_normalize_pairs, owner._sar3d_normalize_pairs,
], ],
split_index=11, split_index=13,
) )
owner._processing_mode_pages.addWidget(sar3d_page) owner._processing_mode_pages.addWidget(sar3d_page)
@@ -839,14 +892,19 @@ def build_processing_group(owner) -> QGroupBox:
owner._sar3d_apply_freq_phase_correction.toggled.connect(owner._on_processing_live_settings_changed) owner._sar3d_apply_freq_phase_correction.toggled.connect(owner._on_processing_live_settings_changed)
owner._sar3d_frame_radius.valueChanged.connect(owner._on_processing_live_settings_changed) owner._sar3d_frame_radius.valueChanged.connect(owner._on_processing_live_settings_changed)
owner._sar3d_edge_frame_weight.valueChanged.connect(owner._on_processing_live_settings_changed) owner._sar3d_edge_frame_weight.valueChanged.connect(owner._on_processing_live_settings_changed)
owner._sar3d_pitch_override_enabled.toggled.connect(owner._on_processing_live_settings_changed)
owner._sar3d_pitch_deg.valueChanged.connect(owner._on_processing_live_settings_changed) owner._sar3d_pitch_deg.valueChanged.connect(owner._on_processing_live_settings_changed)
owner._sar3d_center_depth_m.valueChanged.connect(owner._on_processing_live_settings_changed) owner._sar3d_ground_depth_m.valueChanged.connect(owner._on_processing_live_settings_changed)
owner._sar3d_half_width_x_m.valueChanged.connect(owner._on_processing_live_settings_changed) owner._sar3d_x_margin_m.valueChanged.connect(owner._on_processing_live_settings_changed)
owner._sar3d_half_width_y_m.valueChanged.connect(owner._on_processing_live_settings_changed) owner._sar3d_y_min_m.valueChanged.connect(owner._on_processing_live_settings_changed)
owner._sar3d_half_width_z_m.valueChanged.connect(owner._on_processing_live_settings_changed) owner._sar3d_y_max_m.valueChanged.connect(owner._on_processing_live_settings_changed)
owner._sar3d_grid_nx.valueChanged.connect(owner._on_processing_live_settings_changed) owner._sar3d_voxel_x_m.valueChanged.connect(owner._on_processing_live_settings_changed)
owner._sar3d_grid_ny.valueChanged.connect(owner._on_processing_live_settings_changed) owner._sar3d_voxel_y_m.valueChanged.connect(owner._on_processing_live_settings_changed)
owner._sar3d_grid_nz.valueChanged.connect(owner._on_processing_live_settings_changed) owner._sar3d_voxel_z_m.valueChanged.connect(owner._on_processing_live_settings_changed)
owner._sar3d_norm_edge_margin_m.valueChanged.connect(owner._on_processing_live_settings_changed)
owner._sar3d_smooth_sigma.valueChanged.connect(owner._on_processing_live_settings_changed)
owner._sar3d_comp_range_power.valueChanged.connect(owner._on_processing_live_settings_changed)
owner._sar3d_tx_per_segment.valueChanged.connect(owner._on_processing_live_settings_changed)
owner._sar3d_use_compensation.toggled.connect(owner._on_processing_live_settings_changed) owner._sar3d_use_compensation.toggled.connect(owner._on_processing_live_settings_changed)
owner._sar3d_normalize_pairs.toggled.connect(owner._on_processing_live_settings_changed) owner._sar3d_normalize_pairs.toggled.connect(owner._on_processing_live_settings_changed)
owner._sar3d_bottom_view.currentTextChanged.connect(owner._on_processing_live_settings_changed) owner._sar3d_bottom_view.currentTextChanged.connect(owner._on_processing_live_settings_changed)
+80 -24
View File
@@ -533,39 +533,84 @@ def gui_profile_from_dict(payload: dict[str, Any]) -> GuiProfileModel:
gui.processing.sar3d.edge_frame_weight, gui.processing.sar3d.edge_frame_weight,
"gui.processing.sar3d", "gui.processing.sar3d",
), ),
pitch_override_enabled=_optional_bool(
sar3d_object,
"pitch_override_enabled",
gui.processing.sar3d.pitch_override_enabled,
"gui.processing.sar3d",
),
pitch_deg=_optional_float( pitch_deg=_optional_float(
sar3d_object, sar3d_object,
"pitch_deg", "pitch_deg",
gui.processing.sar3d.pitch_deg, gui.processing.sar3d.pitch_deg,
"gui.processing.sar3d", "gui.processing.sar3d",
), ),
center_depth_m=_optional_float( ground_depth_m=_optional_float(
sar3d_object, sar3d_object,
"center_depth_m", "ground_depth_m",
gui.processing.sar3d.center_depth_m, gui.processing.sar3d.ground_depth_m,
"gui.processing.sar3d", "gui.processing.sar3d",
), ),
half_width_x_m=_optional_float( x_margin_m=_optional_float(
sar3d_object, sar3d_object,
"half_width_x_m", "x_margin_m",
gui.processing.sar3d.half_width_x_m, gui.processing.sar3d.x_margin_m,
"gui.processing.sar3d", "gui.processing.sar3d",
), ),
half_width_y_m=_optional_float( y_min_m=_optional_float(
sar3d_object, sar3d_object,
"half_width_y_m", "y_min_m",
gui.processing.sar3d.half_width_y_m, gui.processing.sar3d.y_min_m,
"gui.processing.sar3d", "gui.processing.sar3d",
), ),
half_width_z_m=_optional_float( y_max_m=_optional_float(
sar3d_object, sar3d_object,
"half_width_z_m", "y_max_m",
gui.processing.sar3d.half_width_z_m, gui.processing.sar3d.y_max_m,
"gui.processing.sar3d",
),
voxel_x_m=_optional_float(
sar3d_object,
"voxel_x_m",
gui.processing.sar3d.voxel_x_m,
"gui.processing.sar3d",
),
voxel_y_m=_optional_float(
sar3d_object,
"voxel_y_m",
gui.processing.sar3d.voxel_y_m,
"gui.processing.sar3d",
),
voxel_z_m=_optional_float(
sar3d_object,
"voxel_z_m",
gui.processing.sar3d.voxel_z_m,
"gui.processing.sar3d",
),
norm_edge_margin_m=_optional_float(
sar3d_object,
"norm_edge_margin_m",
gui.processing.sar3d.norm_edge_margin_m,
"gui.processing.sar3d",
),
smooth_sigma=_optional_float(
sar3d_object,
"smooth_sigma",
gui.processing.sar3d.smooth_sigma,
"gui.processing.sar3d",
),
comp_range_power=_optional_float(
sar3d_object,
"comp_range_power",
gui.processing.sar3d.comp_range_power,
"gui.processing.sar3d",
),
tx_per_segment=_optional_int(
sar3d_object,
"tx_per_segment",
gui.processing.sar3d.tx_per_segment,
"gui.processing.sar3d", "gui.processing.sar3d",
), ),
grid_nx=_optional_int(sar3d_object, "grid_nx", gui.processing.sar3d.grid_nx, "gui.processing.sar3d"),
grid_ny=_optional_int(sar3d_object, "grid_ny", gui.processing.sar3d.grid_ny, "gui.processing.sar3d"),
grid_nz=_optional_int(sar3d_object, "grid_nz", gui.processing.sar3d.grid_nz, "gui.processing.sar3d"),
use_compensation=_optional_bool( use_compensation=_optional_bool(
sar3d_object, sar3d_object,
"use_compensation", "use_compensation",
@@ -628,8 +673,14 @@ def gui_profile_from_dict(payload: dict[str, Any]) -> GuiProfileModel:
raise ValueError("gui.processing.sar3d.frame_radius must be >= 0") raise ValueError("gui.processing.sar3d.frame_radius must be >= 0")
if not 0.0 <= gui.processing.sar3d.edge_frame_weight <= 1.0: if not 0.0 <= gui.processing.sar3d.edge_frame_weight <= 1.0:
raise ValueError("gui.processing.sar3d.edge_frame_weight must be within [0, 1]") raise ValueError("gui.processing.sar3d.edge_frame_weight must be within [0, 1]")
if min(gui.processing.sar3d.grid_nx, gui.processing.sar3d.grid_ny, gui.processing.sar3d.grid_nz) < 2: if min(gui.processing.sar3d.voxel_x_m, gui.processing.sar3d.voxel_y_m, gui.processing.sar3d.voxel_z_m) <= 0.0:
raise ValueError("gui.processing.sar3d.grid_nx/ny/nz must each be >= 2") raise ValueError("gui.processing.sar3d.voxel_x/y/z_m must each be > 0")
if gui.processing.sar3d.y_max_m <= gui.processing.sar3d.y_min_m:
raise ValueError("gui.processing.sar3d.y_max_m must be > y_min_m")
if gui.processing.sar3d.ground_depth_m < 0.0:
raise ValueError("gui.processing.sar3d.ground_depth_m must be >= 0")
if gui.processing.sar3d.tx_per_segment < 1:
raise ValueError("gui.processing.sar3d.tx_per_segment must be >= 1")
data_actions_object = _as_dict(gui_object.get("data_actions"), "gui.data_actions") data_actions_object = _as_dict(gui_object.get("data_actions"), "gui.data_actions")
gui.data_actions = GuiDataActionsStateModel( gui.data_actions = GuiDataActionsStateModel(
@@ -795,14 +846,19 @@ def gui_profile_to_dict(model: GuiProfileModel) -> dict[str, Any]:
"apply_freq_phase_correction": gui.processing.sar3d.apply_freq_phase_correction, "apply_freq_phase_correction": gui.processing.sar3d.apply_freq_phase_correction,
"frame_radius": gui.processing.sar3d.frame_radius, "frame_radius": gui.processing.sar3d.frame_radius,
"edge_frame_weight": gui.processing.sar3d.edge_frame_weight, "edge_frame_weight": gui.processing.sar3d.edge_frame_weight,
"pitch_override_enabled": gui.processing.sar3d.pitch_override_enabled,
"pitch_deg": gui.processing.sar3d.pitch_deg, "pitch_deg": gui.processing.sar3d.pitch_deg,
"center_depth_m": gui.processing.sar3d.center_depth_m, "ground_depth_m": gui.processing.sar3d.ground_depth_m,
"half_width_x_m": gui.processing.sar3d.half_width_x_m, "x_margin_m": gui.processing.sar3d.x_margin_m,
"half_width_y_m": gui.processing.sar3d.half_width_y_m, "y_min_m": gui.processing.sar3d.y_min_m,
"half_width_z_m": gui.processing.sar3d.half_width_z_m, "y_max_m": gui.processing.sar3d.y_max_m,
"grid_nx": gui.processing.sar3d.grid_nx, "voxel_x_m": gui.processing.sar3d.voxel_x_m,
"grid_ny": gui.processing.sar3d.grid_ny, "voxel_y_m": gui.processing.sar3d.voxel_y_m,
"grid_nz": gui.processing.sar3d.grid_nz, "voxel_z_m": gui.processing.sar3d.voxel_z_m,
"norm_edge_margin_m": gui.processing.sar3d.norm_edge_margin_m,
"smooth_sigma": gui.processing.sar3d.smooth_sigma,
"comp_range_power": gui.processing.sar3d.comp_range_power,
"tx_per_segment": gui.processing.sar3d.tx_per_segment,
"use_compensation": gui.processing.sar3d.use_compensation, "use_compensation": gui.processing.sar3d.use_compensation,
"normalize_pairs": gui.processing.sar3d.normalize_pairs, "normalize_pairs": gui.processing.sar3d.normalize_pairs,
"bottom_view_deg": gui.processing.sar3d.bottom_view_deg, "bottom_view_deg": gui.processing.sar3d.bottom_view_deg,
+21 -16
View File
@@ -128,31 +128,36 @@ class GuiSar3dStateModel:
"""UI-only defaults for the 3D SAR (sar3d) live settings. """UI-only defaults for the 3D SAR (sar3d) live settings.
The band, depth gate, antenna selection and motion knobs mirror the shared The band, depth gate, antenna selection and motion knobs mirror the shared
gpr_* live-config fields; the sar3d_* knobs mirror the VNA_AlGo gpr_core gpr_* live-config fields; the sar3d_* knobs mirror the reference notebook
defaults used by the C++ sar3d processor. (Multi_antenna_3D_clean_v1.ipynb) defaults used by the C++ sar3d processor.
""" """
input_positions: str = "" input_positions: str = ""
output_positions: str = "" output_positions: str = ""
min_depth_m: float = 2.0 min_depth_m: float = 2.5
max_depth_m: float = 14.0 max_depth_m: float = 8.0
start_freq_mhz: float = 3000.0 start_freq_mhz: float = 2000.0
stop_freq_mhz: float = 6000.0 stop_freq_mhz: float = 6000.0
speed_m_s: float = 0.0 speed_m_s: float = 0.0
ignore_socket_speed_enabled: bool = False ignore_socket_speed_enabled: bool = False
look_angle_deg: float = 0.0 look_angle_deg: float = 17.5
direction_sign: float = 1.0 direction_sign: float = 1.0
apply_freq_phase_correction: bool = True apply_freq_phase_correction: bool = True
frame_radius: int = 6 frame_radius: int = 0
edge_frame_weight: float = 0.75 edge_frame_weight: float = 0.8
pitch_deg: float = 6.0 pitch_override_enabled: bool = False
center_depth_m: float = 5.6 pitch_deg: float = 17.5
half_width_x_m: float = 1.8 ground_depth_m: float = 1.25
half_width_y_m: float = 1.0 x_margin_m: float = 2.0
half_width_z_m: float = 1.8 y_min_m: float = -0.5
grid_nx: int = 150 y_max_m: float = 2.0
grid_ny: int = 120 voxel_x_m: float = 0.025
grid_nz: int = 160 voxel_y_m: float = 0.02
voxel_z_m: float = 0.02
norm_edge_margin_m: float = 0.3
smooth_sigma: float = 1.5
comp_range_power: float = 0.3
tx_per_segment: int = 2
use_compensation: bool = True use_compensation: bool = True
normalize_pairs: bool = True normalize_pairs: bool = True
# Bottom-view projection angle around the vertical Y axis, degrees: # Bottom-view projection angle around the vertical Y axis, degrees:
@@ -72,19 +72,31 @@ class ProcessingLiveConfig:
gpr_visible_x_max_m: float = 2.0 gpr_visible_x_max_m: float = 2.0
gpr_visible_z_min_m: float = 0.0 gpr_visible_z_min_m: float = 0.0
gpr_visible_z_max_m: float = 14.0 gpr_visible_z_max_m: float = 14.0
# 3D SAR (sar3d processor) parameters; defaults mirror VNA_AlGo gpr_core. # 3D SAR (sar3d processor) parameters; defaults mirror the reference
# The frequency band, depth gate, antenna selection and motion parameters # notebook Multi_antenna_3D_clean_v1.ipynb. The frequency band, depth gate,
# are shared with the gpr_* fields above. # antenna selection and motion parameters are shared with the gpr_* fields.
sar3d_frame_radius: int = 6 sar3d_frame_radius: int = 0
sar3d_edge_frame_weight: float = 0.75 sar3d_edge_frame_weight: float = 0.8
sar3d_pitch_deg: float = 6.0 # Frame pitch override; disabled -> the pitch follows gpr_look_angle_deg.
sar3d_center_depth_m: float = 5.6 sar3d_pitch_override_enabled: bool = False
sar3d_half_width_x_m: float = 1.8 sar3d_pitch_deg: float = 17.5
sar3d_half_width_y_m: float = 1.0 # Height of the lower antenna row above the ground (scene Y=0 = ground).
sar3d_half_width_z_m: float = 1.8 sar3d_ground_depth_m: float = 1.25
sar3d_grid_nx: int = 150 # Full-region grid: X spans the antennas +- margin, Y is height above the
sar3d_grid_ny: int = 120 # ground, Z covers the radar depth gate over the Y range.
sar3d_grid_nz: int = 160 sar3d_x_margin_m: float = 2.0
sar3d_y_min_m: float = -0.5
sar3d_y_max_m: float = 2.0
sar3d_voxel_x_m: float = 0.025
sar3d_voxel_y_m: float = 0.02
sar3d_voxel_z_m: float = 0.02
# Volume finalization: Y-edge margin excluded from normalization, Gaussian
# smoothing sigma over (Z, X) planes.
sar3d_norm_edge_margin_m: float = 0.3
sar3d_smooth_sigma: float = 1.5
sar3d_comp_range_power: float = 0.3
# Fallback frame segmentation when per-trace capture stamps are absent.
sar3d_tx_per_segment: int = 2
sar3d_use_compensation: bool = True sar3d_use_compensation: bool = True
sar3d_normalize_pairs: bool = True sar3d_normalize_pairs: bool = True
# Bottom-view projection angle around the vertical Y axis, degrees: # Bottom-view projection angle around the vertical Y axis, degrees:
@@ -156,14 +168,19 @@ class ProcessingLiveConfig:
"gpr_visible_z_max_m": float(self.gpr_visible_z_max_m), "gpr_visible_z_max_m": float(self.gpr_visible_z_max_m),
"sar3d_frame_radius": int(self.sar3d_frame_radius), "sar3d_frame_radius": int(self.sar3d_frame_radius),
"sar3d_edge_frame_weight": float(self.sar3d_edge_frame_weight), "sar3d_edge_frame_weight": float(self.sar3d_edge_frame_weight),
"sar3d_pitch_override_enabled": bool(self.sar3d_pitch_override_enabled),
"sar3d_pitch_deg": float(self.sar3d_pitch_deg), "sar3d_pitch_deg": float(self.sar3d_pitch_deg),
"sar3d_center_depth_m": float(self.sar3d_center_depth_m), "sar3d_ground_depth_m": float(self.sar3d_ground_depth_m),
"sar3d_half_width_x_m": float(self.sar3d_half_width_x_m), "sar3d_x_margin_m": float(self.sar3d_x_margin_m),
"sar3d_half_width_y_m": float(self.sar3d_half_width_y_m), "sar3d_y_min_m": float(self.sar3d_y_min_m),
"sar3d_half_width_z_m": float(self.sar3d_half_width_z_m), "sar3d_y_max_m": float(self.sar3d_y_max_m),
"sar3d_grid_nx": int(self.sar3d_grid_nx), "sar3d_voxel_x_m": float(self.sar3d_voxel_x_m),
"sar3d_grid_ny": int(self.sar3d_grid_ny), "sar3d_voxel_y_m": float(self.sar3d_voxel_y_m),
"sar3d_grid_nz": int(self.sar3d_grid_nz), "sar3d_voxel_z_m": float(self.sar3d_voxel_z_m),
"sar3d_norm_edge_margin_m": float(self.sar3d_norm_edge_margin_m),
"sar3d_smooth_sigma": float(self.sar3d_smooth_sigma),
"sar3d_comp_range_power": float(self.sar3d_comp_range_power),
"sar3d_tx_per_segment": int(self.sar3d_tx_per_segment),
"sar3d_use_compensation": bool(self.sar3d_use_compensation), "sar3d_use_compensation": bool(self.sar3d_use_compensation),
"sar3d_normalize_pairs": bool(self.sar3d_normalize_pairs), "sar3d_normalize_pairs": bool(self.sar3d_normalize_pairs),
"sar3d_bottom_view_deg": float(self.sar3d_bottom_view_deg), "sar3d_bottom_view_deg": float(self.sar3d_bottom_view_deg),
+35 -5
View File
@@ -20,6 +20,7 @@ never see time jump backwards. ``collection_id`` increases forever.
from __future__ import annotations from __future__ import annotations
import argparse import argparse
import json
import logging import logging
from pathlib import Path from pathlib import Path
import re import re
@@ -75,6 +76,22 @@ def _load_dataset(dataset_dir: Path, combos: list[ComboKey], expected_points: in
timestamps_ns: list[int] = [] timestamps_ns: list[int] = []
for name, timestamp_ns in named: for name, timestamp_ns in named:
folder = dataset_dir / name folder = dataset_dir / name
# Recorded per-trace capture windows from the snapshot's meta.json: the
# sar3d segmented timing model reconstructs the acquisition segments
# from them. Missing/partial meta leaves the zero "unmeasured" sentinel
# and the consumer falls back to its uniform-segment model.
windows: dict[tuple[int, int], tuple[int, int]] = {}
meta_path = folder / "meta.json"
if meta_path.is_file():
try:
meta = json.loads(meta_path.read_text())
for entry in meta.get("traces", []):
windows[(int(entry["input"]), int(entry["output"]))] = (
int(entry["capture_start_ns"]),
int(entry["capture_end_ns"]),
)
except (OSError, ValueError, KeyError, TypeError):
windows = {}
traces: list[TraceData] = [] traces: list[TraceData] = []
for combo in combos: for combo in combos:
s21_path = folder / f"i{combo.input}_o{combo.output}_s21.npy" s21_path = folder / f"i{combo.input}_o{combo.output}_s21.npy"
@@ -94,12 +111,15 @@ def _load_dataset(dataset_dir: Path, combos: list[ComboKey], expected_points: in
f"{s21_path}: {s21.size} points, but radar.sweep.points = " f"{s21_path}: {s21.size} points, but radar.sweep.points = "
f"{expected_points}. Align the profile's sweep with the dataset." f"{expected_points}. Align the profile's sweep with the dataset."
) )
window = windows.get((combo.input, combo.output), (0, 0))
traces.append( traces.append(
TraceData( TraceData(
combo=combo, combo=combo,
frequency_hz=freq, frequency_hz=freq,
s11=np.zeros_like(s21), s11=np.zeros_like(s21),
s21=s21, s21=s21,
capture_start_ns=window[0],
capture_end_ns=window[1],
) )
) )
snapshots.append(traces) snapshots.append(traces)
@@ -114,28 +134,38 @@ def _build_collection(
collection_id: int, collection_id: int,
frame_ns: int, frame_ns: int,
) -> SweepCollection: ) -> SweepCollection:
"""Wrap one preloaded snapshot in a SweepCollection at the given frame time.""" """Wrap one preloaded snapshot in a SweepCollection at the given frame time.
Recorded per-trace capture windows are re-anchored by the same shift as the
frame time, preserving the real intra-frame segment layout. Traces without
recorded windows get a synthetic window spanning the median frame gap.
"""
span_ns = dataset.median_gap_ns span_ns = dataset.median_gap_ns
start_ns = frame_ns - span_ns // 2 start_ns = frame_ns - span_ns // 2
end_ns = frame_ns + span_ns // 2 end_ns = frame_ns + span_ns // 2
shift_ns = frame_ns - dataset.timestamps_ns[index]
traces = [] traces = []
for source in dataset.snapshots[index]: for source in dataset.snapshots[index]:
if source.capture_end_ns > source.capture_start_ns:
capture = (source.capture_start_ns + shift_ns, source.capture_end_ns + shift_ns)
else:
capture = (start_ns, end_ns)
traces.append( traces.append(
TraceData( TraceData(
combo=source.combo, combo=source.combo,
frequency_hz=source.frequency_hz, frequency_hz=source.frequency_hz,
s11=source.s11, s11=source.s11,
s21=source.s21, s21=source.s21,
capture_start_ns=start_ns, capture_start_ns=capture[0],
capture_end_ns=end_ns, capture_end_ns=capture[1],
) )
) )
return SweepCollection( return SweepCollection(
collection_id=collection_id, collection_id=collection_id,
monotonic_ns=frame_ns, monotonic_ns=frame_ns,
traces=traces, traces=traces,
capture_start_ns=start_ns, capture_start_ns=min(t.capture_start_ns for t in traces),
capture_end_ns=end_ns, capture_end_ns=max(t.capture_end_ns for t in traces),
) )
+18 -7
View File
@@ -84,14 +84,25 @@ def _sar3d_live_config(profile_path: Path) -> ProcessingLiveConfig:
live.sar3d_edge_frame_weight = float( live.sar3d_edge_frame_weight = float(
sar3d.get("edge_frame_weight", live.sar3d_edge_frame_weight) sar3d.get("edge_frame_weight", live.sar3d_edge_frame_weight)
) )
live.sar3d_pitch_override_enabled = bool(
sar3d.get("pitch_override_enabled", live.sar3d_pitch_override_enabled)
)
live.sar3d_pitch_deg = float(sar3d.get("pitch_deg", live.sar3d_pitch_deg)) live.sar3d_pitch_deg = float(sar3d.get("pitch_deg", live.sar3d_pitch_deg))
live.sar3d_center_depth_m = float(sar3d.get("center_depth_m", live.sar3d_center_depth_m)) live.sar3d_ground_depth_m = float(sar3d.get("ground_depth_m", live.sar3d_ground_depth_m))
live.sar3d_half_width_x_m = float(sar3d.get("half_width_x_m", live.sar3d_half_width_x_m)) live.sar3d_x_margin_m = float(sar3d.get("x_margin_m", live.sar3d_x_margin_m))
live.sar3d_half_width_y_m = float(sar3d.get("half_width_y_m", live.sar3d_half_width_y_m)) live.sar3d_y_min_m = float(sar3d.get("y_min_m", live.sar3d_y_min_m))
live.sar3d_half_width_z_m = float(sar3d.get("half_width_z_m", live.sar3d_half_width_z_m)) live.sar3d_y_max_m = float(sar3d.get("y_max_m", live.sar3d_y_max_m))
live.sar3d_grid_nx = int(sar3d.get("grid_nx", live.sar3d_grid_nx)) live.sar3d_voxel_x_m = float(sar3d.get("voxel_x_m", live.sar3d_voxel_x_m))
live.sar3d_grid_ny = int(sar3d.get("grid_ny", live.sar3d_grid_ny)) live.sar3d_voxel_y_m = float(sar3d.get("voxel_y_m", live.sar3d_voxel_y_m))
live.sar3d_grid_nz = int(sar3d.get("grid_nz", live.sar3d_grid_nz)) live.sar3d_voxel_z_m = float(sar3d.get("voxel_z_m", live.sar3d_voxel_z_m))
live.sar3d_norm_edge_margin_m = float(
sar3d.get("norm_edge_margin_m", live.sar3d_norm_edge_margin_m)
)
live.sar3d_smooth_sigma = float(sar3d.get("smooth_sigma", live.sar3d_smooth_sigma))
live.sar3d_comp_range_power = float(
sar3d.get("comp_range_power", live.sar3d_comp_range_power)
)
live.sar3d_tx_per_segment = int(sar3d.get("tx_per_segment", live.sar3d_tx_per_segment))
live.sar3d_use_compensation = bool(sar3d.get("use_compensation", live.sar3d_use_compensation)) live.sar3d_use_compensation = bool(sar3d.get("use_compensation", live.sar3d_use_compensation))
live.sar3d_normalize_pairs = bool(sar3d.get("normalize_pairs", live.sar3d_normalize_pairs)) live.sar3d_normalize_pairs = bool(sar3d.get("normalize_pairs", live.sar3d_normalize_pairs))
live.sar3d_bottom_view_deg = float(sar3d.get("bottom_view_deg", live.sar3d_bottom_view_deg)) live.sar3d_bottom_view_deg = float(sar3d.get("bottom_view_deg", live.sar3d_bottom_view_deg))