diff --git a/RFG_ADC_dataplotter.py b/RFG_ADC_dataplotter.py index 3a4c964..d10acc9 100755 --- a/RFG_ADC_dataplotter.py +++ b/RFG_ADC_dataplotter.py @@ -478,6 +478,15 @@ def main(): "Напр. 2,98. 'off' — отключить" ), ) + parser.add_argument( + "--spec-mean-sec", + type=float, + default=0.0, + help=( + "Вычитание среднего по каждой частоте за последние N секунд " + "в водопаде спектров (0 — отключить)" + ), + ) parser.add_argument("--title", default="ADC Sweeps", help="Заголовок окна") parser.add_argument( "--fancy", @@ -548,6 +557,7 @@ def main(): freq_shared: Optional[np.ndarray] = None # Параметры контраста водопада спектров spec_clip = _parse_spec_clip(getattr(args, "spec_clip", None)) + spec_mean_sec = float(getattr(args, "spec_mean_sec", 0.0)) # Ползунки управления Y для B-scan и контрастом ymin_slider = None ymax_slider = None @@ -780,6 +790,24 @@ def main(): base_t = ring_time if head == 0 else np.roll(ring_time, -head) return base_t + def _subtract_recent_mean_fft(disp_fft: np.ndarray) -> np.ndarray: + """Вычесть среднее по каждой частоте за последние spec_mean_sec секунд.""" + if spec_mean_sec <= 0.0: + return disp_fft + disp_times = make_display_times() + if disp_times is None: + return disp_fft + now_t = time.time() + mask = np.isfinite(disp_times) & (disp_times >= (now_t - spec_mean_sec)) + if not np.any(mask): + return disp_fft + try: + mean_spec = np.nanmean(disp_fft[:, mask], axis=1) + except Exception: + return disp_fft + mean_spec = np.nan_to_num(mean_spec, nan=0.0) + return disp_fft - mean_spec[:, None] + def make_display_ring_fft(): if ring_fft is None: return np.zeros((1, 1), dtype=np.float32) @@ -847,6 +875,7 @@ def main(): # Обновление водопада спектров if changed and ring_fft is not None: disp_fft = make_display_ring_fft() + disp_fft = _subtract_recent_mean_fft(disp_fft) # Новые данные справа: без реверса img_fft_obj.set_data(disp_fft) # Подписи времени не обновляем динамически (оставляем авто-тики) @@ -971,6 +1000,7 @@ def run_pyqtgraph(args): # Состояние ring: Optional[np.ndarray] = None + ring_time: Optional[np.ndarray] = None head = 0 width: Optional[int] = None x_shared: Optional[np.ndarray] = None @@ -984,6 +1014,7 @@ def run_pyqtgraph(args): y_min_fft, y_max_fft = None, None # Параметры контраста водопада спектров (процентильная обрезка) spec_clip = _parse_spec_clip(getattr(args, "spec_clip", None)) + spec_mean_sec = float(getattr(args, "spec_mean_sec", 0.0)) # Диапазон по Y: авто по умолчанию (поддерживает отрицательные значения) fixed_ylim: Optional[Tuple[float, float]] = None if args.ylim: @@ -996,12 +1027,13 @@ def run_pyqtgraph(args): p_line.setYRange(fixed_ylim[0], fixed_ylim[1], padding=0) def ensure_buffer(_w: int): - nonlocal ring, head, width, x_shared, ring_fft, freq_shared + nonlocal ring, ring_time, head, width, x_shared, ring_fft, freq_shared if ring is not None: return width = WF_WIDTH x_shared = np.arange(width, dtype=np.int32) ring = np.full((max_sweeps, width), np.nan, dtype=np.float32) + ring_time = np.full((max_sweeps,), np.nan, dtype=np.float64) head = 0 # Водопад: время по оси X, X по оси Y img.setImage(ring.T, autoLevels=False) @@ -1046,7 +1078,7 @@ def run_pyqtgraph(args): return (vmin, vmax) def push_sweep(s: np.ndarray): - nonlocal ring, head, ring_fft, y_min_fft, y_max_fft + nonlocal ring, ring_time, head, ring_fft, y_min_fft, y_max_fft if s is None or s.size == 0 or ring is None: return w = ring.shape[1] @@ -1054,6 +1086,8 @@ def run_pyqtgraph(args): take = min(w, s.size) row[:take] = s[:take] ring[head, :] = row + if ring_time is not None: + ring_time[head] = time.time() head = (head + 1) % ring.shape[0] # FFT строка (дБ) if ring_fft is not None: @@ -1152,6 +1186,18 @@ def run_pyqtgraph(args): if changed and ring_fft is not None: disp_fft = ring_fft if head == 0 else np.roll(ring_fft, -head, axis=0) disp_fft = disp_fft.T[:, ::-1] + if spec_mean_sec > 0.0 and ring_time is not None: + disp_times = ring_time if head == 0 else np.roll(ring_time, -head) + disp_times = disp_times[::-1] + now_t = time.time() + mask = np.isfinite(disp_times) & (disp_times >= (now_t - spec_mean_sec)) + if np.any(mask): + try: + mean_spec = np.nanmean(disp_fft[:, mask], axis=1) + mean_spec = np.nan_to_num(mean_spec, nan=0.0) + disp_fft = disp_fft - mean_spec[:, None] + except Exception: + pass # Автодиапазон по среднему спектру за видимый интервал (как в хорошей версии) levels = None try: