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
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,
freqs: Optional[np.ndarray],
bins: int,
) -> np.ndarray:
"""Посчитать FFT-строку, используя калиброванную частотную ось при подготовке входа."""
"""Посчитать линейный модуль FFT-строки, используя калиброванную частотную ось."""
if bins <= 0:
return np.zeros((0,), dtype=np.float32)
@ -217,10 +224,18 @@ def _compute_fft_row(
fft_in[:take_fft] = fft_seg * win
spec = np.fft.ifft(fft_in)
mag = np.abs(spec).astype(np.float32)
fft_row = 20.0 * np.log10(mag + 1e-9)
if fft_row.shape[0] != bins:
fft_row = fft_row[:bins]
return fft_row
if mag.shape[0] != bins:
mag = mag[:bins]
return mag
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:
@ -1080,8 +1095,8 @@ def main():
parser.add_argument(
"--backend",
choices=["auto", "pg", "mpl"],
default="auto",
help="Графический бэкенд: pyqtgraph (pg) — быстрее; matplotlib (mpl) — совместимый. По умолчанию auto",
default="pg",
help="Графический бэкенд: pyqtgraph (pg) — быстрее; matplotlib (mpl) — совместимый. По умолчанию pg",
)
parser.add_argument(
"--norm-type",
@ -1119,14 +1134,18 @@ def main():
args = parser.parse_args()
# Попробуем быстрый бэкенд (pyqtgraph) при auto/pg
if args.backend in ("pg"):
if args.backend == "pg":
try:
return run_pyqtgraph(args)
except Exception as e:
if args.backend == "pg":
sys.stderr.write(f"[error] PyQtGraph бэкенд недоступен: {e}\n")
sys.exit(1)
# При auto — тихо откатываемся на matplotlib
sys.stderr.write(f"[error] PyQtGraph бэкенд недоступен: {e}\n")
sys.exit(1)
if args.backend == "auto":
try:
return run_pyqtgraph(args)
except Exception:
pass # При auto — тихо откатываемся на matplotlib
try:
import matplotlib
@ -1186,6 +1205,7 @@ def main():
ymax_slider = None
contrast_slider = None
calib_enabled = False
bg_subtract_enabled = False
peak_calibrate_mode = bool(getattr(args, "calibrate", False))
current_peak_width: Optional[float] = None
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:
return _normalize_by_calib(raw, calib, norm_type=norm_type)
def _set_calib_enabled():
nonlocal calib_enabled, current_sweep_norm
def _sync_checkbox_states():
nonlocal calib_enabled, bg_subtract_enabled, current_sweep_norm
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:
calib_enabled = False
bg_subtract_enabled = False
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)
else:
@ -1299,11 +1322,11 @@ def main():
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_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")
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")
cb = CheckButtons(ax_cb, ["калибровка"], [False])
cb = CheckButtons(ax_cb, ["нормировка", "вычет фона"], [False, False])
def _on_ylim_change(_val):
try:
@ -1318,7 +1341,7 @@ def main():
ymax_slider.on_changed(_on_ylim_change)
# Контраст влияет на верхнюю границу цветовой шкалы (процент от авто-диапазона)
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:
pass
@ -1449,11 +1472,12 @@ def main():
if ring_time is not None:
ring_time[head] = time.time()
head = (head + 1) % ring.shape[0]
# FFT строка (дБ)
# FFT строка (линейный модуль; перевод в дБ делаем при отображении)
if ring_fft is not None:
bins = ring_fft.shape[1]
fft_row = _compute_fft_row(s, freqs, bins)
ring_fft[(head - 1) % ring_fft.shape[0], :] = fft_row
fft_mag = _compute_fft_mag_row(s, freqs, bins)
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_max = np.nanmax(fft_row)
@ -1519,7 +1543,7 @@ def main():
return base_t
def _subtract_recent_mean_fft(disp_fft: np.ndarray) -> np.ndarray:
"""Вычесть среднее по каждой частоте за последние spec_mean_sec секунд."""
"""Вычесть среднее по каждой дальности за последние spec_mean_sec секунд в линейной области."""
if spec_mean_sec <= 0.0:
return disp_fft
disp_times = make_display_times()
@ -1536,6 +1560,33 @@ def main():
mean_spec = np.nan_to_num(mean_spec, nan=0.0)
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():
if ring_fft is None:
return np.zeros((1, 1), dtype=np.float32)
@ -1658,39 +1709,61 @@ def main():
# Обновление водопада спектров
if changed and ring_fft is not None:
disp_fft = make_display_ring_fft()
disp_fft = _subtract_recent_mean_fft(disp_fft)
disp_fft_lin = make_display_ring_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)
# Подписи времени не обновляем динамически (оставляем авто-тики)
# Автодиапазон по среднему спектру за видимый интервал (как в хорошей версии)
try:
# disp_fft имеет форму (bins, time); берём среднее по времени
mean_spec = np.nanmean(disp_fft, axis=1)
vmin_v = float(np.nanmin(mean_spec))
vmax_v = float(np.nanmax(mean_spec))
except Exception:
vmin_v = vmax_v = None
# Если средние не дают валидный диапазон — используем процентильную обрезку (если задана)
if (vmin_v is None or not np.isfinite(vmin_v)) or (vmax_v is None or not np.isfinite(vmax_v)) or vmin_v == vmax_v:
if spec_clip is not None:
try:
vmin_v = float(np.nanpercentile(disp_fft, spec_clip[0]))
vmax_v = float(np.nanpercentile(disp_fft, spec_clip[1]))
except Exception:
vmin_v = vmax_v = None
# Фолбэк к отслеживаемым минимум/максимумам
if (vmin_v is None or not np.isfinite(vmin_v)) or (vmax_v is None or not np.isfinite(vmax_v)) or vmin_v == vmax_v:
if y_min_fft is not None and y_max_fft is not None and np.isfinite(y_min_fft) and np.isfinite(y_max_fft) and y_min_fft != y_max_fft:
vmin_v, vmax_v = y_min_fft, y_max_fft
if vmin_v is not None and vmax_v is not None and vmin_v != vmax_v:
# Применим скалирование контрастом (верхняя граница)
if bg_spec is not None:
try:
c = float(contrast_slider.val) / 100.0 if contrast_slider is not None else 1.0
p5 = float(np.nanpercentile(disp_fft, 5))
p95 = float(np.nanpercentile(disp_fft, 95))
span = max(abs(p5), abs(p95))
except Exception:
c = 1.0
vmax_eff = vmin_v + c * (vmax_v - vmin_v)
img_fft_obj.set_clim(vmin=vmin_v, vmax=vmax_eff)
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:
# disp_fft имеет форму (bins, time); берём среднее по времени
mean_spec = np.nanmean(disp_fft, axis=1)
vmin_v = float(np.nanmin(mean_spec))
vmax_v = float(np.nanmax(mean_spec))
except Exception:
vmin_v = vmax_v = None
# Если средние не дают валидный диапазон — используем процентильную обрезку (если задана)
if (vmin_v is None or not np.isfinite(vmin_v)) or (vmax_v is None or not np.isfinite(vmax_v)) or vmin_v == vmax_v:
if spec_clip is not None:
try:
vmin_v = float(np.nanpercentile(disp_fft, spec_clip[0]))
vmax_v = float(np.nanpercentile(disp_fft, spec_clip[1]))
except Exception:
vmin_v = vmax_v = None
# Фолбэк к отслеживаемым минимум/максимумам
if (vmin_v is None or not np.isfinite(vmin_v)) or (vmax_v is None or not np.isfinite(vmax_v)) or vmin_v == vmax_v:
if y_min_fft is not None and y_max_fft is not None and np.isfinite(y_min_fft) and np.isfinite(y_max_fft) and y_min_fft != y_max_fft:
vmin_v, vmax_v = y_min_fft, y_max_fft
if vmin_v is not None and vmax_v is not None and vmin_v != vmax_v:
# Применим скалирование контрастом (верхняя граница)
try:
c = float(contrast_slider.val) / 100.0 if contrast_slider is not None else 1.0
except Exception:
c = 1.0
vmax_eff = vmin_v + c * (vmax_v - vmin_v)
img_fft_obj.set_clim(vmin=vmin_v, vmax=vmax_eff)
if changed and current_info:
status_payload = dict(current_info)
@ -1776,16 +1849,11 @@ def run_pyqtgraph(args):
peak_calibrate_mode = bool(getattr(args, "calibrate", False))
try:
import pyqtgraph as pg
from PyQt5 import QtCore, QtWidgets # noqa: F401
except Exception:
# Возможно установлена PySide6
try:
import pyqtgraph as pg
from PySide6 import QtCore, QtWidgets # noqa: F401
except Exception as e:
raise RuntimeError(
"pyqtgraph/PyQt5(Pyside6) не найдены. Установите: pip install pyqtgraph PyQt5"
) from e
from pyqtgraph.Qt import QtCore, QtWidgets # type: ignore
except Exception as e:
raise RuntimeError(
"pyqtgraph и совместимый Qt backend не найдены. Установите: pip install pyqtgraph PyQt5"
) from e
# Очередь завершённых свипов и поток чтения
q: Queue[SweepPacket] = Queue(maxsize=1000)
@ -1823,8 +1891,25 @@ def run_pyqtgraph(args):
app.setQuitOnLastWindowClosed(True)
except Exception:
pass
win = pg.GraphicsLayoutWidget(show=True, title=args.title)
win.resize(1200, 600)
main_window = QtWidgets.QWidget()
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="Сырые данные")
@ -1889,20 +1974,17 @@ def run_pyqtgraph(args):
spec_left_line.setVisible(False)
spec_right_line.setVisible(False)
# Отдельное окно контролов: GraphicsLayoutWidget не принимает обычные QWidget через addItem.
calib_cb = QtWidgets.QCheckBox("калибровка")
control_window = None
control_layout = None
if peak_calibrate_mode:
control_window = QtWidgets.QWidget()
try:
control_window.setWindowTitle(f"{args.title} controls")
except Exception:
pass
control_layout = QtWidgets.QVBoxLayout(control_window)
control_layout.setContentsMargins(8, 8, 8, 8)
control_layout.setSpacing(6)
control_layout.addWidget(calib_cb)
# Правая панель настроек внутри основного окна.
calib_cb = QtWidgets.QCheckBox("нормировка")
bg_subtract_cb = QtWidgets.QCheckBox("вычет фона")
try:
settings_title = QtWidgets.QLabel("Настройки")
settings_layout.addWidget(settings_title)
except Exception:
pass
settings_layout.addWidget(calib_cb)
settings_layout.addWidget(bg_subtract_cb)
calib_window = None
# Статусная строка (внизу окна)
status = pg.LabelItem(justify="left")
@ -1931,6 +2013,7 @@ def run_pyqtgraph(args):
spec_clip = _parse_spec_clip(getattr(args, "spec_clip", None))
spec_mean_sec = float(getattr(args, "spec_mean_sec", 0.0))
calib_enabled = False
bg_subtract_enabled = False
current_peak_width: Optional[float] = None
norm_type = str(getattr(args, "norm_type", "projector")).strip().lower()
c_edits = []
@ -1959,16 +2042,31 @@ def run_pyqtgraph(args):
else:
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:
calib_cb.stateChanged.connect(lambda _v: _set_calib_enabled())
except Exception:
pass
try:
bg_subtract_cb.stateChanged.connect(lambda _v: _set_bg_subtract_enabled())
except Exception:
pass
if peak_calibrate_mode:
try:
c_widget = QtWidgets.QWidget()
c_layout = QtWidgets.QFormLayout(c_widget)
c_layout.setContentsMargins(0, 0, 0, 0)
calib_window = QtWidgets.QWidget()
try:
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):
global CALIBRATION_C
@ -1990,17 +2088,18 @@ def run_pyqtgraph(args):
edit.editingFinished.connect(lambda i=idx, e=edit: _apply_c_value(i, e))
except Exception:
pass
c_layout.addRow(f"C{idx}", edit)
calib_layout.addRow(f"C{idx}", edit)
c_edits.append(edit)
if control_layout is not None:
control_layout.addWidget(c_widget)
except Exception:
pass
if control_window is not None:
try:
control_window.show()
try:
calib_window.show()
except Exception:
pass
except Exception:
pass
try:
settings_layout.addStretch(1)
except Exception:
pass
def ensure_buffer(_w: int):
nonlocal ring, ring_time, head, width, x_shared, ring_fft, distance_shared
@ -2076,6 +2175,33 @@ def run_pyqtgraph(args):
return None
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):
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:
@ -2088,11 +2214,12 @@ def run_pyqtgraph(args):
if ring_time is not None:
ring_time[head] = time.time()
head = (head + 1) % ring.shape[0]
# FFT строка (дБ)
# FFT строка (линейный модуль; перевод в дБ делаем при отображении)
if ring_fft is not None:
bins = ring_fft.shape[1]
fft_row = _compute_fft_row(s, freqs, bins)
ring_fft[(head - 1) % ring_fft.shape[0], :] = fft_row
fft_mag = _compute_fft_mag_row(s, freqs, bins)
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_max = np.nanmax(fft_row)
if y_min_fft is None or (not np.isnan(fr_min) and fr_min < y_min_fft):
@ -2272,41 +2399,58 @@ def run_pyqtgraph(args):
pass
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
disp_fft_lin = ring_fft if head == 0 else np.roll(ring_fft, -head, axis=0)
disp_fft_lin = disp_fft_lin.T
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)
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.nanmean(disp_fft_lin[:, mask], axis=1)
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:
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
try:
mean_spec = np.nanmean(disp_fft, axis=1)
vmin_v = float(np.nanmin(mean_spec))
vmax_v = float(np.nanmax(mean_spec))
if np.isfinite(vmin_v) and np.isfinite(vmax_v) and vmin_v != vmax_v:
levels = (vmin_v, vmax_v)
except Exception:
levels = None
# Процентильная обрезка как запасной вариант
if levels is None and spec_clip is not None:
if bg_spec is not None:
try:
vmin_v = float(np.nanpercentile(disp_fft, spec_clip[0]))
vmax_v = float(np.nanpercentile(disp_fft, spec_clip[1]))
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:
mean_spec = np.nanmean(disp_fft, axis=1)
vmin_v = float(np.nanmin(mean_spec))
vmax_v = float(np.nanmax(mean_spec))
if np.isfinite(vmin_v) and np.isfinite(vmax_v) and vmin_v != vmax_v:
levels = (vmin_v, vmax_v)
except Exception:
levels = None
# Ещё один фолбэк — глобальные накопленные мин/макс
if levels is None and y_min_fft is not None and y_max_fft is not None and np.isfinite(y_min_fft) and np.isfinite(y_max_fft) and y_min_fft != y_max_fft:
levels = (y_min_fft, y_max_fft)
# Процентильная обрезка как запасной вариант
if levels is None and spec_clip is not None:
try:
vmin_v = float(np.nanpercentile(disp_fft, spec_clip[0]))
vmax_v = float(np.nanpercentile(disp_fft, spec_clip[1]))
if np.isfinite(vmin_v) and np.isfinite(vmax_v) and vmin_v != vmax_v:
levels = (vmin_v, vmax_v)
except Exception:
levels = None
# Ещё один фолбэк — глобальные накопленные мин/макс
if levels is None and y_min_fft is not None and y_max_fft is not None and np.isfinite(y_min_fft) and np.isfinite(y_max_fft) and y_min_fft != y_max_fft:
levels = (y_min_fft, y_max_fft)
if levels is not None:
img_fft.setImage(disp_fft, autoLevels=False, levels=levels)
else:
@ -2337,9 +2481,13 @@ def run_pyqtgraph(args):
pass
stop_event.set()
reader.join(timeout=1.0)
if control_window is not None:
try:
main_window.close()
except Exception:
pass
if calib_window is not None:
try:
control_window.close()
calib_window.close()
except Exception:
pass
@ -2352,7 +2500,7 @@ def run_pyqtgraph(args):
except Exception:
prev_sigint = None
orig_close_event = getattr(win, "closeEvent", None)
orig_close_event = getattr(main_window, "closeEvent", None)
def _close_event(event):
try:
@ -2371,12 +2519,16 @@ def run_pyqtgraph(args):
pass
try:
win.closeEvent = _close_event # type: ignore[method-assign]
main_window.closeEvent = _close_event # type: ignore[method-assign]
except Exception:
pass
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)
try:
exec_fn()