Rework radar system

This commit is contained in:
Ayzen
2026-10-06 12:32:26 +03:00
parent 552cdc688a
commit 5bb86b7165
299 changed files with 13537 additions and 27763 deletions
+25 -23
View File
@@ -2,19 +2,19 @@
from __future__ import annotations
from pathlib import Path
from contextlib import ExitStack
import unittest
from unittest import mock
import numpy as np
from python_app.hardware_full.matrix_radar_service import create_acquisition_service
from python_app.hardware_full.matrix_radar_service import create_matrix_radar_service
from python_app.hardware_full.point_target_mock_service import PointTargetMockService
from python_app.hardware_full.single_radar_service import create_single_radar_service
from python_app.models.dataset_model import ComboKey
from python_app.models.run_config_model import RunConfigModel
from python_app.models.run_config_schema import RunConfigModel
from python_app.models.run_config_schema import GprRxGeometryModel, GprTxGeometryModel
from python_app.workflows.multi_radar_capture_workflow import MultiRadarSequentialCaptureSession
from python_app.workflows.radar_config_variants import RadarConfigVariant
from python_app.workflows.sequential_capture_workflow import SequentialCaptureSession
@@ -41,13 +41,15 @@ def _config() -> RunConfigModel:
class PointTargetMockTest(unittest.TestCase):
def setUp(self) -> None:
self.clock = self.enterContext(mock.patch(_CLOCK, return_value=_EPOCH_NS))
self.sleep = self.enterContext(mock.patch(_SLEEP))
cleanup = ExitStack()
self.addCleanup(cleanup.close)
self.clock = cleanup.enter_context(mock.patch(_CLOCK, return_value=_EPOCH_NS))
self.sleep = cleanup.enter_context(mock.patch(_SLEEP))
self.config = _config()
self.service = PointTargetMockService(self.config)
self.service.configure(self.config.radar.sweep)
def test_all_channels_follow_bistatic_and_monostatic_distance(self) -> None:
def test_all_channels_follow_bistatic_distance(self) -> None:
collection = self.service.acquire_collection(collection_id=43)
target = np.array([0, .25, 3.75])
self.assertEqual(collection.collection_id, 43)
@@ -59,7 +61,6 @@ class PointTargetMockTest(unittest.TestCase):
r_rx = np.linalg.norm(target - [rx.x_m, rx.y_m, rx.z_m])
phase_per_m = -2j * np.pi * trace.frequency_hz.astype(np.float64) / 3e8
np.testing.assert_allclose(trace.s21, .5 / (r_tx * r_rx) * np.exp(phase_per_m * (r_tx + r_rx)), rtol=1e-6)
np.testing.assert_allclose(trace.s11, .5 / r_tx**2 * np.exp(phase_per_m * 2 * r_tx), rtol=1e-6)
self.assertEqual((trace.capture_start_ns, trace.capture_end_ns), (_EPOCH_NS, _EPOCH_NS))
self.assertEqual(trace.s21.dtype, np.dtype(np.complex64))
self.assertEqual((collection.capture_start_ns, collection.capture_end_ns, collection.monotonic_ns), (_EPOCH_NS,) * 3)
@@ -70,14 +71,12 @@ class PointTargetMockTest(unittest.TestCase):
traces = self.service.acquire_collection().traces
for trace in traces[1:]:
np.testing.assert_array_equal(trace.s21, traces[0].s21)
np.testing.assert_array_equal(trace.s11, traces[0].s11)
def test_geometry_changes_receiver_path_without_changing_transmitter_echo(self) -> None:
def test_geometry_changes_receiver_path(self) -> None:
before = self.service.acquire_collection().traces[0]
self.config.gpr.rx_geometry[0].z_m = 1
after = self.service.acquire_collection().traces[0]
self.assertFalse(np.array_equal(before.s21, after.s21))
np.testing.assert_array_equal(before.s11, after.s11)
def test_motion_uses_elapsed_time_and_returns_after_eight_seconds(self) -> None:
tx = {0: (-1., 0., 0.), 1: (1., 0., 0.)}
@@ -132,13 +131,16 @@ class PointTargetMockTest(unittest.TestCase):
self.assertLess(max(ranges), unambiguous_range_m)
def test_single_manual_capture_selects_actual_antenna_and_keeps_capture_time(self) -> None:
session = SequentialCaptureSession(self.config, "s21_calibration", "mock_geometry")
radar = mock.Mock(wraps=self.service)
with mock.patch("python_app.workflows.sequential_capture_workflow.create_single_radar_service", return_value=radar):
session = SequentialCaptureSession(self.config, "s21_calibration", "mock_geometry")
session.open()
try:
first = session.capture_current_combo()
second = session.capture_current_combo()
first = session.capture_current_combo().display_trace
second = session.capture_current_combo().display_trace
finally:
session.close()
radar.configure.assert_called_once_with(self.config.radar.sweep)
self.assertEqual((first.combo, second.combo), (ComboKey(0, 0), ComboKey(1, 0)))
self.assertFalse(np.array_equal(first.s21, second.s21))
self.assertEqual((second.capture_start_ns, second.capture_end_ns), (_EPOCH_NS, _EPOCH_NS))
@@ -147,13 +149,13 @@ class PointTargetMockTest(unittest.TestCase):
single = create_single_radar_service(self.config)
self.assertIsInstance(single, PointTargetMockService)
single.configure(self.config.radar.sweep)
self.assertIsInstance(create_acquisition_service(self.config), PointTargetMockService)
self.assertIsInstance(create_matrix_radar_service(self.config), PointTargetMockService)
self.config.radar.model = RunConfigModel.LIBREVNA_MULTI_MODEL
self.config.radar.multi_device.stimulus_switch_positions = 2
self.config.radar.multi_device.input_switch_positions = 2
self.config.radar.multi_device.output_switch_positions = 2
self.config.apply_device_model_constraints()
matrix = create_acquisition_service(self.config)
matrix = create_matrix_radar_service(self.config)
self.assertIsInstance(matrix, PointTargetMockService)
matrix.open()
matrix.configure(self.config.radar.sweep)
@@ -166,15 +168,15 @@ class PointTargetMockTest(unittest.TestCase):
def test_multi_variant_single_capture_drains_each_variant_before_sampling(self) -> None:
radar = mock.Mock(wraps=self.service)
events = []
radar.drain_after_switch = mock.Mock(side_effect=lambda: events.append("drain"))
radar.prepare_combo.side_effect = lambda combo: (events.append("prepare"), self.service.prepare_combo(combo))
radar.acquire.side_effect = lambda: (events.append("acquire"), self.service.acquire())[1]
variants = [RadarConfigVariant(Path(name), name, self.config, name) for name in ("a", "b")]
variants = [RadarConfigVariant(name, self.config, name) for name in ("a", "b")]
with mock.patch(
"python_app.workflows.multi_radar_capture_workflow.create_single_radar_service",
"python_app.workflows.sequential_capture_workflow.create_single_radar_service",
return_value=radar,
):
session = MultiRadarSequentialCaptureSession(
base_config=self.config, kind="s21_calibration", set_name="variants", radar_variants=variants,
session = SequentialCaptureSession(
config=self.config, kind="s21_calibration", set_name="variants", radar_variants=variants,
)
session.open()
try:
@@ -182,14 +184,14 @@ class PointTargetMockTest(unittest.TestCase):
batch = session.capture_current_combo()
finally:
session.close()
self.assertEqual(events, ["drain", "acquire"] * 4)
self.assertEqual(events, ["prepare", "acquire"] * 4)
self.assertEqual([trace.combo for trace in batch.traces], [ComboKey(1, 0)] * 2)
for trace in batch.traces:
self.assertEqual((trace.capture_start_ns, trace.capture_end_ns), (_EPOCH_NS, _EPOCH_NS))
def test_invalid_or_unconfigured_mock_fails_clearly(self) -> None:
with self.assertRaisesRegex(ValueError, "outside the antenna matrix"):
self.service.acquire_combo_collection(input_pos=2, output_pos=0)
self.service.acquire_collection(combo=ComboKey(2, 0))
with self.assertRaisesRegex(RuntimeError, "not configured"):
PointTargetMockService(self.config).acquire_collection()
self.config.input_switch.positions = 0