implemented reference subtraction from B_scan. Reference is average from all visible B-scan.

This commit is contained in:
2026-03-04 16:22:27 +03:00
parent 6724dc0abc
commit e66e7aef83

View File

@ -198,12 +198,19 @@ def _prepare_fft_segment(
return resampled, take_fft return resampled, take_fft
def _compute_fft_row( def _fft_mag_to_db(mag: np.ndarray) -> np.ndarray:
"""Перевод модуля спектра в дБ с отсечкой отрицательных значений после вычитания фона."""
mag_arr = np.asarray(mag, dtype=np.float32)
safe_mag = np.maximum(mag_arr, 0.0)
return (20.0 * np.log10(safe_mag + 1e-9)).astype(np.float32, copy=False)
def _compute_fft_mag_row(
sweep: np.ndarray, sweep: np.ndarray,
freqs: Optional[np.ndarray], freqs: Optional[np.ndarray],
bins: int, bins: int,
) -> np.ndarray: ) -> np.ndarray:
"""Посчитать FFT-строку, используя калиброванную частотную ось при подготовке входа.""" """Посчитать линейный модуль FFT-строки, используя калиброванную частотную ось."""
if bins <= 0: if bins <= 0:
return np.zeros((0,), dtype=np.float32) return np.zeros((0,), dtype=np.float32)
@ -217,10 +224,18 @@ def _compute_fft_row(
fft_in[:take_fft] = fft_seg * win fft_in[:take_fft] = fft_seg * win
spec = np.fft.ifft(fft_in) spec = np.fft.ifft(fft_in)
mag = np.abs(spec).astype(np.float32) mag = np.abs(spec).astype(np.float32)
fft_row = 20.0 * np.log10(mag + 1e-9) if mag.shape[0] != bins:
if fft_row.shape[0] != bins: mag = mag[:bins]
fft_row = fft_row[:bins] return mag
return fft_row
def _compute_fft_row(
sweep: np.ndarray,
freqs: Optional[np.ndarray],
bins: int,
) -> np.ndarray:
"""Посчитать FFT-строку в дБ."""
return _fft_mag_to_db(_compute_fft_mag_row(sweep, freqs, bins))
def _compute_distance_axis(freqs: Optional[np.ndarray], bins: int) -> np.ndarray: def _compute_distance_axis(freqs: Optional[np.ndarray], bins: int) -> np.ndarray:
@ -1080,8 +1095,8 @@ def main():
parser.add_argument( parser.add_argument(
"--backend", "--backend",
choices=["auto", "pg", "mpl"], choices=["auto", "pg", "mpl"],
default="auto", default="pg",
help="Графический бэкенд: pyqtgraph (pg) — быстрее; matplotlib (mpl) — совместимый. По умолчанию auto", help="Графический бэкенд: pyqtgraph (pg) — быстрее; matplotlib (mpl) — совместимый. По умолчанию pg",
) )
parser.add_argument( parser.add_argument(
"--norm-type", "--norm-type",
@ -1119,14 +1134,18 @@ def main():
args = parser.parse_args() args = parser.parse_args()
# Попробуем быстрый бэкенд (pyqtgraph) при auto/pg # Попробуем быстрый бэкенд (pyqtgraph) при auto/pg
if args.backend in ("pg"): if args.backend == "pg":
try: try:
return run_pyqtgraph(args) return run_pyqtgraph(args)
except Exception as e: except Exception as e:
if args.backend == "pg":
sys.stderr.write(f"[error] PyQtGraph бэкенд недоступен: {e}\n") sys.stderr.write(f"[error] PyQtGraph бэкенд недоступен: {e}\n")
sys.exit(1) sys.exit(1)
# При auto — тихо откатываемся на matplotlib
if args.backend == "auto":
try:
return run_pyqtgraph(args)
except Exception:
pass # При auto — тихо откатываемся на matplotlib
try: try:
import matplotlib import matplotlib
@ -1186,6 +1205,7 @@ def main():
ymax_slider = None ymax_slider = None
contrast_slider = None contrast_slider = None
calib_enabled = False calib_enabled = False
bg_subtract_enabled = False
peak_calibrate_mode = bool(getattr(args, "calibrate", False)) peak_calibrate_mode = bool(getattr(args, "calibrate", False))
current_peak_width: Optional[float] = None current_peak_width: Optional[float] = None
norm_type = str(getattr(args, "norm_type", "projector")).strip().lower() norm_type = str(getattr(args, "norm_type", "projector")).strip().lower()
@ -1283,12 +1303,15 @@ def main():
def _normalize_sweep(raw: np.ndarray, calib: np.ndarray) -> np.ndarray: def _normalize_sweep(raw: np.ndarray, calib: np.ndarray) -> np.ndarray:
return _normalize_by_calib(raw, calib, norm_type=norm_type) return _normalize_by_calib(raw, calib, norm_type=norm_type)
def _set_calib_enabled(): def _sync_checkbox_states():
nonlocal calib_enabled, current_sweep_norm nonlocal calib_enabled, bg_subtract_enabled, current_sweep_norm
try: try:
calib_enabled = bool(cb.get_status()[0]) if cb is not None else False 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
except Exception: except Exception:
calib_enabled = False calib_enabled = False
bg_subtract_enabled = False
if calib_enabled and current_sweep_raw is not None and last_calib_sweep is not None: 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) current_sweep_norm = _normalize_sweep(current_sweep_raw, last_calib_sweep)
else: else:
@ -1299,11 +1322,11 @@ def main():
ax_smin = fig.add_axes([0.92, 0.55, 0.02, 0.35]) 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_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_sctr = fig.add_axes([0.98, 0.55, 0.02, 0.35])
ax_cb = fig.add_axes([0.92, 0.45, 0.08, 0.08]) ax_cb = fig.add_axes([0.90, 0.40, 0.10, 0.14])
ymin_slider = Slider(ax_smin, "R min", 0.0, 1.0, valinit=0.0, orientation="vertical") 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") 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") contrast_slider = Slider(ax_sctr, "Int max", 0, 100, valinit=100, valstep=1, orientation="vertical")
cb = CheckButtons(ax_cb, ["калибровка"], [False]) cb = CheckButtons(ax_cb, ["нормировка", "вычет фона"], [False, False])
def _on_ylim_change(_val): def _on_ylim_change(_val):
try: try:
@ -1318,7 +1341,7 @@ def main():
ymax_slider.on_changed(_on_ylim_change) ymax_slider.on_changed(_on_ylim_change)
# Контраст влияет на верхнюю границу цветовой шкалы (процент от авто-диапазона) # Контраст влияет на верхнюю границу цветовой шкалы (процент от авто-диапазона)
contrast_slider.on_changed(lambda _v: fig.canvas.draw_idle()) contrast_slider.on_changed(lambda _v: fig.canvas.draw_idle())
cb.on_clicked(lambda _v: _set_calib_enabled()) cb.on_clicked(lambda _v: _sync_checkbox_states())
except Exception: except Exception:
pass pass
@ -1449,11 +1472,12 @@ def main():
if ring_time is not None: if ring_time is not None:
ring_time[head] = time.time() ring_time[head] = time.time()
head = (head + 1) % ring.shape[0] head = (head + 1) % ring.shape[0]
# FFT строка (дБ) # FFT строка (линейный модуль; перевод в дБ делаем при отображении)
if ring_fft is not None: if ring_fft is not None:
bins = ring_fft.shape[1] bins = ring_fft.shape[1]
fft_row = _compute_fft_row(s, freqs, bins) fft_mag = _compute_fft_mag_row(s, freqs, bins)
ring_fft[(head - 1) % ring_fft.shape[0], :] = fft_row ring_fft[(head - 1) % ring_fft.shape[0], :] = fft_mag
fft_row = _fft_mag_to_db(fft_mag)
# Экстремумы для цветовой шкалы # Экстремумы для цветовой шкалы
fr_min = np.nanmin(fft_row) fr_min = np.nanmin(fft_row)
fr_max = np.nanmax(fft_row) fr_max = np.nanmax(fft_row)
@ -1519,7 +1543,7 @@ def main():
return base_t return base_t
def _subtract_recent_mean_fft(disp_fft: np.ndarray) -> np.ndarray: def _subtract_recent_mean_fft(disp_fft: np.ndarray) -> np.ndarray:
"""Вычесть среднее по каждой частоте за последние spec_mean_sec секунд.""" """Вычесть среднее по каждой дальности за последние spec_mean_sec секунд в линейной области."""
if spec_mean_sec <= 0.0: if spec_mean_sec <= 0.0:
return disp_fft return disp_fft
disp_times = make_display_times() disp_times = make_display_times()
@ -1536,6 +1560,33 @@ def main():
mean_spec = np.nan_to_num(mean_spec, nan=0.0) mean_spec = np.nan_to_num(mean_spec, nan=0.0)
return disp_fft - mean_spec[:, None] return disp_fft - mean_spec[:, None]
def _visible_bg_fft(disp_fft: np.ndarray) -> Optional[np.ndarray]:
"""Оценка фона по медиане в текущем видимом по времени окне B-scan."""
if not bg_subtract_enabled or disp_fft.size == 0:
return None
ny, nx = disp_fft.shape
if ny <= 0 or nx <= 0:
return None
try:
x0, x1 = ax_spec.get_xlim()
except Exception:
x0, x1 = 0.0, float(nx - 1)
xmin, xmax = sorted((float(x0), float(x1)))
ix0 = max(0, min(nx - 1, int(np.floor(xmin))))
ix1 = max(0, min(nx - 1, int(np.ceil(xmax))))
if ix1 < ix0:
ix1 = ix0
window = disp_fft[:, ix0 : ix1 + 1]
if window.size == 0:
return None
try:
bg_spec = np.nanmedian(window, axis=1)
except Exception:
return None
if not np.any(np.isfinite(bg_spec)):
return None
return np.nan_to_num(bg_spec, nan=0.0).astype(np.float32, copy=False)
def make_display_ring_fft(): def make_display_ring_fft():
if ring_fft is None: if ring_fft is None:
return np.zeros((1, 1), dtype=np.float32) return np.zeros((1, 1), dtype=np.float32)
@ -1658,12 +1709,34 @@ def main():
# Обновление водопада спектров # Обновление водопада спектров
if changed and ring_fft is not None: if changed and ring_fft is not None:
disp_fft = make_display_ring_fft() disp_fft_lin = make_display_ring_fft()
disp_fft = _subtract_recent_mean_fft(disp_fft) disp_fft_lin = _subtract_recent_mean_fft(disp_fft_lin)
bg_spec = _visible_bg_fft(disp_fft_lin)
if bg_spec is not None:
num = np.maximum(disp_fft_lin, 0.0).astype(np.float32, copy=False) + 1e-9
den = bg_spec[:, None] + 1e-9
disp_fft = (20.0 * np.log10(num / den)).astype(np.float32, copy=False)
else:
disp_fft = _fft_mag_to_db(disp_fft_lin)
# Новые данные справа: без реверса # Новые данные справа: без реверса
img_fft_obj.set_data(disp_fft) img_fft_obj.set_data(disp_fft)
# Подписи времени не обновляем динамически (оставляем авто-тики) # Подписи времени не обновляем динамически (оставляем авто-тики)
# Автодиапазон по среднему спектру за видимый интервал (как в хорошей версии) # Автодиапазон по среднему спектру за видимый интервал (как в хорошей версии)
if bg_spec is not None:
try:
p5 = float(np.nanpercentile(disp_fft, 5))
p95 = float(np.nanpercentile(disp_fft, 95))
span = max(abs(p5), abs(p95))
except Exception:
span = float("nan")
if np.isfinite(span) and span > 0.0:
try:
c = float(contrast_slider.val) / 100.0 if contrast_slider is not None else 1.0
except Exception:
c = 1.0
span_eff = max(span * c, 1e-6)
img_fft_obj.set_clim(vmin=-span_eff, vmax=span_eff)
else:
try: try:
# disp_fft имеет форму (bins, time); берём среднее по времени # disp_fft имеет форму (bins, time); берём среднее по времени
mean_spec = np.nanmean(disp_fft, axis=1) mean_spec = np.nanmean(disp_fft, axis=1)
@ -1776,15 +1849,10 @@ def run_pyqtgraph(args):
peak_calibrate_mode = bool(getattr(args, "calibrate", False)) peak_calibrate_mode = bool(getattr(args, "calibrate", False))
try: try:
import pyqtgraph as pg import pyqtgraph as pg
from PyQt5 import QtCore, QtWidgets # noqa: F401 from pyqtgraph.Qt import QtCore, QtWidgets # type: ignore
except Exception:
# Возможно установлена PySide6
try:
import pyqtgraph as pg
from PySide6 import QtCore, QtWidgets # noqa: F401
except Exception as e: except Exception as e:
raise RuntimeError( raise RuntimeError(
"pyqtgraph/PyQt5(Pyside6) не найдены. Установите: pip install pyqtgraph PyQt5" "pyqtgraph и совместимый Qt backend не найдены. Установите: pip install pyqtgraph PyQt5"
) from e ) from e
# Очередь завершённых свипов и поток чтения # Очередь завершённых свипов и поток чтения
@ -1823,8 +1891,25 @@ def run_pyqtgraph(args):
app.setQuitOnLastWindowClosed(True) app.setQuitOnLastWindowClosed(True)
except Exception: except Exception:
pass pass
win = pg.GraphicsLayoutWidget(show=True, title=args.title) main_window = QtWidgets.QWidget()
win.resize(1200, 600) try:
main_window.setWindowTitle(str(args.title))
except Exception:
pass
main_layout = QtWidgets.QHBoxLayout(main_window)
main_layout.setContentsMargins(6, 6, 6, 6)
main_layout.setSpacing(6)
win = pg.GraphicsLayoutWidget(show=False, title=args.title)
main_layout.addWidget(win)
settings_widget = QtWidgets.QWidget()
settings_layout = QtWidgets.QVBoxLayout(settings_widget)
settings_layout.setContentsMargins(6, 6, 6, 6)
settings_layout.setSpacing(8)
try:
settings_widget.setMinimumWidth(170)
except Exception:
pass
main_layout.addWidget(settings_widget)
# Плот последнего свипа (слева-сверху) # Плот последнего свипа (слева-сверху)
p_line = win.addPlot(row=0, col=0, title="Сырые данные") p_line = win.addPlot(row=0, col=0, title="Сырые данные")
@ -1889,20 +1974,17 @@ def run_pyqtgraph(args):
spec_left_line.setVisible(False) spec_left_line.setVisible(False)
spec_right_line.setVisible(False) spec_right_line.setVisible(False)
# Отдельное окно контролов: GraphicsLayoutWidget не принимает обычные QWidget через addItem. # Правая панель настроек внутри основного окна.
calib_cb = QtWidgets.QCheckBox("калибровка") calib_cb = QtWidgets.QCheckBox("нормировка")
control_window = None bg_subtract_cb = QtWidgets.QCheckBox("вычет фона")
control_layout = None
if peak_calibrate_mode:
control_window = QtWidgets.QWidget()
try: try:
control_window.setWindowTitle(f"{args.title} controls") settings_title = QtWidgets.QLabel("Настройки")
settings_layout.addWidget(settings_title)
except Exception: except Exception:
pass pass
control_layout = QtWidgets.QVBoxLayout(control_window) settings_layout.addWidget(calib_cb)
control_layout.setContentsMargins(8, 8, 8, 8) settings_layout.addWidget(bg_subtract_cb)
control_layout.setSpacing(6) calib_window = None
control_layout.addWidget(calib_cb)
# Статусная строка (внизу окна) # Статусная строка (внизу окна)
status = pg.LabelItem(justify="left") status = pg.LabelItem(justify="left")
@ -1931,6 +2013,7 @@ def run_pyqtgraph(args):
spec_clip = _parse_spec_clip(getattr(args, "spec_clip", None)) spec_clip = _parse_spec_clip(getattr(args, "spec_clip", None))
spec_mean_sec = float(getattr(args, "spec_mean_sec", 0.0)) spec_mean_sec = float(getattr(args, "spec_mean_sec", 0.0))
calib_enabled = False calib_enabled = False
bg_subtract_enabled = False
current_peak_width: Optional[float] = None current_peak_width: Optional[float] = None
norm_type = str(getattr(args, "norm_type", "projector")).strip().lower() norm_type = str(getattr(args, "norm_type", "projector")).strip().lower()
c_edits = [] c_edits = []
@ -1959,16 +2042,31 @@ def run_pyqtgraph(args):
else: else:
current_sweep_norm = None current_sweep_norm = None
def _set_bg_subtract_enabled():
nonlocal bg_subtract_enabled
try:
bg_subtract_enabled = bool(bg_subtract_cb.isChecked())
except Exception:
bg_subtract_enabled = False
try: try:
calib_cb.stateChanged.connect(lambda _v: _set_calib_enabled()) calib_cb.stateChanged.connect(lambda _v: _set_calib_enabled())
except Exception: except Exception:
pass pass
try:
bg_subtract_cb.stateChanged.connect(lambda _v: _set_bg_subtract_enabled())
except Exception:
pass
if peak_calibrate_mode: if peak_calibrate_mode:
try: try:
c_widget = QtWidgets.QWidget() calib_window = QtWidgets.QWidget()
c_layout = QtWidgets.QFormLayout(c_widget) try:
c_layout.setContentsMargins(0, 0, 0, 0) calib_window.setWindowTitle(f"{args.title} freq calibration")
except Exception:
pass
calib_layout = QtWidgets.QFormLayout(calib_window)
calib_layout.setContentsMargins(8, 8, 8, 8)
def _apply_c_value(idx: int, edit): def _apply_c_value(idx: int, edit):
global CALIBRATION_C global CALIBRATION_C
@ -1990,15 +2088,16 @@ def run_pyqtgraph(args):
edit.editingFinished.connect(lambda i=idx, e=edit: _apply_c_value(i, e)) edit.editingFinished.connect(lambda i=idx, e=edit: _apply_c_value(i, e))
except Exception: except Exception:
pass pass
c_layout.addRow(f"C{idx}", edit) calib_layout.addRow(f"C{idx}", edit)
c_edits.append(edit) c_edits.append(edit)
if control_layout is not None: try:
control_layout.addWidget(c_widget) calib_window.show()
except Exception:
pass
except Exception: except Exception:
pass pass
if control_window is not None:
try: try:
control_window.show() settings_layout.addStretch(1)
except Exception: except Exception:
pass pass
@ -2076,6 +2175,33 @@ def run_pyqtgraph(args):
return None return None
return (vmin, vmax) return (vmin, vmax)
def _visible_bg_fft(disp_fft: np.ndarray) -> Optional[np.ndarray]:
"""Оценка фона по медиане в текущем видимом по времени окне B-scan."""
if not bg_subtract_enabled or disp_fft.size == 0:
return None
ny, nx = disp_fft.shape
if ny <= 0 or nx <= 0:
return None
try:
(x0, x1), _ = p_spec.viewRange()
except Exception:
x0, x1 = 0.0, float(nx - 1)
xmin, xmax = sorted((float(x0), float(x1)))
ix0 = max(0, min(nx - 1, int(np.floor(xmin))))
ix1 = max(0, min(nx - 1, int(np.ceil(xmax))))
if ix1 < ix0:
ix1 = ix0
window = disp_fft[:, ix0 : ix1 + 1]
if window.size == 0:
return None
try:
bg_spec = np.nanmedian(window, axis=1)
except Exception:
return None
if not np.any(np.isfinite(bg_spec)):
return None
return np.nan_to_num(bg_spec, nan=0.0).astype(np.float32, copy=False)
def push_sweep(s: np.ndarray, freqs: Optional[np.ndarray] = None): def push_sweep(s: np.ndarray, freqs: Optional[np.ndarray] = None):
nonlocal ring, ring_time, 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: if s is None or s.size == 0 or ring is None:
@ -2088,11 +2214,12 @@ def run_pyqtgraph(args):
if ring_time is not None: if ring_time is not None:
ring_time[head] = time.time() ring_time[head] = time.time()
head = (head + 1) % ring.shape[0] head = (head + 1) % ring.shape[0]
# FFT строка (дБ) # FFT строка (линейный модуль; перевод в дБ делаем при отображении)
if ring_fft is not None: if ring_fft is not None:
bins = ring_fft.shape[1] bins = ring_fft.shape[1]
fft_row = _compute_fft_row(s, freqs, bins) fft_mag = _compute_fft_mag_row(s, freqs, bins)
ring_fft[(head - 1) % ring_fft.shape[0], :] = fft_row ring_fft[(head - 1) % ring_fft.shape[0], :] = fft_mag
fft_row = _fft_mag_to_db(fft_mag)
fr_min = np.nanmin(fft_row) fr_min = np.nanmin(fft_row)
fr_max = np.nanmax(fft_row) fr_max = np.nanmax(fft_row)
if y_min_fft is None or (not np.isnan(fr_min) and fr_min < y_min_fft): if y_min_fft is None or (not np.isnan(fr_min) and fr_min < y_min_fft):
@ -2272,21 +2399,38 @@ def run_pyqtgraph(args):
pass pass
if changed and ring_fft is not None: 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_lin = ring_fft if head == 0 else np.roll(ring_fft, -head, axis=0)
disp_fft = disp_fft.T disp_fft_lin = disp_fft_lin.T
if spec_mean_sec > 0.0 and ring_time is not None: 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 = ring_time if head == 0 else np.roll(ring_time, -head)
now_t = time.time() now_t = time.time()
mask = np.isfinite(disp_times) & (disp_times >= (now_t - spec_mean_sec)) mask = np.isfinite(disp_times) & (disp_times >= (now_t - spec_mean_sec))
if np.any(mask): if np.any(mask):
try: try:
mean_spec = np.nanmean(disp_fft[:, mask], axis=1) mean_spec = np.nanmean(disp_fft_lin[:, mask], axis=1)
mean_spec = np.nan_to_num(mean_spec, nan=0.0) mean_spec = np.nan_to_num(mean_spec, nan=0.0)
disp_fft = disp_fft - mean_spec[:, None] disp_fft_lin = disp_fft_lin - mean_spec[:, None]
except Exception: except Exception:
pass pass
bg_spec = _visible_bg_fft(disp_fft_lin)
if bg_spec is not None:
num = np.maximum(disp_fft_lin, 0.0).astype(np.float32, copy=False) + 1e-9
den = bg_spec[:, None] + 1e-9
disp_fft = (20.0 * np.log10(num / den)).astype(np.float32, copy=False)
else:
disp_fft = _fft_mag_to_db(disp_fft_lin)
# Автодиапазон по среднему спектру за видимый интервал (как в хорошей версии) # Автодиапазон по среднему спектру за видимый интервал (как в хорошей версии)
levels = None levels = None
if bg_spec is not None:
try:
p5 = float(np.nanpercentile(disp_fft, 5))
p95 = float(np.nanpercentile(disp_fft, 95))
span = max(abs(p5), abs(p95))
if np.isfinite(span) and span > 0.0:
levels = (-span, span)
except Exception:
levels = None
else:
try: try:
mean_spec = np.nanmean(disp_fft, axis=1) mean_spec = np.nanmean(disp_fft, axis=1)
vmin_v = float(np.nanmin(mean_spec)) vmin_v = float(np.nanmin(mean_spec))
@ -2337,9 +2481,13 @@ def run_pyqtgraph(args):
pass pass
stop_event.set() stop_event.set()
reader.join(timeout=1.0) reader.join(timeout=1.0)
if control_window is not None:
try: try:
control_window.close() main_window.close()
except Exception:
pass
if calib_window is not None:
try:
calib_window.close()
except Exception: except Exception:
pass pass
@ -2352,7 +2500,7 @@ def run_pyqtgraph(args):
except Exception: except Exception:
prev_sigint = None prev_sigint = None
orig_close_event = getattr(win, "closeEvent", None) orig_close_event = getattr(main_window, "closeEvent", None)
def _close_event(event): def _close_event(event):
try: try:
@ -2371,12 +2519,16 @@ def run_pyqtgraph(args):
pass pass
try: try:
win.closeEvent = _close_event # type: ignore[method-assign] main_window.closeEvent = _close_event # type: ignore[method-assign]
except Exception: except Exception:
pass pass
app.aboutToQuit.connect(on_quit) app.aboutToQuit.connect(on_quit)
win.show() try:
main_window.resize(1200, 680)
except Exception:
pass
main_window.show()
exec_fn = getattr(app, "exec_", None) or getattr(app, "exec", None) exec_fn = getattr(app, "exec_", None) or getattr(app, "exec", None)
try: try:
exec_fn() exec_fn()