test new variant
This commit is contained in:
@ -111,9 +111,11 @@ Legacy binary:
|
||||
|
||||
`--bin` понимает mixed 8-байтный поток:
|
||||
- `0x000A,step,ch1_i16,ch2_i16` для CH1/CH2 из `kamil_adc` (`tty:/tmp/ttyADC_data`)
|
||||
- `0x00A3,step,ch1_i16,ch2_i16` и `0x00A4,step,ch1_i16,ch2_i16` для DO1 LOW/HIGH tagged fast-tty
|
||||
- `0x001A,step,data_i16,0x0000` для логарифмического детектора
|
||||
|
||||
Для `0x000A` сырая кривая строится как `ch1^2 + ch2^2`, а FFT рассчитывается от комплексного сигнала `ch1 + i*ch2`.
|
||||
Для `0x00A3/0x00A4` tagged-режим определяется автоматически: LOW/HIGH отображаются раздельно в raw/aux/phase, а waterfall/FFT/B-scan скрываются.
|
||||
Для `0x001A` signed `data_i16` сначала переводится в В, затем raw отображается как `V`, а FFT рассчитывается от `exp(V)`.
|
||||
Параметр `--tty-range-v` применяется к обоим типам `--bin`-данных.
|
||||
|
||||
|
||||
@ -73,9 +73,12 @@ def build_parser() -> argparse.ArgumentParser:
|
||||
help=(
|
||||
"8-байтный бинарный протокол: либо legacy старт "
|
||||
"0xFFFF,0xFFFF,0xFFFF,(CH<<8)|0x0A и точки step,uint32(hi16,lo16),0x000A, "
|
||||
"либо mixed поток 0x000A,step,ch1_i16,ch2_i16 и 0x001A,step,data_i16,0x0000. "
|
||||
"либо mixed поток 0x000A,step,ch1_i16,ch2_i16; "
|
||||
"0x00A3/0x00A4,step,ch1_i16,ch2_i16 (DO1 LOW/HIGH tagged); "
|
||||
"и 0x001A,step,data_i16,0x0000. "
|
||||
"Для 0x000A: после парсинга int16 переводятся в В, "
|
||||
"сырая кривая = ch1^2+ch2^2 (В^2), FFT вход = ch1+i*ch2 (В). "
|
||||
"Для 0x00A3/0x00A4: auto-detect tagged режим с раздельным отображением LOW/HIGH в raw. "
|
||||
"Для 0x001A: code_i16 переводится в В, raw = V, FFT вход = exp(V)"
|
||||
),
|
||||
)
|
||||
|
||||
@ -8,7 +8,7 @@ import sys
|
||||
import threading
|
||||
import time
|
||||
from queue import Empty, Queue
|
||||
from typing import Dict, List, Optional, Sequence, Tuple
|
||||
from typing import Any, Dict, List, Optional, Sequence, Tuple
|
||||
|
||||
import numpy as np
|
||||
|
||||
@ -67,6 +67,7 @@ TTY_RANGE_DEFAULT_V = 5.0
|
||||
TTY_RANGE_MIN_V = 1e-6
|
||||
TTY_RANGE_MAX_V = 1_000_000.0
|
||||
LOGDET_EXP_INPUT_LIMIT = 80.0
|
||||
DO1_TAGGED_INFO_KEY = "_do1_tagged_payload"
|
||||
|
||||
|
||||
def sanitize_curve_data_for_display(
|
||||
@ -491,6 +492,50 @@ def resolve_visible_aux_curves(aux_curves: SweepAuxCurves, enabled: bool) -> Swe
|
||||
return aux_1_arr, aux_2_arr
|
||||
|
||||
|
||||
def compute_do1_tagged_aggregate(
|
||||
raw_low: Optional[np.ndarray],
|
||||
raw_high: Optional[np.ndarray],
|
||||
) -> Optional[np.ndarray]:
|
||||
"""Build a merged raw series from LOW/HIGH tagged curves using nan-aware averaging."""
|
||||
if raw_low is None and raw_high is None:
|
||||
return None
|
||||
if raw_low is None:
|
||||
return np.asarray(raw_high, dtype=np.float32).reshape(-1)
|
||||
if raw_high is None:
|
||||
return np.asarray(raw_low, dtype=np.float32).reshape(-1)
|
||||
low_arr = np.asarray(raw_low, dtype=np.float32).reshape(-1)
|
||||
high_arr = np.asarray(raw_high, dtype=np.float32).reshape(-1)
|
||||
width = min(low_arr.size, high_arr.size)
|
||||
if width <= 0:
|
||||
return np.zeros((0,), dtype=np.float32)
|
||||
low_arr = low_arr[:width]
|
||||
high_arr = high_arr[:width]
|
||||
out = np.full((width,), np.nan, dtype=np.float32)
|
||||
low_valid = np.isfinite(low_arr)
|
||||
high_valid = np.isfinite(high_arr)
|
||||
both_valid = low_valid & high_valid
|
||||
low_only = low_valid & (~high_valid)
|
||||
high_only = high_valid & (~low_valid)
|
||||
out[low_only] = low_arr[low_only]
|
||||
out[high_only] = high_arr[high_only]
|
||||
out[both_valid] = (low_arr[both_valid] + high_arr[both_valid]) * 0.5
|
||||
return out
|
||||
|
||||
|
||||
def resolve_visible_do1_tagged_aux_curves(
|
||||
aux_low: SweepAuxCurves,
|
||||
aux_high: SweepAuxCurves,
|
||||
enabled: bool,
|
||||
) -> Tuple[SweepAuxCurves, SweepAuxCurves]:
|
||||
"""Return visible LOW/HIGH CH1/CH2 pairs for the DO1 tagged raw mode."""
|
||||
if not enabled:
|
||||
return (None, None)
|
||||
return (
|
||||
resolve_visible_aux_curves(aux_low, enabled=True),
|
||||
resolve_visible_aux_curves(aux_high, enabled=True),
|
||||
)
|
||||
|
||||
|
||||
def compute_aux_phase_curve(aux_curves: SweepAuxCurves) -> Optional[np.ndarray]:
|
||||
"""Compute phase-like curve atan2(aux_2, aux_1) for raw CH2/CH1 display."""
|
||||
if aux_curves is None:
|
||||
@ -508,6 +553,17 @@ def compute_aux_phase_curve(aux_curves: SweepAuxCurves) -> Optional[np.ndarray]:
|
||||
return phase
|
||||
|
||||
|
||||
def compute_do1_tagged_phase_curves(
|
||||
aux_low: SweepAuxCurves,
|
||||
aux_high: SweepAuxCurves,
|
||||
) -> Tuple[Optional[np.ndarray], Optional[np.ndarray]]:
|
||||
"""Compute separate phase curves for DO1 LOW/HIGH tagged auxiliary data."""
|
||||
return (
|
||||
compute_aux_phase_curve(aux_low),
|
||||
compute_aux_phase_curve(aux_high),
|
||||
)
|
||||
|
||||
|
||||
def sanitize_tty_voltage_range(range_v: float, default: float = TTY_RANGE_DEFAULT_V) -> float:
|
||||
"""Return a finite positive full-scale voltage range for tty int16 conversion."""
|
||||
try:
|
||||
@ -810,6 +866,8 @@ def run_pyqtgraph(args) -> None:
|
||||
p_line = win.addPlot(row=0, col=0, title="Сырые данные")
|
||||
p_line.showGrid(x=True, y=True, alpha=0.3)
|
||||
curve = p_line.plot(pen=pg.mkPen((80, 120, 255), width=1))
|
||||
curve_raw_low = p_line.plot(pen=pg.mkPen((255, 90, 90), width=1))
|
||||
curve_raw_high = p_line.plot(pen=pg.mkPen((90, 220, 255), width=1))
|
||||
p_line_aux_vb = None
|
||||
if bin_iq_power_mode:
|
||||
p_line_aux_vb = pg.ViewBox()
|
||||
@ -823,15 +881,23 @@ def run_pyqtgraph(args) -> None:
|
||||
p_line_aux_vb = None
|
||||
curve_aux_1 = pg.PlotDataItem(pen=pg.mkPen((255, 170, 40), width=1))
|
||||
curve_aux_2 = pg.PlotDataItem(pen=pg.mkPen((170, 70, 255), width=1))
|
||||
curve_aux_3 = pg.PlotDataItem(pen=pg.mkPen((255, 120, 20), width=1))
|
||||
curve_aux_4 = pg.PlotDataItem(pen=pg.mkPen((120, 60, 220), width=1))
|
||||
if p_line_aux_vb is not None:
|
||||
p_line_aux_vb.addItem(curve_aux_1)
|
||||
p_line_aux_vb.addItem(curve_aux_2)
|
||||
p_line_aux_vb.addItem(curve_aux_3)
|
||||
p_line_aux_vb.addItem(curve_aux_4)
|
||||
else:
|
||||
p_line.addItem(curve_aux_1)
|
||||
p_line.addItem(curve_aux_2)
|
||||
p_line.addItem(curve_aux_3)
|
||||
p_line.addItem(curve_aux_4)
|
||||
else:
|
||||
curve_aux_1 = p_line.plot(pen=pg.mkPen((255, 170, 40), width=1))
|
||||
curve_aux_2 = p_line.plot(pen=pg.mkPen((170, 70, 255), width=1))
|
||||
curve_aux_3 = p_line.plot(pen=pg.mkPen((255, 120, 20), width=1))
|
||||
curve_aux_4 = p_line.plot(pen=pg.mkPen((120, 60, 220), width=1))
|
||||
curve_calib = p_line.plot(pen=pg.mkPen((220, 60, 60), width=1))
|
||||
curve_norm = p_line.plot(pen=pg.mkPen((60, 180, 90), width=1))
|
||||
p_line.setLabel("bottom", "ГГц")
|
||||
@ -864,6 +930,7 @@ def run_pyqtgraph(args) -> None:
|
||||
p_line_phase = win.addPlot(row=2, col=0, title="Raw phase: atan(CH2/CH1)")
|
||||
p_line_phase.showGrid(x=True, y=True, alpha=0.3)
|
||||
curve_phase = p_line_phase.plot(pen=pg.mkPen((230, 180, 40), width=1))
|
||||
curve_phase_high = p_line_phase.plot(pen=pg.mkPen((80, 220, 220), width=1))
|
||||
p_line_phase.setLabel("bottom", "ГГц")
|
||||
p_line_phase.setLabel("left", "рад")
|
||||
try:
|
||||
@ -1312,18 +1379,23 @@ def run_pyqtgraph(args) -> None:
|
||||
if not isinstance(payload, dict):
|
||||
return None
|
||||
signal_kind = payload.get("signal_kind")
|
||||
if signal_kind in {"bin_iq", "bin_logdet"}:
|
||||
if signal_kind in {"bin_iq", "bin_logdet", "bin_iq_do1_tagged"}:
|
||||
return str(signal_kind)
|
||||
return None
|
||||
|
||||
def current_packet_is_do1_tagged(info: Optional[SweepInfo] = None) -> bool:
|
||||
return get_signal_kind(info) == "bin_iq_do1_tagged"
|
||||
|
||||
def current_packet_is_complex() -> bool:
|
||||
return bool(complex_sweep_mode) and get_signal_kind() != "bin_logdet"
|
||||
signal_kind = get_signal_kind()
|
||||
return bool(complex_sweep_mode) and signal_kind not in {"bin_logdet", "bin_iq_do1_tagged"}
|
||||
|
||||
def refresh_signal_mode_labels() -> None:
|
||||
signal_kind = get_signal_kind()
|
||||
active_complex = current_packet_is_complex()
|
||||
is_logdet = signal_kind == "bin_logdet"
|
||||
is_bin_iq = signal_kind == "bin_iq"
|
||||
is_do1_tagged = signal_kind == "bin_iq_do1_tagged"
|
||||
|
||||
try:
|
||||
if is_logdet:
|
||||
@ -1331,6 +1403,11 @@ def run_pyqtgraph(args) -> None:
|
||||
p_line.setLabel("left", "В")
|
||||
p_fft.setTitle("FFT: exp(V)")
|
||||
parsed_data_cb.setText("Сырые log-detector (В)")
|
||||
elif is_do1_tagged:
|
||||
p_line.setTitle("DO1 tagged raw: LOW/HIGH CH1^2 + CH2^2 (В^2)")
|
||||
p_line.setLabel("left", "CH1^2 + CH2^2, В^2")
|
||||
p_fft.setTitle("FFT")
|
||||
parsed_data_cb.setText("DO1 tagged CH1/CH2 (В)")
|
||||
elif is_bin_iq:
|
||||
p_line.setTitle("Сырые CH1/CH2 (В) и CH1^2 + CH2^2 (В^2)")
|
||||
p_line.setLabel("left", "CH1^2 + CH2^2, В^2")
|
||||
@ -1347,7 +1424,10 @@ def run_pyqtgraph(args) -> None:
|
||||
p_fft.setTitle("FFT")
|
||||
parsed_data_cb.setText("данные после парсинга")
|
||||
p_fft.setLabel("left", "Амплитуда" if active_complex else "дБ")
|
||||
p_complex_calib.setVisible(bool(active_complex))
|
||||
p_img.setVisible(not is_do1_tagged)
|
||||
p_fft.setVisible(not is_do1_tagged)
|
||||
p_spec.setVisible(not is_do1_tagged)
|
||||
p_complex_calib.setVisible((not is_do1_tagged) and bool(active_complex))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@ -1365,6 +1445,12 @@ def run_pyqtgraph(args) -> None:
|
||||
return False
|
||||
ch_1_v = convert_tty_i16_to_voltage(code_1_arr[:width], tty_range_v)
|
||||
ch_2_v = convert_tty_i16_to_voltage(code_2_arr[:width], tty_range_v)
|
||||
runtime.full_do1_tagged_raw_low = None
|
||||
runtime.full_do1_tagged_raw_high = None
|
||||
runtime.full_do1_tagged_aux_low = None
|
||||
runtime.full_do1_tagged_aux_high = None
|
||||
runtime.full_do1_tagged_aux_low_codes = None
|
||||
runtime.full_do1_tagged_aux_high_codes = None
|
||||
runtime.full_current_aux_curves = (ch_1_v, ch_2_v)
|
||||
runtime.full_current_fft_source = ch_1_v.astype(np.complex64) + (1j * ch_2_v.astype(np.complex64))
|
||||
ch_1_v_f64 = ch_1_v.astype(np.float64, copy=False)
|
||||
@ -1373,6 +1459,44 @@ def run_pyqtgraph(args) -> None:
|
||||
runtime.full_current_sweep_codes = None
|
||||
return True
|
||||
|
||||
def rebuild_do1_tagged_voltage_curves_from_codes() -> bool:
|
||||
if (not bin_iq_power_mode) or runtime.full_do1_tagged_aux_low_codes is None or runtime.full_do1_tagged_aux_high_codes is None:
|
||||
return False
|
||||
try:
|
||||
low_code_1, low_code_2 = runtime.full_do1_tagged_aux_low_codes
|
||||
high_code_1, high_code_2 = runtime.full_do1_tagged_aux_high_codes
|
||||
except Exception:
|
||||
return False
|
||||
low_code_1_arr = np.asarray(low_code_1, dtype=np.float32).reshape(-1)
|
||||
low_code_2_arr = np.asarray(low_code_2, dtype=np.float32).reshape(-1)
|
||||
high_code_1_arr = np.asarray(high_code_1, dtype=np.float32).reshape(-1)
|
||||
high_code_2_arr = np.asarray(high_code_2, dtype=np.float32).reshape(-1)
|
||||
width = min(low_code_1_arr.size, low_code_2_arr.size, high_code_1_arr.size, high_code_2_arr.size)
|
||||
if width <= 0:
|
||||
return False
|
||||
|
||||
low_ch_1_v = convert_tty_i16_to_voltage(low_code_1_arr[:width], tty_range_v)
|
||||
low_ch_2_v = convert_tty_i16_to_voltage(low_code_2_arr[:width], tty_range_v)
|
||||
high_ch_1_v = convert_tty_i16_to_voltage(high_code_1_arr[:width], tty_range_v)
|
||||
high_ch_2_v = convert_tty_i16_to_voltage(high_code_2_arr[:width], tty_range_v)
|
||||
|
||||
low_ch_1_v_f64 = low_ch_1_v.astype(np.float64, copy=False)
|
||||
low_ch_2_v_f64 = low_ch_2_v.astype(np.float64, copy=False)
|
||||
high_ch_1_v_f64 = high_ch_1_v.astype(np.float64, copy=False)
|
||||
high_ch_2_v_f64 = high_ch_2_v.astype(np.float64, copy=False)
|
||||
raw_low = np.asarray((low_ch_1_v_f64 * low_ch_1_v_f64) + (low_ch_2_v_f64 * low_ch_2_v_f64), dtype=np.float32)
|
||||
raw_high = np.asarray((high_ch_1_v_f64 * high_ch_1_v_f64) + (high_ch_2_v_f64 * high_ch_2_v_f64), dtype=np.float32)
|
||||
runtime.full_do1_tagged_raw_low = raw_low
|
||||
runtime.full_do1_tagged_raw_high = raw_high
|
||||
runtime.full_do1_tagged_aux_low = (low_ch_1_v, low_ch_2_v)
|
||||
runtime.full_do1_tagged_aux_high = (high_ch_1_v, high_ch_2_v)
|
||||
runtime.full_current_aux_curves = None
|
||||
runtime.full_current_aux_curves_codes = None
|
||||
runtime.full_current_sweep_codes = None
|
||||
runtime.full_current_fft_source = None
|
||||
runtime.full_current_sweep_raw = compute_do1_tagged_aggregate(raw_low, raw_high)
|
||||
return runtime.full_current_sweep_raw is not None and runtime.full_current_sweep_raw.size > 0
|
||||
|
||||
def rebuild_logdet_voltage_curve_from_codes() -> bool:
|
||||
if (not bin_iq_power_mode) or runtime.full_current_sweep_codes is None:
|
||||
return False
|
||||
@ -1380,6 +1504,12 @@ def run_pyqtgraph(args) -> None:
|
||||
if code_arr.size <= 0:
|
||||
return False
|
||||
sweep_raw_v, fft_input = build_logdet_voltage_fft_input(code_arr, tty_range_v)
|
||||
runtime.full_do1_tagged_raw_low = None
|
||||
runtime.full_do1_tagged_raw_high = None
|
||||
runtime.full_do1_tagged_aux_low = None
|
||||
runtime.full_do1_tagged_aux_high = None
|
||||
runtime.full_do1_tagged_aux_low_codes = None
|
||||
runtime.full_do1_tagged_aux_high_codes = None
|
||||
runtime.full_current_aux_curves = None
|
||||
runtime.full_current_aux_curves_codes = None
|
||||
runtime.full_current_sweep_raw = sweep_raw_v
|
||||
@ -1390,6 +1520,8 @@ def run_pyqtgraph(args) -> None:
|
||||
signal_kind = get_signal_kind()
|
||||
if signal_kind == "bin_logdet":
|
||||
return rebuild_logdet_voltage_curve_from_codes()
|
||||
if signal_kind == "bin_iq_do1_tagged":
|
||||
return rebuild_do1_tagged_voltage_curves_from_codes()
|
||||
return rebuild_tty_voltage_curves_from_codes()
|
||||
|
||||
def reset_background_state(*, clear_profile: bool = True) -> None:
|
||||
@ -1427,6 +1559,7 @@ def run_pyqtgraph(args) -> None:
|
||||
if reset_ring:
|
||||
reset_ring_buffers()
|
||||
|
||||
tagged_mode = current_packet_is_do1_tagged()
|
||||
if runtime.full_current_freqs is None or runtime.full_current_sweep_raw is None:
|
||||
runtime.current_freqs = None
|
||||
runtime.current_sweep_raw = None
|
||||
@ -1434,6 +1567,10 @@ def run_pyqtgraph(args) -> None:
|
||||
runtime.current_fft_input = None
|
||||
runtime.current_fft_complex = None
|
||||
runtime.current_aux_curves = None
|
||||
runtime.current_do1_tagged_raw_low = None
|
||||
runtime.current_do1_tagged_raw_high = None
|
||||
runtime.current_do1_tagged_aux_low = None
|
||||
runtime.current_do1_tagged_aux_high = None
|
||||
runtime.current_sweep_norm = None
|
||||
runtime.current_fft_mag = None
|
||||
runtime.current_fft_db = None
|
||||
@ -1462,6 +1599,40 @@ def run_pyqtgraph(args) -> None:
|
||||
runtime.range_min_ghz,
|
||||
runtime.range_max_ghz,
|
||||
)
|
||||
if tagged_mode:
|
||||
runtime.current_do1_tagged_raw_low = apply_working_range_to_signal(
|
||||
runtime.full_current_freqs,
|
||||
runtime.full_current_sweep_raw,
|
||||
runtime.full_do1_tagged_raw_low,
|
||||
runtime.range_min_ghz,
|
||||
runtime.range_max_ghz,
|
||||
)
|
||||
runtime.current_do1_tagged_raw_high = apply_working_range_to_signal(
|
||||
runtime.full_current_freqs,
|
||||
runtime.full_current_sweep_raw,
|
||||
runtime.full_do1_tagged_raw_high,
|
||||
runtime.range_min_ghz,
|
||||
runtime.range_max_ghz,
|
||||
)
|
||||
runtime.current_do1_tagged_aux_low = apply_working_range_to_aux_curves(
|
||||
runtime.full_current_freqs,
|
||||
runtime.full_current_sweep_raw,
|
||||
runtime.full_do1_tagged_aux_low,
|
||||
runtime.range_min_ghz,
|
||||
runtime.range_max_ghz,
|
||||
)
|
||||
runtime.current_do1_tagged_aux_high = apply_working_range_to_aux_curves(
|
||||
runtime.full_current_freqs,
|
||||
runtime.full_current_sweep_raw,
|
||||
runtime.full_do1_tagged_aux_high,
|
||||
runtime.range_min_ghz,
|
||||
runtime.range_max_ghz,
|
||||
)
|
||||
else:
|
||||
runtime.current_do1_tagged_raw_low = None
|
||||
runtime.current_do1_tagged_raw_high = None
|
||||
runtime.current_do1_tagged_aux_low = None
|
||||
runtime.current_do1_tagged_aux_high = None
|
||||
|
||||
if runtime.current_sweep_raw.size == 0:
|
||||
if push_to_ring:
|
||||
@ -1472,6 +1643,10 @@ def run_pyqtgraph(args) -> None:
|
||||
runtime.current_fft_input = None
|
||||
runtime.current_fft_complex = None
|
||||
runtime.current_aux_curves = None
|
||||
runtime.current_do1_tagged_raw_low = None
|
||||
runtime.current_do1_tagged_raw_high = None
|
||||
runtime.current_do1_tagged_aux_low = None
|
||||
runtime.current_do1_tagged_aux_high = None
|
||||
runtime.current_sweep_norm = None
|
||||
runtime.current_fft_mag = None
|
||||
runtime.current_fft_db = None
|
||||
@ -1482,6 +1657,16 @@ def run_pyqtgraph(args) -> None:
|
||||
recompute_current_processed_sweep(push_to_ring=push_to_ring)
|
||||
|
||||
def recompute_current_processed_sweep(push_to_ring: bool = False) -> None:
|
||||
if current_packet_is_do1_tagged():
|
||||
runtime.current_sweep_norm = None
|
||||
runtime.current_fft_source = None
|
||||
runtime.current_fft_input = None
|
||||
runtime.current_fft_complex = None
|
||||
runtime.current_fft_mag = None
|
||||
runtime.current_fft_db = None
|
||||
runtime.current_distances = None
|
||||
return
|
||||
|
||||
fft_source = runtime.current_fft_source
|
||||
if fft_source is None and runtime.current_sweep_raw is not None:
|
||||
fft_source = np.asarray(runtime.current_sweep_raw, dtype=np.float32)
|
||||
@ -2207,8 +2392,76 @@ def run_pyqtgraph(args) -> None:
|
||||
runtime.full_current_aux_curves_codes = None
|
||||
runtime.full_current_sweep_codes = None
|
||||
runtime.full_current_fft_source = None
|
||||
runtime.full_do1_tagged_raw_low = None
|
||||
runtime.full_do1_tagged_raw_high = None
|
||||
runtime.full_do1_tagged_aux_low = None
|
||||
runtime.full_do1_tagged_aux_high = None
|
||||
runtime.full_do1_tagged_aux_low_codes = None
|
||||
runtime.full_do1_tagged_aux_high_codes = None
|
||||
signal_kind = get_signal_kind(info)
|
||||
if signal_kind == "bin_logdet":
|
||||
if signal_kind == "bin_iq_do1_tagged":
|
||||
calibrated = calibrate_freqs(
|
||||
{
|
||||
"F": base_freqs,
|
||||
"I": sweep,
|
||||
}
|
||||
)
|
||||
runtime.full_current_freqs = np.asarray(calibrated["F"], dtype=np.float64)
|
||||
payload = info.get(DO1_TAGGED_INFO_KEY) if isinstance(info, dict) else None
|
||||
if isinstance(payload, dict):
|
||||
raw_low_payload = payload.get("raw_low")
|
||||
raw_high_payload = payload.get("raw_high")
|
||||
if raw_low_payload is not None:
|
||||
runtime.full_do1_tagged_raw_low = np.asarray(
|
||||
calibrate_freqs({"F": base_freqs, "I": raw_low_payload})["I"],
|
||||
dtype=np.float32,
|
||||
)
|
||||
if raw_high_payload is not None:
|
||||
runtime.full_do1_tagged_raw_high = np.asarray(
|
||||
calibrate_freqs({"F": base_freqs, "I": raw_high_payload})["I"],
|
||||
dtype=np.float32,
|
||||
)
|
||||
runtime.full_current_sweep_raw = compute_do1_tagged_aggregate(
|
||||
runtime.full_do1_tagged_raw_low,
|
||||
runtime.full_do1_tagged_raw_high,
|
||||
)
|
||||
|
||||
aux_low_payload = payload.get("aux_low")
|
||||
aux_high_payload = payload.get("aux_high")
|
||||
if (
|
||||
bin_iq_power_mode
|
||||
and isinstance(aux_low_payload, (tuple, list))
|
||||
and len(aux_low_payload) == 2
|
||||
and isinstance(aux_high_payload, (tuple, list))
|
||||
and len(aux_high_payload) == 2
|
||||
):
|
||||
try:
|
||||
low_aux_1 = np.asarray(
|
||||
calibrate_freqs({"F": base_freqs, "I": aux_low_payload[0]})["I"],
|
||||
dtype=np.float32,
|
||||
)
|
||||
low_aux_2 = np.asarray(
|
||||
calibrate_freqs({"F": base_freqs, "I": aux_low_payload[1]})["I"],
|
||||
dtype=np.float32,
|
||||
)
|
||||
high_aux_1 = np.asarray(
|
||||
calibrate_freqs({"F": base_freqs, "I": aux_high_payload[0]})["I"],
|
||||
dtype=np.float32,
|
||||
)
|
||||
high_aux_2 = np.asarray(
|
||||
calibrate_freqs({"F": base_freqs, "I": aux_high_payload[1]})["I"],
|
||||
dtype=np.float32,
|
||||
)
|
||||
runtime.full_do1_tagged_aux_low_codes = (low_aux_1, low_aux_2)
|
||||
runtime.full_do1_tagged_aux_high_codes = (high_aux_1, high_aux_2)
|
||||
rebuild_do1_tagged_voltage_curves_from_codes()
|
||||
except Exception:
|
||||
runtime.full_do1_tagged_aux_low_codes = None
|
||||
runtime.full_do1_tagged_aux_high_codes = None
|
||||
if runtime.full_current_sweep_raw is None:
|
||||
runtime.full_current_sweep_raw = np.asarray(calibrated["I"], dtype=np.float32)
|
||||
runtime.full_current_fft_source = None
|
||||
elif signal_kind == "bin_logdet":
|
||||
calibrated = calibrate_freqs(
|
||||
{
|
||||
"F": base_freqs,
|
||||
@ -2242,7 +2495,7 @@ def run_pyqtgraph(args) -> None:
|
||||
runtime.full_current_aux_curves_codes = None
|
||||
runtime.full_current_fft_source = None
|
||||
|
||||
if runtime.full_current_fft_source is None:
|
||||
if (signal_kind != "bin_iq_do1_tagged") and runtime.full_current_fft_source is None:
|
||||
calibrated = calibrate_freqs(
|
||||
{
|
||||
"F": base_freqs,
|
||||
@ -2327,11 +2580,13 @@ def run_pyqtgraph(args) -> None:
|
||||
or last_bscan_refresh_stride <= 1
|
||||
or (update_ticks % last_bscan_refresh_stride) == 0
|
||||
)
|
||||
do1_tagged_now = current_packet_is_do1_tagged()
|
||||
|
||||
if redraw_needed:
|
||||
refresh_signal_mode_labels()
|
||||
active_signal_kind = get_signal_kind()
|
||||
active_complex_mode = current_packet_is_complex()
|
||||
active_do1_tagged = active_signal_kind == "bin_iq_do1_tagged"
|
||||
xs = resolve_curve_xs(
|
||||
runtime.current_sweep_raw.size
|
||||
if runtime.current_sweep_raw is not None
|
||||
@ -2339,20 +2594,78 @@ def run_pyqtgraph(args) -> None:
|
||||
)
|
||||
displayed_calib = None
|
||||
displayed_aux = resolve_visible_aux_curves(runtime.current_aux_curves, parsed_data_enabled)
|
||||
displayed_phase = compute_aux_phase_curve(runtime.current_aux_curves)
|
||||
displayed_tagged_aux_low, displayed_tagged_aux_high = resolve_visible_do1_tagged_aux_curves(
|
||||
runtime.current_do1_tagged_aux_low,
|
||||
runtime.current_do1_tagged_aux_high,
|
||||
parsed_data_enabled,
|
||||
)
|
||||
if active_do1_tagged:
|
||||
displayed_phase, displayed_phase_high = compute_do1_tagged_phase_curves(
|
||||
runtime.current_do1_tagged_aux_low,
|
||||
runtime.current_do1_tagged_aux_high,
|
||||
)
|
||||
else:
|
||||
displayed_phase = compute_aux_phase_curve(runtime.current_aux_curves)
|
||||
displayed_phase_high = None
|
||||
try:
|
||||
p_line_phase.setVisible(displayed_phase is not None and displayed_phase.size > 0)
|
||||
p_line_phase.setVisible(
|
||||
(displayed_phase is not None and displayed_phase.size > 0)
|
||||
or (displayed_phase_high is not None and displayed_phase_high.size > 0)
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if runtime.current_sweep_raw is not None:
|
||||
if active_do1_tagged:
|
||||
curve.setData([], [])
|
||||
if runtime.current_do1_tagged_raw_low is not None:
|
||||
raw_low_x, raw_low_y = decimate_curve_for_display(xs, runtime.current_do1_tagged_raw_low)
|
||||
raw_low_x, raw_low_y = sanitize_curve_data_for_display(raw_low_x, raw_low_y)
|
||||
curve_raw_low.setData(raw_low_x, raw_low_y, autoDownsample=False)
|
||||
else:
|
||||
curve_raw_low.setData([], [])
|
||||
if runtime.current_do1_tagged_raw_high is not None:
|
||||
raw_high_x, raw_high_y = decimate_curve_for_display(xs, runtime.current_do1_tagged_raw_high)
|
||||
raw_high_x, raw_high_y = sanitize_curve_data_for_display(raw_high_x, raw_high_y)
|
||||
curve_raw_high.setData(raw_high_x, raw_high_y, autoDownsample=False)
|
||||
else:
|
||||
curve_raw_high.setData([], [])
|
||||
elif runtime.current_sweep_raw is not None:
|
||||
raw_x, raw_y = decimate_curve_for_display(xs, runtime.current_sweep_raw)
|
||||
raw_x, raw_y = sanitize_curve_data_for_display(raw_x, raw_y)
|
||||
curve.setData(raw_x, raw_y, autoDownsample=False)
|
||||
curve_raw_low.setData([], [])
|
||||
curve_raw_high.setData([], [])
|
||||
else:
|
||||
curve.setData([], [])
|
||||
curve_raw_low.setData([], [])
|
||||
curve_raw_high.setData([], [])
|
||||
|
||||
if displayed_aux is not None:
|
||||
if active_do1_tagged:
|
||||
if displayed_tagged_aux_low is not None:
|
||||
aux_low_1, aux_low_2 = displayed_tagged_aux_low
|
||||
aux_low_width = min(xs.size, aux_low_1.size, aux_low_2.size)
|
||||
low_x_1, low_y_1 = decimate_curve_for_display(xs[:aux_low_width], aux_low_1[:aux_low_width])
|
||||
low_x_2, low_y_2 = decimate_curve_for_display(xs[:aux_low_width], aux_low_2[:aux_low_width])
|
||||
low_x_1, low_y_1 = sanitize_curve_data_for_display(low_x_1, low_y_1)
|
||||
low_x_2, low_y_2 = sanitize_curve_data_for_display(low_x_2, low_y_2)
|
||||
curve_aux_1.setData(low_x_1, low_y_1, autoDownsample=False)
|
||||
curve_aux_2.setData(low_x_2, low_y_2, autoDownsample=False)
|
||||
else:
|
||||
curve_aux_1.setData([], [])
|
||||
curve_aux_2.setData([], [])
|
||||
if displayed_tagged_aux_high is not None:
|
||||
aux_high_1, aux_high_2 = displayed_tagged_aux_high
|
||||
aux_high_width = min(xs.size, aux_high_1.size, aux_high_2.size)
|
||||
high_x_1, high_y_1 = decimate_curve_for_display(xs[:aux_high_width], aux_high_1[:aux_high_width])
|
||||
high_x_2, high_y_2 = decimate_curve_for_display(xs[:aux_high_width], aux_high_2[:aux_high_width])
|
||||
high_x_1, high_y_1 = sanitize_curve_data_for_display(high_x_1, high_y_1)
|
||||
high_x_2, high_y_2 = sanitize_curve_data_for_display(high_x_2, high_y_2)
|
||||
curve_aux_3.setData(high_x_1, high_y_1, autoDownsample=False)
|
||||
curve_aux_4.setData(high_x_2, high_y_2, autoDownsample=False)
|
||||
else:
|
||||
curve_aux_3.setData([], [])
|
||||
curve_aux_4.setData([], [])
|
||||
elif displayed_aux is not None:
|
||||
aux_1, aux_2 = displayed_aux
|
||||
aux_width = min(xs.size, aux_1.size, aux_2.size)
|
||||
aux_x_1, aux_y_1 = decimate_curve_for_display(xs[:aux_width], aux_1[:aux_width])
|
||||
@ -2361,9 +2674,13 @@ def run_pyqtgraph(args) -> None:
|
||||
aux_x_2, aux_y_2 = sanitize_curve_data_for_display(aux_x_2, aux_y_2)
|
||||
curve_aux_1.setData(aux_x_1, aux_y_1, autoDownsample=False)
|
||||
curve_aux_2.setData(aux_x_2, aux_y_2, autoDownsample=False)
|
||||
curve_aux_3.setData([], [])
|
||||
curve_aux_4.setData([], [])
|
||||
else:
|
||||
curve_aux_1.setData([], [])
|
||||
curve_aux_2.setData([], [])
|
||||
curve_aux_3.setData([], [])
|
||||
curve_aux_4.setData([], [])
|
||||
|
||||
if displayed_phase is not None:
|
||||
phase_width = min(xs.size, displayed_phase.size)
|
||||
@ -2372,8 +2689,18 @@ def run_pyqtgraph(args) -> None:
|
||||
curve_phase.setData(phase_x, phase_y, autoDownsample=False)
|
||||
else:
|
||||
curve_phase.setData([], [])
|
||||
if displayed_phase_high is not None:
|
||||
phase_high_width = min(xs.size, displayed_phase_high.size)
|
||||
phase_high_x, phase_high_y = decimate_curve_for_display(
|
||||
xs[:phase_high_width],
|
||||
displayed_phase_high[:phase_high_width],
|
||||
)
|
||||
phase_high_x, phase_high_y = sanitize_curve_data_for_display(phase_high_x, phase_high_y)
|
||||
curve_phase_high.setData(phase_high_x, phase_high_y, autoDownsample=False)
|
||||
else:
|
||||
curve_phase_high.setData([], [])
|
||||
|
||||
if runtime.calib_envelope is not None:
|
||||
if (not active_do1_tagged) and runtime.calib_envelope is not None:
|
||||
if runtime.current_sweep_raw is not None:
|
||||
displayed_calib = resample_envelope(runtime.calib_envelope, runtime.current_sweep_raw.size)
|
||||
xs_calib = xs[: displayed_calib.size]
|
||||
@ -2386,7 +2713,7 @@ def run_pyqtgraph(args) -> None:
|
||||
else:
|
||||
curve_calib.setData([], [])
|
||||
|
||||
if runtime.current_sweep_norm is not None:
|
||||
if (not active_do1_tagged) and runtime.current_sweep_norm is not None:
|
||||
norm_display = runtime.current_sweep_norm * norm_display_scale
|
||||
norm_x, norm_y = decimate_curve_for_display(xs[: norm_display.size], norm_display)
|
||||
norm_x, norm_y = sanitize_curve_data_for_display(norm_x, norm_y)
|
||||
@ -2395,7 +2722,13 @@ def run_pyqtgraph(args) -> None:
|
||||
curve_norm.setData([], [])
|
||||
|
||||
if fixed_ylim is None:
|
||||
if active_signal_kind == "bin_iq":
|
||||
if active_do1_tagged:
|
||||
y_series = [
|
||||
runtime.current_do1_tagged_raw_low,
|
||||
runtime.current_do1_tagged_raw_high,
|
||||
runtime.current_sweep_raw,
|
||||
]
|
||||
elif active_signal_kind == "bin_iq":
|
||||
y_series = [
|
||||
runtime.current_sweep_raw,
|
||||
displayed_calib,
|
||||
@ -2416,10 +2749,14 @@ def run_pyqtgraph(args) -> None:
|
||||
aux_limits = compute_auto_ylim(
|
||||
displayed_aux[0] if displayed_aux is not None else None,
|
||||
displayed_aux[1] if displayed_aux is not None else None,
|
||||
displayed_tagged_aux_low[0] if displayed_tagged_aux_low is not None else None,
|
||||
displayed_tagged_aux_low[1] if displayed_tagged_aux_low is not None else None,
|
||||
displayed_tagged_aux_high[0] if displayed_tagged_aux_high is not None else None,
|
||||
displayed_tagged_aux_high[1] if displayed_tagged_aux_high is not None else None,
|
||||
)
|
||||
if aux_limits is not None:
|
||||
p_line_aux_vb.setYRange(aux_limits[0], aux_limits[1], padding=0)
|
||||
phase_limits = compute_auto_ylim(displayed_phase)
|
||||
phase_limits = compute_auto_ylim(displayed_phase, displayed_phase_high)
|
||||
if phase_limits is not None:
|
||||
p_line_phase.setYRange(phase_limits[0], phase_limits[1], padding=0)
|
||||
|
||||
@ -2457,10 +2794,12 @@ def run_pyqtgraph(args) -> None:
|
||||
curve_complex_calib_real.setData([], [])
|
||||
curve_complex_calib_imag.setData([], [])
|
||||
|
||||
sweep_for_fft = runtime.current_fft_input
|
||||
if sweep_for_fft is None:
|
||||
sweep_for_fft = None if active_do1_tagged else runtime.current_fft_input
|
||||
if (not active_do1_tagged) and sweep_for_fft is None:
|
||||
sweep_for_fft = runtime.current_sweep_norm if runtime.current_sweep_norm is not None else runtime.current_sweep_raw
|
||||
distance_axis = runtime.current_distances if runtime.current_distances is not None else runtime.ring.distance_axis
|
||||
distance_axis = None if active_do1_tagged else (
|
||||
runtime.current_distances if runtime.current_distances is not None else runtime.ring.distance_axis
|
||||
)
|
||||
if sweep_for_fft is not None and sweep_for_fft.size > 0 and distance_axis is not None:
|
||||
if (
|
||||
runtime.current_fft_mag is None
|
||||
@ -2706,7 +3045,7 @@ def run_pyqtgraph(args) -> None:
|
||||
refresh_peak_params_label([])
|
||||
runtime.plot_dirty = False
|
||||
|
||||
if changed and runtime.ring.ring is not None:
|
||||
if changed and runtime.ring.ring is not None and (not do1_tagged_now):
|
||||
if refresh_heavy_views:
|
||||
disp = sanitize_image_for_display(runtime.ring.get_display_raw_decimated(RAW_WATERFALL_MAX_POINTS))
|
||||
if disp is not None:
|
||||
@ -2727,6 +3066,8 @@ def run_pyqtgraph(args) -> None:
|
||||
if redraw_needed or status_dirty:
|
||||
try:
|
||||
status_payload = dict(runtime.current_info) if runtime.current_info else {}
|
||||
if status_payload:
|
||||
status_payload = {k: v for k, v in status_payload.items() if not str(k).startswith("_")}
|
||||
if peak_calibrate_mode and runtime.current_peak_width is not None:
|
||||
status_payload["peak_w"] = runtime.current_peak_width
|
||||
if peak_calibrate_mode and runtime.current_peak_amplitude is not None:
|
||||
@ -2771,7 +3112,7 @@ def run_pyqtgraph(args) -> None:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if redraw_needed and runtime.ring.ring_fft is not None:
|
||||
if redraw_needed and runtime.ring.ring_fft is not None and (not do1_tagged_now):
|
||||
if not refresh_bscan_views:
|
||||
log_debug_event(
|
||||
"suppressed_fft_image_refresh",
|
||||
|
||||
@ -5,12 +5,13 @@ from __future__ import annotations
|
||||
import math
|
||||
import time
|
||||
from collections import deque
|
||||
from typing import List, Optional, Sequence, Set
|
||||
from typing import Dict, List, Optional, Sequence, Set
|
||||
|
||||
import numpy as np
|
||||
|
||||
from rfg_adc_plotter.constants import DATA_INVERSION_THRESHOLD, LOG_BASE, LOG_EXP_LIMIT, LOG_POSTSCALER, LOG_SCALER
|
||||
from rfg_adc_plotter.types import (
|
||||
Do1Level,
|
||||
ParserEvent,
|
||||
PointEvent,
|
||||
SignalKind,
|
||||
@ -162,6 +163,14 @@ class LegacyBinaryParser:
|
||||
self._seen_points = False
|
||||
self._mode: Optional[str] = None
|
||||
self._current_signal_kind: Optional[SignalKind] = None
|
||||
self._last_tagged_step_by_level: Dict[Do1Level, Optional[int]] = {
|
||||
"low": None,
|
||||
"high": None,
|
||||
}
|
||||
|
||||
def _reset_tagged_steps(self) -> None:
|
||||
self._last_tagged_step_by_level["low"] = None
|
||||
self._last_tagged_step_by_level["high"] = None
|
||||
|
||||
@staticmethod
|
||||
def _u16_at(buf: bytearray, offset: int) -> int:
|
||||
@ -172,6 +181,7 @@ class LegacyBinaryParser:
|
||||
self._last_step = None
|
||||
self._seen_points = False
|
||||
self._current_signal_kind = None
|
||||
self._reset_tagged_steps()
|
||||
events.append(StartEvent(ch=int(ch)))
|
||||
|
||||
def _emit_bin_start(self, events: List[ParserEvent], signal_kind: SignalKind) -> None:
|
||||
@ -179,6 +189,7 @@ class LegacyBinaryParser:
|
||||
self._last_step = None
|
||||
self._seen_points = False
|
||||
self._current_signal_kind = signal_kind
|
||||
self._reset_tagged_steps()
|
||||
events.append(StartEvent(ch=0, signal_kind=signal_kind))
|
||||
|
||||
def _emit_tty_start(self, events: List[ParserEvent]) -> None:
|
||||
@ -187,6 +198,7 @@ class LegacyBinaryParser:
|
||||
def _emit_legacy_point(self, events: List[ParserEvent], step: int, value_word_hi: int, value_word_lo: int, ch: int) -> None:
|
||||
self._mode = "legacy"
|
||||
self._current_signal_kind = None
|
||||
self._reset_tagged_steps()
|
||||
if self._seen_points and self._last_step is not None and step <= self._last_step:
|
||||
events.append(StartEvent(ch=int(ch)))
|
||||
self._seen_points = True
|
||||
@ -194,7 +206,13 @@ class LegacyBinaryParser:
|
||||
value = u32_to_i32((int(value_word_hi) << 16) | int(value_word_lo))
|
||||
events.append(PointEvent(ch=int(ch), x=int(step), y=float(value)))
|
||||
|
||||
def _prepare_bin_point(self, events: List[ParserEvent], step: int, signal_kind: SignalKind) -> None:
|
||||
def _prepare_bin_point(
|
||||
self,
|
||||
events: List[ParserEvent],
|
||||
step: int,
|
||||
signal_kind: SignalKind,
|
||||
do1_level: Optional[Do1Level] = None,
|
||||
) -> None:
|
||||
self._mode = "bin"
|
||||
if self._current_signal_kind != signal_kind:
|
||||
if self._seen_points:
|
||||
@ -202,12 +220,28 @@ class LegacyBinaryParser:
|
||||
self._last_step = None
|
||||
self._seen_points = False
|
||||
self._current_signal_kind = signal_kind
|
||||
self._reset_tagged_steps()
|
||||
|
||||
if signal_kind == "bin_iq_do1_tagged":
|
||||
level: Do1Level = "high" if do1_level == "high" else "low"
|
||||
last_level_step = self._last_tagged_step_by_level[level]
|
||||
if self._seen_points and last_level_step is not None and step <= last_level_step:
|
||||
events.append(StartEvent(ch=0, signal_kind=signal_kind))
|
||||
self._last_step = None
|
||||
self._seen_points = False
|
||||
self._reset_tagged_steps()
|
||||
self._seen_points = True
|
||||
self._last_tagged_step_by_level[level] = int(step)
|
||||
self._last_step = int(step)
|
||||
return
|
||||
|
||||
if self._seen_points and self._last_step is not None and step <= self._last_step:
|
||||
events.append(StartEvent(ch=0, signal_kind=signal_kind))
|
||||
self._last_step = None
|
||||
self._seen_points = False
|
||||
self._seen_points = True
|
||||
self._last_step = int(step)
|
||||
self._reset_tagged_steps()
|
||||
|
||||
def _emit_tty_point(self, events: List[ParserEvent], step: int, ch_1_word: int, ch_2_word: int) -> None:
|
||||
self._prepare_bin_point(events, step=int(step), signal_kind="bin_iq")
|
||||
@ -223,6 +257,33 @@ class LegacyBinaryParser:
|
||||
)
|
||||
)
|
||||
|
||||
def _emit_tty_tagged_point(
|
||||
self,
|
||||
events: List[ParserEvent],
|
||||
step: int,
|
||||
ch_1_word: int,
|
||||
ch_2_word: int,
|
||||
do1_level: Do1Level,
|
||||
) -> None:
|
||||
self._prepare_bin_point(
|
||||
events,
|
||||
step=int(step),
|
||||
signal_kind="bin_iq_do1_tagged",
|
||||
do1_level=do1_level,
|
||||
)
|
||||
ch_1 = u16_to_i16(int(ch_1_word))
|
||||
ch_2 = u16_to_i16(int(ch_2_word))
|
||||
events.append(
|
||||
PointEvent(
|
||||
ch=0,
|
||||
x=int(step),
|
||||
y=tty_ch_pair_to_sweep(ch_1, ch_2),
|
||||
aux=(float(ch_1), float(ch_2)),
|
||||
signal_kind="bin_iq_do1_tagged",
|
||||
do1_level=do1_level,
|
||||
)
|
||||
)
|
||||
|
||||
def _emit_logdet_point(self, events: List[ParserEvent], step: int, value_word: int) -> None:
|
||||
self._prepare_bin_point(events, step=int(step), signal_kind="bin_logdet")
|
||||
value = u16_to_i16(int(value_word))
|
||||
@ -249,6 +310,8 @@ class LegacyBinaryParser:
|
||||
is_tty_start = (w0 == 0x000A and w1 == 0xFFFF and w2 == 0xFFFF and w3 == 0xFFFF)
|
||||
is_legacy_point = (self._buf[6] == 0x0A and w0 != 0xFFFF)
|
||||
is_tty_point = (w0 == 0x000A and w1 != 0xFFFF)
|
||||
is_tty_tagged_low_point = (w0 == 0x00A3 and w1 != 0xFFFF)
|
||||
is_tty_tagged_high_point = (w0 == 0x00A4 and w1 != 0xFFFF)
|
||||
is_logdet_point = (w0 == 0x001A and w3 == 0x0000)
|
||||
|
||||
if is_legacy_start:
|
||||
@ -281,6 +344,26 @@ class LegacyBinaryParser:
|
||||
self._emit_tty_point(events, step=int(w1), ch_1_word=int(w2), ch_2_word=int(w3))
|
||||
del self._buf[:8]
|
||||
continue
|
||||
if is_tty_tagged_low_point and (not is_legacy_point):
|
||||
self._emit_tty_tagged_point(
|
||||
events,
|
||||
step=int(w1),
|
||||
ch_1_word=int(w2),
|
||||
ch_2_word=int(w3),
|
||||
do1_level="low",
|
||||
)
|
||||
del self._buf[:8]
|
||||
continue
|
||||
if is_tty_tagged_high_point and (not is_legacy_point):
|
||||
self._emit_tty_tagged_point(
|
||||
events,
|
||||
step=int(w1),
|
||||
ch_1_word=int(w2),
|
||||
ch_2_word=int(w3),
|
||||
do1_level="high",
|
||||
)
|
||||
del self._buf[:8]
|
||||
continue
|
||||
del self._buf[:1]
|
||||
continue
|
||||
|
||||
@ -289,6 +372,26 @@ class LegacyBinaryParser:
|
||||
self._emit_tty_point(events, step=int(w1), ch_1_word=int(w2), ch_2_word=int(w3))
|
||||
del self._buf[:8]
|
||||
continue
|
||||
if is_tty_tagged_low_point:
|
||||
self._emit_tty_tagged_point(
|
||||
events,
|
||||
step=int(w1),
|
||||
ch_1_word=int(w2),
|
||||
ch_2_word=int(w3),
|
||||
do1_level="low",
|
||||
)
|
||||
del self._buf[:8]
|
||||
continue
|
||||
if is_tty_tagged_high_point:
|
||||
self._emit_tty_tagged_point(
|
||||
events,
|
||||
step=int(w1),
|
||||
ch_1_word=int(w2),
|
||||
ch_2_word=int(w3),
|
||||
do1_level="high",
|
||||
)
|
||||
del self._buf[:8]
|
||||
continue
|
||||
if is_legacy_point and (not is_tty_point):
|
||||
self._emit_legacy_point(
|
||||
events,
|
||||
@ -309,6 +412,28 @@ class LegacyBinaryParser:
|
||||
del self._buf[:8]
|
||||
continue
|
||||
|
||||
if is_tty_tagged_low_point and (not is_legacy_point):
|
||||
self._emit_tty_tagged_point(
|
||||
events,
|
||||
step=int(w1),
|
||||
ch_1_word=int(w2),
|
||||
ch_2_word=int(w3),
|
||||
do1_level="low",
|
||||
)
|
||||
del self._buf[:8]
|
||||
continue
|
||||
|
||||
if is_tty_tagged_high_point and (not is_legacy_point):
|
||||
self._emit_tty_tagged_point(
|
||||
events,
|
||||
step=int(w1),
|
||||
ch_1_word=int(w2),
|
||||
ch_2_word=int(w3),
|
||||
do1_level="high",
|
||||
)
|
||||
del self._buf[:8]
|
||||
continue
|
||||
|
||||
if is_legacy_point and (not is_tty_point):
|
||||
self._emit_legacy_point(
|
||||
events,
|
||||
@ -524,15 +649,34 @@ class SweepAssembler:
|
||||
self._ys: list[float] = []
|
||||
self._aux_1: list[float] = []
|
||||
self._aux_2: list[float] = []
|
||||
self._tagged_low_xs: list[int] = []
|
||||
self._tagged_low_ys: list[float] = []
|
||||
self._tagged_low_aux_1: list[float] = []
|
||||
self._tagged_low_aux_2: list[float] = []
|
||||
self._tagged_high_xs: list[int] = []
|
||||
self._tagged_high_ys: list[float] = []
|
||||
self._tagged_high_aux_1: list[float] = []
|
||||
self._tagged_high_aux_2: list[float] = []
|
||||
self._cur_channel: Optional[int] = None
|
||||
self._cur_signal_kind: Optional[SignalKind] = None
|
||||
self._cur_channels: set[int] = set()
|
||||
|
||||
def _reset_tagged_current(self) -> None:
|
||||
self._tagged_low_xs.clear()
|
||||
self._tagged_low_ys.clear()
|
||||
self._tagged_low_aux_1.clear()
|
||||
self._tagged_low_aux_2.clear()
|
||||
self._tagged_high_xs.clear()
|
||||
self._tagged_high_ys.clear()
|
||||
self._tagged_high_aux_1.clear()
|
||||
self._tagged_high_aux_2.clear()
|
||||
|
||||
def _reset_current(self) -> None:
|
||||
self._xs.clear()
|
||||
self._ys.clear()
|
||||
self._aux_1.clear()
|
||||
self._aux_2.clear()
|
||||
self._reset_tagged_current()
|
||||
self._cur_channel = None
|
||||
self._cur_signal_kind = None
|
||||
self._cur_channels.clear()
|
||||
@ -567,6 +711,24 @@ class SweepAssembler:
|
||||
if last_idx < series.size - 1:
|
||||
series[last_idx + 1 :] = series[last_idx]
|
||||
|
||||
@staticmethod
|
||||
def _nanmean_pair(primary: np.ndarray, secondary: np.ndarray) -> np.ndarray:
|
||||
width = min(primary.size, secondary.size)
|
||||
if width <= 0:
|
||||
return np.zeros((0,), dtype=np.float32)
|
||||
first = np.asarray(primary[:width], dtype=np.float32)
|
||||
second = np.asarray(secondary[:width], dtype=np.float32)
|
||||
out = np.full((width,), np.nan, dtype=np.float32)
|
||||
first_valid = np.isfinite(first)
|
||||
second_valid = np.isfinite(second)
|
||||
both_valid = first_valid & second_valid
|
||||
only_first = first_valid & (~second_valid)
|
||||
only_second = second_valid & (~first_valid)
|
||||
out[only_first] = first[only_first]
|
||||
out[only_second] = second[only_second]
|
||||
out[both_valid] = (first[both_valid] + second[both_valid]) * 0.5
|
||||
return out
|
||||
|
||||
def consume(self, event: ParserEvent) -> Optional[SweepPacket]:
|
||||
if isinstance(event, StartEvent):
|
||||
packet = self.finalize_current()
|
||||
@ -598,7 +760,21 @@ class SweepAssembler:
|
||||
self._cur_channels.add(point_ch)
|
||||
self._xs.append(int(event.x))
|
||||
self._ys.append(float(event.y))
|
||||
if event.aux is not None:
|
||||
if self._cur_signal_kind == "bin_iq_do1_tagged":
|
||||
level = "high" if event.do1_level == "high" else "low"
|
||||
if level == "low":
|
||||
self._tagged_low_xs.append(int(event.x))
|
||||
self._tagged_low_ys.append(float(event.y))
|
||||
if event.aux is not None:
|
||||
self._tagged_low_aux_1.append(float(event.aux[0]))
|
||||
self._tagged_low_aux_2.append(float(event.aux[1]))
|
||||
else:
|
||||
self._tagged_high_xs.append(int(event.x))
|
||||
self._tagged_high_ys.append(float(event.y))
|
||||
if event.aux is not None:
|
||||
self._tagged_high_aux_1.append(float(event.aux[0]))
|
||||
self._tagged_high_aux_2.append(float(event.aux[1]))
|
||||
elif event.aux is not None:
|
||||
self._aux_1.append(float(event.aux[0]))
|
||||
self._aux_2.append(float(event.aux[1]))
|
||||
return packet
|
||||
@ -613,13 +789,37 @@ class SweepAssembler:
|
||||
self._max_width = max(self._max_width, width)
|
||||
target_width = self._max_width if self._fancy else width
|
||||
|
||||
sweep = self._scatter(self._xs, self._ys, target_width)
|
||||
aux_curves: SweepAuxCurves = None
|
||||
if self._aux_1 and self._aux_2 and len(self._aux_1) == len(self._xs):
|
||||
aux_curves = (
|
||||
self._scatter(self._xs, self._aux_1, target_width),
|
||||
self._scatter(self._xs, self._aux_2, target_width),
|
||||
)
|
||||
do1_tagged_payload = None
|
||||
if self._cur_signal_kind == "bin_iq_do1_tagged":
|
||||
raw_low = self._scatter(self._tagged_low_xs, self._tagged_low_ys, target_width)
|
||||
raw_high = self._scatter(self._tagged_high_xs, self._tagged_high_ys, target_width)
|
||||
sweep = self._nanmean_pair(raw_low, raw_high)
|
||||
aux_low = None
|
||||
if self._tagged_low_aux_1 and self._tagged_low_aux_2 and len(self._tagged_low_aux_1) == len(self._tagged_low_xs):
|
||||
aux_low = (
|
||||
self._scatter(self._tagged_low_xs, self._tagged_low_aux_1, target_width),
|
||||
self._scatter(self._tagged_low_xs, self._tagged_low_aux_2, target_width),
|
||||
)
|
||||
aux_high = None
|
||||
if self._tagged_high_aux_1 and self._tagged_high_aux_2 and len(self._tagged_high_aux_1) == len(self._tagged_high_xs):
|
||||
aux_high = (
|
||||
self._scatter(self._tagged_high_xs, self._tagged_high_aux_1, target_width),
|
||||
self._scatter(self._tagged_high_xs, self._tagged_high_aux_2, target_width),
|
||||
)
|
||||
do1_tagged_payload = {
|
||||
"raw_low": raw_low,
|
||||
"raw_high": raw_high,
|
||||
"aux_low": aux_low,
|
||||
"aux_high": aux_high,
|
||||
}
|
||||
else:
|
||||
sweep = self._scatter(self._xs, self._ys, target_width)
|
||||
if self._aux_1 and self._aux_2 and len(self._aux_1) == len(self._xs):
|
||||
aux_curves = (
|
||||
self._scatter(self._xs, self._aux_1, target_width),
|
||||
self._scatter(self._xs, self._aux_2, target_width),
|
||||
)
|
||||
|
||||
n_valid_cur = int(np.count_nonzero(np.isfinite(sweep)))
|
||||
|
||||
@ -670,4 +870,6 @@ class SweepAssembler:
|
||||
"std": std,
|
||||
"dt_ms": dt_ms,
|
||||
}
|
||||
if do1_tagged_payload is not None:
|
||||
info["_do1_tagged_payload"] = do1_tagged_payload
|
||||
return (sweep, info, aux_curves)
|
||||
|
||||
@ -50,7 +50,7 @@ def _looks_like_legacy_8byte_stream(data: bytes) -> bool:
|
||||
w0 = _u16le_at(buf, base)
|
||||
w1 = _u16le_at(buf, base + 2)
|
||||
w3 = _u16le_at(buf, base + 6)
|
||||
if w0 == 0x000A and w1 != 0xFFFF:
|
||||
if w0 in {0x000A, 0x00A3, 0x00A4} and w1 != 0xFFFF:
|
||||
matched_steps_tty.append(w1)
|
||||
elif w0 == 0x001A and w3 == 0x0000:
|
||||
matched_steps_logdet.append(w1)
|
||||
@ -232,7 +232,8 @@ class SweepReader(threading.Thread):
|
||||
)
|
||||
)
|
||||
sys.stderr.write(
|
||||
"[hint] parser_16_bit_x2: if source is 8-byte tty CH1/CH2 stream (0x000A,step,ch1,ch2), try --bin\n"
|
||||
"[hint] parser_16_bit_x2: if source is 8-byte tty CH1/CH2 stream "
|
||||
"(0x000A/0x00A3/0x00A4,step,ch1,ch2), try --bin\n"
|
||||
)
|
||||
assembler = SweepAssembler(fancy=self._fancy, apply_inversion=False)
|
||||
return parser, assembler, []
|
||||
@ -358,7 +359,8 @@ class SweepReader(threading.Thread):
|
||||
and (now_s - self._started_at) >= _NO_PACKET_HINT_AFTER_S
|
||||
):
|
||||
sys.stderr.write(
|
||||
"[hint] parser_16_bit_x2 still has no sweeps; if source is tty CH1/CH2, rerun with --bin\n"
|
||||
"[hint] parser_16_bit_x2 still has no sweeps; "
|
||||
"if source is tty CH1/CH2 (0x000A/0x00A3/0x00A4), rerun with --bin\n"
|
||||
)
|
||||
parser_hint_emitted = True
|
||||
time.sleep(0.0005)
|
||||
|
||||
@ -24,6 +24,12 @@ class RuntimeState:
|
||||
full_current_fft_source: Optional[np.ndarray] = None
|
||||
full_current_aux_curves: SweepAuxCurves = None
|
||||
full_current_aux_curves_codes: SweepAuxCurves = None
|
||||
full_do1_tagged_raw_low: Optional[np.ndarray] = None
|
||||
full_do1_tagged_raw_high: Optional[np.ndarray] = None
|
||||
full_do1_tagged_aux_low: SweepAuxCurves = None
|
||||
full_do1_tagged_aux_high: SweepAuxCurves = None
|
||||
full_do1_tagged_aux_low_codes: SweepAuxCurves = None
|
||||
full_do1_tagged_aux_high_codes: SweepAuxCurves = None
|
||||
current_freqs: Optional[np.ndarray] = None
|
||||
current_distances: Optional[np.ndarray] = None
|
||||
current_sweep_raw: Optional[np.ndarray] = None
|
||||
@ -31,6 +37,10 @@ class RuntimeState:
|
||||
current_fft_input: Optional[np.ndarray] = None
|
||||
current_fft_complex: Optional[np.ndarray] = None
|
||||
current_aux_curves: SweepAuxCurves = None
|
||||
current_do1_tagged_raw_low: Optional[np.ndarray] = None
|
||||
current_do1_tagged_raw_high: Optional[np.ndarray] = None
|
||||
current_do1_tagged_aux_low: SweepAuxCurves = None
|
||||
current_do1_tagged_aux_high: SweepAuxCurves = None
|
||||
current_sweep_norm: Optional[np.ndarray] = None
|
||||
current_fft_mag: Optional[np.ndarray] = None
|
||||
current_fft_db: Optional[np.ndarray] = None
|
||||
|
||||
@ -9,7 +9,8 @@ import numpy as np
|
||||
|
||||
|
||||
Number = Union[int, float]
|
||||
SignalKind = Literal["bin_iq", "bin_logdet"]
|
||||
SignalKind = Literal["bin_iq", "bin_logdet", "bin_iq_do1_tagged"]
|
||||
Do1Level = Literal["low", "high"]
|
||||
SweepInfo = Dict[str, Any]
|
||||
SweepData = Dict[str, np.ndarray]
|
||||
SweepAuxCurves = Optional[Tuple[np.ndarray, np.ndarray]]
|
||||
@ -29,6 +30,7 @@ class PointEvent:
|
||||
y: float
|
||||
aux: Optional[Tuple[float, float]] = None
|
||||
signal_kind: Optional[SignalKind] = None
|
||||
do1_level: Optional[Do1Level] = None
|
||||
|
||||
|
||||
ParserEvent: TypeAlias = Union[StartEvent, PointEvent]
|
||||
|
||||
@ -46,6 +46,7 @@ class CliTests(unittest.TestCase):
|
||||
self.assertIn("--parser_16_bit_x2", proc.stdout)
|
||||
self.assertIn("--parser_complex_ascii", proc.stdout)
|
||||
self.assertIn("--opengl", proc.stdout)
|
||||
self.assertIn("0x00A3/0x00A4", proc.stdout)
|
||||
|
||||
def test_backend_mpl_reports_removal(self):
|
||||
proc = _run("-m", "rfg_adc_plotter.main", "/dev/null", "--backend", "mpl")
|
||||
|
||||
@ -15,6 +15,8 @@ from rfg_adc_plotter.gui.pyqtgraph_backend import (
|
||||
coalesce_packets_for_ui,
|
||||
compute_background_subtracted_bscan_levels,
|
||||
compute_aux_phase_curve,
|
||||
compute_do1_tagged_aggregate,
|
||||
compute_do1_tagged_phase_curves,
|
||||
convert_tty_i16_to_voltage,
|
||||
decimate_bscan_rows_for_display,
|
||||
decimate_curve_for_display,
|
||||
@ -30,6 +32,7 @@ from rfg_adc_plotter.gui.pyqtgraph_backend import (
|
||||
set_image_rect_if_ready,
|
||||
resolve_visible_fft_curves,
|
||||
resolve_visible_aux_curves,
|
||||
resolve_visible_do1_tagged_aux_curves,
|
||||
)
|
||||
from rfg_adc_plotter.processing.calibration import (
|
||||
build_calib_envelope,
|
||||
@ -316,6 +319,53 @@ class ProcessingTests(unittest.TestCase):
|
||||
self.assertEqual(phase.shape, expected.shape)
|
||||
self.assertTrue(np.allclose(phase, expected, atol=1e-6))
|
||||
|
||||
def test_compute_do1_tagged_aggregate_nanmean_merges_low_and_high(self):
|
||||
low = np.asarray([1.0, np.nan, 5.0, np.nan], dtype=np.float32)
|
||||
high = np.asarray([3.0, 7.0, np.nan, np.nan], dtype=np.float32)
|
||||
|
||||
merged = compute_do1_tagged_aggregate(low, high)
|
||||
|
||||
self.assertIsNotNone(merged)
|
||||
self.assertTrue(np.allclose(merged[:3], np.asarray([2.0, 7.0, 5.0], dtype=np.float32), equal_nan=True))
|
||||
self.assertTrue(np.isnan(merged[3]))
|
||||
|
||||
def test_resolve_visible_do1_tagged_aux_curves_obeys_checkbox_state(self):
|
||||
aux_low = (
|
||||
np.asarray([1.0, 2.0], dtype=np.float32),
|
||||
np.asarray([3.0, 4.0], dtype=np.float32),
|
||||
)
|
||||
aux_high = (
|
||||
np.asarray([5.0, 6.0], dtype=np.float32),
|
||||
np.asarray([7.0, 8.0], dtype=np.float32),
|
||||
)
|
||||
|
||||
hidden_low, hidden_high = resolve_visible_do1_tagged_aux_curves(aux_low, aux_high, enabled=False)
|
||||
self.assertIsNone(hidden_low)
|
||||
self.assertIsNone(hidden_high)
|
||||
|
||||
visible_low, visible_high = resolve_visible_do1_tagged_aux_curves(aux_low, aux_high, enabled=True)
|
||||
self.assertIsNotNone(visible_low)
|
||||
self.assertIsNotNone(visible_high)
|
||||
self.assertTrue(np.allclose(visible_low[0], aux_low[0]))
|
||||
self.assertTrue(np.allclose(visible_high[1], aux_high[1]))
|
||||
|
||||
def test_compute_do1_tagged_phase_curves_returns_two_independent_series(self):
|
||||
aux_low = (
|
||||
np.asarray([1.0, 1.0], dtype=np.float32),
|
||||
np.asarray([0.0, 1.0], dtype=np.float32),
|
||||
)
|
||||
aux_high = (
|
||||
np.asarray([1.0, -1.0], dtype=np.float32),
|
||||
np.asarray([1.0, 1.0], dtype=np.float32),
|
||||
)
|
||||
|
||||
phase_low, phase_high = compute_do1_tagged_phase_curves(aux_low, aux_high)
|
||||
|
||||
self.assertIsNotNone(phase_low)
|
||||
self.assertIsNotNone(phase_high)
|
||||
self.assertTrue(np.allclose(phase_low, np.asarray([0.0, np.pi / 4.0], dtype=np.float32), atol=1e-6))
|
||||
self.assertTrue(np.allclose(phase_high, np.asarray([np.pi / 4.0, 3.0 * np.pi / 4.0], dtype=np.float32), atol=1e-6))
|
||||
|
||||
def test_decimate_curve_for_display_preserves_small_series(self):
|
||||
xs = np.linspace(3.3, 14.3, 64, dtype=np.float64)
|
||||
ys = np.linspace(-1.0, 1.0, 64, dtype=np.float32)
|
||||
|
||||
@ -87,6 +87,25 @@ def _pack_tty_point(step: int, ch1: int, ch2: int) -> bytes:
|
||||
)
|
||||
|
||||
|
||||
def _pack_tty_tagged_point(marker_word0: int, step: int, ch1: int, ch2: int) -> bytes:
|
||||
return b"".join(
|
||||
[
|
||||
_u16le(marker_word0),
|
||||
_u16le(step),
|
||||
_u16le(ch1),
|
||||
_u16le(ch2),
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def _pack_tty_tagged_low_point(step: int, ch1: int, ch2: int) -> bytes:
|
||||
return _pack_tty_tagged_point(0x00A3, step, ch1, ch2)
|
||||
|
||||
|
||||
def _pack_tty_tagged_high_point(step: int, ch1: int, ch2: int) -> bytes:
|
||||
return _pack_tty_tagged_point(0x00A4, step, ch1, ch2)
|
||||
|
||||
|
||||
def _pack_logdet_point(step: int, value: int) -> bytes:
|
||||
return b"".join(
|
||||
[
|
||||
@ -189,6 +208,78 @@ class SweepParserCoreTests(unittest.TestCase):
|
||||
self.assertEqual(events[4].aux, (120.0, 80.0))
|
||||
self.assertEqual(events[4].signal_kind, "bin_iq")
|
||||
|
||||
def test_legacy_binary_parser_accepts_tty_do1_tagged_stream(self):
|
||||
parser = LegacyBinaryParser()
|
||||
stream = b"".join(
|
||||
[
|
||||
_pack_tty_start(),
|
||||
_pack_tty_tagged_low_point(1, 100, 90),
|
||||
_pack_tty_tagged_high_point(1, 120, 95),
|
||||
]
|
||||
)
|
||||
|
||||
events = parser.feed(stream)
|
||||
|
||||
self.assertEqual(len(events), 3)
|
||||
self.assertIsInstance(events[0], StartEvent)
|
||||
self.assertEqual(events[0].signal_kind, "bin_iq")
|
||||
self.assertIsInstance(events[1], PointEvent)
|
||||
self.assertEqual(events[1].signal_kind, "bin_iq_do1_tagged")
|
||||
self.assertEqual(events[1].do1_level, "low")
|
||||
self.assertEqual(events[1].x, 1)
|
||||
self.assertEqual(events[1].aux, (100.0, 90.0))
|
||||
self.assertIsInstance(events[2], PointEvent)
|
||||
self.assertEqual(events[2].signal_kind, "bin_iq_do1_tagged")
|
||||
self.assertEqual(events[2].do1_level, "high")
|
||||
self.assertEqual(events[2].x, 1)
|
||||
self.assertEqual(events[2].aux, (120.0, 95.0))
|
||||
|
||||
def test_legacy_binary_parser_keeps_same_step_for_different_do1_levels_in_one_sweep(self):
|
||||
parser = LegacyBinaryParser()
|
||||
stream = b"".join(
|
||||
[
|
||||
_pack_tty_start(),
|
||||
_pack_tty_tagged_low_point(1, 100, 90),
|
||||
_pack_tty_tagged_high_point(1, 120, 95),
|
||||
_pack_tty_tagged_low_point(2, 130, 80),
|
||||
_pack_tty_tagged_high_point(2, 140, 75),
|
||||
]
|
||||
)
|
||||
|
||||
events = parser.feed(stream)
|
||||
|
||||
start_events = [event for event in events if isinstance(event, StartEvent)]
|
||||
self.assertEqual(len(start_events), 1)
|
||||
self.assertEqual(start_events[0].signal_kind, "bin_iq")
|
||||
point_levels = [event.do1_level for event in events if isinstance(event, PointEvent)]
|
||||
self.assertEqual(point_levels, ["low", "high", "low", "high"])
|
||||
|
||||
def test_legacy_binary_parser_resets_tagged_stream_only_on_same_level_step_reset(self):
|
||||
parser = LegacyBinaryParser()
|
||||
stream = b"".join(
|
||||
[
|
||||
_pack_tty_start(),
|
||||
_pack_tty_tagged_low_point(1, 100, 90),
|
||||
_pack_tty_tagged_high_point(1, 120, 95),
|
||||
_pack_tty_tagged_low_point(2, 130, 80),
|
||||
_pack_tty_tagged_high_point(2, 140, 75),
|
||||
_pack_tty_tagged_low_point(1, 110, 85),
|
||||
]
|
||||
)
|
||||
|
||||
events = parser.feed(stream)
|
||||
|
||||
self.assertIsInstance(events[0], StartEvent)
|
||||
self.assertIsInstance(events[1], PointEvent)
|
||||
self.assertIsInstance(events[2], PointEvent)
|
||||
self.assertIsInstance(events[3], PointEvent)
|
||||
self.assertIsInstance(events[4], PointEvent)
|
||||
self.assertIsInstance(events[5], StartEvent)
|
||||
self.assertEqual(events[5].signal_kind, "bin_iq_do1_tagged")
|
||||
self.assertIsInstance(events[6], PointEvent)
|
||||
self.assertEqual(events[6].do1_level, "low")
|
||||
self.assertEqual(events[6].x, 1)
|
||||
|
||||
def test_legacy_binary_parser_tty_mode_does_not_flip_to_legacy_on_ch2_low_byte_0x0a(self):
|
||||
parser = LegacyBinaryParser()
|
||||
stream = b"".join(
|
||||
@ -377,6 +468,40 @@ class SweepParserCoreTests(unittest.TestCase):
|
||||
self.assertEqual(aux[0][1], 100.0)
|
||||
self.assertEqual(aux[1][2], 95.0)
|
||||
|
||||
def test_sweep_assembler_builds_tagged_payload_and_nanmean_aggregate(self):
|
||||
assembler = SweepAssembler(fancy=False, apply_inversion=False)
|
||||
self.assertIsNone(assembler.consume(StartEvent(ch=0, signal_kind="bin_iq_do1_tagged")))
|
||||
assembler.consume(
|
||||
PointEvent(
|
||||
ch=0,
|
||||
x=1,
|
||||
y=10.0,
|
||||
aux=(100.0, 90.0),
|
||||
signal_kind="bin_iq_do1_tagged",
|
||||
do1_level="low",
|
||||
)
|
||||
)
|
||||
assembler.consume(
|
||||
PointEvent(
|
||||
ch=0,
|
||||
x=1,
|
||||
y=20.0,
|
||||
aux=(120.0, 95.0),
|
||||
signal_kind="bin_iq_do1_tagged",
|
||||
do1_level="high",
|
||||
)
|
||||
)
|
||||
sweep, info, aux = assembler.finalize_current()
|
||||
self.assertIsNone(aux)
|
||||
self.assertEqual(info["signal_kind"], "bin_iq_do1_tagged")
|
||||
self.assertAlmostEqual(float(sweep[1]), 15.0, places=6)
|
||||
payload = info.get("_do1_tagged_payload")
|
||||
self.assertIsInstance(payload, dict)
|
||||
self.assertIn("raw_low", payload)
|
||||
self.assertIn("raw_high", payload)
|
||||
self.assertIn("aux_low", payload)
|
||||
self.assertIn("aux_high", payload)
|
||||
|
||||
def test_sweep_assembler_splits_packet_on_channel_switch(self):
|
||||
assembler = SweepAssembler(fancy=False, apply_inversion=False)
|
||||
self.assertIsNone(assembler.consume(PointEvent(ch=1, x=1, y=10.0)))
|
||||
|
||||
@ -66,6 +66,25 @@ def _pack_tty_point(step: int, ch1: int, ch2: int) -> bytes:
|
||||
)
|
||||
|
||||
|
||||
def _pack_tty_tagged_point(marker_word0: int, step: int, ch1: int, ch2: int) -> bytes:
|
||||
return b"".join(
|
||||
[
|
||||
_u16le(marker_word0),
|
||||
_u16le(step),
|
||||
_u16le(ch1),
|
||||
_u16le(ch2),
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def _pack_tty_tagged_low(step: int, ch1: int, ch2: int) -> bytes:
|
||||
return _pack_tty_tagged_point(0x00A3, step, ch1, ch2)
|
||||
|
||||
|
||||
def _pack_tty_tagged_high(step: int, ch1: int, ch2: int) -> bytes:
|
||||
return _pack_tty_tagged_point(0x00A4, step, ch1, ch2)
|
||||
|
||||
|
||||
def _pack_logdet_point(step: int, value: int) -> bytes:
|
||||
return b"".join(
|
||||
[
|
||||
@ -166,6 +185,29 @@ class SweepReaderTests(unittest.TestCase):
|
||||
reader.join(timeout=1.0)
|
||||
stack.close()
|
||||
|
||||
def test_parser_16_bit_x2_falls_back_to_tty_do1_tagged_stream(self):
|
||||
payload = bytearray()
|
||||
while len(payload) < (_PARSER_16_BIT_X2_PROBE_BYTES + 40):
|
||||
payload += _pack_tty_start()
|
||||
payload += _pack_tty_tagged_low(1, 100, 90)
|
||||
payload += _pack_tty_tagged_high(1, 120, 95)
|
||||
payload += _pack_tty_tagged_low(2, 110, 80)
|
||||
payload += _pack_tty_tagged_high(2, 130, 70)
|
||||
payload += _pack_tty_tagged_low(1, 105, 85)
|
||||
|
||||
stack, reader, queue, stop_event, stderr = self._start_reader(bytes(payload), parser_16_bit_x2=True)
|
||||
try:
|
||||
sweep, info, aux = queue.get(timeout=2.0)
|
||||
self.assertEqual(info["signal_kind"], "bin_iq_do1_tagged")
|
||||
self.assertIsNone(aux)
|
||||
self.assertIn("_do1_tagged_payload", info)
|
||||
self.assertGreaterEqual(sweep.shape[0], 2)
|
||||
self.assertIn("fallback -> legacy", stderr.getvalue())
|
||||
finally:
|
||||
stop_event.set()
|
||||
reader.join(timeout=1.0)
|
||||
stack.close()
|
||||
|
||||
def test_parser_16_bit_x2_keeps_true_complex_stream(self):
|
||||
payload = b"".join(
|
||||
[
|
||||
|
||||
Reference in New Issue
Block a user