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
+24 -80
View File
@@ -3,22 +3,12 @@
from __future__ import annotations
from collections import deque
from typing import TypeVar
from dataclasses import astuple
from python_app.models.dataset_model import ResultCollection, SweepCollection
from python_app.models.run_config_model import RunConfigModel
from python_app.orchestration.preprocess_assets import PREPROCESS_ASSET_KEYS, preprocess_asset_model
THistoryCollection = TypeVar("THistoryCollection", SweepCollection, ResultCollection)
def collection_key(collection: SweepCollection | ResultCollection) -> tuple[int, int]:
"""Identity of a sweep through the pipeline: its result keeps the same id and timestamp.
`monotonic_ns` is stamped by `data_preprocessor` on the system monotonic clock, so
it also orders sweeps by acquisition, across runs too (ids restart from 1).
"""
return int(collection.collection_id), int(collection.monotonic_ns)
from python_app.models.collection_identity import collection_key
from python_app.models.dataset_model import ResultCollection
from python_app.models.run_config_schema import RunConfigModel
from python_app.orchestration.preprocess_assets import PREPROCESS_ASSET_NAMES, preprocess_asset_model
def record_result_history(
@@ -33,6 +23,8 @@ def record_result_history(
delivers sweeps whose first result the GUI missed, and appending those would
show them out of order.
"""
if result_history.maxlen == 0:
return False
key = collection_key(collection)
position = len(result_history)
while position > 0:
@@ -40,7 +32,7 @@ def record_result_history(
if collection_key(existing) == key:
result_history[position - 1] = collection
return False
if existing.monotonic_ns < collection.monotonic_ns:
if existing.capture_start_ns < collection.capture_start_ns:
break
position -= 1
@@ -53,48 +45,19 @@ def record_result_history(
result_history.popleft()
position -= 1
result_history.insert(position, collection)
return False
def remove_last_aligned_histories(
raw_history: list[SweepCollection],
preprocessed_history: list[SweepCollection],
result_history: list[ResultCollection],
) -> tuple[list[SweepCollection], list[SweepCollection], list[ResultCollection]]:
"""Remove the newest aligned history entry using results as preferred anchor."""
retained_raw = list(raw_history)
retained_preprocessed = list(preprocessed_history)
retained_results = list(result_history)
if retained_results:
target_key = _tail_occurrence_key(retained_results, len(retained_results) - 1)
retained_results.pop()
_remove_by_tail_occurrence_key(retained_raw, target_key)
_remove_by_tail_occurrence_key(retained_preprocessed, target_key)
return retained_raw, retained_preprocessed, retained_results
if retained_preprocessed:
target_key = _tail_occurrence_key(retained_preprocessed, len(retained_preprocessed) - 1)
retained_preprocessed.pop()
_remove_by_tail_occurrence_key(retained_raw, target_key)
return retained_raw, retained_preprocessed, retained_results
if retained_raw:
retained_raw.pop()
return retained_raw, retained_preprocessed, retained_results
return True
def build_processor_run_signature(
config: RunConfigModel,
) -> tuple[object, ...]:
"""Build signature of settings that change the data SHAPE the data_processor parses.
"""Settings consumed at processor startup, rather than through the live file.
Excludes the preprocess set names on purpose: the data_processor does not consume
calibration/reference sets (those are applied upstream by the data_preprocessor), so
changing a set must NOT restart the processor. Restarting it would also tear down the
locator TCP server it hosts and drop every connected client. Only genuine shape changes
(radar model, sweep, switch layout, combos) require a processor restart.
locator TCP server it hosts and drop every connected client. Shape, ring endpoints,
locator settings, live-file path and polling interval require a restart.
"""
combos_signature = tuple((int(combo.input), int(combo.output)) for combo in config.combos)
sweep_points_signature: object = "adc" if config.is_kamil_adc else int(config.radar.sweep.points)
@@ -110,49 +73,30 @@ def build_processor_run_signature(
float(config.radar.sweep.if_bandwidth_hz),
float(config.radar.sweep.power_dbm),
str(config.input_switch.driver_mode),
str(config.input_switch.driver),
int(config.input_switch.positions),
bool(config.input_switch.invert_logic),
str(config.output_switch.driver_mode),
str(config.output_switch.driver),
int(config.output_switch.positions),
bool(config.output_switch.invert_logic),
combos_signature,
astuple(config.rings),
astuple(config.runtime.locator_server),
str(config.runtime.processing_live_config_path),
int(config.runtime.idle_sleep_ms),
)
def build_run_history_signature(
config: RunConfigModel,
) -> tuple[object, ...]:
"""Build full signature for GUI display-history reset (processor shape + preprocess sets).
"""Reset display history when startup settings or preprocessing change.
Display history still resets when the reference/calibration set changes (the on-screen
B-scan would otherwise mix old- and new-reference frames), even though the processor
process itself is intentionally kept alive across that change.
"""
preprocess_signature = tuple(preprocess_asset_model(config, key).set_name for key in PREPROCESS_ASSET_KEYS)
return build_processor_run_signature(config) + (preprocess_signature,)
def _tail_occurrence_key(history: list[THistoryCollection], index: int) -> tuple[int, int]:
"""Return `(collection_id, occurrence_from_tail)` for the item at `index`."""
collection_id = int(history[index].collection_id)
occurrence_from_tail = 0
for cursor in range(len(history) - 1, index, -1):
if int(history[cursor].collection_id) == collection_id:
occurrence_from_tail += 1
return collection_id, occurrence_from_tail
def _remove_by_tail_occurrence_key(history: list[THistoryCollection], key: tuple[int, int]) -> bool:
"""Remove newest matching entry identified by `(collection_id, occurrence_from_tail)`."""
collection_id, target_occurrence = key
occurrence_from_tail = 0
for index in range(len(history) - 1, -1, -1):
if int(history[index].collection_id) != collection_id:
continue
if occurrence_from_tail == target_occurrence:
history.pop(index)
return True
occurrence_from_tail += 1
return False
preprocess_signature = tuple(astuple(preprocess_asset_model(config, key)) for key in PREPROCESS_ASSET_NAMES)
notch = config.preprocess.notch
notch_signature = (
bool(notch.enabled), tuple(tuple(band) for band in notch.bands_hz),
float(notch.taper_width_hz), str(notch.taper_type),
)
return build_processor_run_signature(config) + (preprocess_signature, notch_signature)