Добавлено вычитание среднего спектра за последние N секунд в водопаде и параметр CLI --spec-mean-sec (float, по умолчанию 0.0)

для управления этим.
This commit is contained in:
2026-02-09 16:38:45 +03:00
parent 42d4400c99
commit 877a8a6cd0

View File

@ -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: