move to new version
This commit is contained in:
@@ -64,6 +64,10 @@ _WEB_LIVE_SCHEMA = [
|
||||
("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_plane_mode", "Geometry & depth", ("gpr",), _attr("_gpr_plane_mode")),
|
||||
("gpr_ground_depth_m", "Geometry & depth", ("gpr",), _attr("_gpr_ground_depth_m")),
|
||||
("gpr_ground_pitch_override_enabled", "Geometry & depth", ("gpr",), _attr("_gpr_ground_pitch_override_enabled")),
|
||||
("gpr_ground_pitch_deg", "Geometry & depth", ("gpr",), _attr("_gpr_ground_pitch_deg")),
|
||||
("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": "_legacy_gpr_background_subtract_enabled"})),
|
||||
@@ -82,6 +86,8 @@ _WEB_LIVE_SCHEMA = [
|
||||
("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_timing_mode", "Motion", ("gpr",), _attr("_gpr_timing_mode")),
|
||||
("gpr_tx_per_segment", "Motion", ("gpr",), _attr("_gpr_tx_per_segment")),
|
||||
("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"})),
|
||||
|
||||
@@ -309,6 +309,12 @@ class AppWindowConfigProfileIOMixin:
|
||||
self._gpr_background_mean_count,
|
||||
self._gpr_remove_sidelobe_objects_enabled,
|
||||
self._gpr_imaging_plane_y_m,
|
||||
self._gpr_timing_mode,
|
||||
self._gpr_tx_per_segment,
|
||||
self._gpr_plane_mode,
|
||||
self._gpr_ground_depth_m,
|
||||
self._gpr_ground_pitch_override_enabled,
|
||||
self._gpr_ground_pitch_deg,
|
||||
self._gpr_render_mode,
|
||||
self._gpr_visible_x_min_m,
|
||||
self._gpr_visible_x_max_m,
|
||||
@@ -507,6 +513,16 @@ class AppWindowConfigProfileIOMixin:
|
||||
bool(gui_state.processing.gpr.remove_sidelobe_objects_enabled)
|
||||
)
|
||||
self._gpr_imaging_plane_y_m.setValue(float(gui_state.processing.gpr.imaging_plane_y_m))
|
||||
self._set_combo_current_text(self._gpr_timing_mode, gui_state.processing.gpr.timing_mode)
|
||||
self._gpr_tx_per_segment.setValue(int(gui_state.processing.gpr.tx_per_segment))
|
||||
self._set_combo_current_text(self._gpr_plane_mode, gui_state.processing.gpr.plane_mode)
|
||||
self._gpr_ground_depth_m.setValue(float(gui_state.processing.gpr.ground_depth_m))
|
||||
ground_pitch_override_enabled = bool(
|
||||
gui_state.processing.gpr.ground_pitch_override_enabled
|
||||
)
|
||||
self._gpr_ground_pitch_override_enabled.setChecked(ground_pitch_override_enabled)
|
||||
self._gpr_ground_pitch_deg.setValue(float(gui_state.processing.gpr.ground_pitch_deg))
|
||||
self._gpr_ground_pitch_deg.setEnabled(ground_pitch_override_enabled)
|
||||
self._set_combo_current_text(self._gpr_render_mode, gui_state.processing.gpr.render_mode)
|
||||
self._gpr_visible_x_min_m.setValue(float(gui_state.processing.gpr.visible_x_min_m))
|
||||
self._gpr_visible_x_max_m.setValue(float(gui_state.processing.gpr.visible_x_max_m))
|
||||
|
||||
@@ -391,6 +391,12 @@ class AppWindowConfigStateBuildersMixin:
|
||||
background_mean_count=int(self._gpr_background_mean_count.value()),
|
||||
remove_sidelobe_objects_enabled=bool(self._gpr_remove_sidelobe_objects_enabled.isChecked()),
|
||||
imaging_plane_y_m=float(self._gpr_imaging_plane_y_m.value()),
|
||||
timing_mode=self._gpr_timing_mode.currentText(),
|
||||
tx_per_segment=int(self._gpr_tx_per_segment.value()),
|
||||
plane_mode=self._gpr_plane_mode.currentText(),
|
||||
ground_depth_m=float(self._gpr_ground_depth_m.value()),
|
||||
ground_pitch_override_enabled=bool(self._gpr_ground_pitch_override_enabled.isChecked()),
|
||||
ground_pitch_deg=float(self._gpr_ground_pitch_deg.value()),
|
||||
render_mode=self._gpr_render_mode.currentText(),
|
||||
visible_x_min_m=float(self._gpr_visible_x_min_m.value()),
|
||||
visible_x_max_m=float(self._gpr_visible_x_max_m.value()),
|
||||
|
||||
@@ -376,9 +376,58 @@ def build_processing_group(owner) -> QGroupBox:
|
||||
owner._gpr_imaging_plane_y_m.setSingleStep(0.05)
|
||||
owner._gpr_imaging_plane_y_m.setValue(float(gpr_live_defaults.imaging_plane_y_m))
|
||||
owner._gpr_imaging_plane_y_m.setToolTip(
|
||||
"Y coordinate of the BP imaging slice (m). Use 0 for legacy 1D antenna layouts."
|
||||
"Y coordinate of the BP imaging slice (m) in 'const' plane mode. "
|
||||
"Use 0 for legacy 1D antenna layouts."
|
||||
)
|
||||
|
||||
owner._gpr_timing_mode = QComboBox()
|
||||
owner._gpr_timing_mode.addItems(["segmented_interleaved", "interleaved"])
|
||||
owner._set_combo_current_text(owner._gpr_timing_mode, gpr_live_defaults.timing_mode)
|
||||
owner._gpr_timing_mode.setToolTip(
|
||||
"Acquisition timing model for motion correction: 'segmented_interleaved' — Tx run "
|
||||
"in sequential segments (real hardware), 'interleaved' — legacy whole-frame model."
|
||||
)
|
||||
|
||||
owner._gpr_tx_per_segment = QSpinBox()
|
||||
owner._gpr_tx_per_segment.setRange(1, 64)
|
||||
owner._gpr_tx_per_segment.setValue(int(gpr_live_defaults.tx_per_segment))
|
||||
owner._gpr_tx_per_segment.setToolTip(
|
||||
"Fallback Tx count per segment when per-trace capture timestamps are absent; "
|
||||
"timestamp grouping wins when available."
|
||||
)
|
||||
|
||||
owner._gpr_plane_mode = QComboBox()
|
||||
owner._gpr_plane_mode.addItems(["ground", "const"])
|
||||
owner._set_combo_current_text(owner._gpr_plane_mode, gpr_live_defaults.plane_mode)
|
||||
owner._gpr_plane_mode.setToolTip(
|
||||
"BP imaging plane: 'ground' follows the ground given the frame pitch "
|
||||
"(needed with vertically spread antennas), 'const' is the legacy fixed Y."
|
||||
)
|
||||
|
||||
owner._gpr_ground_depth_m = QDoubleSpinBox()
|
||||
owner._gpr_ground_depth_m.setDecimals(3)
|
||||
owner._gpr_ground_depth_m.setRange(0.0, 50.0)
|
||||
owner._gpr_ground_depth_m.setSingleStep(0.05)
|
||||
owner._gpr_ground_depth_m.setValue(float(gpr_live_defaults.ground_depth_m))
|
||||
owner._gpr_ground_depth_m.setToolTip(
|
||||
"Height of the lower antenna row above the ground (m); structural constant."
|
||||
)
|
||||
|
||||
owner._gpr_ground_pitch_override_enabled = QCheckBox("Override ground pitch")
|
||||
owner._gpr_ground_pitch_override_enabled.setChecked(
|
||||
bool(gpr_live_defaults.ground_pitch_override_enabled)
|
||||
)
|
||||
owner._gpr_ground_pitch_override_enabled.setToolTip(
|
||||
"Unchecked: the ground plane uses 'Look angle deg' as the frame pitch."
|
||||
)
|
||||
|
||||
owner._gpr_ground_pitch_deg = QDoubleSpinBox()
|
||||
owner._gpr_ground_pitch_deg.setDecimals(2)
|
||||
owner._gpr_ground_pitch_deg.setRange(-90.0, 90.0)
|
||||
owner._gpr_ground_pitch_deg.setSingleStep(0.1)
|
||||
owner._gpr_ground_pitch_deg.setValue(float(gpr_live_defaults.ground_pitch_deg))
|
||||
owner._gpr_ground_pitch_deg.setEnabled(bool(gpr_live_defaults.ground_pitch_override_enabled))
|
||||
|
||||
gpr_page = _build_processing_mode_page(
|
||||
owner._processing_mode_pages,
|
||||
[
|
||||
@@ -400,6 +449,12 @@ def build_processing_group(owner) -> QGroupBox:
|
||||
("Approach min frames", owner._gpr_object_approach_min_frames),
|
||||
("Start MHz", owner._gpr_start_freq_mhz),
|
||||
("Stop MHz", owner._gpr_stop_freq_mhz),
|
||||
("Timing mode", owner._gpr_timing_mode),
|
||||
("Tx per segment", owner._gpr_tx_per_segment),
|
||||
("Plane mode", owner._gpr_plane_mode),
|
||||
("Ground depth m", owner._gpr_ground_depth_m),
|
||||
owner._gpr_ground_pitch_override_enabled,
|
||||
("Ground pitch deg", owner._gpr_ground_pitch_deg),
|
||||
("Imaging plane Y m", owner._gpr_imaging_plane_y_m),
|
||||
("Visible X min m", owner._gpr_visible_x_min_m),
|
||||
("Visible X max m", owner._gpr_visible_x_max_m),
|
||||
@@ -793,6 +848,13 @@ def build_processing_group(owner) -> QGroupBox:
|
||||
owner._gpr_background_mean_count.valueChanged.connect(owner._on_processing_live_settings_changed)
|
||||
owner._gpr_remove_sidelobe_objects_enabled.toggled.connect(owner._on_processing_live_settings_changed)
|
||||
owner._gpr_imaging_plane_y_m.valueChanged.connect(owner._on_processing_live_settings_changed)
|
||||
owner._gpr_timing_mode.currentTextChanged.connect(owner._on_processing_live_settings_changed)
|
||||
owner._gpr_tx_per_segment.valueChanged.connect(owner._on_processing_live_settings_changed)
|
||||
owner._gpr_plane_mode.currentTextChanged.connect(owner._on_processing_live_settings_changed)
|
||||
owner._gpr_ground_depth_m.valueChanged.connect(owner._on_processing_live_settings_changed)
|
||||
owner._gpr_ground_pitch_override_enabled.toggled.connect(owner._gpr_ground_pitch_deg.setEnabled)
|
||||
owner._gpr_ground_pitch_override_enabled.toggled.connect(owner._on_processing_live_settings_changed)
|
||||
owner._gpr_ground_pitch_deg.valueChanged.connect(owner._on_processing_live_settings_changed)
|
||||
owner._gpr_render_mode.currentTextChanged.connect(owner._on_gpr_visual_settings_changed)
|
||||
owner._gpr_max_detected_objects_to_draw.valueChanged.connect(owner._on_gpr_locator_threshold_changed)
|
||||
owner._gpr_draw_top_m_objects.valueChanged.connect(owner._on_gpr_locator_threshold_changed)
|
||||
|
||||
@@ -380,6 +380,42 @@ def gui_profile_from_dict(payload: dict[str, Any]) -> GuiProfileModel:
|
||||
gui.processing.gpr.imaging_plane_y_m,
|
||||
"gui.processing.gpr",
|
||||
),
|
||||
timing_mode=_optional_string(
|
||||
gpr_object,
|
||||
"timing_mode",
|
||||
gui.processing.gpr.timing_mode,
|
||||
"gui.processing.gpr",
|
||||
),
|
||||
tx_per_segment=_optional_int(
|
||||
gpr_object,
|
||||
"tx_per_segment",
|
||||
gui.processing.gpr.tx_per_segment,
|
||||
"gui.processing.gpr",
|
||||
),
|
||||
plane_mode=_optional_string(
|
||||
gpr_object,
|
||||
"plane_mode",
|
||||
gui.processing.gpr.plane_mode,
|
||||
"gui.processing.gpr",
|
||||
),
|
||||
ground_depth_m=_optional_float(
|
||||
gpr_object,
|
||||
"ground_depth_m",
|
||||
gui.processing.gpr.ground_depth_m,
|
||||
"gui.processing.gpr",
|
||||
),
|
||||
ground_pitch_override_enabled=_optional_bool(
|
||||
gpr_object,
|
||||
"ground_pitch_override_enabled",
|
||||
gui.processing.gpr.ground_pitch_override_enabled,
|
||||
"gui.processing.gpr",
|
||||
),
|
||||
ground_pitch_deg=_optional_float(
|
||||
gpr_object,
|
||||
"ground_pitch_deg",
|
||||
gui.processing.gpr.ground_pitch_deg,
|
||||
"gui.processing.gpr",
|
||||
),
|
||||
render_mode=_optional_string(
|
||||
gpr_object,
|
||||
"render_mode",
|
||||
@@ -598,6 +634,16 @@ def gui_profile_from_dict(payload: dict[str, Any]) -> GuiProfileModel:
|
||||
raise ValueError("gui.processing.gpr.score_mode must be one of: peak, combined")
|
||||
if gui.processing.gpr.motion_mode not in {"int_minus", "int_focus"}:
|
||||
raise ValueError("gui.processing.gpr.motion_mode must be one of: int_minus, int_focus")
|
||||
if gui.processing.gpr.timing_mode not in {"segmented_interleaved", "interleaved"}:
|
||||
raise ValueError(
|
||||
"gui.processing.gpr.timing_mode must be one of: segmented_interleaved, interleaved"
|
||||
)
|
||||
if gui.processing.gpr.tx_per_segment < 1:
|
||||
raise ValueError("gui.processing.gpr.tx_per_segment must be >= 1")
|
||||
if gui.processing.gpr.plane_mode not in {"ground", "const"}:
|
||||
raise ValueError("gui.processing.gpr.plane_mode must be one of: ground, const")
|
||||
if gui.processing.gpr.ground_depth_m < 0.0:
|
||||
raise ValueError("gui.processing.gpr.ground_depth_m must be >= 0")
|
||||
if gui.processing.legacy_gpr.mode not in {"point", "extended"}:
|
||||
raise ValueError("gui.processing.legacy_gpr.mode must be one of: point, extended")
|
||||
if gui.processing.legacy_gpr.render_mode not in {"heatmap", "objects_only"}:
|
||||
@@ -750,6 +796,12 @@ def gui_profile_to_dict(model: GuiProfileModel) -> dict[str, Any]:
|
||||
"background_mean_count": gui.processing.gpr.background_mean_count,
|
||||
"remove_sidelobe_objects_enabled": gui.processing.gpr.remove_sidelobe_objects_enabled,
|
||||
"imaging_plane_y_m": gui.processing.gpr.imaging_plane_y_m,
|
||||
"timing_mode": gui.processing.gpr.timing_mode,
|
||||
"tx_per_segment": gui.processing.gpr.tx_per_segment,
|
||||
"plane_mode": gui.processing.gpr.plane_mode,
|
||||
"ground_depth_m": gui.processing.gpr.ground_depth_m,
|
||||
"ground_pitch_override_enabled": gui.processing.gpr.ground_pitch_override_enabled,
|
||||
"ground_pitch_deg": gui.processing.gpr.ground_pitch_deg,
|
||||
"render_mode": gui.processing.gpr.render_mode,
|
||||
"visible_x_min_m": gui.processing.gpr.visible_x_min_m,
|
||||
"visible_x_max_m": gui.processing.gpr.visible_x_max_m,
|
||||
|
||||
@@ -87,6 +87,18 @@ class GuiGprStateModel:
|
||||
background_mean_count: int = 10
|
||||
remove_sidelobe_objects_enabled: bool = True
|
||||
imaging_plane_y_m: float = 0.0
|
||||
# Acquisition timing model for motion correction: "segmented_interleaved"
|
||||
# (real hardware: Tx segments run sequentially) or "interleaved" (legacy:
|
||||
# all Tx interleave over the whole frame).
|
||||
timing_mode: str = "segmented_interleaved"
|
||||
# Fallback segment size when per-trace capture timestamps are absent.
|
||||
tx_per_segment: int = 2
|
||||
# Imaging plane: "ground" follows the ground given the frame pitch;
|
||||
# "const" is the legacy fixed y = imaging_plane_y_m.
|
||||
plane_mode: str = "ground"
|
||||
ground_depth_m: float = 1.25
|
||||
ground_pitch_override_enabled: bool = False
|
||||
ground_pitch_deg: float = 6.5
|
||||
render_mode: str = "heatmap"
|
||||
visible_x_min_m: float = -2.0
|
||||
visible_x_max_m: float = 2.0
|
||||
|
||||
@@ -62,6 +62,25 @@ class ProcessingLiveConfig:
|
||||
gpr_background_mean_count: int = 10
|
||||
gpr_remove_sidelobe_objects_enabled: bool = True
|
||||
gpr_imaging_plane_y_m: float = 0.0
|
||||
# Acquisition timing model for backprojection motion correction:
|
||||
# "segmented_interleaved" — Tx outputs run in sequential segments, each
|
||||
# segment sweeps all frequencies over its window while its Tx interleave
|
||||
# per frequency (real hardware scheme, notebook FREQUENCY_TIMING_MODE);
|
||||
# "interleaved" — legacy model, all Tx interleave over the whole frame.
|
||||
gpr_timing_mode: str = "segmented_interleaved"
|
||||
# Fallback segment size when per-trace capture timestamps are absent;
|
||||
# timestamp grouping wins when available.
|
||||
gpr_tx_per_segment: int = 2
|
||||
# Imaging plane for the 2D BP map: "ground" — plane follows the ground
|
||||
# given the frame pitch (notebook BP_PLANE_MODE='ground'), "const" —
|
||||
# legacy fixed y = gpr_imaging_plane_y_m.
|
||||
gpr_plane_mode: str = "ground"
|
||||
# Height of the LOWER antenna row above the ground, metres (structural
|
||||
# constant, notebook BP_GROUND_DEPTH_M).
|
||||
gpr_ground_depth_m: float = 1.25
|
||||
# Frame pitch used by the ground plane. Disabled -> gpr_look_angle_deg.
|
||||
gpr_ground_pitch_override_enabled: bool = False
|
||||
gpr_ground_pitch_deg: float = 6.5
|
||||
# Locator filter parameter consumed by the C++ TCP locator server. Coherent BP
|
||||
# objects are already finalized in the processor (no score threshold); only legacy
|
||||
# GPR still thresholds, on a pair count.
|
||||
@@ -149,6 +168,12 @@ class ProcessingLiveConfig:
|
||||
"gpr_background_mean_count": int(self.gpr_background_mean_count),
|
||||
"gpr_remove_sidelobe_objects_enabled": bool(self.gpr_remove_sidelobe_objects_enabled),
|
||||
"gpr_imaging_plane_y_m": float(self.gpr_imaging_plane_y_m),
|
||||
"gpr_timing_mode": str(self.gpr_timing_mode),
|
||||
"gpr_tx_per_segment": int(self.gpr_tx_per_segment),
|
||||
"gpr_plane_mode": str(self.gpr_plane_mode),
|
||||
"gpr_ground_depth_m": float(self.gpr_ground_depth_m),
|
||||
"gpr_ground_pitch_override_enabled": bool(self.gpr_ground_pitch_override_enabled),
|
||||
"gpr_ground_pitch_deg": float(self.gpr_ground_pitch_deg),
|
||||
"legacy_gpr_min_visible_pair_count": float(self.legacy_gpr_min_visible_pair_count),
|
||||
"gpr_visible_x_min_m": float(self.gpr_visible_x_min_m),
|
||||
"gpr_visible_x_max_m": float(self.gpr_visible_x_max_m),
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
"""Notebook-formula reference for the C++ `gpr_timing_check` tool.
|
||||
|
||||
Recomputes the same synthetic 4x4 scenario with the Multi_antenna_3D notebook
|
||||
formulas (compute_pair_timestamps_for_snapshot + compute_frequency_sample_times
|
||||
+ apply_intra_sweep_phase_correction, 'segmented_interleaved' branch) and
|
||||
prints the motion phase at the same probe indices in the same format. Run:
|
||||
|
||||
make gpr_timing_check && build/bin/gpr_timing_check > /tmp/cpp.txt
|
||||
python python_app/scripts/gpr_timing_check_reference.py > /tmp/ref.txt
|
||||
|
||||
The `int_minus` / `int_focus` lines of both outputs must match to ~1e-9 rad
|
||||
(phases are compared wrapped to (-pi, pi]).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import numpy as np
|
||||
|
||||
SPEED_OF_LIGHT = 299_792_458.0 # C++ kSpeedOfLightMetersPerSec
|
||||
SPEED_M_S = 1.42
|
||||
LOOK_ANGLE_DEG = 19.5
|
||||
DIRECTION_SIGN = 1.0
|
||||
F_START = 1.5e9
|
||||
F_STOP = 6.0e9
|
||||
N_FREQ = 11
|
||||
|
||||
# Trace windows of the synthetic collection (absolute seconds): segment A is
|
||||
# Tx0/Tx1, segment B is Tx2/Tx3 (260 ms, stretched).
|
||||
WINDOWS = {0: (1.000, 1.223), 1: (1.000, 1.223), 2: (1.223, 1.483), 3: (1.223, 1.483)}
|
||||
TX_ORDER = [0, 1, 2, 3]
|
||||
PROBE_INDICES = (0, 5, 10)
|
||||
|
||||
|
||||
def wrap(phase: np.ndarray) -> np.ndarray:
|
||||
"""Wrap phases to (-pi, pi] the way -arg(exp(-1j*phi)) reports them."""
|
||||
return -np.angle(np.exp(-1j * phase))
|
||||
|
||||
|
||||
def main() -> None:
|
||||
freqs = np.linspace(F_START, F_STOP, N_FREQ)
|
||||
first = min(start for start, _ in WINDOWS.values())
|
||||
last = max(end for _, end in WINDOWS.values())
|
||||
acq_s = last - first
|
||||
t_ref = 0.5 * acq_s
|
||||
|
||||
# Group Tx with identical windows into segments (snapshot_acquisition_timing
|
||||
# + compute_pair_timestamps_for_snapshot).
|
||||
groups: dict[tuple[float, float], list[int]] = {}
|
||||
for tx in TX_ORDER:
|
||||
groups.setdefault(WINDOWS[tx], []).append(tx)
|
||||
seg_of = {}
|
||||
for window in sorted(groups):
|
||||
for slot, tx in enumerate(groups[window]):
|
||||
seg_of[tx] = (window, slot, len(groups[window]))
|
||||
|
||||
cos_theta = np.cos(np.radians(LOOK_ANGLE_DEG))
|
||||
motion_factor = DIRECTION_SIGN * SPEED_M_S * cos_theta
|
||||
|
||||
print(f"reference: frame_center_s={t_ref:.9f} acq_s={acq_s:.9f}")
|
||||
phi_by_tx = {}
|
||||
for tx in TX_ORDER:
|
||||
(t0_abs, t1_abs), slot, n_slots = seg_of[tx]
|
||||
seg_start = t0_abs - first
|
||||
seg_end = t1_abs - first
|
||||
t_seg = seg_end - seg_start
|
||||
t_center = 0.5 * (seg_start + seg_end)
|
||||
|
||||
# compute_frequency_sample_times, segmented branch.
|
||||
dt_event = t_seg / (n_slots * N_FREQ)
|
||||
t_abs = seg_start + (n_slots * np.arange(N_FREQ) + slot) * dt_event
|
||||
|
||||
dt_intra = t_abs - t_center
|
||||
dtau_intra = 2.0 * motion_factor * dt_intra / SPEED_OF_LIGHT
|
||||
dt_pair = t_center - t_ref
|
||||
dtau_pair = 2.0 * motion_factor * dt_pair / SPEED_OF_LIGHT
|
||||
phi = 2.0 * np.pi * freqs * (dtau_intra + dtau_pair)
|
||||
phi_by_tx[tx] = phi
|
||||
|
||||
wrapped = wrap(phi)
|
||||
print(
|
||||
f"int_minus tx o{tx}: "
|
||||
+ " ".join(f"phi(k={k})={wrapped[k]:.12e}" for k in PROBE_INDICES)
|
||||
)
|
||||
|
||||
for tx in TX_ORDER:
|
||||
phi = phi_by_tx[tx]
|
||||
# int_focus: remove the constant + linear-in-frequency component
|
||||
# (least squares on [1, f_rel], notebook intra_phase_focus_only).
|
||||
f_rel = freqs - freqs.mean()
|
||||
design = np.column_stack([np.ones_like(f_rel), f_rel])
|
||||
beta, *_ = np.linalg.lstsq(design, phi, rcond=None)
|
||||
residual = phi - design @ beta
|
||||
wrapped = wrap(residual)
|
||||
print(
|
||||
f"int_focus tx o{tx}: "
|
||||
+ " ".join(f"phi(k={k})={wrapped[k]:.12e}" for k in PROBE_INDICES)
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -68,6 +68,18 @@ class GuiProfileCodecTest(unittest.TestCase):
|
||||
with self.assertRaises(ValueError): # combos_text must be a JSON string, not a number
|
||||
GuiProfileModel.from_dict({"gui": {"switches": {"combos_text": 123}}})
|
||||
|
||||
def test_invalid_gpr_timing_mode_is_rejected(self) -> None:
|
||||
with self.assertRaisesRegex(ValueError, "timing_mode"):
|
||||
GuiProfileModel.from_dict({"gui": {"processing": {"gpr": {"timing_mode": "bogus"}}}})
|
||||
|
||||
def test_invalid_gpr_plane_mode_is_rejected(self) -> None:
|
||||
with self.assertRaisesRegex(ValueError, "plane_mode"):
|
||||
GuiProfileModel.from_dict({"gui": {"processing": {"gpr": {"plane_mode": "bogus"}}}})
|
||||
|
||||
def test_invalid_gpr_tx_per_segment_is_rejected(self) -> None:
|
||||
with self.assertRaisesRegex(ValueError, "tx_per_segment"):
|
||||
GuiProfileModel.from_dict({"gui": {"processing": {"gpr": {"tx_per_segment": 0}}}})
|
||||
|
||||
def test_legacy_gpr_payload_migrates_selected_mode(self) -> None:
|
||||
# An old payload that selects 'gpr' but carries a legacy root gpr.mode is migrated.
|
||||
decoded = GuiProfileModel.from_dict({
|
||||
|
||||
Reference in New Issue
Block a user