init commit

This commit is contained in:
Ayzen
2026-03-05 14:42:33 +03:00
commit fd4618b20d
964 changed files with 325114 additions and 0 deletions
+19
View File
@@ -0,0 +1,19 @@
"""Plotting helpers for trace and B-scan visualization."""
from python_app.gui.plotting.bscan_history import (
build_bscan_signature,
pick_bscan_display_key,
rebuild_bscan_history_from_results,
)
from python_app.gui.plotting.bscan_math import (
bscan_levels,
bscan_lookup_table,
)
__all__ = [
"bscan_levels",
"bscan_lookup_table",
"build_bscan_signature",
"pick_bscan_display_key",
"rebuild_bscan_history_from_results",
]
+114
View File
@@ -0,0 +1,114 @@
"""Helpers for B-scan history signatures and cache rebuilding."""
from __future__ import annotations
from collections import deque
import numpy as np
from python_app.models.dataset_model import ResultCollection
from python_app.orchestration.live_processing_config import ProcessingLiveConfig
def _result_tail(
*,
result_history: list[ResultCollection],
history_limit: int,
floor_collection_id: int,
) -> list[ResultCollection]:
"""Return filtered and de-duplicated result-history tail for B-scan usage."""
filtered = [
collection
for collection in result_history[-history_limit:]
if int(collection.collection_id) > int(floor_collection_id)
]
unique_tail: list[ResultCollection] = []
seen_keys: set[tuple[int, int]] = set()
for collection in filtered:
key = (int(collection.collection_id), int(collection.monotonic_ns))
if key in seen_keys:
continue
seen_keys.add(key)
unique_tail.append(collection)
return unique_tail
def build_bscan_signature(
live_config: ProcessingLiveConfig,
result_history: list[ResultCollection],
history_limit: int,
floor_collection_id: int,
) -> tuple[object, ...]:
"""Build deterministic signature used to detect B-scan cache invalidation."""
result_tail = _result_tail(
result_history=result_history,
history_limit=history_limit,
floor_collection_id=floor_collection_id,
)
return (
str(live_config.bscan_axis),
float(live_config.bscan_cut_m),
float(live_config.bscan_max_depth_m),
float(live_config.bscan_gain),
float(live_config.bscan_start_freq_mhz),
float(live_config.bscan_stop_freq_mhz),
int(floor_collection_id),
tuple((int(collection.collection_id), int(collection.monotonic_ns), len(collection.blocks)) for collection in result_tail),
)
def rebuild_bscan_history_from_results(
result_history: list[ResultCollection],
history_limit: int,
floor_collection_id: int,
) -> tuple[dict[tuple[int, int], deque[np.ndarray]], dict[tuple[int, int], np.ndarray]]:
"""Rebuild B-scan history and depth axes from processed result payloads."""
history_by_combo: dict[tuple[int, int], deque[np.ndarray]] = {}
depth_axis_by_combo: dict[tuple[int, int], np.ndarray] = {}
result_tail = _result_tail(
result_history=result_history,
history_limit=history_limit,
floor_collection_id=floor_collection_id,
)
for collection in result_tail:
for block in collection.blocks:
key = (block.combo.input_pos, block.combo.output_pos)
for payload in block.payloads:
if payload.kind != 1 or payload.processing_name != "bscan":
continue
if payload.frequency_hz.size == 0 or payload.trace.size == 0:
continue
if payload.frequency_hz.size != payload.trace.size:
continue
depth_axis = np.asarray(payload.frequency_hz, dtype=np.float32)
amplitudes = np.asarray(np.real(payload.trace), dtype=np.float32)
if depth_axis.size == 0 or amplitudes.size == 0:
continue
history = history_by_combo.get(key)
stored_axis = depth_axis_by_combo.get(key)
if (
history is None
or stored_axis is None
or stored_axis.shape != depth_axis.shape
or not np.allclose(stored_axis, depth_axis, rtol=1e-4, atol=1e-6)
):
history = deque(maxlen=history_limit)
history_by_combo[key] = history
depth_axis_by_combo[key] = depth_axis.copy()
history.append(amplitudes.copy())
return history_by_combo, depth_axis_by_combo
def pick_bscan_display_key(
history_by_combo: dict[tuple[int, int], deque[np.ndarray]],
) -> tuple[int, int] | None:
"""Choose combo key to display when multiple histories are present."""
if not history_by_combo:
return None
return next(iter(history_by_combo.keys()))
+37
View File
@@ -0,0 +1,37 @@
"""Color-scaling helpers for B-scan visualization."""
from __future__ import annotations
import numpy as np
import pyqtgraph as pg
def bscan_lookup_table(axis_mode: str) -> np.ndarray:
"""Build B-scan colormap table for selected axis mode."""
if axis_mode == "abs":
return build_lut(["#440154", "#31688e", "#35b779", "#fde725"])
return build_lut(["#2166ac", "#67a9cf", "#f7f7f7", "#ef8a62", "#b2182b"])
def build_lut(stops: list[str], *, size: int = 256) -> np.ndarray:
"""Interpolate hex color stops into 8-bit RGB LUT array."""
stop_positions = np.linspace(0.0, 1.0, num=len(stops), dtype=np.float32)
sample_positions = np.linspace(0.0, 1.0, num=size, dtype=np.float32)
stop_colors = np.asarray([pg.mkColor(value).getRgb()[:3] for value in stops], dtype=np.float32)
lut = np.empty((size, 3), dtype=np.uint8)
for channel in range(3):
lut[:, channel] = np.interp(sample_positions, stop_positions, stop_colors[:, channel]).astype(np.uint8)
return lut
def bscan_levels(sweeps: np.ndarray, axis_mode: str) -> tuple[float, float]:
"""Compute image levels for B-scan data based on axis mode."""
min_value = float(np.min(sweeps))
max_value = float(np.max(sweeps))
if axis_mode == "abs":
if max_value <= min_value:
return min_value, min_value + 1e-6
return min_value, max_value
max_abs = max(abs(min_value), abs(max_value), 1e-6)
return -max_abs, max_abs