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"})),
("sar3d_frame_radius", "3D SAR", ("sar3d",), _attr("_sar3d_frame_radius")),
("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_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_ground_depth_m", "3D SAR", ("sar3d",), _attr("_sar3d_ground_depth_m")),
("sar3d_x_margin_m", "3D SAR", ("sar3d",), _attr("_sar3d_x_margin_m")),
("sar3d_y_min_m", "3D SAR", ("sar3d",), _attr("_sar3d_y_min_m")),
("sar3d_y_max_m", "3D SAR", ("sar3d",), _attr("_sar3d_y_max_m")),
("sar3d_voxel_x_m", "3D SAR", ("sar3d",), _attr("_sar3d_voxel_x_m")),
("sar3d_voxel_y_m", "3D SAR", ("sar3d",), _attr("_sar3d_voxel_y_m")),
("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_normalize_pairs", "3D SAR", ("sar3d",), _attr("_sar3d_normalize_pairs")),
("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"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)"
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"ground_depth={self._sar3d_ground_depth_m.value():g} m)"
)
def _clear_history_mode_caches(self) -> None:
@@ -350,14 +350,19 @@ class AppWindowConfigProfileIOMixin:
self._sar3d_apply_freq_phase_correction,
self._sar3d_frame_radius,
self._sar3d_edge_frame_weight,
self._sar3d_pitch_override_enabled,
self._sar3d_pitch_deg,
self._sar3d_center_depth_m,
self._sar3d_half_width_x_m,
self._sar3d_half_width_y_m,
self._sar3d_half_width_z_m,
self._sar3d_grid_nx,
self._sar3d_grid_ny,
self._sar3d_grid_nz,
self._sar3d_ground_depth_m,
self._sar3d_x_margin_m,
self._sar3d_y_min_m,
self._sar3d_y_max_m,
self._sar3d_voxel_x_m,
self._sar3d_voxel_y_m,
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_normalize_pairs,
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_frame_radius.setValue(int(sar3d_state.frame_radius))
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_center_depth_m.setValue(float(sar3d_state.center_depth_m))
self._sar3d_half_width_x_m.setValue(float(sar3d_state.half_width_x_m))
self._sar3d_half_width_y_m.setValue(float(sar3d_state.half_width_y_m))
self._sar3d_half_width_z_m.setValue(float(sar3d_state.half_width_z_m))
self._sar3d_grid_nx.setValue(int(sar3d_state.grid_nx))
self._sar3d_grid_ny.setValue(int(sar3d_state.grid_ny))
self._sar3d_grid_nz.setValue(int(sar3d_state.grid_nz))
self._sar3d_ground_depth_m.setValue(float(sar3d_state.ground_depth_m))
self._sar3d_x_margin_m.setValue(float(sar3d_state.x_margin_m))
self._sar3d_y_min_m.setValue(float(sar3d_state.y_min_m))
self._sar3d_y_max_m.setValue(float(sar3d_state.y_max_m))
self._sar3d_voxel_x_m.setValue(float(sar3d_state.voxel_x_m))
self._sar3d_voxel_y_m.setValue(float(sar3d_state.voxel_y_m))
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_normalize_pairs.setChecked(bool(sar3d_state.normalize_pairs))
self._set_combo_current_text(
@@ -438,14 +438,19 @@ class AppWindowConfigStateBuildersMixin:
apply_freq_phase_correction=bool(self._sar3d_apply_freq_phase_correction.isChecked()),
frame_radius=int(self._sar3d_frame_radius.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()),
center_depth_m=float(self._sar3d_center_depth_m.value()),
half_width_x_m=float(self._sar3d_half_width_x_m.value()),
half_width_y_m=float(self._sar3d_half_width_y_m.value()),
half_width_z_m=float(self._sar3d_half_width_z_m.value()),
grid_nx=int(self._sar3d_grid_nx.value()),
grid_ny=int(self._sar3d_grid_ny.value()),
grid_nz=int(self._sar3d_grid_nz.value()),
ground_depth_m=float(self._sar3d_ground_depth_m.value()),
x_margin_m=float(self._sar3d_x_margin_m.value()),
y_min_m=float(self._sar3d_y_min_m.value()),
y_max_m=float(self._sar3d_y_max_m.value()),
voxel_x_m=float(self._sar3d_voxel_x_m.value()),
voxel_y_m=float(self._sar3d_voxel_y_m.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()),
normalize_pairs=bool(self._sar3d_normalize_pairs.isChecked()),
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)."
)
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.setDecimals(2)
owner._sar3d_pitch_deg.setRange(-90.0, 90.0)
owner._sar3d_pitch_deg.setSingleStep(0.5)
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_center_depth_m.setDecimals(2)
owner._sar3d_center_depth_m.setRange(0.0, 50.0)
owner._sar3d_center_depth_m.setSingleStep(0.1)
owner._sar3d_center_depth_m.setValue(float(sar3d_defaults.center_depth_m))
owner._sar3d_center_depth_m.setToolTip("Range distance to the center of the imaged volume (m).")
owner._sar3d_ground_depth_m = QDoubleSpinBox()
owner._sar3d_ground_depth_m.setDecimals(2)
owner._sar3d_ground_depth_m.setRange(0.0, 10.0)
owner._sar3d_ground_depth_m.setSingleStep(0.05)
owner._sar3d_ground_depth_m.setValue(float(sar3d_defaults.ground_depth_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_half_width_x_m.setDecimals(2)
owner._sar3d_half_width_x_m.setRange(0.01, 50.0)
owner._sar3d_half_width_x_m.setSingleStep(0.1)
owner._sar3d_half_width_x_m.setValue(float(sar3d_defaults.half_width_x_m))
owner._sar3d_x_margin_m = QDoubleSpinBox()
owner._sar3d_x_margin_m.setDecimals(2)
owner._sar3d_x_margin_m.setRange(0.0, 20.0)
owner._sar3d_x_margin_m.setSingleStep(0.1)
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_half_width_y_m.setDecimals(2)
owner._sar3d_half_width_y_m.setRange(0.01, 50.0)
owner._sar3d_half_width_y_m.setSingleStep(0.1)
owner._sar3d_half_width_y_m.setValue(float(sar3d_defaults.half_width_y_m))
owner._sar3d_y_min_m = QDoubleSpinBox()
owner._sar3d_y_min_m.setDecimals(2)
owner._sar3d_y_min_m.setRange(-10.0, 10.0)
owner._sar3d_y_min_m.setSingleStep(0.1)
owner._sar3d_y_min_m.setValue(float(sar3d_defaults.y_min_m))
owner._sar3d_half_width_z_m = QDoubleSpinBox()
owner._sar3d_half_width_z_m.setDecimals(2)
owner._sar3d_half_width_z_m.setRange(0.01, 50.0)
owner._sar3d_half_width_z_m.setSingleStep(0.1)
owner._sar3d_half_width_z_m.setValue(float(sar3d_defaults.half_width_z_m))
owner._sar3d_y_max_m = QDoubleSpinBox()
owner._sar3d_y_max_m.setDecimals(2)
owner._sar3d_y_max_m.setRange(-10.0, 10.0)
owner._sar3d_y_max_m.setSingleStep(0.1)
owner._sar3d_y_max_m.setValue(float(sar3d_defaults.y_max_m))
owner._sar3d_grid_nx = QSpinBox()
owner._sar3d_grid_nx.setRange(2, 1024)
owner._sar3d_grid_nx.setValue(int(sar3d_defaults.grid_nx))
owner._sar3d_voxel_x_m = QDoubleSpinBox()
owner._sar3d_voxel_x_m.setDecimals(3)
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_grid_ny.setRange(2, 1024)
owner._sar3d_grid_ny.setValue(int(sar3d_defaults.grid_ny))
owner._sar3d_voxel_y_m = QDoubleSpinBox()
owner._sar3d_voxel_y_m.setDecimals(3)
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_grid_nz.setRange(2, 1024)
owner._sar3d_grid_nz.setValue(int(sar3d_defaults.grid_nz))
owner._sar3d_voxel_z_m = QDoubleSpinBox()
owner._sar3d_voxel_z_m.setDecimals(3)
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.setChecked(bool(sar3d_defaults.use_compensation))
@@ -738,19 +786,24 @@ def build_processing_group(owner) -> QGroupBox:
owner._sar3d_apply_freq_phase_correction,
("Frame radius", owner._sar3d_frame_radius),
("Edge frame weight", owner._sar3d_edge_frame_weight),
owner._sar3d_pitch_override_enabled,
("Pitch deg", owner._sar3d_pitch_deg),
("Center depth m", owner._sar3d_center_depth_m),
("Half width X m", owner._sar3d_half_width_x_m),
("Half width Y m", owner._sar3d_half_width_y_m),
("Half width Z m", owner._sar3d_half_width_z_m),
("Grid Nx", owner._sar3d_grid_nx),
("Grid Ny", owner._sar3d_grid_ny),
("Grid Nz", owner._sar3d_grid_nz),
("Ground depth m", owner._sar3d_ground_depth_m),
("X margin m", owner._sar3d_x_margin_m),
("Y min m", owner._sar3d_y_min_m),
("Y max m", owner._sar3d_y_max_m),
("Voxel X m", owner._sar3d_voxel_x_m),
("Voxel Y m", owner._sar3d_voxel_y_m),
("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),
owner._sar3d_use_compensation,
owner._sar3d_normalize_pairs,
],
split_index=11,
split_index=13,
)
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_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_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_center_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_half_width_y_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_grid_nx.valueChanged.connect(owner._on_processing_live_settings_changed)
owner._sar3d_grid_ny.valueChanged.connect(owner._on_processing_live_settings_changed)
owner._sar3d_grid_nz.valueChanged.connect(owner._on_processing_live_settings_changed)
owner._sar3d_ground_depth_m.valueChanged.connect(owner._on_processing_live_settings_changed)
owner._sar3d_x_margin_m.valueChanged.connect(owner._on_processing_live_settings_changed)
owner._sar3d_y_min_m.valueChanged.connect(owner._on_processing_live_settings_changed)
owner._sar3d_y_max_m.valueChanged.connect(owner._on_processing_live_settings_changed)
owner._sar3d_voxel_x_m.valueChanged.connect(owner._on_processing_live_settings_changed)
owner._sar3d_voxel_y_m.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_normalize_pairs.toggled.connect(owner._on_processing_live_settings_changed)
owner._sar3d_bottom_view.currentTextChanged.connect(owner._on_processing_live_settings_changed)