new mode --bin24
This commit is contained in:
@@ -13,6 +13,7 @@ from rfg_adc_plotter.gui.pyqtgraph_backend import (
|
||||
build_logdet_voltage_fft_input,
|
||||
build_main_window_layout,
|
||||
coalesce_packets_for_ui,
|
||||
TTY_CODE_SCALE_DENOM_24,
|
||||
compute_background_subtracted_bscan_levels,
|
||||
compute_aux_phase_curve,
|
||||
compute_do1_tagged_aggregate,
|
||||
@@ -87,6 +88,26 @@ class ProcessingTests(unittest.TestCase):
|
||||
self.assertTrue(np.all(volts >= -5.0))
|
||||
self.assertTrue(np.all(volts <= 5.0))
|
||||
|
||||
def test_convert_tty_i16_to_voltage_int24_denominator_maps_full_scale(self):
|
||||
full_scale = float(TTY_CODE_SCALE_DENOM_24)
|
||||
codes = np.asarray([-full_scale - 1.0, 0.0, full_scale], dtype=np.float32)
|
||||
volts = convert_tty_i16_to_voltage(codes, 5.0, denom=TTY_CODE_SCALE_DENOM_24)
|
||||
|
||||
self.assertAlmostEqual(float(volts[0]), -5.0, places=4)
|
||||
self.assertAlmostEqual(float(volts[1]), 0.0, places=6)
|
||||
self.assertAlmostEqual(float(volts[2]), 5.0, places=4)
|
||||
|
||||
# A quarter-scale 24-bit code must yield ~1.25 V with the int24 denom, but the
|
||||
# default int16 denom over-scales it ~256x and clips to the ±5 V range. This
|
||||
# guards that --bin24 uses the wider full-scale rather than the int16 one.
|
||||
quarter = np.asarray([full_scale / 4.0], dtype=np.float32)
|
||||
self.assertAlmostEqual(
|
||||
float(convert_tty_i16_to_voltage(quarter, 5.0, denom=TTY_CODE_SCALE_DENOM_24)[0]),
|
||||
1.25,
|
||||
places=4,
|
||||
)
|
||||
self.assertAlmostEqual(float(convert_tty_i16_to_voltage(quarter, 5.0)[0]), 5.0, places=6)
|
||||
|
||||
def test_build_logdet_voltage_fft_input_converts_codes_and_exponentiates(self):
|
||||
codes = np.asarray([-32768.0, 0.0, 32767.0], dtype=np.float32)
|
||||
volts, fft_input = build_logdet_voltage_fft_input(codes, 5.0)
|
||||
|
||||
@@ -10,6 +10,7 @@ from rfg_adc_plotter.io.sweep_parser_core import (
|
||||
BatchPointEvent,
|
||||
ComplexAsciiSweepParser,
|
||||
LegacyBinaryParser,
|
||||
LegacyBinaryParser24,
|
||||
LogScale16BitX2BinaryParser,
|
||||
LogScaleBinaryParser32,
|
||||
ParserTestStreamParser,
|
||||
@@ -17,6 +18,7 @@ from rfg_adc_plotter.io.sweep_parser_core import (
|
||||
StartEvent,
|
||||
SweepAssembler,
|
||||
log_pair_to_sweep,
|
||||
u24_to_i24,
|
||||
)
|
||||
|
||||
|
||||
@@ -120,6 +122,35 @@ def _pack_logdet_point(step: int, value: int) -> bytes:
|
||||
)
|
||||
|
||||
|
||||
def _i24le(value: int) -> bytes:
|
||||
v = int(value) & 0xFFFFFF
|
||||
return bytes((v & 0xFF, (v >> 8) & 0xFF, (v >> 16) & 0xFF))
|
||||
|
||||
|
||||
def _pack_tty24_start() -> bytes:
|
||||
return _u16le(0x000A) + b"\xff" * 8
|
||||
|
||||
|
||||
def _pack_tty24_point(step: int, ch1: int, ch2: int) -> bytes:
|
||||
return _u16le(0x000A) + _u16le(step) + _i24le(ch1) + _i24le(ch2)
|
||||
|
||||
|
||||
def _pack_tty24_tagged_point(marker_word0: int, step: int, ch1: int, ch2: int) -> bytes:
|
||||
return _u16le(marker_word0) + _u16le(step) + _i24le(ch1) + _i24le(ch2)
|
||||
|
||||
|
||||
def _pack_tty24_tagged_low_point(step: int, ch1: int, ch2: int) -> bytes:
|
||||
return _pack_tty24_tagged_point(0x00A3, step, ch1, ch2)
|
||||
|
||||
|
||||
def _pack_tty24_tagged_high_point(step: int, ch1: int, ch2: int) -> bytes:
|
||||
return _pack_tty24_tagged_point(0x00A4, step, ch1, ch2)
|
||||
|
||||
|
||||
def _pack_tty24_secondary_point(step: int, ch1: int, ch2: int) -> bytes:
|
||||
return _u16le(0x00A8) + _u16le(step) + _i24le(ch1) + _i24le(ch2)
|
||||
|
||||
|
||||
class SweepParserCoreTests(unittest.TestCase):
|
||||
def test_ascii_parser_emits_start_and_points(self):
|
||||
parser = AsciiSweepParser()
|
||||
@@ -697,5 +728,154 @@ class SweepParserCoreTests(unittest.TestCase):
|
||||
self.assertNotIn("_secondary_payload", info)
|
||||
|
||||
|
||||
class LegacyBinaryParser24Tests(unittest.TestCase):
|
||||
def test_u24_to_i24_sign_extension_boundaries(self):
|
||||
self.assertEqual(u24_to_i24(0x000000), 0)
|
||||
self.assertEqual(u24_to_i24(0x000001), 1)
|
||||
self.assertEqual(u24_to_i24(0x7FFFFF), 8388607)
|
||||
self.assertEqual(u24_to_i24(0x800000), -8388608)
|
||||
self.assertEqual(u24_to_i24(0xFFFFFF), -1)
|
||||
|
||||
def test_accepts_tty24_ch1_ch2_stream(self):
|
||||
parser = LegacyBinaryParser24()
|
||||
stream = b"".join(
|
||||
[
|
||||
_pack_tty24_start(),
|
||||
_pack_tty24_point(1, 100, 90),
|
||||
_pack_tty24_point(2, 120, 95),
|
||||
]
|
||||
)
|
||||
|
||||
events = parser.feed(stream)
|
||||
|
||||
self.assertIsInstance(events[0], StartEvent)
|
||||
self.assertEqual(events[0].ch, 0)
|
||||
self.assertIsInstance(events[1], PointEvent)
|
||||
self.assertEqual(events[1].x, 1)
|
||||
self.assertEqual(events[1].y, 18100.0)
|
||||
self.assertEqual(events[1].aux, (100.0, 90.0))
|
||||
self.assertEqual(events[1].signal_kind, "bin_iq")
|
||||
self.assertEqual(events[2].x, 2)
|
||||
self.assertEqual(events[2].y, 23425.0)
|
||||
self.assertEqual(events[2].aux, (120.0, 95.0))
|
||||
|
||||
def test_decodes_negative_int24_values(self):
|
||||
parser = LegacyBinaryParser24()
|
||||
stream = _pack_tty24_start() + _pack_tty24_point(1, -8388608, 8388607)
|
||||
|
||||
events = parser.feed(stream)
|
||||
|
||||
point = events[1]
|
||||
self.assertEqual(point.aux, (-8388608.0, 8388607.0))
|
||||
self.assertEqual(point.y, float(8388608 ** 2 + 8388607 ** 2))
|
||||
|
||||
def test_never_emits_batch_event(self):
|
||||
parser = LegacyBinaryParser24()
|
||||
stream = _pack_tty24_start() + b"".join(
|
||||
_pack_tty24_point(i, i * 10, -i * 5) for i in range(1, 6)
|
||||
)
|
||||
|
||||
events = parser.feed(stream)
|
||||
|
||||
self.assertFalse(any(isinstance(e, BatchPointEvent) for e in events))
|
||||
self.assertFalse(parser._try_emit_tty_batch(events, require_not_legacy=False))
|
||||
|
||||
def test_resynchronizes_after_garbage(self):
|
||||
parser = LegacyBinaryParser24()
|
||||
stream = b"\x11\x22\x33" + _pack_tty24_start() + _pack_tty24_point(5, 7, -3)
|
||||
|
||||
events = parser.feed(stream)
|
||||
|
||||
points = [e for e in events if isinstance(e, PointEvent)]
|
||||
self.assertEqual(len(points), 1)
|
||||
self.assertEqual(points[0].x, 5)
|
||||
self.assertEqual(points[0].aux, (7.0, -3.0))
|
||||
|
||||
def test_detects_new_sweep_on_step_reset(self):
|
||||
parser = LegacyBinaryParser24()
|
||||
stream = _pack_tty24_start() + b"".join(
|
||||
[
|
||||
_pack_tty24_point(5, 1, 1),
|
||||
_pack_tty24_point(6, 2, 2),
|
||||
_pack_tty24_point(1, 3, 3),
|
||||
]
|
||||
)
|
||||
|
||||
events = parser.feed(stream)
|
||||
|
||||
start_count = sum(1 for e in events if isinstance(e, StartEvent))
|
||||
self.assertEqual(start_count, 2)
|
||||
|
||||
def test_do1_tagged_routing(self):
|
||||
parser = LegacyBinaryParser24()
|
||||
stream = b"".join(
|
||||
[
|
||||
_pack_tty24_tagged_low_point(1, 10, 20),
|
||||
_pack_tty24_tagged_high_point(1, 30, 40),
|
||||
]
|
||||
)
|
||||
|
||||
events = parser.feed(stream)
|
||||
points = [e for e in events if isinstance(e, PointEvent)]
|
||||
|
||||
self.assertEqual(points[0].signal_kind, "bin_iq_do1_tagged")
|
||||
self.assertEqual(points[0].do1_level, "low")
|
||||
self.assertEqual(points[0].aux, (10.0, 20.0))
|
||||
self.assertEqual(points[1].do1_level, "high")
|
||||
self.assertEqual(points[1].aux, (30.0, 40.0))
|
||||
|
||||
def test_secondary_point(self):
|
||||
parser = LegacyBinaryParser24()
|
||||
stream = _pack_tty24_start() + _pack_tty24_secondary_point(3, -51, -36)
|
||||
|
||||
events = parser.feed(stream)
|
||||
secondary = [e for e in events if isinstance(e, PointEvent) and e.is_secondary]
|
||||
|
||||
self.assertEqual(len(secondary), 1)
|
||||
self.assertEqual(secondary[0].x, 3)
|
||||
self.assertEqual(secondary[0].y, 0.0)
|
||||
self.assertEqual(secondary[0].aux, (-51.0, -36.0))
|
||||
|
||||
def test_assembles_sweep_packet(self):
|
||||
parser = LegacyBinaryParser24()
|
||||
assembler = SweepAssembler(fancy=False, apply_inversion=True)
|
||||
stream = _pack_tty24_start() + b"".join(
|
||||
_pack_tty24_point(i, i * 10, -i * 5) for i in range(1, 20)
|
||||
)
|
||||
|
||||
packets = []
|
||||
for event in parser.feed(stream):
|
||||
packet = assembler.consume(event)
|
||||
if packet is not None:
|
||||
packets.append(packet)
|
||||
final = assembler.finalize_current()
|
||||
if final is not None:
|
||||
packets.append(final)
|
||||
|
||||
self.assertTrue(packets)
|
||||
sweep, info, _aux = packets[-1]
|
||||
self.assertEqual(info["signal_kind"], "bin_iq")
|
||||
self.assertTrue(np.isfinite(info["mean"]))
|
||||
|
||||
|
||||
class SweepReaderBin24Tests(unittest.TestCase):
|
||||
def test_build_parser_selects_int24_parser(self):
|
||||
from queue import Queue
|
||||
import threading
|
||||
|
||||
from rfg_adc_plotter.io.sweep_reader import SweepReader
|
||||
|
||||
reader = SweepReader(
|
||||
"unused",
|
||||
115200,
|
||||
Queue(),
|
||||
threading.Event(),
|
||||
bin24_mode=True,
|
||||
)
|
||||
parser, _assembler = reader._build_parser()
|
||||
self.assertIsInstance(parser, LegacyBinaryParser24)
|
||||
self.assertEqual(reader._resolve_parser_mode_label(), "legacy_10byte_i24")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user