diff --git a/rfg_adc_plotter/gui/pyqtgraph_backend.py b/rfg_adc_plotter/gui/pyqtgraph_backend.py index f6b4336..bf28776 100644 --- a/rfg_adc_plotter/gui/pyqtgraph_backend.py +++ b/rfg_adc_plotter/gui/pyqtgraph_backend.py @@ -950,7 +950,7 @@ def run_pyqtgraph(args) -> None: p_line_aux_vb = pg.ViewBox() try: p_line.showAxis("right") - p_line.getAxis("right").setLabel("√(CH1²+CH2²), В") + p_line.getAxis("right").setLabel("CH1, CH2 (В)") p_line.scene().addItem(p_line_aux_vb) p_line.getAxis("right").linkToView(p_line_aux_vb) p_line_aux_vb.setXLink(p_line) @@ -960,36 +960,28 @@ def run_pyqtgraph(args) -> None: 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)) - curve_secondary_ch1 = pg.PlotDataItem(pen=pg.mkPen((0, 200, 100), width=1)) - curve_secondary_ch2 = pg.PlotDataItem(pen=pg.mkPen((200, 100, 200), 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) - p_line_aux_vb.addItem(curve_secondary_ch1) - p_line_aux_vb.addItem(curve_secondary_ch2) 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) - p_line.addItem(curve_secondary_ch1) - p_line.addItem(curve_secondary_ch2) 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_secondary_ch1 = p_line.plot(pen=pg.mkPen((0, 200, 100), width=1)) - curve_secondary_ch2 = p_line.plot(pen=pg.mkPen((200, 100, 200), 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", "ГГц") p_line.setLabel("left", "Y") if bin_iq_power_mode: try: - p_line.setLabel("left", "CH1²+CH2², В²") + p_line.setLabel("left", "CH1^2 + CH2^2, В^2") except Exception: pass ch_text = pg.TextItem("", anchor=(1, 1)) @@ -1024,6 +1016,52 @@ def run_pyqtgraph(args) -> None: except Exception: pass + # -- Secondary channel plots (visible only in a800 / bin_iq_power_mode) -- + p_secondary_ch = None + p_secondary_amp = None + p_secondary_phase = None + curve_sec_ch1 = None + curve_sec_ch2 = None + curve_sec_amp = None + curve_sec_phase = None + if bin_iq_power_mode: + p_secondary_ch = win.addPlot(row=3, col=0, title="CH1 / CH2 (В)") + p_secondary_ch.showGrid(x=True, y=True, alpha=0.3) + curve_sec_ch1 = p_secondary_ch.plot(pen=pg.mkPen((0, 200, 100), width=1)) + curve_sec_ch2 = p_secondary_ch.plot(pen=pg.mkPen((200, 100, 200), width=1)) + p_secondary_ch.setLabel("bottom", "ГГц") + p_secondary_ch.setLabel("left", "В") + p_secondary_ch.addLegend() + curve_sec_ch1.setData([], [], name="CH1") + curve_sec_ch2.setData([], [], name="CH2") + try: + p_secondary_ch.setXLink(p_line) + p_secondary_ch.setVisible(False) + except Exception: + pass + + p_secondary_amp = win.addPlot(row=3, col=1, title="√(CH1² + CH2²) (В)") + p_secondary_amp.showGrid(x=True, y=True, alpha=0.3) + curve_sec_amp = p_secondary_amp.plot(pen=pg.mkPen((0, 200, 200), width=1)) + p_secondary_amp.setLabel("bottom", "ГГц") + p_secondary_amp.setLabel("left", "В") + try: + p_secondary_amp.setXLink(p_line) + p_secondary_amp.setVisible(False) + except Exception: + pass + + p_secondary_phase = win.addPlot(row=4, col=0, title="Фаза atan2(CH2, CH1) (рад)") + p_secondary_phase.showGrid(x=True, y=True, alpha=0.3) + curve_sec_phase = p_secondary_phase.plot(pen=pg.mkPen((230, 180, 40), width=1)) + p_secondary_phase.setLabel("bottom", "ГГц") + p_secondary_phase.setLabel("left", "рад") + try: + p_secondary_phase.setXLink(p_line) + p_secondary_phase.setVisible(False) + except Exception: + pass + p_img = win.addPlot(row=0, col=1, title="Сырые данные водопад") p_img.invertY(False) p_img.showGrid(x=False, y=False) @@ -1239,7 +1277,7 @@ def run_pyqtgraph(args) -> None: parsed_data_cb = QtWidgets.QCheckBox("данные после парсинга") if complex_sweep_mode: try: - parsed_data_cb.setText("√(CH1²+CH2²) (В)" if bin_iq_power_mode else "Сырые Re/Im") + parsed_data_cb.setText("CH1/CH2 (В)" if bin_iq_power_mode else "Сырые Re/Im") parsed_data_cb.setChecked(False) except Exception: pass @@ -1721,15 +1759,15 @@ def run_pyqtgraph(args) -> None: 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²+CH2² (В²)") - p_line.setLabel("left", "CH1²+CH2², В²") + 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²) (В)") + parsed_data_cb.setText("DO1 tagged CH1/CH2 (В)") elif is_bin_iq: - p_line.setTitle("√(CH1²+CH2²) (В) и CH1²+CH2² (В²)") - p_line.setLabel("left", "CH1²+CH2², В²") + p_line.setTitle("CH1^2 + CH2^2 (В^2)") + p_line.setLabel("left", "CH1^2 + CH2^2, В^2") p_fft.setTitle("FFT: CH1 + i*CH2") - parsed_data_cb.setText("√(CH1²+CH2²) (В)") + parsed_data_cb.setText("CH1/CH2 (В)") elif complex_sweep_mode: p_line.setTitle("Сырые данные до FFT") p_line.setLabel("left", "Y") @@ -1900,6 +1938,8 @@ def run_pyqtgraph(args) -> None: runtime.current_do1_tagged_aux_high = None runtime.current_secondary_ch1 = None runtime.current_secondary_ch2 = None + runtime.current_secondary_magnitude = None + runtime.current_secondary_phase = None runtime.current_sweep_norm = None runtime.current_fft_mag = None runtime.current_fft_db = None @@ -1983,6 +2023,26 @@ def run_pyqtgraph(args) -> None: ) else: runtime.current_secondary_ch2 = None + if runtime.full_secondary_magnitude is not None: + runtime.current_secondary_magnitude = apply_working_range_to_signal( + runtime.full_current_freqs, + runtime.full_current_sweep_raw, + runtime.full_secondary_magnitude, + runtime.range_min_ghz, + runtime.range_max_ghz, + ) + else: + runtime.current_secondary_magnitude = None + if runtime.full_secondary_phase is not None: + runtime.current_secondary_phase = apply_working_range_to_signal( + runtime.full_current_freqs, + runtime.full_current_sweep_raw, + runtime.full_secondary_phase, + runtime.range_min_ghz, + runtime.range_max_ghz, + ) + else: + runtime.current_secondary_phase = None if runtime.current_sweep_raw.size == 0: if push_to_ring: @@ -1999,6 +2059,8 @@ def run_pyqtgraph(args) -> None: runtime.current_do1_tagged_aux_high = None runtime.current_secondary_ch1 = None runtime.current_secondary_ch2 = None + runtime.current_secondary_magnitude = None + runtime.current_secondary_phase = None runtime.current_sweep_norm = None runtime.current_fft_mag = None runtime.current_fft_db = None @@ -2758,6 +2820,8 @@ def run_pyqtgraph(args) -> None: runtime.full_do1_tagged_aux_high_codes = None runtime.full_secondary_ch1 = None runtime.full_secondary_ch2 = None + runtime.full_secondary_magnitude = None + runtime.full_secondary_phase = None signal_kind = get_signal_kind(info) if signal_kind == "bin_iq_do1_tagged": calibrated = calibrate_freqs( @@ -2885,24 +2949,24 @@ def run_pyqtgraph(args) -> None: if isinstance(secondary_payload, dict): sec_ch1 = secondary_payload.get("ch1") sec_ch2 = secondary_payload.get("ch2") - if sec_ch1 is not None and sec_ch2 is not None: - sec_ch1_calibrated = np.asarray( - calibrate_freqs({"F": base_freqs, "I": sec_ch1})["I"], - dtype=np.float32, - ) - sec_ch2_calibrated = np.asarray( - calibrate_freqs({"F": base_freqs, "I": sec_ch2})["I"], - dtype=np.float32, - ) - v_ch1 = convert_tty_i16_to_voltage(sec_ch1_calibrated, tty_range_v) - v_ch2 = convert_tty_i16_to_voltage(sec_ch2_calibrated, tty_range_v) - runtime.full_secondary_ch1 = np.sqrt(v_ch1 ** 2 + v_ch2 ** 2) - elif sec_ch1 is not None: + if sec_ch1 is not None: sec_ch1_calibrated = np.asarray( 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) + 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) + 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] + v2 = runtime.full_secondary_ch2[:w] + runtime.full_secondary_magnitude = np.sqrt(v1 ** 2 + v2 ** 2) + runtime.full_secondary_phase = np.arctan2(v2, v1) refresh_current_window(push_to_ring=True) processed_frames += 1 last_packet_processed_at = time.time() @@ -3057,20 +3121,46 @@ def run_pyqtgraph(args) -> None: clear_curve_if_needed("raw_low", curve_raw_low) clear_curve_if_needed("raw_high", curve_raw_high) - if runtime.current_secondary_ch1 is not None: + # -- Secondary channel plots (separate graphs) -- + has_sec = runtime.current_secondary_ch1 is not None or runtime.current_secondary_ch2 is not None + if p_secondary_ch is not None: + set_item_visible_if_changed("p_secondary_ch", p_secondary_ch, has_sec) + if p_secondary_amp is not None: + set_item_visible_if_changed("p_secondary_amp", p_secondary_amp, runtime.current_secondary_magnitude is not None) + if p_secondary_phase is not None: + set_item_visible_if_changed("p_secondary_phase", p_secondary_phase, runtime.current_secondary_phase is not None) + + if curve_sec_ch1 is not None and runtime.current_secondary_ch1 is not None: sec_width = min(xs.size, runtime.current_secondary_ch1.size) sec_x1, sec_y1 = decimate_curve_for_display(xs[:sec_width], runtime.current_secondary_ch1[:sec_width]) sec_x1, sec_y1 = sanitize_curve_data_for_display(sec_x1, sec_y1) - set_curve_data("secondary_ch1", curve_secondary_ch1, sec_x1, sec_y1, autoDownsample=False) - else: - clear_curve_if_needed("secondary_ch1", curve_secondary_ch1) - if runtime.current_secondary_ch2 is not None: + set_curve_data("sec_ch1", curve_sec_ch1, sec_x1, sec_y1, autoDownsample=False) + elif curve_sec_ch1 is not None: + clear_curve_if_needed("sec_ch1", curve_sec_ch1) + + if curve_sec_ch2 is not None and runtime.current_secondary_ch2 is not None: sec_width2 = min(xs.size, runtime.current_secondary_ch2.size) sec_x2, sec_y2 = decimate_curve_for_display(xs[:sec_width2], runtime.current_secondary_ch2[:sec_width2]) sec_x2, sec_y2 = sanitize_curve_data_for_display(sec_x2, sec_y2) - set_curve_data("secondary_ch2", curve_secondary_ch2, sec_x2, sec_y2, autoDownsample=False) - else: - clear_curve_if_needed("secondary_ch2", curve_secondary_ch2) + set_curve_data("sec_ch2", curve_sec_ch2, sec_x2, sec_y2, autoDownsample=False) + elif curve_sec_ch2 is not None: + clear_curve_if_needed("sec_ch2", curve_sec_ch2) + + if curve_sec_amp is not None and runtime.current_secondary_magnitude is not None: + amp_w = min(xs.size, runtime.current_secondary_magnitude.size) + amp_x, amp_y = decimate_curve_for_display(xs[:amp_w], runtime.current_secondary_magnitude[:amp_w]) + amp_x, amp_y = sanitize_curve_data_for_display(amp_x, amp_y) + set_curve_data("sec_amp", curve_sec_amp, amp_x, amp_y, autoDownsample=False) + elif curve_sec_amp is not None: + clear_curve_if_needed("sec_amp", curve_sec_amp) + + if curve_sec_phase is not None and runtime.current_secondary_phase is not None: + ph_w = min(xs.size, runtime.current_secondary_phase.size) + ph_x, ph_y = decimate_curve_for_display(xs[:ph_w], runtime.current_secondary_phase[:ph_w]) + ph_x, ph_y = sanitize_curve_data_for_display(ph_x, ph_y) + set_curve_data("sec_phase", curve_sec_phase, ph_x, ph_y, autoDownsample=False) + elif curve_sec_phase is not None: + clear_curve_if_needed("sec_phase", curve_sec_phase) if active_do1_tagged: if displayed_tagged_aux_low is not None: diff --git a/rfg_adc_plotter/state/runtime_state.py b/rfg_adc_plotter/state/runtime_state.py index d38d4f6..1abea97 100644 --- a/rfg_adc_plotter/state/runtime_state.py +++ b/rfg_adc_plotter/state/runtime_state.py @@ -32,6 +32,8 @@ class RuntimeState: full_do1_tagged_aux_high_codes: SweepAuxCurves = None full_secondary_ch1: Optional[np.ndarray] = None full_secondary_ch2: Optional[np.ndarray] = None + full_secondary_magnitude: Optional[np.ndarray] = None + full_secondary_phase: Optional[np.ndarray] = None current_freqs: Optional[np.ndarray] = None current_distances: Optional[np.ndarray] = None current_sweep_raw: Optional[np.ndarray] = None @@ -45,6 +47,8 @@ class RuntimeState: current_do1_tagged_aux_high: SweepAuxCurves = None current_secondary_ch1: Optional[np.ndarray] = None current_secondary_ch2: Optional[np.ndarray] = None + current_secondary_magnitude: Optional[np.ndarray] = None + current_secondary_phase: Optional[np.ndarray] = None current_sweep_norm: Optional[np.ndarray] = None current_fft_mag: Optional[np.ndarray] = None current_fft_db: Optional[np.ndarray] = None