added pass through s11

This commit is contained in:
Ayzen
2026-03-26 17:21:25 +03:00
parent 24f7ebb2fb
commit 9ddbde22bd
38 changed files with 945 additions and 291 deletions
+2 -2
View File
@@ -78,8 +78,8 @@ class AppWindow(
def _init_preprocess_state(self) -> None:
"""Initialize preprocessing dialog and selected set names."""
self._preprocess_dialog: PreprocessDialog | None = None
self._selected_calibration_set = str(self._defaults_config.preprocess.calibration_set)
self._selected_reference_set = str(self._defaults_config.preprocess.reference_set)
self._selected_s21_calibration_set = str(self._defaults_config.preprocess.s21_calibration_set)
self._selected_s21_reference_set = str(self._defaults_config.preprocess.s21_reference_set)
def _init_capture_state(self) -> None:
"""Initialize one-shot capture and sequence-control flags."""
@@ -118,8 +118,8 @@ class AppWindowConfigMixin:
if self._switches_are_effectively_static(config):
config.combos = [ComboModel(input=0, output=0)]
config.preprocess.calibration_set = self._selected_calibration_set
config.preprocess.reference_set = self._selected_reference_set
config.preprocess.s21_calibration_set = self._selected_s21_calibration_set
config.preprocess.s21_reference_set = self._selected_s21_reference_set
config.gpr.mode = self._gpr_config_mode.currentText()
config.gpr.relative_permittivity = float(self._gpr_relative_permittivity.value())
config.gpr.tx_geometry = self._parse_gpr_tx_geometry_text(self._gpr_tx_geometry_input.toPlainText())
@@ -153,6 +153,7 @@ class AppWindowConfigMixin:
processor_mode=self._processing_mode.currentText(),
gain_db=float(self._processing_gain_db.value()),
phase_deg=float(self._processing_phase_deg.value()),
pass_through_channel=self._pass_through_channel.currentText(),
pass_through_fixed_y_enabled=bool(self._pass_through_fixed_y_enabled.isChecked()),
pass_through_y_min_db=min(y_min_db, y_max_db),
pass_through_y_max_db=max(y_min_db, y_max_db),
@@ -38,28 +38,28 @@ class AppWindowPipelineMixin:
run_signature = self._build_run_history_signature(config)
radar_key = self._radar_key(config)
if not config.preprocess.calibration_set or not config.preprocess.reference_set:
if not config.preprocess.s21_calibration_set or not config.preprocess.s21_reference_set:
raise RuntimeError("Select calibration and reference sets in Preprocessing Panel before Start")
combo_keys = [ComboKey(input_pos=combo.input, output_pos=combo.output) for combo in config.combos]
if not self._store.has_combo_coverage(
"calibration", radar_key, config.preprocess.calibration_set, combo_keys
"calibration", radar_key, config.preprocess.s21_calibration_set, combo_keys
):
raise RuntimeError("Selected calibration set does not cover requested run combos")
if not self._store.has_combo_coverage(
"reference", radar_key, config.preprocess.reference_set, combo_keys
"reference", radar_key, config.preprocess.s21_reference_set, combo_keys
):
raise RuntimeError("Selected reference set does not cover requested run combos")
calibration_bundle, reference_bundle = self._config_writer.prepare_bundles(
calibration_bundle, reference_bundle = self._config_writer.prepare_s21_bundles(
self._store,
radar_key,
config.preprocess.calibration_set,
config.preprocess.reference_set,
config.preprocess.s21_calibration_set,
config.preprocess.s21_reference_set,
)
config.preprocess.calibration_bundle_path = str(calibration_bundle)
config.preprocess.reference_bundle_path = str(reference_bundle)
config.preprocess.s21_calibration_bundle_path = str(calibration_bundle)
config.preprocess.s21_reference_bundle_path = str(reference_bundle)
config.runtime.continuous = not single_capture
if not single_capture:
@@ -106,6 +106,7 @@ class AppWindowPlotMixin:
show_phase = self._show_phase_curves()
magnitude_plot = self._trace_magnitude_plot
phase_plot = self._trace_phase_plot
pass_through_channel = self._pass_through_channel.currentText().upper()
magnitude_plot.setVisible(show_magnitude)
phase_plot.setVisible(show_phase)
@@ -119,6 +120,7 @@ class AppWindowPlotMixin:
mag_item.showAxis("left", show=True)
mag_item.showAxis("bottom", show=not show_phase)
magnitude_plot.setLabel("left", "Magnitude", units="dB")
magnitude_plot.setTitle(f"Pass-Through {pass_through_channel}")
if not show_phase:
magnitude_plot.setLabel("bottom", "Frequency", units="Hz")
@@ -130,6 +132,7 @@ class AppWindowPlotMixin:
phase_item.showAxis("bottom", show=True)
phase_plot.setLabel("left", "Phase", units="deg")
phase_plot.setLabel("bottom", "Frequency", units="Hz")
phase_plot.setTitle(f"Pass-Through {pass_through_channel}")
palette = [
"#4cc9f0",
@@ -40,14 +40,14 @@ class AppWindowPreprocessMixin:
def _on_preprocess_selection_changed(self, calibration_set: str, reference_set: str) -> None:
"""Persist selected preprocessing set names from dialog."""
self._selected_calibration_set = calibration_set.strip()
self._selected_reference_set = reference_set.strip()
self._selected_s21_calibration_set = calibration_set.strip()
self._selected_s21_reference_set = reference_set.strip()
self._refresh_preprocess_summary_labels()
def _refresh_preprocess_summary_labels(self) -> None:
"""Update compact summary labels in the main window."""
self._selected_calibration_label.setText(self._selected_calibration_set or "<not selected>")
self._selected_reference_label.setText(self._selected_reference_set or "<not selected>")
self._selected_calibration_label.setText(self._selected_s21_calibration_set or "<not selected>")
self._selected_reference_label.setText(self._selected_s21_reference_set or "<not selected>")
def _refresh_sets(self) -> None:
"""Refresh calibration/reference set lists for current radar key."""
@@ -60,12 +60,12 @@ class AppWindowPreprocessMixin:
dialog.set_calibration_sets(calibration_sets)
dialog.set_reference_sets(reference_sets)
if self._selected_calibration_set not in calibration_sets:
self._selected_calibration_set = calibration_sets[0] if calibration_sets else ""
if self._selected_reference_set not in reference_sets:
self._selected_reference_set = reference_sets[0] if reference_sets else ""
if self._selected_s21_calibration_set not in calibration_sets:
self._selected_s21_calibration_set = calibration_sets[0] if calibration_sets else ""
if self._selected_s21_reference_set not in reference_sets:
self._selected_s21_reference_set = reference_sets[0] if reference_sets else ""
dialog.set_selected_sets(self._selected_calibration_set, self._selected_reference_set)
dialog.set_selected_sets(self._selected_s21_calibration_set, self._selected_s21_reference_set)
self._refresh_preprocess_summary_labels()
self._log(f"Set lists refreshed for key={radar_key}")
@@ -147,9 +147,9 @@ class AppWindowPreprocessMixin:
self._cleanup_capture_session()
if kind == "calibration":
self._selected_calibration_set = set_name
self._selected_s21_calibration_set = set_name
else:
self._selected_reference_set = set_name
self._selected_s21_reference_set = set_name
self._refresh_sets()
dialog.set_status(f"{kind.title()} set saved: {set_name} ({len(collection.traces)} traces)")
@@ -79,7 +79,7 @@ class AppWindowUiMixin:
# Default view on startup is pass-through traces.
self._plot_stack.setCurrentWidget(self._trace_plots_container)
root_layout.addWidget(self._plot_stack, stretch=11)
root_layout.addWidget(self._plot_stack, stretch=12)
def _build_bscan_plot_page(self) -> None:
"""Create B-scan page in plot stack."""
@@ -141,7 +141,7 @@ class AppWindowUiMixin:
def _build_settings_panel(self, root_layout: QHBoxLayout, root: QWidget) -> None:
"""Build right settings panel with controls, status labels, and log."""
self._settings_panel = QWidget(root)
self._settings_panel.setMinimumWidth(659)
self._settings_panel.setMinimumWidth(530)
right_layout = QVBoxLayout(self._settings_panel)
right_layout.setContentsMargins(0, 0, 0, 0)
right_layout.setSpacing(10)
@@ -163,7 +163,7 @@ class AppWindowUiMixin:
right_layout.addWidget(self._history_label)
right_layout.addWidget(self._log_box, stretch=0)
root_layout.addWidget(self._settings_panel, stretch=8)
root_layout.addWidget(self._settings_panel, stretch=6)
def _build_settings_scroll(self) -> QScrollArea:
"""Build scroll area with all control groups in display order."""
@@ -2,6 +2,7 @@
from __future__ import annotations
from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import QGroupBox, QHBoxLayout, QLabel, QLineEdit, QPushButton, QSpinBox, QVBoxLayout
@@ -11,16 +12,13 @@ def build_data_actions_group(owner) -> QGroupBox:
layout = QVBoxLayout(group)
layout.setSpacing(8)
save_row = QHBoxLayout()
save_row.setSpacing(8)
save_button = QPushButton("Save Numpy Snapshot")
save_button = QPushButton("Save Snapshot")
save_button.clicked.connect(owner._save_snapshot)
save_vna_json_button = QPushButton("Save VNA History JSON")
save_vna_json_button = QPushButton("Save VNA JSON")
save_vna_json_button.clicked.connect(owner._save_vna_history_json)
remove_last_button = QPushButton("Remove Last Runtime Measurement")
remove_last_button = QPushButton("Remove Last Measurement")
remove_last_button.clicked.connect(owner._remove_last_runtime_history)
clear_history_button = QPushButton("Clear ALL Runtime History")
clear_history_button = QPushButton("Clear Runtime History")
clear_history_button.clicked.connect(owner._clear_all_runtime_history)
owner._save_count = QSpinBox()
owner._save_count.setMinimum(1)
@@ -35,14 +33,20 @@ def build_data_actions_group(owner) -> QGroupBox:
owner._vna_json_output_index.setMaximum(65_535)
owner._vna_json_output_index.setValue(0)
save_row.addWidget(save_button)
save_row.addWidget(save_vna_json_button)
save_row.addWidget(remove_last_button)
save_row.addWidget(clear_history_button)
save_row.addWidget(QLabel("Last N"))
save_row.addWidget(owner._save_count)
save_row.addStretch(1)
layout.addLayout(save_row)
button_column = QVBoxLayout()
button_column.setSpacing(8)
button_column.addWidget(save_button, alignment=Qt.AlignmentFlag.AlignLeft)
button_column.addWidget(save_vna_json_button, alignment=Qt.AlignmentFlag.AlignLeft)
button_column.addWidget(remove_last_button, alignment=Qt.AlignmentFlag.AlignLeft)
button_column.addWidget(clear_history_button, alignment=Qt.AlignmentFlag.AlignLeft)
layout.addLayout(button_column)
count_row = QHBoxLayout()
count_row.setSpacing(8)
count_row.addWidget(QLabel("Last N"))
count_row.addWidget(owner._save_count)
count_row.addStretch(1)
layout.addLayout(count_row)
json_row = QHBoxLayout()
json_row.setSpacing(8)
@@ -2,24 +2,25 @@
from __future__ import annotations
from PyQt6.QtWidgets import QGroupBox, QHBoxLayout, QPushButton
from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import QGroupBox, QPushButton, QVBoxLayout
def build_hardware_actions_group(owner) -> QGroupBox:
"""Create hardware action buttons section."""
group = QGroupBox("Hardware Actions")
layout = QHBoxLayout(group)
layout = QVBoxLayout(group)
layout.setSpacing(8)
apply_radar_button = QPushButton("Apply Radar Settings")
apply_radar_button = QPushButton("Apply Radar")
apply_radar_button.clicked.connect(owner._apply_radar_settings)
layout.addWidget(apply_radar_button)
layout.addWidget(apply_radar_button, alignment=Qt.AlignmentFlag.AlignLeft)
save_config_button = QPushButton("Save Current Config")
save_config_button = QPushButton("Save Config")
save_config_button.clicked.connect(owner._save_current_config)
layout.addWidget(save_config_button)
layout.addWidget(save_config_button, alignment=Qt.AlignmentFlag.AlignLeft)
preprocess_button = QPushButton("Preprocessing Panel")
preprocess_button = QPushButton("Preprocessing")
preprocess_button.clicked.connect(owner._open_preprocess_panel)
layout.addWidget(preprocess_button)
layout.addWidget(preprocess_button, alignment=Qt.AlignmentFlag.AlignLeft)
return group
@@ -2,7 +2,8 @@
from __future__ import annotations
from PyQt6.QtWidgets import QGroupBox, QHBoxLayout, QLabel, QPushButton, QVBoxLayout
from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import QGroupBox, QLabel, QPushButton, QVBoxLayout
def build_pipeline_group(owner) -> QGroupBox:
@@ -11,22 +12,17 @@ def build_pipeline_group(owner) -> QGroupBox:
layout = QVBoxLayout(group)
layout.setSpacing(8)
action_row = QHBoxLayout()
action_row.setSpacing(8)
start_button = QPushButton("Start")
start_button.clicked.connect(owner._start_run)
action_row.addWidget(start_button)
layout.addWidget(start_button, alignment=Qt.AlignmentFlag.AlignLeft)
single_button = QPushButton("Single Capture")
single_button.clicked.connect(owner._start_single_capture)
action_row.addWidget(single_button)
layout.addWidget(single_button, alignment=Qt.AlignmentFlag.AlignLeft)
stop_button = QPushButton("Stop")
stop_button.clicked.connect(owner._stop_run)
action_row.addWidget(stop_button)
layout.addLayout(action_row)
layout.addWidget(stop_button, alignment=Qt.AlignmentFlag.AlignLeft)
hint = QLabel("Start continuous run or single processed collection capture.")
hint.setObjectName("hintLabel")
@@ -61,6 +61,9 @@ def build_processing_group(owner) -> QGroupBox:
owner._processing_phase_deg.setSingleStep(1.0)
owner._processing_phase_deg.setValue(0.0)
owner._pass_through_channel = QComboBox()
owner._pass_through_channel.addItems(["s21", "s11"])
owner._show_magnitude_checkbox = QCheckBox("Show magnitude")
owner._show_magnitude_checkbox.setChecked(True)
@@ -91,6 +94,7 @@ def build_processing_group(owner) -> QGroupBox:
pass_through_form.addRow("Gain dB (live)", owner._processing_gain_db)
pass_through_form.addRow("Phase deg (live)", owner._processing_phase_deg)
pass_through_form.addRow("Channel", owner._pass_through_channel)
pass_through_form.addRow(owner._show_magnitude_checkbox)
pass_through_form.addRow(owner._show_phase_checkbox)
pass_through_form.addRow(owner._pass_through_fixed_y_enabled)
@@ -206,6 +210,7 @@ def build_processing_group(owner) -> QGroupBox:
owner._processing_mode.currentTextChanged.connect(owner._on_processing_mode_changed)
owner._processing_gain_db.valueChanged.connect(owner._on_processing_live_settings_changed)
owner._processing_phase_deg.valueChanged.connect(owner._on_processing_live_settings_changed)
owner._pass_through_channel.currentTextChanged.connect(owner._on_processing_live_settings_changed)
owner._show_magnitude_checkbox.toggled.connect(owner._on_trace_visibility_changed)
owner._show_phase_checkbox.toggled.connect(owner._on_trace_visibility_changed)
owner._pass_through_fixed_y_enabled.toggled.connect(sync_pass_through_y_controls)
+1 -1
View File
@@ -23,7 +23,7 @@ def main() -> int:
apply_dark_theme(app)
pg.setConfigOptions(antialias=True, foreground="#dbe4f1")
window = AppWindow(PROJECT_ROOT)
window.show()
window.showMaximized()
return app.exec()
+2 -2
View File
@@ -79,8 +79,8 @@ def build_run_history_signature(
str(config.output_switch.driver),
int(config.output_switch.positions),
bool(config.output_switch.invert_logic),
str(config.preprocess.calibration_set),
str(config.preprocess.reference_set),
str(config.preprocess.s21_calibration_set),
str(config.preprocess.s21_reference_set),
combos_signature,
)
+49 -10
View File
@@ -65,13 +65,47 @@ def run_config_from_dict(payload: dict[str, Any]) -> RunConfigModel:
run_payload.get("processing_live_config_path", model.runtime.processing_live_config_path)
)
model.preprocess.calibration_set = str(preprocess_payload.get("calibration_set", model.preprocess.calibration_set))
model.preprocess.reference_set = str(preprocess_payload.get("reference_set", model.preprocess.reference_set))
model.preprocess.calibration_bundle_path = str(
preprocess_payload.get("calibration_bundle_path", model.preprocess.calibration_bundle_path)
model.preprocess.s21_calibration_set = str(
preprocess_payload.get("s21_calibration_set", model.preprocess.s21_calibration_set)
)
model.preprocess.reference_bundle_path = str(
preprocess_payload.get("reference_bundle_path", model.preprocess.reference_bundle_path)
model.preprocess.s21_reference_set = str(
preprocess_payload.get("s21_reference_set", model.preprocess.s21_reference_set)
)
model.preprocess.s21_calibration_bundle_path = str(
preprocess_payload.get(
"s21_calibration_bundle_path",
model.preprocess.s21_calibration_bundle_path,
)
)
model.preprocess.s21_reference_bundle_path = str(
preprocess_payload.get(
"s21_reference_bundle_path",
model.preprocess.s21_reference_bundle_path,
)
)
model.preprocess.s11_open_calibration_bundle_path = str(
preprocess_payload.get(
"s11_open_calibration_bundle_path",
model.preprocess.s11_open_calibration_bundle_path,
)
)
model.preprocess.s11_short_calibration_bundle_path = str(
preprocess_payload.get(
"s11_short_calibration_bundle_path",
model.preprocess.s11_short_calibration_bundle_path,
)
)
model.preprocess.s11_load_calibration_bundle_path = str(
preprocess_payload.get(
"s11_load_calibration_bundle_path",
model.preprocess.s11_load_calibration_bundle_path,
)
)
model.preprocess.s11_reference_bundle_path = str(
preprocess_payload.get(
"s11_reference_bundle_path",
model.preprocess.s11_reference_bundle_path,
)
)
model.gpr.mode = str(gpr_payload.get("mode", model.gpr.mode))
@@ -127,6 +161,7 @@ def run_config_from_dict(payload: dict[str, Any]) -> RunConfigModel:
model.ensure_combos()
return model
def run_config_to_dict(model: RunConfigModel) -> dict[str, Any]:
"""Encode :class:`RunConfigModel` to C++ pipeline-compatible JSON structure."""
model.ensure_combos()
@@ -178,10 +213,14 @@ def run_config_to_dict(model: RunConfigModel) -> dict[str, Any]:
"combos": [{"input": combo.input, "output": combo.output} for combo in model.combos],
},
"preprocess": {
"calibration_set": model.preprocess.calibration_set,
"reference_set": model.preprocess.reference_set,
"calibration_bundle_path": model.preprocess.calibration_bundle_path,
"reference_bundle_path": model.preprocess.reference_bundle_path,
"s21_calibration_set": model.preprocess.s21_calibration_set,
"s21_reference_set": model.preprocess.s21_reference_set,
"s21_calibration_bundle_path": model.preprocess.s21_calibration_bundle_path,
"s21_reference_bundle_path": model.preprocess.s21_reference_bundle_path,
"s11_open_calibration_bundle_path": model.preprocess.s11_open_calibration_bundle_path,
"s11_short_calibration_bundle_path": model.preprocess.s11_short_calibration_bundle_path,
"s11_load_calibration_bundle_path": model.preprocess.s11_load_calibration_bundle_path,
"s11_reference_bundle_path": model.preprocess.s11_reference_bundle_path,
},
"gpr": {
"mode": model.gpr.mode,
+8 -4
View File
@@ -89,10 +89,14 @@ class RuntimeModel:
class PreprocessModel:
"""Selected preprocessing artifacts for live acquisition."""
calibration_set: str = ""
reference_set: str = ""
calibration_bundle_path: str = ""
reference_bundle_path: str = ""
s21_calibration_set: str = ""
s21_reference_set: str = ""
s21_calibration_bundle_path: str = ""
s21_reference_bundle_path: str = ""
s11_open_calibration_bundle_path: str = ""
s11_short_calibration_bundle_path: str = ""
s11_load_calibration_bundle_path: str = ""
s11_reference_bundle_path: str = ""
@dataclass(slots=True)
+7 -7
View File
@@ -17,19 +17,19 @@ class ConfigWriter:
self._runtime_dir = runtime_dir
self._runtime_dir.mkdir(parents=True, exist_ok=True)
def prepare_bundles(
def prepare_s21_bundles(
self,
store: NpzStore,
radar_key: str,
calibration_set: str,
reference_set: str,
s21_calibration_set: str,
s21_reference_set: str,
) -> tuple[Path, Path]:
"""Export calibration/reference sets into binary bundles for preprocessor."""
calibration_bundle = self._runtime_dir / "calibration_bundle.bin"
reference_bundle = self._runtime_dir / "reference_bundle.bin"
calibration_bundle = self._runtime_dir / "s21_calibration_bundle.bin"
reference_bundle = self._runtime_dir / "s21_reference_bundle.bin"
store.export_set_bundle("calibration", radar_key, calibration_set, calibration_bundle)
store.export_set_bundle("reference", radar_key, reference_set, reference_bundle)
store.export_set_bundle("calibration", radar_key, s21_calibration_set, calibration_bundle)
store.export_set_bundle("reference", radar_key, s21_reference_set, reference_bundle)
return calibration_bundle, reference_bundle
def write(self, config: RunConfigModel, output_path: Path) -> Path:
@@ -14,6 +14,7 @@ class ProcessingLiveConfig:
processor_mode: str = "pass_through"
gain_db: float = 0.0
phase_deg: float = 0.0
pass_through_channel: str = "s21"
pass_through_fixed_y_enabled: bool = False
pass_through_y_min_db: float = -100.0
pass_through_y_max_db: float = 0.0
@@ -52,6 +53,7 @@ class ProcessingLiveConfig:
"processor_mode": str(self.processor_mode),
"gain_db": float(self.gain_db),
"phase_deg": float(self.phase_deg),
"pass_through_channel": str(self.pass_through_channel),
"pass_through_fixed_y_enabled": bool(self.pass_through_fixed_y_enabled),
"pass_through_y_min_db": float(self.pass_through_y_min_db),
"pass_through_y_max_db": float(self.pass_through_y_max_db),
+8 -4
View File
@@ -79,10 +79,14 @@
]
},
"preprocess": {
"calibration_set": "smoke_cal",
"reference_set": "smoke_ref",
"calibration_bundle_path": "/home/europa/Documents/radar_system/python_app/runtime/calibration_bundle.bin",
"reference_bundle_path": "/home/europa/Documents/radar_system/python_app/runtime/reference_bundle.bin"
"s21_calibration_set": "smoke_cal",
"s21_reference_set": "smoke_ref",
"s21_calibration_bundle_path": "/home/europa/Documents/radar_system/python_app/runtime/s21_calibration_bundle.bin",
"s21_reference_bundle_path": "/home/europa/Documents/radar_system/python_app/runtime/s21_reference_bundle.bin",
"s11_open_calibration_bundle_path": "/home/europa/Documents/radar_system/data_acq_and_processing/preprocessing/testdata/s11_open_calibration_bundle.bin",
"s11_short_calibration_bundle_path": "/home/europa/Documents/radar_system/data_acq_and_processing/preprocessing/testdata/s11_short_calibration_bundle.bin",
"s11_load_calibration_bundle_path": "/home/europa/Documents/radar_system/data_acq_and_processing/preprocessing/testdata/s11_load_calibration_bundle.bin",
"s11_reference_bundle_path": "/home/europa/Documents/radar_system/data_acq_and_processing/preprocessing/testdata/s11_reference_bundle.bin"
},
"gpr": {
"mode": "point",
+10 -5
View File
@@ -113,11 +113,16 @@ def main() -> int:
store.save_set("calibration", radar_key, "smoke_cal", calibration_set)
store.save_set("reference", radar_key, "smoke_ref", reference_set)
calibration_bundle, reference_bundle = config_writer.prepare_bundles(store, radar_key, "smoke_cal", "smoke_ref")
config.preprocess.calibration_set = "smoke_cal"
config.preprocess.reference_set = "smoke_ref"
config.preprocess.calibration_bundle_path = str(calibration_bundle)
config.preprocess.reference_bundle_path = str(reference_bundle)
calibration_bundle, reference_bundle = config_writer.prepare_s21_bundles(
store,
radar_key,
"smoke_cal",
"smoke_ref",
)
config.preprocess.s21_calibration_set = "smoke_cal"
config.preprocess.s21_reference_set = "smoke_ref"
config.preprocess.s21_calibration_bundle_path = str(calibration_bundle)
config.preprocess.s21_reference_bundle_path = str(reference_bundle)
config_path = config_writer.write(config, project_root / "python_app/runtime/run_config_smoke.json")