From 696887b4c7144865843b8925ddcd61ae8c86c119 Mon Sep 17 00:00:00 2001 From: Theodor Chikin Date: Thu, 20 Nov 2025 23:00:51 +0300 Subject: [PATCH] =?UTF-8?q?implemented=20averagignd=20and=20subtracting=20?= =?UTF-8?q?avg=20from=20B-scan.=20Control=20byBG=5FAVG=5FWINDOW=5FSEC=20?= =?UTF-8?q?=3D=205.0=20BG=5FSUBTRACT=5FALPHA=20=3D=201.0=20BG=5FSUBTRACT?= =?UTF-8?q?=5FENABLED=20=3D=20True=20#=20=D0=9C=D0=B5=D1=82=D0=BE=D0=B4=20?= =?UTF-8?q?=D1=80=D0=B0=D1=81=D1=87=D1=91=D1=82=D0=B0=20=D1=84=D0=BE=D0=BD?= =?UTF-8?q?=D0=B0:=20'median'=20=D0=B8=D0=BB=D0=B8=20'mean'=20BG=5FBASELIN?= =?UTF-8?q?E=5FMETHOD=20=3D=20'median'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 89 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 88 insertions(+), 1 deletion(-) diff --git a/main.py b/main.py index ec55bdb..1789123 100755 --- a/main.py +++ b/main.py @@ -14,6 +14,7 @@ from datetime import datetime, timedelta import threading import queue from sys import argv +from collections import deque # ================================================================================ # ПАРАМЕТРЫ И КОНСТАНТЫ @@ -45,6 +46,13 @@ FILES_STORED_N_MAX = 100 # Минимально допустимое число точек F0 для принятия данных MIN_F0_POINTS = 100 +# Усреднение B-scan по времени (фон) +BG_AVG_WINDOW_SEC = 5.0 +BG_SUBTRACT_ALPHA = 1.0 +BG_SUBTRACT_ENABLED = True +# Метод расчёта фона: 'median' или 'mean' +BG_BASELINE_METHOD = 'median' + # ПЕРЕЧИСЛЕНИЕ ТИПОВ ДАННЫХ DATA_TYPE_RAW = "RAW" DATA_TYPE_SYNC_DET = "SYNC_DET" @@ -453,6 +461,13 @@ class DataAnalyzerApp: self.processed_count = 0 self.skipped_count = 0 + # Буфер последних колонок для фонового усреднения B-scan + self.bscan_buffer = deque() # элементов: (np.ndarray col, datetime t, str type) + self.bg_avg_window_sec = BG_AVG_WINDOW_SEC + self.bg_subtract_alpha = BG_SUBTRACT_ALPHA + self.bg_subtract_enabled = BG_SUBTRACT_ENABLED + self.bg_method = BG_BASELINE_METHOD + # ============================================================================ # ИНИЦИАЛИЗАЦИЯ ИНТЕРФЕЙСА # ============================================================================ @@ -854,6 +869,13 @@ class DataAnalyzerApp: # Собираем данные для отображения display_cols = [] display_types = [] # Тип каждого столбца + # Предрасчёт фонового усреднения по строкам текущего окна + bg_vec = None + if self.bg_subtract_enabled and self.bscan_buffer: + desired_row_start = row_min + desired_row_end = row_max + 1 + bg_vec = self.compute_background_slice(desired_row_start, desired_row_end) + alpha = self.bg_subtract_alpha if self.bg_subtract_enabled else 0.0 for i in range(col_min, col_max): col_data = self.B_scan_data[i] col_type = self.B_scan_types[i] @@ -862,7 +884,13 @@ class DataAnalyzerApp: row_end = min(row_max + 1, len(col_data)) row_start = min(row_min, len(col_data) - 1) if row_end > row_start: - display_cols.append(col_data[row_start:row_end]) + seg = col_data[row_start:row_end] + if bg_vec is not None and col_type != "GAP": + take = min(len(seg), len(bg_vec)) + if take > 0 and alpha != 0.0: + seg = seg.copy() + seg[:take] = seg[:take] - alpha * bg_vec[:take] + display_cols.append(seg) display_types.append(col_type) display_times = self.B_scan_times[col_min:col_max] @@ -1054,6 +1082,10 @@ class DataAnalyzerApp: self.B_scan_times.append(current_time) self.B_scan_types.append(data_type) self.last_file_time = current_time + # Сохраняем в буфер для фонового усреднения (не добавляем GAP) + if data_type != "GAP": + self.bscan_buffer.append((np.asarray(data_col, dtype=float), current_time, data_type)) + self.prune_bscan_buffer(current_time) return # Используем переменный интервал между файлами @@ -1084,6 +1116,61 @@ class DataAnalyzerApp: self.B_scan_times.append(current_time) self.B_scan_types.append(data_type) self.last_file_time = current_time + # Сохраняем в буфер для фонового усреднения (не добавляем GAP) + if data_type != "GAP": + self.bscan_buffer.append((np.asarray(data_col, dtype=float), current_time, data_type)) + self.prune_bscan_buffer(current_time) + + def prune_bscan_buffer(self, now_time: datetime): + """Удаляет столбцы старше окна усреднения.""" + try: + threshold = now_time - timedelta(seconds=self.bg_avg_window_sec) + while self.bscan_buffer and self.bscan_buffer[0][1] < threshold: + self.bscan_buffer.popleft() + except Exception: + pass + + def compute_background_slice(self, row_start: int, row_end: int) -> np.ndarray: + """Пер-строчная статистика (median/mean) по окну последних колонок для строк [row_start:row_end).""" + n_rows = max(0, row_end - row_start) + if n_rows <= 0 or not self.bscan_buffer: + return np.zeros((0,), dtype=float) + + # Собираем сегменты для указанного диапазона строк + segments = [] + for col, t, tname in self.bscan_buffer: + if col is None: + continue + L = len(col) + if L <= row_start: + continue + take = min(n_rows, L - row_start) + if take <= 0: + continue + seg = np.asarray(col[row_start:row_start + take], dtype=float) + segments.append(seg) + + if not segments: + return np.zeros((n_rows,), dtype=float) + + # Формируем матрицу (num_cols x n_rows) с NaN, заполняя доступные значения + num_cols = len(segments) + M = np.full((num_cols, n_rows), np.nan, dtype=float) + for i, seg in enumerate(segments): + take = min(n_rows, seg.shape[0]) + if take > 0: + M[i, :take] = seg[:take] + + # Агрегирование по времени (ось 0), игнорируя NaN + with np.errstate(all='ignore'): + if getattr(self, 'bg_method', 'median') == 'mean': + bg = np.nanmean(M, axis=0) + else: + bg = np.nanmedian(M, axis=0) + + # Заменим NaN на 0 (строки без данных в окне) + bg = np.nan_to_num(bg, nan=0.0) + return bg def compute_fft(self): """Вычисляем FFT спектр."""