projector version

This commit is contained in:
awe
2026-09-15 19:06:05 +03:00
parent 62c0266a6b
commit b3339d101c
33 changed files with 2605 additions and 51 deletions
+162 -2
View File
@@ -14,6 +14,7 @@ from python_app.models.gui_profile_schema import (
GuiPreprocessDialogStateModel,
GuiProcessingStateModel,
GuiProfileModel,
GuiSar3dStateModel,
GuiStateModel,
GuiSwitchStateModel,
)
@@ -106,6 +107,7 @@ def gui_profile_from_dict(payload: dict[str, Any]) -> GuiProfileModel:
bscan_object = _as_dict(processing_object.get("bscan"), "gui.processing.bscan")
gpr_object = _as_dict(processing_object.get("gpr"), "gui.processing.gpr")
legacy_gpr_object = _as_dict(processing_object.get("legacy_gpr"), "gui.processing.legacy_gpr")
sar3d_object = _as_dict(processing_object.get("sar3d"), "gui.processing.sar3d")
root_gpr_object = payload.get("gpr")
selected_mode = _optional_string(
@@ -452,9 +454,136 @@ def gui_profile_from_dict(payload: dict[str, Any]) -> GuiProfileModel:
visible_z_min_m=legacy_float("visible_z_min_m", gui.processing.legacy_gpr.visible_z_min_m),
visible_z_max_m=legacy_float("visible_z_max_m", gui.processing.legacy_gpr.visible_z_max_m),
),
sar3d=GuiSar3dStateModel(
input_positions=_optional_string(
sar3d_object,
"input_positions",
gui.processing.sar3d.input_positions,
"gui.processing.sar3d",
),
output_positions=_optional_string(
sar3d_object,
"output_positions",
gui.processing.sar3d.output_positions,
"gui.processing.sar3d",
),
min_depth_m=_optional_float(
sar3d_object,
"min_depth_m",
gui.processing.sar3d.min_depth_m,
"gui.processing.sar3d",
),
max_depth_m=_optional_float(
sar3d_object,
"max_depth_m",
gui.processing.sar3d.max_depth_m,
"gui.processing.sar3d",
),
start_freq_mhz=_optional_float(
sar3d_object,
"start_freq_mhz",
gui.processing.sar3d.start_freq_mhz,
"gui.processing.sar3d",
),
stop_freq_mhz=_optional_float(
sar3d_object,
"stop_freq_mhz",
gui.processing.sar3d.stop_freq_mhz,
"gui.processing.sar3d",
),
speed_m_s=_optional_float(
sar3d_object,
"speed_m_s",
gui.processing.sar3d.speed_m_s,
"gui.processing.sar3d",
),
ignore_socket_speed_enabled=_optional_bool(
sar3d_object,
"ignore_socket_speed_enabled",
gui.processing.sar3d.ignore_socket_speed_enabled,
"gui.processing.sar3d",
),
look_angle_deg=_optional_float(
sar3d_object,
"look_angle_deg",
gui.processing.sar3d.look_angle_deg,
"gui.processing.sar3d",
),
direction_sign=_optional_float(
sar3d_object,
"direction_sign",
gui.processing.sar3d.direction_sign,
"gui.processing.sar3d",
),
apply_freq_phase_correction=_optional_bool(
sar3d_object,
"apply_freq_phase_correction",
gui.processing.sar3d.apply_freq_phase_correction,
"gui.processing.sar3d",
),
frame_radius=_optional_int(
sar3d_object,
"frame_radius",
gui.processing.sar3d.frame_radius,
"gui.processing.sar3d",
),
edge_frame_weight=_optional_float(
sar3d_object,
"edge_frame_weight",
gui.processing.sar3d.edge_frame_weight,
"gui.processing.sar3d",
),
pitch_deg=_optional_float(
sar3d_object,
"pitch_deg",
gui.processing.sar3d.pitch_deg,
"gui.processing.sar3d",
),
center_depth_m=_optional_float(
sar3d_object,
"center_depth_m",
gui.processing.sar3d.center_depth_m,
"gui.processing.sar3d",
),
half_width_x_m=_optional_float(
sar3d_object,
"half_width_x_m",
gui.processing.sar3d.half_width_x_m,
"gui.processing.sar3d",
),
half_width_y_m=_optional_float(
sar3d_object,
"half_width_y_m",
gui.processing.sar3d.half_width_y_m,
"gui.processing.sar3d",
),
half_width_z_m=_optional_float(
sar3d_object,
"half_width_z_m",
gui.processing.sar3d.half_width_z_m,
"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(
sar3d_object,
"use_compensation",
gui.processing.sar3d.use_compensation,
"gui.processing.sar3d",
),
normalize_pairs=_optional_bool(
sar3d_object,
"normalize_pairs",
gui.processing.sar3d.normalize_pairs,
"gui.processing.sar3d",
),
),
)
if gui.processing.selected_mode not in {"pass_through", "bscan", "gpr", "legacy_gpr"}:
raise ValueError("gui.processing.selected_mode must be one of: pass_through, bscan, gpr, legacy_gpr")
if gui.processing.selected_mode not in {"pass_through", "bscan", "gpr", "legacy_gpr", "sar3d"}:
raise ValueError(
"gui.processing.selected_mode must be one of: pass_through, bscan, gpr, legacy_gpr, sar3d"
)
if gui.processing.bscan.axis not in {"abs", "real", "phase"}:
raise ValueError("gui.processing.bscan.axis must be one of: abs, real, phase")
if gui.processing.gpr.render_mode not in {"heatmap", "objects_only"}:
@@ -489,6 +618,12 @@ def gui_profile_from_dict(payload: dict[str, Any]) -> GuiProfileModel:
raise ValueError("gui.processing.legacy_gpr.snr_comp_max must be >= 0")
if gui.processing.legacy_gpr.min_visible_pair_count < 1:
raise ValueError("gui.processing.legacy_gpr.min_visible_pair_count must be >= 1")
if gui.processing.sar3d.frame_radius < 0:
raise ValueError("gui.processing.sar3d.frame_radius must be >= 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]")
if min(gui.processing.sar3d.grid_nx, gui.processing.sar3d.grid_ny, gui.processing.sar3d.grid_nz) < 2:
raise ValueError("gui.processing.sar3d.grid_nx/ny/nz must each be >= 2")
data_actions_object = _as_dict(gui_object.get("data_actions"), "gui.data_actions")
gui.data_actions = GuiDataActionsStateModel(
@@ -640,6 +775,31 @@ def gui_profile_to_dict(model: GuiProfileModel) -> dict[str, Any]:
"visible_z_min_m": gui.processing.legacy_gpr.visible_z_min_m,
"visible_z_max_m": gui.processing.legacy_gpr.visible_z_max_m,
},
"sar3d": {
"input_positions": gui.processing.sar3d.input_positions,
"output_positions": gui.processing.sar3d.output_positions,
"min_depth_m": gui.processing.sar3d.min_depth_m,
"max_depth_m": gui.processing.sar3d.max_depth_m,
"start_freq_mhz": gui.processing.sar3d.start_freq_mhz,
"stop_freq_mhz": gui.processing.sar3d.stop_freq_mhz,
"speed_m_s": gui.processing.sar3d.speed_m_s,
"ignore_socket_speed_enabled": gui.processing.sar3d.ignore_socket_speed_enabled,
"look_angle_deg": gui.processing.sar3d.look_angle_deg,
"direction_sign": gui.processing.sar3d.direction_sign,
"apply_freq_phase_correction": gui.processing.sar3d.apply_freq_phase_correction,
"frame_radius": gui.processing.sar3d.frame_radius,
"edge_frame_weight": gui.processing.sar3d.edge_frame_weight,
"pitch_deg": gui.processing.sar3d.pitch_deg,
"center_depth_m": gui.processing.sar3d.center_depth_m,
"half_width_x_m": gui.processing.sar3d.half_width_x_m,
"half_width_y_m": gui.processing.sar3d.half_width_y_m,
"half_width_z_m": gui.processing.sar3d.half_width_z_m,
"grid_nx": gui.processing.sar3d.grid_nx,
"grid_ny": gui.processing.sar3d.grid_ny,
"grid_nz": gui.processing.sar3d.grid_nz,
"use_compensation": gui.processing.sar3d.use_compensation,
"normalize_pairs": gui.processing.sar3d.normalize_pairs,
},
},
"data_actions": {
"save_count": gui.data_actions.save_count,
+2
View File
@@ -10,6 +10,7 @@ from python_app.models.gui_profile_schema import (
GuiPreprocessDialogStateModel,
GuiProcessingStateModel,
GuiProfileModel,
GuiSar3dStateModel,
GuiStateModel,
GuiSwitchStateModel,
)
@@ -23,6 +24,7 @@ __all__ = [
"GuiPreprocessDialogStateModel",
"GuiProcessingStateModel",
"GuiProfileModel",
"GuiSar3dStateModel",
"GuiStateModel",
"GuiSwitchStateModel",
"gui_profile_from_dict",
+35
View File
@@ -123,6 +123,40 @@ class GuiLegacyGprStateModel:
visible_z_max_m: float = 14.0
@dataclass(slots=True)
class GuiSar3dStateModel:
"""UI-only defaults for the 3D SAR (sar3d) live settings.
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
defaults used by the C++ sar3d processor.
"""
input_positions: str = ""
output_positions: str = ""
min_depth_m: float = 2.0
max_depth_m: float = 14.0
start_freq_mhz: float = 3000.0
stop_freq_mhz: float = 6000.0
speed_m_s: float = 0.0
ignore_socket_speed_enabled: bool = False
look_angle_deg: float = 0.0
direction_sign: float = 1.0
apply_freq_phase_correction: bool = True
frame_radius: int = 6
edge_frame_weight: float = 0.75
pitch_deg: float = 6.0
center_depth_m: float = 5.6
half_width_x_m: float = 1.8
half_width_y_m: float = 1.0
half_width_z_m: float = 1.8
grid_nx: int = 150
grid_ny: int = 120
grid_nz: int = 160
use_compensation: bool = True
normalize_pairs: bool = True
@dataclass(slots=True)
class GuiProcessingStateModel:
"""UI-only processing-section defaults."""
@@ -132,6 +166,7 @@ class GuiProcessingStateModel:
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)
@dataclass(slots=True)
+13
View File
@@ -175,6 +175,7 @@ def run_config_from_dict(payload: dict[str, Any]) -> RunConfigModel:
"run.locator_server",
)
multi_device_payload = _as_dict(radar_payload.get("multi_device"), "radar.multi_device")
replay_payload = _as_dict(radar_payload.get("replay"), "radar.replay")
kamil_adc_payload = _as_dict(radar_payload.get("kamil_adc"), "radar.kamil_adc")
laser_control_payload = _as_dict(radar_payload.get("laser_control"), "radar.laser_control")
@@ -235,6 +236,13 @@ def run_config_from_dict(payload: dict[str, Any]) -> RunConfigModel:
"stimulus_switch_positions",
model.radar.multi_device.stimulus_switch_positions,
)
model.radar.replay.dataset_dir = _read_str(
replay_payload, "dataset_dir", model.radar.replay.dataset_dir
)
model.radar.replay.loop = _read_bool(replay_payload, "loop", model.radar.replay.loop)
model.radar.replay.rate_scale = _read_float(
replay_payload, "rate_scale", model.radar.replay.rate_scale
)
model.radar.kamil_adc.project_dir = _read_str(
kamil_adc_payload, "project_dir", model.radar.kamil_adc.project_dir
)
@@ -519,6 +527,11 @@ def run_config_to_dict(model: RunConfigModel) -> dict[str, Any]:
"input_switch_positions": model.radar.multi_device.input_switch_positions,
"stimulus_switch_positions": model.radar.multi_device.stimulus_switch_positions
},
"replay": {
"dataset_dir": model.radar.replay.dataset_dir,
"loop": model.radar.replay.loop,
"rate_scale": model.radar.replay.rate_scale,
},
"kamil_adc": {
"project_dir": model.radar.kamil_adc.project_dir,
"executable_path": model.radar.kamil_adc.executable_path,
+20
View File
@@ -52,6 +52,25 @@ class RadarMultiDeviceModel:
stimulus_switch_positions: int = 1
@dataclass(slots=True)
class RadarReplayModel:
"""Recorded-dataset replay settings (no hardware).
When ``dataset_dir`` is non-empty the supervisor launches
``python_app.scripts.replay_raw_producer`` instead of the model's normal
acquisition producer. The dataset is a directory of snapshot folders named
``NNNN_idK_ns<timestamp>``, each holding ``i{input}_o{output}_s21.npy`` /
``_freq.npy`` files (the VNA_AlGo preprocessed-capture layout).
"""
dataset_dir: str = ""
# Restart from the first snapshot when the dataset ends; the replay
# timeline keeps advancing monotonically across the seam.
loop: bool = True
# Playback speed multiplier: 1.0 replays at the recorded frame cadence.
rate_scale: float = 1.0
@dataclass(slots=True)
class KamilAdcPhaseCalibrationModel:
"""Affine law mapping the reference signal's unwrapped phase to frequency.
@@ -159,6 +178,7 @@ class RadarModel:
visa_library: str = ""
sweep: RadarSweepModel = field(default_factory=RadarSweepModel)
multi_device: RadarMultiDeviceModel = field(default_factory=RadarMultiDeviceModel)
replay: RadarReplayModel = field(default_factory=RadarReplayModel)
kamil_adc: KamilAdcModel = field(default_factory=KamilAdcModel)
laser_control: LaserControlModel = field(default_factory=LaserControlModel)