From 0ecb83751f5da0ad7fb1ec064d9b450de0a93edf Mon Sep 17 00:00:00 2001 From: awe Date: Fri, 13 Feb 2026 17:45:14 +0300 Subject: [PATCH] add background remove --- background.npy | Bin 0 -> 3164 bytes rfg_adc_plotter/gui/matplotlib_backend.py | 20 ++++++++- rfg_adc_plotter/gui/pyqtgraph_backend.py | 28 +++++++++++- rfg_adc_plotter/state/app_state.py | 51 ++++++++++++++++++++++ 4 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 background.npy diff --git a/background.npy b/background.npy new file mode 100644 index 0000000000000000000000000000000000000000..c3d18dadbddf1486d2f6ad0013ba757f99e44435 GIT binary patch literal 3164 zcmbVOX;f547A}nv+|jt26S-88A9#&J~0CEJ)Ffa?u6G`ssXJbg@Y(sVVc8MJJ^!TqNZa=Oq{xL2g()Z}}qZ z2aX&b;N#`vvcl#6{s@K(w)?ibc{R3AQ@h(D!h`55W5;^U}>c@rJYJHTY99`VWKs zJS}2Vpy;%xH(jV%!lTX%7o`?=ttopAe;`gO_dWcPgJi+jw6eN~d-&18D>RFD0QMQe zGvp$;7~xB*VCo8BnMA{!b~NjHF5;gq;v>Jd<&N1ralWWJdS7GAOeen2 zu=5#we256U?G6lZX9n?fr2{x`RI=)CD-y$Dn@xMa-U;V5JfPkM_EN=`?W;9rO@V%z z=vJKzenz_ure;BwEZXez6Cs!4MR1P-&a6fuG*|F#)Q+rfRG5A);)A%W54hsE5$Dow z$#|yEgxoAKiRsNaH;dwgM+&nBik563a_k4*YFUwC1kPQlp>KEES)GdYN~-ct<}KJ> z)afhUl0AX%Pcclu7$osU#LQK^tC+^OUd`u65@#bf!>O^!9XcaL{hB}W;-6n=PPRTG z&-b#8vAN2@{Li>$i3d3qIEv6ob9v~xSPGq#Z8I8o@rWz6z?3{Q$YM#?s=pZ_(!vgc z8?(X>J!5OeGv-pptXWhm?s3nv*ATOyH}NskZc8uje)giq^v=M@q^2fKi~HgjV)z|T z3p>n1yt+_M^=afr)>0NZ#2fi~H%C!fej9vW!Om+8IU`2Q04^D#%0ComEl^j8rjZ{m&Xj^9%!ammDevLS)<0^k2 zNYmyLrG=g3;o-U3w-3Lw1&tX?5m!E@9&@^C;0~LSRC%sK*}Q$N9p}TkF+`94g)806cveyh{fK&~X~%icg}JD$463|V$j7{#DE2OK zqEhee+*Gkoc{rsFMRe;#P2(0)d2$)zd;sylyTM2cJI;5k3xO|23cr%WLw-NSAE)F2 z3$|khQN*SI3hHuD#ufRF#hdk`mM)ICn@WkZI>HYJ+WROResbzi?Z!=0e8|+n8|Rsn z)+b*Ho%Ad2I#aPvDj8oM<{RCcP!C4BdVLqa_0PwAfo?W^fY?t*zw1)}is$VMgr8_94NFqrbcilZ6fam4O~(vt&_1J{rjP3~uB zLbjOZ>r%-vI+99%bf*1{5jZzdX3KcW`!$MQ7A~jZOB3k&p&(k&m;}C=WIki0h(C9F zm%}KBRLb?~O@*W5sNkn1bj2}%_y*Xq#!zyE8_~(p~WPDVSQEq#{LUJLA6jj}Fj{)K278I?d^t(!^{f9emp ztTQu(8d_1n-#u_IgQog~P)Q8y4*1cX$VGIcVJZDQ*PYJ! zyOFE5gaR%k1CPPpDAO<$Rx*g5tXZ#gsNQL-n1H&L*s2`pjdnY-{(%n+ncwDfnJ+2BUdmKCOFhh1$o2R} zNzSTVztNF$d?W30=~iH=pW!9G2~snNhHqNAbW~d7Ry??=KKB4XypZyGd@}2D-(0PsTxG!XQZ%sjX zb7#36GDeNrqv?auox%N?(r=C<>QUz0gm3v^^oD;x=QVc^2`9l_=@;G;Hv6D|#h^Dw zQbHYiCa@8AG9CrK+K0l!9njYgAxGfGBuD9sF%GkDwY zNemPE7ycK$#gVgK#!8-zrV|g*(^SdZOx@`dx_cpxYKrjYRH+9$`at~8sE03dzk(8r yoX9<7CWTB#e}PWOrJ?A@;q-9(vSxkoY&B*w4Or(&rR$yXJ=fF39;g$mf&L4)kdI&h literal 0 HcmV?d00001 diff --git a/rfg_adc_plotter/gui/matplotlib_backend.py b/rfg_adc_plotter/gui/matplotlib_backend.py index e7f737f..b936a22 100644 --- a/rfg_adc_plotter/gui/matplotlib_backend.py +++ b/rfg_adc_plotter/gui/matplotlib_backend.py @@ -12,7 +12,7 @@ from rfg_adc_plotter.constants import FFT_LEN, FREQ_SPAN_GHZ, IFFT_LEN _IFFT_T_MAX_NS = float((IFFT_LEN - 1) / (FREQ_SPAN_GHZ * 1e9) * 1e9) from rfg_adc_plotter.io.sweep_reader import SweepReader from rfg_adc_plotter.processing.normalizer import build_calib_envelopes -from rfg_adc_plotter.state.app_state import CALIB_ENVELOPE_PATH, AppState, format_status +from rfg_adc_plotter.state.app_state import BACKGROUND_PATH, CALIB_ENVELOPE_PATH, AppState, format_status from rfg_adc_plotter.state.ring_buffer import RingBuffer from rfg_adc_plotter.types import SweepPacket @@ -204,6 +204,24 @@ def run_matplotlib(args): state.set_calib_enabled(bool(calib_cb.get_status()[0])) fig.canvas.draw_idle() + ax_btn_bg = fig.add_axes([0.92, 0.27, 0.08, 0.05]) + ax_cb_bg = fig.add_axes([0.92, 0.20, 0.08, 0.06]) + from matplotlib.widgets import Button as MplButton + save_bg_btn = MplButton(ax_btn_bg, "Сохр. фон") + bg_cb = CheckButtons(ax_cb_bg, ["вычет фона"], [False]) + + def _on_save_bg(_event): + ok = state.save_background() + if ok: + state.load_background() + fig.canvas.draw_idle() + + def _on_bg_clicked(_v): + state.set_background_enabled(bool(bg_cb.get_status()[0])) + + save_bg_btn.on_clicked(_on_save_bg) + bg_cb.on_clicked(_on_bg_clicked) + ymin_slider.on_changed(_on_ylim_change) ymax_slider.on_changed(_on_ylim_change) contrast_slider.on_changed(lambda _v: fig.canvas.draw_idle()) diff --git a/rfg_adc_plotter/gui/pyqtgraph_backend.py b/rfg_adc_plotter/gui/pyqtgraph_backend.py index ed76229..97aa872 100644 --- a/rfg_adc_plotter/gui/pyqtgraph_backend.py +++ b/rfg_adc_plotter/gui/pyqtgraph_backend.py @@ -10,7 +10,7 @@ import numpy as np from rfg_adc_plotter.constants import FREQ_SPAN_GHZ, IFFT_LEN from rfg_adc_plotter.io.sweep_reader import SweepReader from rfg_adc_plotter.processing.normalizer import build_calib_envelopes -from rfg_adc_plotter.state.app_state import CALIB_ENVELOPE_PATH, AppState, format_status +from rfg_adc_plotter.state.app_state import BACKGROUND_PATH, CALIB_ENVELOPE_PATH, AppState, format_status from rfg_adc_plotter.state.ring_buffer import RingBuffer from rfg_adc_plotter.types import SweepPacket @@ -225,6 +225,32 @@ def run_pyqtgraph(args): calib_cb.stateChanged.connect(_on_calib_toggled) calib_file_cb.stateChanged.connect(lambda _v: _on_calib_file_toggled(calib_file_cb.isChecked())) + # Кнопка сохранения фона + чекбокс вычета фона + bg_widget = QtWidgets.QWidget() + bg_layout = QtWidgets.QHBoxLayout(bg_widget) + bg_layout.setContentsMargins(2, 2, 2, 2) + bg_layout.setSpacing(8) + + save_bg_btn = QtWidgets.QPushButton("Сохр. фон") + bg_cb = QtWidgets.QCheckBox("вычет фона") + bg_cb.setEnabled(False) + + bg_layout.addWidget(save_bg_btn) + bg_layout.addWidget(bg_cb) + + bg_container_proxy = QtWidgets.QGraphicsProxyWidget() + bg_container_proxy.setWidget(bg_widget) + win.addItem(bg_container_proxy, row=2, col=0) + + def _on_save_bg(): + ok = state.save_background() + if ok: + state.load_background() + bg_cb.setEnabled(True) + + save_bg_btn.clicked.connect(_on_save_bg) + bg_cb.stateChanged.connect(lambda _v: state.set_background_enabled(bg_cb.isChecked())) + # Статусная строка status = pg.LabelItem(justify="left") win.addItem(status, row=3, col=0, colspan=2) diff --git a/rfg_adc_plotter/state/app_state.py b/rfg_adc_plotter/state/app_state.py index 229b273..0530fb4 100644 --- a/rfg_adc_plotter/state/app_state.py +++ b/rfg_adc_plotter/state/app_state.py @@ -15,6 +15,7 @@ from rfg_adc_plotter.state.ring_buffer import RingBuffer from rfg_adc_plotter.types import SweepInfo, SweepPacket CALIB_ENVELOPE_PATH = "calib_envelope.npy" +BACKGROUND_PATH = "background.npy" def format_status(data: Mapping[str, Any]) -> str: @@ -54,6 +55,10 @@ class AppState: # "live" — нормировка по текущему ch0-свипу; "file" — по огибающей из файла self.calib_mode: str = "live" self.calib_file_envelope: Optional[np.ndarray] = None + # Вычет фона + self.background: Optional[np.ndarray] = None + self.background_enabled: bool = False + self._last_sweep_for_ring: Optional[np.ndarray] = None def _normalize(self, raw: np.ndarray, calib: np.ndarray) -> np.ndarray: if self.calib_mode == "file" and self.calib_file_envelope is not None: @@ -96,6 +101,43 @@ class AppState: """Переключить режим калибровки: 'live' или 'file'.""" self.calib_mode = mode + def save_background(self, path: str = BACKGROUND_PATH) -> bool: + """Сохранить текущий sweep_for_ring как фоновый спектр. + + Сохраняет последний свип, который был записан в ринг-буфер + (нормированный, если калибровка включена, иначе сырой). + Возвращает True при успехе. + """ + if self._last_sweep_for_ring is None: + return False + try: + np.save(path, self._last_sweep_for_ring) + return True + except Exception as exc: + import sys + sys.stderr.write(f"[warn] Не удалось сохранить фон: {exc}\n") + return False + + def load_background(self, path: str = BACKGROUND_PATH) -> bool: + """Загрузить фоновый спектр из файла. + + Возвращает True при успехе. + """ + if not os.path.isfile(path): + return False + try: + bg = np.load(path) + self.background = np.asarray(bg, dtype=np.float32) + return True + except Exception as exc: + import sys + sys.stderr.write(f"[warn] Не удалось загрузить фон: {exc}\n") + return False + + def set_background_enabled(self, enabled: bool): + """Включить/выключить вычет фона.""" + self.background_enabled = enabled + def set_calib_enabled(self, enabled: bool): """Включить/выключить режим калибровки, пересчитать norm-свип.""" self.calib_enabled = enabled @@ -140,6 +182,7 @@ class AppState: self.save_calib_envelope() self.current_sweep_norm = None sweep_for_ring = s + self._last_sweep_for_ring = sweep_for_ring else: can_normalize = self.calib_enabled and ( (self.calib_mode == "file" and self.calib_file_envelope is not None) @@ -153,6 +196,14 @@ class AppState: self.current_sweep_norm = None sweep_for_ring = s + # Вычет фона (в том же домене что и sweep_for_ring) + if self.background_enabled and self.background is not None and ch != 0: + w = min(sweep_for_ring.size, self.background.size) + sweep_for_ring = sweep_for_ring.copy() + sweep_for_ring[:w] -= self.background[:w] + self.current_sweep_norm = sweep_for_ring + + self._last_sweep_for_ring = sweep_for_ring ring.ensure_init(s.size) ring.push(sweep_for_ring) return drained