complex calib add

This commit is contained in:
awe
2026-04-14 19:47:28 +03:00
parent cbd76cfd54
commit 5aa4da9beb
6 changed files with 384 additions and 22 deletions

View File

@ -21,16 +21,23 @@ from rfg_adc_plotter.processing.background import (
)
from rfg_adc_plotter.processing.calibration import (
build_calib_envelope,
build_complex_calibration_curve,
calibrate_freqs,
get_calibration_base,
get_calibration_coeffs,
load_calib_envelope,
load_complex_calibration,
save_calib_envelope,
save_complex_calibration,
set_calibration_base_value,
)
from rfg_adc_plotter.processing.fft import compute_fft_complex_row, compute_fft_mag_row, fft_mag_to_db
from rfg_adc_plotter.processing.formatting import compute_auto_ylim, format_status_kv, parse_spec_clip
from rfg_adc_plotter.processing.normalization import normalize_by_envelope, resample_envelope
from rfg_adc_plotter.processing.normalization import (
normalize_by_complex_calibration,
normalize_by_envelope,
resample_envelope,
)
from rfg_adc_plotter.processing.peaks import (
find_peak_width_markers,
find_top_peaks_over_ref,
@ -809,6 +816,7 @@ def run_pyqtgraph(args) -> None:
spec_right_line.setVisible(False)
calib_cb = QtWidgets.QCheckBox("калибровка по огибающей")
complex_calib_cb = QtWidgets.QCheckBox("комплексная калибровка")
range_group = QtWidgets.QGroupBox("Рабочий диапазон")
range_group_layout = QtWidgets.QFormLayout(range_group)
range_group_layout.setContentsMargins(6, 6, 6, 6)
@ -869,6 +877,36 @@ def run_pyqtgraph(args) -> None:
calib_buttons_row.addWidget(calib_save_btn)
calib_buttons_row.addWidget(calib_load_btn)
calib_group_layout.addLayout(calib_buttons_row)
complex_calib_group = QtWidgets.QGroupBox("Комплексная калибровка")
complex_calib_group_layout = QtWidgets.QVBoxLayout(complex_calib_group)
complex_calib_group_layout.setContentsMargins(6, 6, 6, 6)
complex_calib_group_layout.setSpacing(6)
complex_calib_group_layout.addWidget(complex_calib_cb)
complex_calib_path_edit = QtWidgets.QLineEdit("complex_calibration.npy")
try:
complex_calib_path_edit.setPlaceholderText("complex_calibration.npy")
except Exception:
pass
complex_calib_path_row = QtWidgets.QHBoxLayout()
complex_calib_path_row.setContentsMargins(0, 0, 0, 0)
complex_calib_path_row.setSpacing(4)
complex_calib_path_row.addWidget(complex_calib_path_edit)
complex_calib_pick_btn = QtWidgets.QPushButton("Файл...")
complex_calib_path_row.addWidget(complex_calib_pick_btn)
complex_calib_group_layout.addLayout(complex_calib_path_row)
complex_calib_buttons_row = QtWidgets.QHBoxLayout()
complex_calib_buttons_row.setContentsMargins(0, 0, 0, 0)
complex_calib_buttons_row.setSpacing(4)
complex_calib_save_btn = QtWidgets.QPushButton("Сохранить текущую")
complex_calib_load_btn = QtWidgets.QPushButton("Загрузить")
complex_calib_buttons_row.addWidget(complex_calib_save_btn)
complex_calib_buttons_row.addWidget(complex_calib_load_btn)
complex_calib_group_layout.addLayout(complex_calib_buttons_row)
if not complex_sweep_mode:
try:
complex_calib_group.setEnabled(False)
except Exception:
pass
background_group = QtWidgets.QGroupBox("Фон")
background_group_layout = QtWidgets.QVBoxLayout(background_group)
background_group_layout.setContentsMargins(6, 6, 6, 6)
@ -927,6 +965,7 @@ def run_pyqtgraph(args) -> None:
pass
settings_layout.addWidget(range_group)
settings_layout.addWidget(calib_group)
settings_layout.addWidget(complex_calib_group)
settings_layout.addWidget(parsed_data_cb)
settings_layout.addWidget(background_group)
settings_layout.addWidget(fft_mode_label)
@ -940,6 +979,7 @@ def run_pyqtgraph(args) -> None:
win.addItem(status, row=3, col=0, colspan=2)
calib_enabled = False
complex_calib_enabled = False
parsed_data_enabled = False
background_enabled = False
fft_abs_enabled = True
@ -951,6 +991,7 @@ def run_pyqtgraph(args) -> None:
waiting_data_note = ""
status_note_expires_at: Optional[float] = None
status_dirty = True
calibration_toggle_in_progress = False
range_change_in_progress = False
debug_event_counts: Dict[str, int] = {}
last_queue_backlog = 0
@ -1097,6 +1138,13 @@ def run_pyqtgraph(args) -> None:
path = ""
return path or "calibration_envelope.npy"
def get_complex_calib_file_path() -> str:
try:
path = complex_calib_path_edit.text().strip()
except Exception:
path = ""
return path or "complex_calibration.npy"
def get_background_file_path() -> str:
try:
path = background_path_edit.text().strip()
@ -1198,25 +1246,69 @@ def run_pyqtgraph(args) -> None:
if fft_source is None and runtime.current_sweep_raw is not None:
fft_source = np.asarray(runtime.current_sweep_raw, dtype=np.float32)
if (
runtime.current_sweep_raw is not None
and runtime.current_sweep_raw.size > 0
and calib_enabled
and runtime.calib_envelope is not None
):
runtime.current_sweep_norm = normalize_by_envelope(runtime.current_sweep_raw, runtime.calib_envelope)
else:
runtime.current_sweep_norm = None
runtime.current_sweep_norm = None
runtime.current_fft_input = None
complex_calib_applied = False
if complex_calib_enabled and runtime.complex_calib_curve is not None:
complex_source: Optional[np.ndarray] = None
if runtime.current_aux_curves is not None:
try:
aux_1, aux_2 = runtime.current_aux_curves
aux_1_arr = np.asarray(aux_1, dtype=np.float32).reshape(-1)
aux_2_arr = np.asarray(aux_2, dtype=np.float32).reshape(-1)
aux_width = min(aux_1_arr.size, aux_2_arr.size)
if aux_width > 0:
complex_source = (
aux_1_arr[:aux_width].astype(np.complex64)
+ (1j * aux_2_arr[:aux_width].astype(np.complex64))
)
except Exception:
complex_source = None
if complex_source is None and fft_source is not None:
fft_arr = np.asarray(fft_source).reshape(-1)
if fft_arr.size > 0 and np.iscomplexobj(fft_arr):
complex_source = np.asarray(fft_arr, dtype=np.complex64)
if fft_source is None or np.asarray(fft_source).size == 0:
runtime.current_fft_input = None
elif calib_enabled and runtime.calib_envelope is not None:
runtime.current_fft_input = normalize_by_envelope(fft_source, runtime.calib_envelope)
else:
runtime.current_fft_input = np.asarray(
fft_source,
dtype=np.complex64 if np.iscomplexobj(fft_source) else np.float32,
).copy()
if complex_source is not None and complex_source.size > 0:
complex_norm = normalize_by_complex_calibration(
complex_source,
runtime.complex_calib_curve,
)
runtime.current_fft_input = np.asarray(complex_norm, dtype=np.complex64).reshape(-1)
norm_real = runtime.current_fft_input.real.astype(np.float32, copy=False)
norm_imag = runtime.current_fft_input.imag.astype(np.float32, copy=False)
runtime.current_aux_curves = (norm_real, norm_imag)
if bin_iq_power_mode:
norm_real_f64 = norm_real.astype(np.float64, copy=False)
norm_imag_f64 = norm_imag.astype(np.float64, copy=False)
runtime.current_sweep_norm = np.asarray(
(norm_real_f64 * norm_real_f64) + (norm_imag_f64 * norm_imag_f64),
dtype=np.float32,
)
else:
runtime.current_sweep_norm = np.abs(runtime.current_fft_input).astype(np.float32, copy=False)
complex_calib_applied = True
if not complex_calib_applied:
if (
runtime.current_sweep_raw is not None
and runtime.current_sweep_raw.size > 0
and calib_enabled
and runtime.calib_envelope is not None
):
runtime.current_sweep_norm = normalize_by_envelope(runtime.current_sweep_raw, runtime.calib_envelope)
else:
runtime.current_sweep_norm = None
if fft_source is None or np.asarray(fft_source).size == 0:
runtime.current_fft_input = None
elif calib_enabled and runtime.calib_envelope is not None:
runtime.current_fft_input = normalize_by_envelope(fft_source, runtime.calib_envelope)
else:
runtime.current_fft_input = np.asarray(
fft_source,
dtype=np.complex64 if np.iscomplexobj(fft_source) else np.float32,
).copy()
runtime.current_fft_complex = None
runtime.current_fft_mag = None
@ -1246,17 +1338,67 @@ def run_pyqtgraph(args) -> None:
runtime.background_buffer.push(runtime.current_fft_mag)
def set_calib_enabled() -> None:
nonlocal calib_enabled
nonlocal calib_enabled, complex_calib_enabled, calibration_toggle_in_progress
try:
calib_enabled = bool(calib_cb.isChecked())
requested_state = bool(calib_cb.isChecked())
except Exception:
calib_enabled = False
requested_state = False
if calibration_toggle_in_progress:
calib_enabled = requested_state
return
if requested_state and complex_calib_enabled:
calibration_toggle_in_progress = True
try:
complex_calib_cb.setChecked(False)
except Exception:
pass
finally:
calibration_toggle_in_progress = False
complex_calib_enabled = False
set_status_note("калибровка: комплексная выключена (взаимоисключение)")
calib_enabled = requested_state
if calib_enabled and runtime.calib_envelope is None:
set_status_note("калибровка: огибающая не загружена")
reset_background_state(clear_profile=True)
recompute_current_processed_sweep(push_to_ring=False)
runtime.mark_dirty()
def set_complex_calib_enabled() -> None:
nonlocal calib_enabled, complex_calib_enabled, calibration_toggle_in_progress
if not complex_sweep_mode:
complex_calib_enabled = False
return
try:
requested_state = bool(complex_calib_cb.isChecked())
except Exception:
requested_state = False
if calibration_toggle_in_progress:
complex_calib_enabled = requested_state
return
if requested_state and calib_enabled:
calibration_toggle_in_progress = True
try:
calib_cb.setChecked(False)
except Exception:
pass
finally:
calibration_toggle_in_progress = False
calib_enabled = False
set_status_note("калибровка: огибающая выключена (взаимоисключение)")
complex_calib_enabled = requested_state
if complex_calib_enabled and runtime.complex_calib_curve is None:
set_status_note("калибровка: комплексная кривая не загружена")
reset_background_state(clear_profile=True)
recompute_current_processed_sweep(push_to_ring=False)
runtime.mark_dirty()
def set_parsed_data_enabled() -> None:
nonlocal parsed_data_enabled
try:
@ -1332,6 +1474,26 @@ def run_pyqtgraph(args) -> None:
except Exception:
pass
def pick_complex_calib_file() -> None:
start_path = get_complex_calib_file_path()
try:
selected, _ = QtWidgets.QFileDialog.getSaveFileName(
main_window,
"Файл комплексной калибровки",
start_path,
"NumPy (*.npy);;All files (*)",
)
except Exception as exc:
set_status_note(f"калибровка: выбор файла комплексной кривой недоступен ({exc})")
runtime.mark_dirty()
return
if not selected:
return
try:
complex_calib_path_edit.setText(selected)
except Exception:
pass
def pick_background_file() -> None:
start_path = get_background_file_path()
try:
@ -1399,6 +1561,56 @@ def run_pyqtgraph(args) -> None:
set_status_note(f"калибровка загружена: {normalized_path}")
runtime.mark_dirty()
def save_current_complex_calibration() -> None:
if not complex_sweep_mode:
set_status_note("калибровка: комплексный режим не активен")
runtime.mark_dirty()
return
if runtime.full_current_aux_curves is None:
set_status_note("калибровка: нет CH1/CH2 для сохранения комплексной кривой")
runtime.mark_dirty()
return
try:
aux_1, aux_2 = runtime.full_current_aux_curves
curve = build_complex_calibration_curve(aux_1, aux_2)
saved_path = save_complex_calibration(get_complex_calib_file_path(), curve)
except Exception as exc:
set_status_note(f"калибровка: не удалось сохранить комплексную кривую ({exc})")
runtime.mark_dirty()
return
runtime.complex_calib_curve = curve
runtime.complex_calib_file_path = saved_path
try:
complex_calib_path_edit.setText(saved_path)
except Exception:
pass
reset_background_state(clear_profile=True)
recompute_current_processed_sweep(push_to_ring=False)
set_status_note(f"комплексная калибровка сохранена: {saved_path}")
runtime.mark_dirty()
def load_complex_calibration_file() -> None:
path = get_complex_calib_file_path()
try:
curve = load_complex_calibration(path)
except Exception as exc:
set_status_note(f"калибровка: не удалось загрузить комплексную кривую ({exc})")
runtime.mark_dirty()
return
normalized_path = path if path.lower().endswith(".npy") else f"{path}.npy"
runtime.complex_calib_curve = curve
runtime.complex_calib_file_path = normalized_path
try:
complex_calib_path_edit.setText(normalized_path)
except Exception:
pass
reset_background_state(clear_profile=True)
recompute_current_processed_sweep(push_to_ring=False)
set_status_note(f"комплексная калибровка загружена: {normalized_path}")
runtime.mark_dirty()
def save_current_background() -> None:
background = runtime.background_buffer.median()
if background is None or background.size == 0:
@ -1494,6 +1706,8 @@ def run_pyqtgraph(args) -> None:
except Exception:
pass
restore_range_controls()
set_calib_enabled()
set_complex_calib_enabled()
set_parsed_data_enabled()
set_background_enabled()
set_fft_curve_visibility()
@ -1504,10 +1718,14 @@ def run_pyqtgraph(args) -> None:
range_min_spin.valueChanged.connect(lambda _v: set_working_range())
range_max_spin.valueChanged.connect(lambda _v: set_working_range())
calib_cb.stateChanged.connect(lambda _v: set_calib_enabled())
complex_calib_cb.stateChanged.connect(lambda _v: set_complex_calib_enabled())
parsed_data_cb.stateChanged.connect(lambda _v: set_parsed_data_enabled())
calib_pick_btn.clicked.connect(lambda _checked=False: pick_calib_file())
calib_save_btn.clicked.connect(lambda _checked=False: save_current_calibration())
calib_load_btn.clicked.connect(lambda _checked=False: load_calibration_file())
complex_calib_pick_btn.clicked.connect(lambda _checked=False: pick_complex_calib_file())
complex_calib_save_btn.clicked.connect(lambda _checked=False: save_current_complex_calibration())
complex_calib_load_btn.clicked.connect(lambda _checked=False: load_complex_calibration_file())
background_cb.stateChanged.connect(lambda _v: set_background_enabled())
background_pick_btn.clicked.connect(lambda _checked=False: pick_background_file())
background_save_btn.clicked.connect(lambda _checked=False: save_current_background())