diff --git a/RFG_ADC_dataplotter.py b/RFG_ADC_dataplotter.py index 184cf37..d9c286d 100755 --- a/RFG_ADC_dataplotter.py +++ b/RFG_ADC_dataplotter.py @@ -28,7 +28,7 @@ import threading import time from collections import deque from queue import Queue, Empty, Full -from typing import Any, Dict, Mapping, Optional, Tuple, Union +from typing import Any, Dict, List, Mapping, Optional, Tuple, Union import numpy as np @@ -368,6 +368,141 @@ def _find_peak_width_markers(xs: np.ndarray, ys: np.ndarray) -> Optional[Dict[st } +def _rolling_median_ref(xs: np.ndarray, ys: np.ndarray, window_ghz: float) -> np.ndarray: + """Скользящая медиана по оси частоты/расстояния в окне фиксированной ширины.""" + x = np.asarray(xs, dtype=np.float64) + y = np.asarray(ys, dtype=np.float64) + out = np.full(y.shape, np.nan, dtype=np.float64) + if x.size == 0 or y.size == 0 or x.size != y.size: + return out + w = float(window_ghz) + if not np.isfinite(w) or w <= 0.0: + return out + half = 0.5 * w + for i in range(x.size): + xi = x[i] + if not np.isfinite(xi): + continue + left = np.searchsorted(x, xi - half, side="left") + right = np.searchsorted(x, xi + half, side="right") + if right <= left: + continue + seg = y[left:right] + finite = np.isfinite(seg) + if not np.any(finite): + continue + out[i] = float(np.nanmedian(seg)) + return out + + +def _find_top_peaks_over_ref( + xs: np.ndarray, + ys: np.ndarray, + ref: np.ndarray, + top_n: int = 3, +) -> List[Dict[str, float]]: + """Найти top-N пиков по превышению над референсной кривой.""" + x = np.asarray(xs, dtype=np.float64) + y = np.asarray(ys, dtype=np.float64) + r = np.asarray(ref, dtype=np.float64) + if x.size < 3 or y.size != x.size or r.size != x.size: + return [] + + valid = np.isfinite(x) & np.isfinite(y) & np.isfinite(r) + if not np.any(valid): + return [] + d = np.full_like(y, np.nan, dtype=np.float64) + d[valid] = y[valid] - r[valid] + + cand: List[int] = [] + for i in range(1, x.size - 1): + if not (np.isfinite(d[i - 1]) and np.isfinite(d[i]) and np.isfinite(d[i + 1])): + continue + if d[i] <= 0.0: + continue + left_ok = d[i] > d[i - 1] + right_ok = d[i] >= d[i + 1] + alt_left_ok = d[i] >= d[i - 1] + alt_right_ok = d[i] > d[i + 1] + if (left_ok and right_ok) or (alt_left_ok and alt_right_ok): + cand.append(i) + if not cand: + return [] + + cand.sort(key=lambda i: float(d[i]), reverse=True) + + def _interp_cross(x0: float, y0: float, x1: float, y1: float, y_cross: float) -> float: + dy = y1 - y0 + if not np.isfinite(dy) or dy == 0.0: + return x1 + t = (y_cross - y0) / dy + t = min(1.0, max(0.0, t)) + return x0 + t * (x1 - x0) + + picked: List[Dict[str, float]] = [] + for idx in cand: + peak_y = float(y[idx]) + peak_ref = float(r[idx]) + peak_h = float(d[idx]) + if not (np.isfinite(peak_y) and np.isfinite(peak_ref) and np.isfinite(peak_h)) or peak_h <= 0.0: + continue + + half_level = peak_ref + 0.5 * peak_h + + left_x = float(x[0]) + found_left = False + for i in range(idx, 0, -1): + y0 = float(y[i - 1]) + y1 = float(y[i]) + if np.isfinite(y0) and np.isfinite(y1) and (y0 <= half_level <= y1): + left_x = _interp_cross(float(x[i - 1]), y0, float(x[i]), y1, half_level) + found_left = True + break + if not found_left: + left_x = float(x[0]) + + right_x = float(x[-1]) + found_right = False + for i in range(idx, x.size - 1): + y0 = float(y[i]) + y1 = float(y[i + 1]) + if np.isfinite(y0) and np.isfinite(y1) and (y0 >= half_level >= y1): + right_x = _interp_cross(float(x[i]), y0, float(x[i + 1]), y1, half_level) + found_right = True + break + if not found_right: + right_x = float(x[-1]) + + width = float(right_x - left_x) + if not np.isfinite(width) or width <= 0.0: + continue + + overlap = False + for p in picked: + if not (right_x <= p["left"] or left_x >= p["right"]): + overlap = True + break + if overlap: + continue + + picked.append( + { + "x": float(x[idx]), + "peak_y": peak_y, + "ref": peak_ref, + "height": peak_h, + "left": left_x, + "right": right_x, + "width": width, + } + ) + if len(picked) >= int(max(1, top_n)): + break + + picked.sort(key=lambda p: p["x"]) + return picked + + def _normalize_sweep_simple(raw: np.ndarray, calib: np.ndarray) -> np.ndarray: """Простая нормировка: поэлементное деление raw/calib.""" w = min(raw.size, calib.size) @@ -1477,6 +1612,20 @@ def main(): "границ и фона и выводит ширину пика в статус" ), ) + parser.add_argument( + "--peak_search", + action="store_true", + help=( + "Поиск топ-3 пиков на FFT относительно референса (скользящая медиана) " + "с отрисовкой bounding box и параметров пиков" + ), + ) + parser.add_argument( + "--peak_ref_window", + type=float, + default=1.0, + help="Ширина окна скользящей медианы для --peak_search, ГГц/м по оси FFT (по умолчанию 1.0)", + ) args = parser.parse_args() @@ -1557,12 +1706,19 @@ def main(): calib_enabled = False bg_subtract_enabled = False peak_calibrate_mode = bool(getattr(args, "calibrate", False)) + peak_search_enabled = bool(getattr(args, "peak_search", False)) + peak_ref_window = float(getattr(args, "peak_ref_window", 1.0)) + if (not np.isfinite(peak_ref_window)) or peak_ref_window <= 0.0: + peak_ref_window = 1.0 current_peak_width: Optional[float] = None current_peak_amplitude: Optional[float] = None + peak_candidates: List[Dict[str, float]] = [] norm_type = str(getattr(args, "norm_type", "projector")).strip().lower() cb = None c_boxes = [] + peak_textboxes = [] c_values_text = None + peak_values_text = None # Статусная строка (внизу окна) status_text = fig.text( @@ -1597,6 +1753,8 @@ def main(): # Линейный график спектра текущего свипа fft_line_obj, = ax_fft.plot([], [], lw=1) + fft_ref_obj, = ax_fft.plot([], [], lw=1, color="red", alpha=0.9, visible=False) + fft_peak_box_objs = [ax_fft.plot([], [], lw=1, color="red", visible=False)[0] for _ in range(3)] fft_bg_obj = ax_fft.axhline(0.0, lw=1, color="red", visible=False) fft_left_obj = ax_fft.axvline(0.0, lw=1, color="red", visible=False) fft_right_obj = ax_fft.axvline(0.0, lw=1, color="red", visible=False) @@ -1656,14 +1814,16 @@ def main(): return _normalize_by_calib(raw, calib, norm_type=norm_type) def _sync_checkbox_states(): - nonlocal calib_enabled, bg_subtract_enabled, current_sweep_norm + nonlocal calib_enabled, bg_subtract_enabled, peak_search_enabled, current_sweep_norm try: states = cb.get_status() if cb is not None else () calib_enabled = bool(states[0]) if len(states) > 0 else False bg_subtract_enabled = bool(states[1]) if len(states) > 1 else False + peak_search_enabled = bool(states[2]) if len(states) > 2 else False except Exception: calib_enabled = False bg_subtract_enabled = False + peak_search_enabled = False if calib_enabled and current_sweep_raw is not None and last_calib_sweep is not None: current_sweep_norm = _normalize_sweep(current_sweep_raw, last_calib_sweep) else: @@ -1674,11 +1834,15 @@ def main(): ax_smin = fig.add_axes([0.92, 0.55, 0.02, 0.35]) ax_smax = fig.add_axes([0.95, 0.55, 0.02, 0.35]) ax_sctr = fig.add_axes([0.98, 0.55, 0.02, 0.35]) - ax_cb = fig.add_axes([0.90, 0.40, 0.10, 0.14]) + ax_cb = fig.add_axes([0.90, 0.37, 0.10, 0.17]) ymin_slider = Slider(ax_smin, "R min", 0.0, 1.0, valinit=0.0, orientation="vertical") ymax_slider = Slider(ax_smax, "R max", 0.0, 1.0, valinit=1.0, orientation="vertical") contrast_slider = Slider(ax_sctr, "Int max", 0, 100, valinit=100, valstep=1, orientation="vertical") - cb = CheckButtons(ax_cb, ["нормировка", "вычет фона"], [False, False]) + cb = CheckButtons( + ax_cb, + ["нормировка", "вычет фона", "поиск пиков"], + [False, False, peak_search_enabled], + ) def _on_ylim_change(_val): try: @@ -1694,6 +1858,7 @@ def main(): # Контраст влияет на верхнюю границу цветовой шкалы (процент от авто-диапазона) contrast_slider.on_changed(lambda _v: fig.canvas.draw_idle()) cb.on_clicked(lambda _v: _sync_checkbox_states()) + _sync_checkbox_states() except Exception: pass @@ -1741,6 +1906,56 @@ def main(): except Exception: pass + def _refresh_peak_values_text(peaks: List[Dict[str, float]]): + if peak_values_text is None: + return + lines = [] + for idx in range(3): + if idx < len(peaks): + p = peaks[idx] + lines.append(f"peak {idx + 1}:") + lines.append(f" X: {p['x']:.4g} m") + lines.append(f" H: {p['height']:.4g} dB") + lines.append(f" W: {p['width']:.4g} m") + else: + lines.append(f"peak {idx + 1}:") + lines.append(" X: - m") + lines.append(" H: - dB") + lines.append(" W: - m") + if idx != 2: + lines.append("") + peak_values_text.set_text("\n".join(lines)) + + try: + def _set_peak_ref_window(text: str): + nonlocal peak_ref_window + try: + v = float(text.strip()) + if np.isfinite(v) and v > 0.0: + peak_ref_window = v + except Exception: + pass + + ax_peak_win = fig.add_axes([0.90, 0.20, 0.10, 0.045]) + peak_window_tb = TextBox(ax_peak_win, "med,GHz", initial=f"{peak_ref_window:.6g}") + peak_window_tb.on_submit(_set_peak_ref_window) + peak_textboxes.append(peak_window_tb) + ax_peak_info = fig.add_axes([0.90, 0.01, 0.10, 0.19]) + ax_peak_info.axis("off") + peak_values_text = ax_peak_info.text( + 0.0, + 1.0, + "", + ha="left", + va="top", + fontsize=6, + family="monospace", + transform=ax_peak_info.transAxes, + ) + _refresh_peak_values_text([]) + except Exception: + pass + # Для контроля частоты обновления max_fps = max(1.0, float(args.max_fps)) interval_ms = int(1000.0 / max_fps) @@ -1855,7 +2070,8 @@ def main(): if ring_fft is not None: bins = ring_fft.shape[1] fft_mag = _compute_fft_mag_row(s, freqs, bins) - ring_fft[(head - 1) % ring_fft.shape[0], :] = fft_mag + row_idx = (head - 1) % ring_fft.shape[0] + ring_fft[row_idx, :] = fft_mag fft_row = _fft_mag_to_db(fft_mag) # Экстремумы для цветовой шкалы fr_min = np.nanmin(fft_row) @@ -1973,8 +2189,8 @@ def main(): return base.T # (bins, time) def update(_frame): - nonlocal frames_since_ylim_update, current_peak_width, current_peak_amplitude - if peak_calibrate_mode and any(getattr(tb, "capturekeystrokes", False) for tb in c_boxes): + nonlocal frames_since_ylim_update, current_peak_width, current_peak_amplitude, peak_candidates + if any(getattr(tb, "capturekeystrokes", False) for tb in (c_boxes + peak_textboxes)): return ( line_obj, line_avg1_obj, @@ -1983,6 +2199,8 @@ def main(): line_norm_obj, img_obj, fft_line_obj, + fft_ref_obj, + *fft_peak_box_objs, fft_bg_obj, fft_left_obj, fft_right_obj, @@ -2039,15 +2257,53 @@ def main(): xs_fft = current_distances if current_distances is not None else distance_shared if fft_vals.size > xs_fft.size: fft_vals = fft_vals[: xs_fft.size] - fft_line_obj.set_data(xs_fft[: fft_vals.size], fft_vals) + xs_fft = xs_fft[: fft_vals.size] + fft_line_obj.set_data(xs_fft, fft_vals) + + finite_fft = np.isfinite(xs_fft) & np.isfinite(fft_vals) + y_for_range = fft_vals[finite_fft] + if peak_search_enabled: + fft_ref = _rolling_median_ref(xs_fft, fft_vals, peak_ref_window) + finite_ref = np.isfinite(xs_fft) & np.isfinite(fft_ref) + if np.any(finite_ref): + fft_ref_obj.set_data(xs_fft[finite_ref], fft_ref[finite_ref]) + fft_ref_obj.set_visible(True) + y_for_range = np.concatenate((y_for_range, fft_ref[finite_ref])) + else: + fft_ref_obj.set_visible(False) + peak_candidates = _find_top_peaks_over_ref(xs_fft, fft_vals, fft_ref, top_n=3) + _refresh_peak_values_text(peak_candidates) + for idx, box_obj in enumerate(fft_peak_box_objs): + if idx < len(peak_candidates): + p = peak_candidates[idx] + box_obj.set_data( + [p["left"], p["left"], p["right"], p["right"], p["left"]], + [p["ref"], p["peak_y"], p["peak_y"], p["ref"], p["ref"]], + ) + box_obj.set_visible(True) + else: + box_obj.set_visible(False) + else: + peak_candidates = [] + fft_ref_obj.set_visible(False) + _refresh_peak_values_text([]) + for box_obj in fft_peak_box_objs: + box_obj.set_visible(False) + # Авто-диапазон по Y для спектра - if np.isfinite(np.nanmin(fft_vals)) and np.isfinite(np.nanmax(fft_vals)): - finite_x = xs_fft[: fft_vals.size][np.isfinite(xs_fft[: fft_vals.size])] + if np.any(finite_fft): + finite_x = xs_fft[finite_fft] if finite_x.size > 0: ax_fft.set_xlim(float(np.min(finite_x)), float(np.max(finite_x))) - ax_fft.set_ylim(float(np.nanmin(fft_vals)), float(np.nanmax(fft_vals))) + finite_y = y_for_range[np.isfinite(y_for_range)] + if finite_y.size > 0: + y0 = float(np.min(finite_y)) + y1 = float(np.max(finite_y)) + if y1 <= y0: + y1 = y0 + 1e-3 + ax_fft.set_ylim(y0, y1) if peak_calibrate_mode: - markers = _find_peak_width_markers(xs_fft[: fft_vals.size], fft_vals) + markers = _find_peak_width_markers(xs_fft, fft_vals) if markers is not None: fft_bg_obj.set_ydata([markers["background"], markers["background"]]) fft_left_obj.set_xdata([markers["left"], markers["left"]]) @@ -2077,6 +2333,12 @@ def main(): spec_right_obj.set_visible(False) current_peak_width = None current_peak_amplitude = None + else: + fft_ref_obj.set_visible(False) + for box_obj in fft_peak_box_objs: + box_obj.set_visible(False) + peak_candidates = [] + _refresh_peak_values_text([]) # Обновление водопада if changed and ring is not None: @@ -2179,6 +2441,8 @@ def main(): line_norm_obj, img_obj, fft_line_obj, + fft_ref_obj, + *fft_peak_box_objs, fft_bg_obj, fft_left_obj, fft_right_obj, @@ -2231,6 +2495,7 @@ def main(): def run_pyqtgraph(args): """Быстрый GUI на PyQtGraph. Требует pyqtgraph и PyQt5/PySide6.""" peak_calibrate_mode = bool(getattr(args, "calibrate", False)) + peak_search_enabled = bool(getattr(args, "peak_search", False)) try: import pyqtgraph as pg from pyqtgraph.Qt import QtCore, QtWidgets # type: ignore @@ -2292,7 +2557,8 @@ def run_pyqtgraph(args): settings_layout.setContentsMargins(6, 6, 6, 6) settings_layout.setSpacing(8) try: - settings_widget.setMinimumWidth(170) + settings_widget.setMinimumWidth(130) + settings_widget.setMaximumWidth(150) except Exception: pass main_layout.addWidget(settings_widget) @@ -2328,10 +2594,15 @@ def run_pyqtgraph(args): p_fft = win.addPlot(row=1, col=0, title="FFT") p_fft.showGrid(x=True, y=True, alpha=0.3) curve_fft = p_fft.plot(pen=pg.mkPen((255, 120, 80), width=1)) + curve_fft_ref = p_fft.plot(pen=pg.mkPen((255, 0, 0), width=1)) peak_pen = pg.mkPen((255, 0, 0), width=1) + fft_peak_boxes = [p_fft.plot(pen=peak_pen) for _ in range(3)] fft_bg_line = pg.InfiniteLine(angle=0, movable=False, pen=peak_pen) fft_left_line = pg.InfiniteLine(angle=90, movable=False, pen=peak_pen) fft_right_line = pg.InfiniteLine(angle=90, movable=False, pen=peak_pen) + curve_fft_ref.setVisible(False) + for box in fft_peak_boxes: + box.setVisible(False) p_fft.addItem(fft_bg_line) p_fft.addItem(fft_left_line) p_fft.addItem(fft_right_line) @@ -2363,6 +2634,7 @@ def run_pyqtgraph(args): # Правая панель настроек внутри основного окна. calib_cb = QtWidgets.QCheckBox("нормировка") bg_subtract_cb = QtWidgets.QCheckBox("вычет фона") + peak_search_cb = QtWidgets.QCheckBox("поиск пиков") try: settings_title = QtWidgets.QLabel("Настройки") settings_layout.addWidget(settings_title) @@ -2370,6 +2642,7 @@ def run_pyqtgraph(args): pass settings_layout.addWidget(calib_cb) settings_layout.addWidget(bg_subtract_cb) + settings_layout.addWidget(peak_search_cb) calib_window = None # Статусная строка (внизу окна) @@ -2403,9 +2676,16 @@ def run_pyqtgraph(args): bg_subtract_enabled = False current_peak_width: Optional[float] = None current_peak_amplitude: Optional[float] = None + peak_candidates: List[Dict[str, float]] = [] norm_type = str(getattr(args, "norm_type", "projector")).strip().lower() c_edits = [] c_value_labels = [] + peak_group = None + peak_window_edit = None + peak_params_label = None + peak_ref_window = float(getattr(args, "peak_ref_window", 1.0)) + if (not np.isfinite(peak_ref_window)) or peak_ref_window <= 0.0: + peak_ref_window = 1.0 plot_dirty = False # Диапазон по Y: авто по умолчанию (поддерживает отрицательные значения) fixed_ylim: Optional[Tuple[float, float]] = None @@ -2450,6 +2730,90 @@ def run_pyqtgraph(args): except Exception: pass + def _refresh_peak_params_label(peaks: List[Dict[str, float]]): + if peak_params_label is None: + return + lines = [] + for idx in range(3): + if idx < len(peaks): + p = peaks[idx] + lines.append(f"peak {idx + 1}:") + lines.append(f" X: {p['x']:.4g} m") + lines.append(f" H: {p['height']:.4g} dB") + lines.append(f" W: {p['width']:.4g} m") + else: + lines.append(f"peak {idx + 1}:") + lines.append(" X: - m") + lines.append(" H: - dB") + lines.append(" W: - m") + if idx != 2: + lines.append("") + peak_params_label.setText("\n".join(lines)) + + try: + peak_group = QtWidgets.QGroupBox("Поиск пиков") + peak_layout = QtWidgets.QFormLayout(peak_group) + peak_layout.setContentsMargins(6, 6, 6, 6) + peak_layout.setSpacing(6) + peak_window_edit = QtWidgets.QLineEdit(f"{peak_ref_window:.6g}") + peak_layout.addRow("Окно медианы, ГГц", peak_window_edit) + peak_params_label = QtWidgets.QLabel("") + try: + peak_params_label.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse) + except Exception: + pass + peak_layout.addRow("Параметры", peak_params_label) + settings_layout.addWidget(peak_group) + + def _apply_peak_window(): + nonlocal peak_ref_window, plot_dirty + if peak_window_edit is None: + return + try: + v = float(peak_window_edit.text().strip()) + if np.isfinite(v) and v > 0.0: + peak_ref_window = v + plot_dirty = True + except Exception: + pass + try: + peak_window_edit.setText(f"{peak_ref_window:.6g}") + except Exception: + pass + + peak_window_edit.editingFinished.connect(_apply_peak_window) + _refresh_peak_params_label([]) + except Exception: + peak_group = None + peak_window_edit = None + peak_params_label = None + + def _set_peak_search_enabled(): + nonlocal peak_search_enabled, plot_dirty, peak_candidates + try: + peak_search_enabled = bool(peak_search_cb.isChecked()) + except Exception: + peak_search_enabled = False + try: + if peak_group is not None: + peak_group.setEnabled(peak_search_enabled) + except Exception: + pass + if not peak_search_enabled: + peak_candidates = [] + _refresh_peak_params_label([]) + plot_dirty = True + + try: + peak_search_cb.setChecked(peak_search_enabled) + except Exception: + pass + try: + peak_search_cb.stateChanged.connect(lambda _v: _set_peak_search_enabled()) + except Exception: + pass + _set_peak_search_enabled() + if peak_calibrate_mode: try: calib_window = QtWidgets.QWidget() @@ -2638,7 +3002,8 @@ def run_pyqtgraph(args): if ring_fft is not None: bins = ring_fft.shape[1] fft_mag = _compute_fft_mag_row(s, freqs, bins) - ring_fft[(head - 1) % ring_fft.shape[0], :] = fft_mag + row_idx = (head - 1) % ring_fft.shape[0] + ring_fft[row_idx, :] = fft_mag fft_row = _fft_mag_to_db(fft_mag) current_fft_db = fft_row fr_min = np.nanmin(fft_row) @@ -2703,9 +3068,11 @@ def run_pyqtgraph(args): pass def update(): - nonlocal current_peak_width, current_peak_amplitude, plot_dirty, current_fft_db + nonlocal current_peak_width, current_peak_amplitude, plot_dirty, current_fft_db, peak_candidates if peak_calibrate_mode and any(edit.hasFocus() for edit in c_edits): return + if peak_search_enabled and peak_window_edit is not None and peak_window_edit.hasFocus(): + return changed = drain_queue() > 0 redraw_needed = changed or plot_dirty if redraw_needed and current_sweep_raw is not None and x_shared is not None: @@ -2756,13 +3123,51 @@ def run_pyqtgraph(args): xs_fft = current_distances if current_distances is not None else distance_shared if fft_vals.size > xs_fft.size: fft_vals = fft_vals[: xs_fft.size] - curve_fft.setData(xs_fft[: fft_vals.size], fft_vals) - finite_x = xs_fft[: fft_vals.size][np.isfinite(xs_fft[: fft_vals.size])] + xs_fft = xs_fft[: fft_vals.size] + curve_fft.setData(xs_fft, fft_vals) + finite_x = xs_fft[np.isfinite(xs_fft)] if finite_x.size > 0: p_fft.setXRange(float(np.min(finite_x)), float(np.max(finite_x)), padding=0) - p_fft.setYRange(float(np.nanmin(fft_vals)), float(np.nanmax(fft_vals)), padding=0) + + finite_fft = np.isfinite(xs_fft) & np.isfinite(fft_vals) + y_for_range = fft_vals[finite_fft] + if peak_search_enabled: + fft_ref = _rolling_median_ref(xs_fft, fft_vals, peak_ref_window) + finite_ref = np.isfinite(xs_fft) & np.isfinite(fft_ref) + if np.any(finite_ref): + curve_fft_ref.setData(xs_fft[finite_ref], fft_ref[finite_ref]) + curve_fft_ref.setVisible(True) + y_for_range = np.concatenate((y_for_range, fft_ref[finite_ref])) + else: + curve_fft_ref.setVisible(False) + peak_candidates = _find_top_peaks_over_ref(xs_fft, fft_vals, fft_ref, top_n=3) + _refresh_peak_params_label(peak_candidates) + for idx, box in enumerate(fft_peak_boxes): + if idx < len(peak_candidates): + p = peak_candidates[idx] + box.setData( + [p["left"], p["left"], p["right"], p["right"], p["left"]], + [p["ref"], p["peak_y"], p["peak_y"], p["ref"], p["ref"]], + ) + box.setVisible(True) + else: + box.setVisible(False) + else: + peak_candidates = [] + _refresh_peak_params_label([]) + curve_fft_ref.setVisible(False) + for box in fft_peak_boxes: + box.setVisible(False) + + finite_y = y_for_range[np.isfinite(y_for_range)] + if finite_y.size > 0: + y0 = float(np.min(finite_y)) + y1 = float(np.max(finite_y)) + if y1 <= y0: + y1 = y0 + 1e-3 + p_fft.setYRange(y0, y1, padding=0) if peak_calibrate_mode: - markers = _find_peak_width_markers(xs_fft[: fft_vals.size], fft_vals) + markers = _find_peak_width_markers(xs_fft, fft_vals) if markers is not None: fft_bg_line.setValue(markers["background"]) fft_left_line.setValue(markers["left"]) @@ -2792,6 +3197,12 @@ def run_pyqtgraph(args): spec_right_line.setVisible(False) current_peak_width = None current_peak_amplitude = None + else: + curve_fft_ref.setVisible(False) + for box in fft_peak_boxes: + box.setVisible(False) + peak_candidates = [] + _refresh_peak_params_label([]) plot_dirty = False if changed and ring is not None: