new mode --bin24
This commit is contained in:
@@ -92,6 +92,19 @@ def build_parser() -> argparse.ArgumentParser:
|
||||
"Для 0x001A: code_i16 переводится в В, raw = V, FFT вход = exp(V)"
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--bin24",
|
||||
dest="bin24_mode",
|
||||
action="store_true",
|
||||
help=(
|
||||
"Как --bin, но каждый отсчет I/Q — знаковый 24-битный (3 байта), little-endian. "
|
||||
"Запись 10 байт: marker(u16), step(u16), ch1_i24(3б), ch2_i24(3б). "
|
||||
"Маркеры те же: 0x000A (точка), 0x00A8 (secondary), "
|
||||
"0x00A3/0x00A4 (DO1 LOW/HIGH tagged). "
|
||||
"Математика как у --bin: сырая кривая = ch1^2+ch2^2, FFT вход = ch1+i*ch2. "
|
||||
"Полная шкала для пересчета в В = 2^23-1."
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--tty-range-v",
|
||||
type=float,
|
||||
|
||||
@@ -75,6 +75,7 @@ DEFAULT_MAIN_WINDOW_HEIGHT = 680
|
||||
MIN_MAIN_WINDOW_WIDTH = 640
|
||||
MIN_MAIN_WINDOW_HEIGHT = 420
|
||||
TTY_CODE_SCALE_DENOM = 32767.0
|
||||
TTY_CODE_SCALE_DENOM_24 = 8388607.0
|
||||
TTY_RANGE_DEFAULT_V = 5.0
|
||||
TTY_RANGE_MIN_V = 1e-6
|
||||
TTY_RANGE_MAX_V = 1_000_000.0
|
||||
@@ -640,13 +641,18 @@ def sanitize_tty_voltage_range(range_v: float, default: float = TTY_RANGE_DEFAUL
|
||||
return float(np.clip(abs(value), TTY_RANGE_MIN_V, TTY_RANGE_MAX_V))
|
||||
|
||||
|
||||
def convert_tty_i16_to_voltage(codes: np.ndarray, range_v: float) -> np.ndarray:
|
||||
"""Convert signed tty int16 code array to clipped voltage values in ``[-range_v, +range_v]``."""
|
||||
def convert_tty_i16_to_voltage(
|
||||
codes: np.ndarray, range_v: float, *, denom: float = TTY_CODE_SCALE_DENOM
|
||||
) -> np.ndarray:
|
||||
"""Convert signed tty code array to clipped voltage values in ``[-range_v, +range_v]``.
|
||||
|
||||
``denom`` is the code full-scale magnitude (2^15-1 for int16, 2^23-1 for int24).
|
||||
"""
|
||||
code_arr = np.asarray(codes, dtype=np.float32).reshape(-1)
|
||||
if code_arr.size <= 0:
|
||||
return np.zeros((0,), dtype=np.float32)
|
||||
range_abs_v = sanitize_tty_voltage_range(range_v)
|
||||
scale_v = range_abs_v / float(TTY_CODE_SCALE_DENOM)
|
||||
scale_v = range_abs_v / float(denom)
|
||||
volt = code_arr * np.float32(scale_v)
|
||||
return np.clip(volt, -range_abs_v, range_abs_v).astype(np.float32, copy=False)
|
||||
|
||||
@@ -656,9 +662,10 @@ def build_logdet_voltage_fft_input(
|
||||
range_v: float,
|
||||
*,
|
||||
exp_input_limit: float = LOGDET_EXP_INPUT_LIMIT,
|
||||
denom: float = TTY_CODE_SCALE_DENOM,
|
||||
) -> Tuple[np.ndarray, np.ndarray]:
|
||||
"""Convert 1a00 log-detector codes to raw volts and a real FFT input ``exp(V)``."""
|
||||
volts = convert_tty_i16_to_voltage(codes, range_v)
|
||||
volts = convert_tty_i16_to_voltage(codes, range_v, denom=denom)
|
||||
if volts.size <= 0:
|
||||
empty = np.zeros((0,), dtype=np.float32)
|
||||
return empty, empty
|
||||
@@ -889,15 +896,18 @@ def run_pyqtgraph(args) -> None:
|
||||
peak_calibrate_mode = bool(getattr(args, "calibrate", False))
|
||||
peak_search_enabled = bool(getattr(args, "peak_search", False))
|
||||
bin_mode = bool(getattr(args, "bin_mode", False))
|
||||
bin24_mode = bool(getattr(args, "bin24_mode", False))
|
||||
tty_range_v = sanitize_tty_voltage_range(getattr(args, "tty_range_v", TTY_RANGE_DEFAULT_V))
|
||||
tty_code_denom = TTY_CODE_SCALE_DENOM_24 if bin24_mode else TTY_CODE_SCALE_DENOM
|
||||
complex_ascii_mode = bool(getattr(args, "parser_complex_ascii", False))
|
||||
complex_sweep_mode = bool(
|
||||
bin_mode
|
||||
or bin24_mode
|
||||
or complex_ascii_mode
|
||||
or getattr(args, "parser_16_bit_x2", False)
|
||||
or getattr(args, "parser_test", False)
|
||||
)
|
||||
bin_iq_power_mode = bool(bin_mode)
|
||||
bin_iq_power_mode = bool(bin_mode or bin24_mode)
|
||||
if not sys.platform.startswith("win"):
|
||||
display_name = os.environ.get("DISPLAY") or os.environ.get("WAYLAND_DISPLAY")
|
||||
if not display_name:
|
||||
@@ -921,6 +931,7 @@ def run_pyqtgraph(args) -> None:
|
||||
stop_event,
|
||||
fancy=bool(args.fancy),
|
||||
bin_mode=bin_mode,
|
||||
bin24_mode=bin24_mode,
|
||||
logscale=bool(args.logscale),
|
||||
parser_16_bit_x2=bool(args.parser_16_bit_x2),
|
||||
parser_test=bool(args.parser_test),
|
||||
@@ -1904,8 +1915,8 @@ def run_pyqtgraph(args) -> None:
|
||||
width = min(code_1_arr.size, code_2_arr.size)
|
||||
if width <= 0:
|
||||
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)
|
||||
ch_1_v = convert_tty_i16_to_voltage(code_1_arr[:width], tty_range_v, denom=tty_code_denom)
|
||||
ch_2_v = convert_tty_i16_to_voltage(code_2_arr[:width], tty_range_v, denom=tty_code_denom)
|
||||
runtime.full_do1_tagged_raw_low = None
|
||||
runtime.full_do1_tagged_raw_high = None
|
||||
runtime.full_do1_tagged_aux_low = None
|
||||
@@ -1936,10 +1947,10 @@ def run_pyqtgraph(args) -> None:
|
||||
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 = convert_tty_i16_to_voltage(low_code_1_arr[:width], tty_range_v, denom=tty_code_denom)
|
||||
low_ch_2_v = convert_tty_i16_to_voltage(low_code_2_arr[:width], tty_range_v, denom=tty_code_denom)
|
||||
high_ch_1_v = convert_tty_i16_to_voltage(high_code_1_arr[:width], tty_range_v, denom=tty_code_denom)
|
||||
high_ch_2_v = convert_tty_i16_to_voltage(high_code_2_arr[:width], tty_range_v, denom=tty_code_denom)
|
||||
|
||||
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)
|
||||
@@ -1964,7 +1975,7 @@ def run_pyqtgraph(args) -> None:
|
||||
code_arr = np.asarray(runtime.full_current_sweep_codes, dtype=np.float32).reshape(-1)
|
||||
if code_arr.size <= 0:
|
||||
return False
|
||||
sweep_raw_v, fft_input = build_logdet_voltage_fft_input(code_arr, tty_range_v)
|
||||
sweep_raw_v, fft_input = build_logdet_voltage_fft_input(code_arr, tty_range_v, denom=tty_code_denom)
|
||||
runtime.full_do1_tagged_raw_low = None
|
||||
runtime.full_do1_tagged_raw_high = None
|
||||
runtime.full_do1_tagged_aux_low = None
|
||||
@@ -3089,13 +3100,13 @@ def run_pyqtgraph(args) -> None:
|
||||
calibrate_freqs({"F": base_freqs, "I": sec_ch1})["I"],
|
||||
dtype=np.float32,
|
||||
)
|
||||
runtime.full_secondary_ch1 = convert_tty_i16_to_voltage(sec_ch1_calibrated, tty_range_v)
|
||||
runtime.full_secondary_ch1 = convert_tty_i16_to_voltage(sec_ch1_calibrated, tty_range_v, denom=tty_code_denom)
|
||||
if sec_ch2 is not None:
|
||||
sec_ch2_calibrated = np.asarray(
|
||||
calibrate_freqs({"F": base_freqs, "I": sec_ch2})["I"],
|
||||
dtype=np.float32,
|
||||
)
|
||||
runtime.full_secondary_ch2 = convert_tty_i16_to_voltage(sec_ch2_calibrated, tty_range_v)
|
||||
runtime.full_secondary_ch2 = convert_tty_i16_to_voltage(sec_ch2_calibrated, tty_range_v, denom=tty_code_denom)
|
||||
if runtime.full_secondary_ch1 is not None and runtime.full_secondary_ch2 is not None:
|
||||
w = min(runtime.full_secondary_ch1.size, runtime.full_secondary_ch2.size)
|
||||
v1 = runtime.full_secondary_ch1[:w]
|
||||
|
||||
@@ -31,6 +31,10 @@ def u16_to_i16(value: int) -> int:
|
||||
return value - 0x1_0000 if (value & 0x8000) else value
|
||||
|
||||
|
||||
def u24_to_i24(value: int) -> int:
|
||||
return value - 0x100_0000 if (value & 0x80_0000) else value
|
||||
|
||||
|
||||
def log_value_to_linear(value: int) -> float:
|
||||
exponent = max(-LOG_EXP_LIMIT, min(LOG_EXP_LIMIT, float(value) * LOG_SCALER))
|
||||
return float(LOG_BASE ** exponent)
|
||||
@@ -606,6 +610,148 @@ class LegacyBinaryParser:
|
||||
return events
|
||||
|
||||
|
||||
class LegacyBinaryParser24(LegacyBinaryParser):
|
||||
"""Byte-resynchronizing parser for 10-byte tty records with 24-bit I/Q values.
|
||||
|
||||
Same protocol as ``LegacyBinaryParser``'s tty family (markers 0x000A primary,
|
||||
0x00A8 secondary, 0x00A3/0x00A4 DO1 low/high tagged), but each channel value
|
||||
is a signed 24-bit little-endian integer, so a record is 10 bytes:
|
||||
``marker(u16) | step(u16) | ch1_i24(3B) | ch2_i24(3B)``. The legacy 8-byte
|
||||
(byte6==0x0A) and logdet (0x001A) record shapes do not exist here and are
|
||||
omitted so int24 payload bytes cannot be mistaken for them.
|
||||
"""
|
||||
|
||||
RECORD_SIZE = 10
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(batch_events=False)
|
||||
|
||||
@staticmethod
|
||||
def _i24_at(buf: bytearray, offset: int) -> int:
|
||||
u = int(buf[offset]) | (int(buf[offset + 1]) << 8) | (int(buf[offset + 2]) << 16)
|
||||
return u24_to_i24(u)
|
||||
|
||||
def _try_emit_tty_batch(self, events: List[ParserEvent], *, require_not_legacy: bool) -> bool:
|
||||
# int24 has no native numpy dtype; the scalar per-record path handles it.
|
||||
return False
|
||||
|
||||
def _emit_tty_point24(self, events: List[ParserEvent], step: int, ch_1: int, ch_2: int) -> None:
|
||||
self._prepare_bin_point(events, step=int(step), signal_kind="bin_iq")
|
||||
ch_1 = int(ch_1)
|
||||
ch_2 = int(ch_2)
|
||||
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",
|
||||
)
|
||||
)
|
||||
|
||||
def _emit_tty_tagged_point24(
|
||||
self,
|
||||
events: List[ParserEvent],
|
||||
step: int,
|
||||
ch_1: int,
|
||||
ch_2: 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 = int(ch_1)
|
||||
ch_2 = int(ch_2)
|
||||
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_secondary_point24(self, events: List[ParserEvent], step: int, ch_1: int, ch_2: int) -> None:
|
||||
self._mode = "bin"
|
||||
self._current_signal_kind = self._current_signal_kind or "bin_iq"
|
||||
ch_1 = int(ch_1)
|
||||
ch_2 = int(ch_2)
|
||||
events.append(
|
||||
PointEvent(
|
||||
ch=0,
|
||||
x=int(step),
|
||||
y=0.0,
|
||||
aux=(float(ch_1), float(ch_2)),
|
||||
signal_kind="bin_iq",
|
||||
is_secondary=True,
|
||||
)
|
||||
)
|
||||
|
||||
def feed(self, data: bytes) -> List[ParserEvent]:
|
||||
if data:
|
||||
self._buf += data
|
||||
events: List[ParserEvent] = []
|
||||
while len(self._buf) >= self.RECORD_SIZE:
|
||||
w0 = self._u16_at(self._buf, 0)
|
||||
w1 = self._u16_at(self._buf, 2)
|
||||
|
||||
is_tty_start = w0 == 0x000A and all(b == 0xFF for b in self._buf[2:10])
|
||||
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_secondary_point = w0 == 0x00A8 and w1 != 0xFFFF
|
||||
|
||||
if is_tty_start:
|
||||
self._emit_tty_start(events)
|
||||
del self._buf[:10]
|
||||
continue
|
||||
if is_tty_point:
|
||||
self._emit_tty_point24(
|
||||
events,
|
||||
step=int(w1),
|
||||
ch_1=self._i24_at(self._buf, 4),
|
||||
ch_2=self._i24_at(self._buf, 7),
|
||||
)
|
||||
del self._buf[:10]
|
||||
continue
|
||||
if is_tty_tagged_low_point:
|
||||
self._emit_tty_tagged_point24(
|
||||
events,
|
||||
step=int(w1),
|
||||
ch_1=self._i24_at(self._buf, 4),
|
||||
ch_2=self._i24_at(self._buf, 7),
|
||||
do1_level="low",
|
||||
)
|
||||
del self._buf[:10]
|
||||
continue
|
||||
if is_tty_tagged_high_point:
|
||||
self._emit_tty_tagged_point24(
|
||||
events,
|
||||
step=int(w1),
|
||||
ch_1=self._i24_at(self._buf, 4),
|
||||
ch_2=self._i24_at(self._buf, 7),
|
||||
do1_level="high",
|
||||
)
|
||||
del self._buf[:10]
|
||||
continue
|
||||
if is_secondary_point:
|
||||
self._emit_secondary_point24(
|
||||
events,
|
||||
step=int(w1),
|
||||
ch_1=self._i24_at(self._buf, 4),
|
||||
ch_2=self._i24_at(self._buf, 7),
|
||||
)
|
||||
del self._buf[:10]
|
||||
continue
|
||||
del self._buf[:1]
|
||||
return events
|
||||
|
||||
|
||||
class LogScaleBinaryParser32:
|
||||
"""Byte-resynchronizing parser for 32-bit logscale pair records."""
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ from rfg_adc_plotter.io.sweep_parser_core import (
|
||||
AsciiSweepParser,
|
||||
ComplexAsciiSweepParser,
|
||||
LegacyBinaryParser,
|
||||
LegacyBinaryParser24,
|
||||
LogScale16BitX2BinaryParser,
|
||||
LogScaleBinaryParser32,
|
||||
ParserTestStreamParser,
|
||||
@@ -113,6 +114,7 @@ class SweepReader(threading.Thread):
|
||||
stop_event: threading.Event,
|
||||
fancy: bool = False,
|
||||
bin_mode: bool = False,
|
||||
bin24_mode: bool = False,
|
||||
logscale: bool = False,
|
||||
parser_16_bit_x2: bool = False,
|
||||
parser_test: bool = False,
|
||||
@@ -125,6 +127,7 @@ class SweepReader(threading.Thread):
|
||||
self._stop_event = stop_event
|
||||
self._fancy = bool(fancy)
|
||||
self._bin_mode = bool(bin_mode)
|
||||
self._bin24_mode = bool(bin24_mode)
|
||||
self._logscale = bool(logscale)
|
||||
self._parser_16_bit_x2 = bool(parser_16_bit_x2)
|
||||
self._parser_test = bool(parser_test)
|
||||
@@ -143,6 +146,8 @@ class SweepReader(threading.Thread):
|
||||
return "parser_16_bit_x2"
|
||||
if self._logscale:
|
||||
return "logscale_32"
|
||||
if self._bin24_mode:
|
||||
return "legacy_10byte_i24"
|
||||
if self._bin_mode:
|
||||
return "legacy_8byte"
|
||||
return "ascii"
|
||||
@@ -156,6 +161,8 @@ class SweepReader(threading.Thread):
|
||||
return LogScale16BitX2BinaryParser(), SweepAssembler(fancy=self._fancy, apply_inversion=False)
|
||||
if self._logscale:
|
||||
return LogScaleBinaryParser32(), SweepAssembler(fancy=self._fancy, apply_inversion=False)
|
||||
if self._bin24_mode:
|
||||
return LegacyBinaryParser24(), SweepAssembler(fancy=self._fancy, apply_inversion=True)
|
||||
if self._bin_mode:
|
||||
return LegacyBinaryParser(batch_events=True), SweepAssembler(fancy=self._fancy, apply_inversion=True)
|
||||
return AsciiSweepParser(), SweepAssembler(fancy=self._fancy, apply_inversion=True)
|
||||
|
||||
Reference in New Issue
Block a user