sar3d: GPU port of the notebook with four views

The sar3d processor is rewritten as a checked port of Multi_antenna_3D_clean_v1.ipynb:
NVRTC kernels, timing from the trace stamps, notebook parameter names and one cubic
SAR_VOXEL_M. It adds front, side, top and 3D views, drops gpr_cpp and the parity tools,
and the GUI speed now applies only while the socket speed is ignored.
This commit is contained in:
Ayzen
2026-10-02 16:05:22 +03:00
parent 61ef8720e1
commit 45861f4047
50 changed files with 1737 additions and 8144 deletions
@@ -8,6 +8,7 @@ from python_app.models.gui_profile_model import (
GuiPassThroughStateModel,
GuiProcessingStateModel,
GuiProfileModel,
GuiSar3dStateModel,
GuiStateModel,
)
@@ -53,6 +54,31 @@ class GuiProfileCodecTest(unittest.TestCase):
twice = GuiProfileModel.from_dict(once).to_dict()
self.assertEqual(once["gui"], twice["gui"])
def test_sar3d_section_round_trips(self) -> None:
sar3d = GuiSar3dStateModel(
input_positions="0,2",
output_positions="1,3",
min_depth_m=2.5,
max_depth_m=9.0,
start_freq_mhz=2000.0,
stop_freq_mhz=5500.0,
speed_m_s=1.42,
ignore_socket_speed_enabled=True,
look_angle_deg=6.5,
frame_radius=3,
voxel_m=0.035,
bp_ground_depth_m=1.1,
comp_range_power=0.5,
use_compensation=False,
normalize_each_frame=False,
)
profile = GuiProfileModel(gui=GuiStateModel(processing=GuiProcessingStateModel(sar3d=sar3d)))
decoded = GuiProfileModel.from_dict(profile.to_dict())
assert decoded.gui is not None
self.assertEqual(decoded.gui.processing.sar3d, sar3d)
def test_missing_gui_section_yields_none(self) -> None:
self.assertIsNone(GuiProfileModel.from_dict({}).gui)
@@ -0,0 +1,65 @@
"""Speed authority between a locator socket client and the GUI field.
The data_processor uses the speed a socket client streams unless the live config
sets `ignore_socket_speed`; then `gpr_speed_m_s` from the GUI field applies. Each
GPR-like page (gpr, legacy_gpr, sar3d) has its own checkbox and field, and the
field is editable only while it is the one in force.
"""
from __future__ import annotations
import os
import unittest
from pathlib import Path
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
from PyQt6.QtWidgets import QApplication # noqa: E402
from python_app.gui.app_window import AppWindow # noqa: E402
_app: QApplication | None = None
_window: AppWindow | None = None
# mode -> (checkbox, speed field) attributes of its page
_PAGES = {
"gpr": ("_gpr_ignore_socket_speed_enabled", "_gpr_speed_m_s"),
"legacy_gpr": ("_legacy_gpr_ignore_socket_speed_enabled", "_legacy_gpr_speed_m_s"),
"sar3d": ("_sar3d_ignore_socket_speed_enabled", "_sar3d_speed_m_s"),
}
def setUpModule() -> None:
global _app, _window
_app = QApplication.instance() or QApplication([])
_window = AppWindow(Path("."))
def tearDownModule() -> None:
if _window is not None:
_window.close()
class SocketSpeedControlsTest(unittest.TestCase):
def test_speed_field_is_editable_only_while_the_socket_is_ignored(self) -> None:
assert _window is not None
for mode, (checkbox_name, speed_name) in _PAGES.items():
checkbox, speed = getattr(_window, checkbox_name), getattr(_window, speed_name)
for ignore in (True, False):
checkbox.setChecked(ignore)
self.assertEqual(speed.isEnabled(), ignore, msg=f"{mode}, ignore={ignore}")
def test_live_config_takes_the_active_page(self) -> None:
assert _window is not None
for mode, (checkbox_name, speed_name) in _PAGES.items():
_window._set_combo_current_text(_window._processing_mode, mode)
getattr(_window, checkbox_name).setChecked(True)
getattr(_window, speed_name).setValue(1.25)
config = _window._live_processing_config()
self.assertTrue(config.ignore_socket_speed, msg=mode)
self.assertAlmostEqual(config.gpr_speed_m_s, 1.25, msg=mode)
getattr(_window, checkbox_name).setChecked(False)
self.assertFalse(_window._live_processing_config().ignore_socket_speed, msg=mode)
if __name__ == "__main__":
unittest.main()