From 3ab9f7ad21e2349d2558d8718a93e47f0168d366 Mon Sep 17 00:00:00 2001 From: awe Date: Tue, 24 Mar 2026 15:18:08 +0300 Subject: [PATCH] checkbox log det raw --- rfg_adc_plotter/gui/pyqtgraph_backend.py | 111 ++++++++++++++++++++++- rfg_adc_plotter/state/runtime_state.py | 1 + tests/test_processing.py | 36 +++++++- 3 files changed, 145 insertions(+), 3 deletions(-) diff --git a/rfg_adc_plotter/gui/pyqtgraph_backend.py b/rfg_adc_plotter/gui/pyqtgraph_backend.py index fc6f35c..db94ae6 100644 --- a/rfg_adc_plotter/gui/pyqtgraph_backend.py +++ b/rfg_adc_plotter/gui/pyqtgraph_backend.py @@ -112,6 +112,64 @@ def apply_working_range( ) +def apply_working_range_to_aux_curves( + freqs: Optional[np.ndarray], + sweep: Optional[np.ndarray], + aux_curves: SweepAuxCurves, + range_min_ghz: float, + range_max_ghz: float, +) -> SweepAuxCurves: + """Crop parser-side auxiliary curves using the same mask as the raw sweep.""" + if freqs is None or sweep is None or aux_curves is None: + return None + + try: + aux_1, aux_2 = aux_curves + except Exception: + return None + + freq_arr = np.asarray(freqs, dtype=np.float64).reshape(-1) + sweep_arr = np.asarray(sweep, dtype=np.float32).reshape(-1) + aux_1_arr = np.asarray(aux_1, dtype=np.float32).reshape(-1) + aux_2_arr = np.asarray(aux_2, dtype=np.float32).reshape(-1) + width = min(freq_arr.size, sweep_arr.size, aux_1_arr.size, aux_2_arr.size) + if width <= 0: + return None + + freq_arr = freq_arr[:width] + sweep_arr = sweep_arr[:width] + aux_1_arr = aux_1_arr[:width] + aux_2_arr = aux_2_arr[:width] + valid = ( + np.isfinite(freq_arr) + & np.isfinite(sweep_arr) + & (freq_arr >= float(range_min_ghz)) + & (freq_arr <= float(range_max_ghz)) + ) + if not np.any(valid): + return None + + return ( + aux_1_arr[valid].astype(np.float32, copy=False), + aux_2_arr[valid].astype(np.float32, copy=False), + ) + + +def resolve_visible_aux_curves(aux_curves: SweepAuxCurves, enabled: bool) -> SweepAuxCurves: + """Return auxiliary curves only when their display is enabled.""" + if (not enabled) or aux_curves is None: + return None + try: + aux_1, aux_2 = aux_curves + except Exception: + return None + aux_1_arr = np.asarray(aux_1, dtype=np.float32).reshape(-1) + aux_2_arr = np.asarray(aux_2, dtype=np.float32).reshape(-1) + if aux_1_arr.size <= 0 or aux_2_arr.size <= 0: + return None + return aux_1_arr, aux_2_arr + + def run_pyqtgraph(args) -> None: """Start the PyQtGraph GUI.""" peak_calibrate_mode = bool(getattr(args, "calibrate", False)) @@ -192,6 +250,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_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_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", "ГГц") @@ -328,12 +388,14 @@ def run_pyqtgraph(args) -> None: background_buttons_row.addWidget(background_save_btn) background_buttons_row.addWidget(background_load_btn) background_group_layout.addLayout(background_buttons_row) + parsed_data_cb = QtWidgets.QCheckBox("данные после парсинга") try: settings_layout.addWidget(QtWidgets.QLabel("Настройки")) except Exception: pass settings_layout.addWidget(range_group) settings_layout.addWidget(calib_group) + settings_layout.addWidget(parsed_data_cb) settings_layout.addWidget(background_group) settings_layout.addWidget(fft_mode_label) settings_layout.addWidget(fft_mode_combo) @@ -343,6 +405,7 @@ def run_pyqtgraph(args) -> None: win.addItem(status, row=3, col=0, colspan=2) calib_enabled = False + parsed_data_enabled = False background_enabled = False fft_mode = "symmetric" status_note = "" @@ -489,6 +552,7 @@ def run_pyqtgraph(args) -> None: if runtime.full_current_freqs is None or runtime.full_current_sweep_raw is None: runtime.current_freqs = None runtime.current_sweep_raw = None + runtime.current_aux_curves = None runtime.current_sweep_norm = None runtime.current_fft_mag = None runtime.current_fft_db = None @@ -503,12 +567,20 @@ def run_pyqtgraph(args) -> None: ) runtime.current_freqs = current_freqs runtime.current_sweep_raw = current_sweep + runtime.current_aux_curves = apply_working_range_to_aux_curves( + runtime.full_current_freqs, + runtime.full_current_sweep_raw, + runtime.full_current_aux_curves, + runtime.range_min_ghz, + runtime.range_max_ghz, + ) if runtime.current_sweep_raw.size == 0: if push_to_ring: reset_ring_buffers() runtime.current_freqs = None runtime.current_sweep_raw = None + runtime.current_aux_curves = None runtime.current_sweep_norm = None runtime.current_fft_mag = None runtime.current_fft_db = None @@ -559,6 +631,14 @@ def run_pyqtgraph(args) -> None: recompute_current_processed_sweep(push_to_ring=False) runtime.mark_dirty() + def set_parsed_data_enabled() -> None: + nonlocal parsed_data_enabled + try: + parsed_data_enabled = bool(parsed_data_cb.isChecked()) + except Exception: + parsed_data_enabled = False + runtime.mark_dirty() + def restore_range_controls() -> None: nonlocal range_change_in_progress range_change_in_progress = True @@ -760,6 +840,7 @@ def run_pyqtgraph(args) -> None: except Exception: pass restore_range_controls() + set_parsed_data_enabled() set_background_enabled() set_fft_mode() @@ -767,6 +848,7 @@ def run_pyqtgraph(args) -> None: range_min_spin.valueChanged.connect(lambda _v: set_working_range()) range_max_spin.valueChanged.connect(lambda _v: set_working_range()) calib_cb.stateChanged.connect(lambda _v: set_calib_enabled()) + parsed_data_cb.stateChanged.connect(lambda _v: set_parsed_data_enabled()) calib_pick_btn.clicked.connect(lambda _checked=False: pick_calib_file()) calib_save_btn.clicked.connect(lambda _checked=False: save_current_calibration()) calib_load_btn.clicked.connect(lambda _checked=False: load_calibration_file()) @@ -953,15 +1035,28 @@ def run_pyqtgraph(args) -> None: except Empty: break drained += 1 + base_freqs = np.linspace(SWEEP_FREQ_MIN_GHZ, SWEEP_FREQ_MAX_GHZ, sweep.size, dtype=np.float64) calibrated = calibrate_freqs( { - "F": np.linspace(SWEEP_FREQ_MIN_GHZ, SWEEP_FREQ_MAX_GHZ, sweep.size, dtype=np.float64), + "F": base_freqs, "I": sweep, } ) runtime.full_current_freqs = np.asarray(calibrated["F"], dtype=np.float64) runtime.full_current_sweep_raw = np.asarray(calibrated["I"], dtype=np.float32) - runtime.current_aux_curves = aux_curves + if aux_curves is None: + runtime.full_current_aux_curves = None + else: + try: + aux_1, aux_2 = aux_curves + calibrated_aux_1 = calibrate_freqs({"F": base_freqs, "I": aux_1})["I"] + calibrated_aux_2 = calibrate_freqs({"F": base_freqs, "I": aux_2})["I"] + runtime.full_current_aux_curves = ( + np.asarray(calibrated_aux_1, dtype=np.float32), + np.asarray(calibrated_aux_2, dtype=np.float32), + ) + except Exception: + runtime.full_current_aux_curves = None runtime.current_info = info refresh_current_window(push_to_ring=True) if drained > 0: @@ -996,12 +1091,22 @@ def run_pyqtgraph(args) -> None: else (runtime.calib_envelope.size if runtime.calib_envelope is not None else 0) ) displayed_calib = None + displayed_aux = resolve_visible_aux_curves(runtime.current_aux_curves, parsed_data_enabled) if runtime.current_sweep_raw is not None: curve.setData(xs[: runtime.current_sweep_raw.size], runtime.current_sweep_raw, autoDownsample=True) else: curve.setData([], []) + if displayed_aux is not None: + aux_1, aux_2 = displayed_aux + aux_width = min(xs.size, aux_1.size, aux_2.size) + curve_aux_1.setData(xs[:aux_width], aux_1[:aux_width], autoDownsample=True) + curve_aux_2.setData(xs[:aux_width], aux_2[:aux_width], autoDownsample=True) + else: + curve_aux_1.setData([], []) + curve_aux_2.setData([], []) + if 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) @@ -1022,6 +1127,8 @@ def run_pyqtgraph(args) -> None: if fixed_ylim is None: y_series = [ runtime.current_sweep_raw, + displayed_aux[0] if displayed_aux is not None else None, + displayed_aux[1] if displayed_aux is not None else None, displayed_calib, (runtime.current_sweep_norm * norm_display_scale) if runtime.current_sweep_norm is not None else None, ] diff --git a/rfg_adc_plotter/state/runtime_state.py b/rfg_adc_plotter/state/runtime_state.py index 3b99a29..80f3e83 100644 --- a/rfg_adc_plotter/state/runtime_state.py +++ b/rfg_adc_plotter/state/runtime_state.py @@ -20,6 +20,7 @@ class RuntimeState: range_max_ghz: float = 0.0 full_current_freqs: Optional[np.ndarray] = None full_current_sweep_raw: Optional[np.ndarray] = None + full_current_aux_curves: SweepAuxCurves = None current_freqs: Optional[np.ndarray] = None current_distances: Optional[np.ndarray] = None current_sweep_raw: Optional[np.ndarray] = None diff --git a/tests/test_processing.py b/tests/test_processing.py index 9d8eb28..283aef4 100644 --- a/tests/test_processing.py +++ b/tests/test_processing.py @@ -6,7 +6,11 @@ import numpy as np import unittest from rfg_adc_plotter.constants import FFT_LEN, SWEEP_FREQ_MAX_GHZ, SWEEP_FREQ_MIN_GHZ -from rfg_adc_plotter.gui.pyqtgraph_backend import apply_working_range +from rfg_adc_plotter.gui.pyqtgraph_backend import ( + apply_working_range, + apply_working_range_to_aux_curves, + resolve_visible_aux_curves, +) from rfg_adc_plotter.processing.calibration import ( build_calib_envelope, calibrate_freqs, @@ -146,6 +150,36 @@ class ProcessingTests(unittest.TestCase): self.assertEqual(cropped_freqs.shape, (0,)) self.assertEqual(cropped_sweep.shape, (0,)) + def test_apply_working_range_to_aux_curves_uses_same_mask_as_raw_sweep(self): + freqs = np.linspace(3.3, 14.3, 6, dtype=np.float64) + sweep = np.asarray([0.0, 1.0, np.nan, 3.0, 4.0, 5.0], dtype=np.float32) + aux = ( + np.asarray([10.0, 11.0, 12.0, 13.0, 14.0, 15.0], dtype=np.float32), + np.asarray([20.0, 21.0, 22.0, 23.0, 24.0, 25.0], dtype=np.float32), + ) + + cropped_freqs, cropped_sweep = apply_working_range(freqs, sweep, 4.0, 12.5) + cropped_aux = apply_working_range_to_aux_curves(freqs, sweep, aux, 4.0, 12.5) + + self.assertIsNotNone(cropped_aux) + self.assertEqual(cropped_aux[0].shape, cropped_freqs.shape) + self.assertEqual(cropped_aux[1].shape, cropped_freqs.shape) + self.assertEqual(cropped_aux[0].shape, cropped_sweep.shape) + self.assertTrue(np.allclose(cropped_aux[0], np.asarray([11.0, 13.0, 14.0], dtype=np.float32))) + self.assertTrue(np.allclose(cropped_aux[1], np.asarray([21.0, 23.0, 24.0], dtype=np.float32))) + + def test_resolve_visible_aux_curves_obeys_checkbox_state(self): + aux = ( + np.asarray([1.0, 2.0], dtype=np.float32), + np.asarray([3.0, 4.0], dtype=np.float32), + ) + + self.assertIsNone(resolve_visible_aux_curves(aux, enabled=False)) + visible = resolve_visible_aux_curves(aux, enabled=True) + self.assertIsNotNone(visible) + self.assertTrue(np.allclose(visible[0], aux[0])) + self.assertTrue(np.allclose(visible[1], aux[1])) + def test_fft_helpers_return_expected_shapes(self): sweep = np.sin(np.linspace(0.0, 4.0 * np.pi, 128)).astype(np.float32) freqs = np.linspace(3.3, 14.3, 128, dtype=np.float64)