added white theme, some features to processing and saving
This commit is contained in:
@@ -94,10 +94,23 @@ struct S11PreprocessConfig {
|
|||||||
PreprocessAssetConfig reference{};
|
PreprocessAssetConfig reference{};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct PreprocessNotchBandConfig {
|
||||||
|
float low_hz = 0.0F;
|
||||||
|
float high_hz = 0.0F;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PreprocessNotchConfig {
|
||||||
|
bool enabled = false;
|
||||||
|
std::vector<PreprocessNotchBandConfig> bands_hz{};
|
||||||
|
float taper_width_hz = 40'000'000.0F;
|
||||||
|
std::string taper_type = "cosine";
|
||||||
|
};
|
||||||
|
|
||||||
struct PreprocessConfig {
|
struct PreprocessConfig {
|
||||||
// Channel-specific preprocessing assets selected by Python GUI layer.
|
// Channel-specific preprocessing assets selected by Python GUI layer.
|
||||||
S21PreprocessConfig s21{};
|
S21PreprocessConfig s21{};
|
||||||
S11PreprocessConfig s11{};
|
S11PreprocessConfig s11{};
|
||||||
|
PreprocessNotchConfig notch{};
|
||||||
};
|
};
|
||||||
|
|
||||||
struct GprTxGeometry {
|
struct GprTxGeometry {
|
||||||
|
|||||||
@@ -154,6 +154,46 @@ using Json = nlohmann::json;
|
|||||||
return asset;
|
return asset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] auto parse_preprocess_notch(
|
||||||
|
const Json& object,
|
||||||
|
const std::string& context
|
||||||
|
) -> PreprocessNotchConfig {
|
||||||
|
PreprocessNotchConfig notch{};
|
||||||
|
notch.enabled = optional_bool(object, "enabled", false);
|
||||||
|
notch.taper_width_hz = optional_f32(object, "taper_width_hz", 40'000'000.0F);
|
||||||
|
notch.taper_type = optional_string(object, "taper_type", "cosine");
|
||||||
|
|
||||||
|
if (const auto* bands_value = optional_field(object, "bands_hz"); bands_value != nullptr) {
|
||||||
|
const auto* bands_array = as_array(*bands_value, context + ".bands_hz");
|
||||||
|
notch.bands_hz.reserve(bands_array->size());
|
||||||
|
for (const auto& band_value : *bands_array) {
|
||||||
|
const auto* band_array = as_array(band_value, context + ".bands_hz[]");
|
||||||
|
if (band_array->size() != 2U) {
|
||||||
|
throw std::runtime_error(context + ".bands_hz[] must contain exactly two numbers");
|
||||||
|
}
|
||||||
|
|
||||||
|
PreprocessNotchBandConfig band{};
|
||||||
|
band.low_hz = static_cast<float>(as_number((*band_array)[0], context + ".bands_hz[][0]"));
|
||||||
|
band.high_hz = static_cast<float>(as_number((*band_array)[1], context + ".bands_hz[][1]"));
|
||||||
|
notch.bands_hz.push_back(band);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (notch.taper_width_hz < 0.0F) {
|
||||||
|
throw std::runtime_error(context + ".taper_width_hz must be >= 0");
|
||||||
|
}
|
||||||
|
if (notch.taper_type != "cosine" && notch.taper_type != "hard") {
|
||||||
|
throw std::runtime_error(context + ".taper_type must be 'cosine' or 'hard'");
|
||||||
|
}
|
||||||
|
for (const auto& band : notch.bands_hz) {
|
||||||
|
if (band.high_hz < band.low_hz) {
|
||||||
|
throw std::runtime_error(context + ".bands_hz[] high_hz must be >= low_hz");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return notch;
|
||||||
|
}
|
||||||
|
|
||||||
[[nodiscard]] auto parse_driver_mode(const std::string& value) -> DriverMode {
|
[[nodiscard]] auto parse_driver_mode(const std::string& value) -> DriverMode {
|
||||||
if (value == "mock") {
|
if (value == "mock") {
|
||||||
return DriverMode::Mock;
|
return DriverMode::Mock;
|
||||||
@@ -466,6 +506,10 @@ auto load_run_config(const std::string& path) -> RunConfig {
|
|||||||
config.preprocess.s11.calibration.load =
|
config.preprocess.s11.calibration.load =
|
||||||
parse_preprocess_asset(*s11_calibration_obj, "load", "preprocess.s11.calibration");
|
parse_preprocess_asset(*s11_calibration_obj, "load", "preprocess.s11.calibration");
|
||||||
config.preprocess.s11.reference = parse_preprocess_asset(*s11_obj, "reference", "preprocess.s11");
|
config.preprocess.s11.reference = parse_preprocess_asset(*s11_obj, "reference", "preprocess.s11");
|
||||||
|
|
||||||
|
if (const auto* notch_value = optional_field(*preprocess_obj, "notch"); notch_value != nullptr) {
|
||||||
|
config.preprocess.notch = parse_preprocess_notch(*as_object(*notch_value, "preprocess.notch"), "preprocess.notch");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (const auto* gpr_value = optional_field(*root_obj, "gpr"); gpr_value != nullptr) {
|
if (const auto* gpr_value = optional_field(*root_obj, "gpr"); gpr_value != nullptr) {
|
||||||
|
|||||||
@@ -1,11 +1,94 @@
|
|||||||
#include "data_preprocessor.hpp"
|
#include "data_preprocessor.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
#include <cmath>
|
||||||
|
#include <span>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
#include <string_view>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace radar::preprocessing {
|
namespace radar::preprocessing {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr double kPi = 3.14159265358979323846;
|
||||||
|
|
||||||
|
[[nodiscard]] auto should_apply_notch(const config::PreprocessNotchConfig& notch) -> bool {
|
||||||
|
return notch.enabled && !notch.bands_hz.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] auto build_notch_mask(
|
||||||
|
std::span<const float> frequency_hz,
|
||||||
|
const config::PreprocessNotchConfig& notch
|
||||||
|
) -> std::vector<float> {
|
||||||
|
std::vector<float> mask(frequency_hz.size(), 1.0F);
|
||||||
|
if (!should_apply_notch(notch)) {
|
||||||
|
return mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool hard_taper = notch.taper_type == "hard";
|
||||||
|
const double taper_width_hz = std::max(0.0, static_cast<double>(notch.taper_width_hz));
|
||||||
|
for (const auto& band : notch.bands_hz) {
|
||||||
|
const double low_hz = static_cast<double>(band.low_hz);
|
||||||
|
const double high_hz = static_cast<double>(band.high_hz);
|
||||||
|
const double trans_low_hz = low_hz - taper_width_hz;
|
||||||
|
const double trans_high_hz = high_hz + taper_width_hz;
|
||||||
|
|
||||||
|
for (std::size_t index = 0U; index < frequency_hz.size(); ++index) {
|
||||||
|
const double frequency = static_cast<double>(frequency_hz[index]);
|
||||||
|
if (frequency >= low_hz && frequency <= high_hz) {
|
||||||
|
mask[index] = 0.0F;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hard_taper || taper_width_hz <= 0.0) {
|
||||||
|
if (frequency >= trans_low_hz && frequency <= trans_high_hz) {
|
||||||
|
mask[index] = 0.0F;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (frequency >= trans_low_hz && frequency < low_hz) {
|
||||||
|
const double arg = kPi * (frequency - trans_low_hz) / taper_width_hz;
|
||||||
|
mask[index] *= static_cast<float>(0.5 * (1.0 - std::cos(arg)));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (frequency > high_hz && frequency <= trans_high_hz) {
|
||||||
|
const double arg = kPi * (frequency - high_hz) / taper_width_hz;
|
||||||
|
mask[index] *= static_cast<float>(0.5 * (1.0 + std::cos(arg)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
void apply_notch_mask(std::vector<ipc::Complex32>& samples, std::span<const float> mask) {
|
||||||
|
const std::size_t point_count = std::min(samples.size(), mask.size());
|
||||||
|
for (std::size_t index = 0U; index < point_count; ++index) {
|
||||||
|
samples[index].re *= mask[index];
|
||||||
|
samples[index].im *= mask[index];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] auto apply_notch_filter(
|
||||||
|
const ipc::SweepTraceBlock& trace,
|
||||||
|
const config::PreprocessNotchConfig& notch
|
||||||
|
) -> ipc::SweepTraceBlock {
|
||||||
|
if (!should_apply_notch(notch)) {
|
||||||
|
return trace;
|
||||||
|
}
|
||||||
|
|
||||||
|
ipc::SweepTraceBlock filtered = trace;
|
||||||
|
const auto mask = build_notch_mask(filtered.frequency_hz, notch);
|
||||||
|
apply_notch_mask(filtered.s21, mask);
|
||||||
|
apply_notch_mask(filtered.s11, mask);
|
||||||
|
return filtered;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
DataPreprocessor::DataPreprocessor(
|
DataPreprocessor::DataPreprocessor(
|
||||||
const config::RunConfig& config,
|
const config::RunConfig& config,
|
||||||
@@ -78,7 +161,7 @@ auto DataPreprocessor::preprocess_collection(const ipc::RawSweepCollection& raw_
|
|||||||
// Pipeline order is fixed: calibration first, then reference subtraction.
|
// Pipeline order is fixed: calibration first, then reference subtraction.
|
||||||
const auto calibrated = calibration_master_.apply_to_trace(raw_trace);
|
const auto calibrated = calibration_master_.apply_to_trace(raw_trace);
|
||||||
const auto referenced = reference_master_.apply_to_trace(calibrated);
|
const auto referenced = reference_master_.apply_to_trace(calibrated);
|
||||||
preprocessed.traces.push_back(referenced);
|
preprocessed.traces.push_back(apply_notch_filter(referenced, config_.preprocess.notch));
|
||||||
}
|
}
|
||||||
|
|
||||||
return preprocessed;
|
return preprocessed;
|
||||||
|
|||||||
@@ -168,7 +168,8 @@ class AppWindowLiveProcessingMixin:
|
|||||||
f"cut={self._bscan_cut_m.value():g} m, "
|
f"cut={self._bscan_cut_m.value():g} m, "
|
||||||
f"max_depth={self._bscan_max_depth_m.value():g} m, "
|
f"max_depth={self._bscan_max_depth_m.value():g} m, "
|
||||||
f"gain={self._bscan_gain.value():g}, "
|
f"gain={self._bscan_gain.value():g}, "
|
||||||
f"freq={self._bscan_start_freq_mhz.value():g}..{self._bscan_stop_freq_mhz.value():g} MHz)"
|
f"freq={self._bscan_start_freq_mhz.value():g}..{self._bscan_stop_freq_mhz.value():g} MHz, "
|
||||||
|
f"subtract_mean_ascan={self._bscan_subtract_mean_ascan.isChecked()})"
|
||||||
)
|
)
|
||||||
elif mode == "gpr":
|
elif mode == "gpr":
|
||||||
self._log(
|
self._log(
|
||||||
|
|||||||
@@ -169,6 +169,7 @@ class AppWindowConfigProfileIOMixin:
|
|||||||
self._bscan_gain,
|
self._bscan_gain,
|
||||||
self._bscan_start_freq_mhz,
|
self._bscan_start_freq_mhz,
|
||||||
self._bscan_stop_freq_mhz,
|
self._bscan_stop_freq_mhz,
|
||||||
|
self._bscan_subtract_mean_ascan,
|
||||||
self._gpr_config_mode,
|
self._gpr_config_mode,
|
||||||
self._gpr_relative_permittivity,
|
self._gpr_relative_permittivity,
|
||||||
self._gpr_tx_geometry_input,
|
self._gpr_tx_geometry_input,
|
||||||
@@ -228,6 +229,7 @@ class AppWindowConfigProfileIOMixin:
|
|||||||
self._bscan_gain.setValue(float(gui_state.processing.bscan.gain))
|
self._bscan_gain.setValue(float(gui_state.processing.bscan.gain))
|
||||||
self._bscan_start_freq_mhz.setValue(float(gui_state.processing.bscan.start_freq_mhz))
|
self._bscan_start_freq_mhz.setValue(float(gui_state.processing.bscan.start_freq_mhz))
|
||||||
self._bscan_stop_freq_mhz.setValue(float(gui_state.processing.bscan.stop_freq_mhz))
|
self._bscan_stop_freq_mhz.setValue(float(gui_state.processing.bscan.stop_freq_mhz))
|
||||||
|
self._bscan_subtract_mean_ascan.setChecked(bool(gui_state.processing.bscan.subtract_mean_ascan))
|
||||||
|
|
||||||
self._set_combo_current_text(self._gpr_config_mode, str(config.gpr.mode))
|
self._set_combo_current_text(self._gpr_config_mode, str(config.gpr.mode))
|
||||||
self._gpr_relative_permittivity.setValue(float(config.gpr.relative_permittivity))
|
self._gpr_relative_permittivity.setValue(float(config.gpr.relative_permittivity))
|
||||||
|
|||||||
@@ -157,6 +157,7 @@ class AppWindowConfigStateBuildersMixin:
|
|||||||
gain=1.0,
|
gain=1.0,
|
||||||
start_freq_mhz=100.0,
|
start_freq_mhz=100.0,
|
||||||
stop_freq_mhz=8800.0,
|
stop_freq_mhz=8800.0,
|
||||||
|
subtract_mean_ascan=False,
|
||||||
),
|
),
|
||||||
gpr=GuiGprStateModel(
|
gpr=GuiGprStateModel(
|
||||||
input_positions=self._default_gpr_input_positions_from_config(config),
|
input_positions=self._default_gpr_input_positions_from_config(config),
|
||||||
@@ -235,6 +236,7 @@ class AppWindowConfigStateBuildersMixin:
|
|||||||
gain=float(self._bscan_gain.value()),
|
gain=float(self._bscan_gain.value()),
|
||||||
start_freq_mhz=float(self._bscan_start_freq_mhz.value()),
|
start_freq_mhz=float(self._bscan_start_freq_mhz.value()),
|
||||||
stop_freq_mhz=float(self._bscan_stop_freq_mhz.value()),
|
stop_freq_mhz=float(self._bscan_stop_freq_mhz.value()),
|
||||||
|
subtract_mean_ascan=bool(self._bscan_subtract_mean_ascan.isChecked()),
|
||||||
),
|
),
|
||||||
gpr=GuiGprStateModel(
|
gpr=GuiGprStateModel(
|
||||||
input_positions=self._gpr_input_positions_input.text().strip(),
|
input_positions=self._gpr_input_positions_input.text().strip(),
|
||||||
|
|||||||
@@ -39,6 +39,8 @@ def _result_tail(
|
|||||||
|
|
||||||
def build_bscan_signature(
|
def build_bscan_signature(
|
||||||
live_config: ProcessingLiveConfig,
|
live_config: ProcessingLiveConfig,
|
||||||
|
*,
|
||||||
|
subtract_mean_ascan_enabled: bool,
|
||||||
result_history: list[ResultCollection],
|
result_history: list[ResultCollection],
|
||||||
history_limit: int,
|
history_limit: int,
|
||||||
floor_collection_id: int,
|
floor_collection_id: int,
|
||||||
@@ -57,11 +59,25 @@ def build_bscan_signature(
|
|||||||
float(live_config.bscan_gain),
|
float(live_config.bscan_gain),
|
||||||
float(live_config.bscan_start_freq_mhz),
|
float(live_config.bscan_start_freq_mhz),
|
||||||
float(live_config.bscan_stop_freq_mhz),
|
float(live_config.bscan_stop_freq_mhz),
|
||||||
|
bool(subtract_mean_ascan_enabled),
|
||||||
int(floor_collection_id),
|
int(floor_collection_id),
|
||||||
tuple((int(collection.collection_id), int(collection.monotonic_ns), len(collection.blocks)) for collection in result_tail),
|
tuple((int(collection.collection_id), int(collection.monotonic_ns), len(collection.blocks)) for collection in result_tail),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def apply_mean_ascan_subtraction(
|
||||||
|
history: deque[np.ndarray],
|
||||||
|
*,
|
||||||
|
enabled: bool,
|
||||||
|
) -> np.ndarray:
|
||||||
|
"""Subtract mean A-scan across sweeps when enabled."""
|
||||||
|
sweeps = np.vstack(history).astype(np.float32, copy=False)
|
||||||
|
if not enabled:
|
||||||
|
return sweeps
|
||||||
|
mean_trace = np.mean(sweeps, axis=0, dtype=np.float32)
|
||||||
|
return sweeps - mean_trace
|
||||||
|
|
||||||
|
|
||||||
def rebuild_bscan_history_from_results(
|
def rebuild_bscan_history_from_results(
|
||||||
result_history: list[ResultCollection],
|
result_history: list[ResultCollection],
|
||||||
history_limit: int,
|
history_limit: int,
|
||||||
@@ -183,7 +199,10 @@ class AppWindowBscanPlotMixin:
|
|||||||
if not history or depth_axis is None:
|
if not history or depth_axis is None:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
sweeps = np.vstack(history).astype(np.float32, copy=False)
|
sweeps = apply_mean_ascan_subtraction(
|
||||||
|
history,
|
||||||
|
enabled=bool(self._bscan_subtract_mean_ascan.isChecked()),
|
||||||
|
)
|
||||||
if sweeps.size == 0:
|
if sweeps.size == 0:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -229,6 +248,7 @@ class AppWindowBscanPlotMixin:
|
|||||||
result_history = list(self._result_history)
|
result_history = list(self._result_history)
|
||||||
return build_bscan_signature(
|
return build_bscan_signature(
|
||||||
live_config=live_config,
|
live_config=live_config,
|
||||||
|
subtract_mean_ascan_enabled=bool(self._bscan_subtract_mean_ascan.isChecked()),
|
||||||
result_history=result_history,
|
result_history=result_history,
|
||||||
history_limit=self._bscan_history_limit,
|
history_limit=self._bscan_history_limit,
|
||||||
floor_collection_id=self._bscan_history_floor_collection_id,
|
floor_collection_id=self._bscan_history_floor_collection_id,
|
||||||
|
|||||||
@@ -18,9 +18,9 @@ class AppWindowSnapshotMixin:
|
|||||||
return snapshot_dir / "config_profile.json"
|
return snapshot_dir / "config_profile.json"
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _vna_json_config_profile_path(output_root: Path, output_stem: str) -> Path:
|
def _vna_json_config_profile_path(output_dir: Path) -> Path:
|
||||||
"""Return companion config-profile path for one VNA-history JSON export batch."""
|
"""Return companion config-profile path for one VNA-history JSON export batch."""
|
||||||
return output_root / f"{output_stem}_config_profile.json"
|
return output_dir / "config_profile.json"
|
||||||
|
|
||||||
def _save_snapshot(self) -> None:
|
def _save_snapshot(self) -> None:
|
||||||
"""Save runtime snapshot in numpy-directory format."""
|
"""Save runtime snapshot in numpy-directory format."""
|
||||||
@@ -98,8 +98,9 @@ class AppWindowSnapshotMixin:
|
|||||||
output_stem = str(summary.get("output_stem", "")).strip()
|
output_stem = str(summary.get("output_stem", "")).strip()
|
||||||
if not output_stem:
|
if not output_stem:
|
||||||
raise RuntimeError("VNA history JSON export did not report output_stem for config companion save")
|
raise RuntimeError("VNA history JSON export did not report output_stem for config companion save")
|
||||||
|
output_dir = Path(str(summary.get("output_dir", "")).strip() or output_paths[0].parent)
|
||||||
|
|
||||||
config_profile_path = self._vna_json_config_profile_path(output_root, output_stem)
|
config_profile_path = self._vna_json_config_profile_path(output_dir)
|
||||||
try:
|
try:
|
||||||
self._write_gui_profile_to_path(config_profile_path, allow_overwrite=False)
|
self._write_gui_profile_to_path(config_profile_path, allow_overwrite=False)
|
||||||
except Exception as exc: # noqa: BLE001
|
except Exception as exc: # noqa: BLE001
|
||||||
@@ -110,6 +111,7 @@ class AppWindowSnapshotMixin:
|
|||||||
"VNA history JSON files were saved, but the adjacent config profile could not be written",
|
"VNA history JSON files were saved, but the adjacent config profile could not be written",
|
||||||
details=(
|
details=(
|
||||||
f"output_root={output_root}\n"
|
f"output_root={output_root}\n"
|
||||||
|
f"output_dir={output_dir}\n"
|
||||||
f"config_profile_path={config_profile_path}\n"
|
f"config_profile_path={config_profile_path}\n"
|
||||||
f"saved_json_files={len(output_paths)}\n"
|
f"saved_json_files={len(output_paths)}\n"
|
||||||
f"{exported_preview}\n\n"
|
f"{exported_preview}\n\n"
|
||||||
|
|||||||
@@ -134,6 +134,9 @@ def build_processing_group(owner) -> QGroupBox:
|
|||||||
owner._bscan_stop_freq_mhz.setSingleStep(10.0)
|
owner._bscan_stop_freq_mhz.setSingleStep(10.0)
|
||||||
owner._bscan_stop_freq_mhz.setValue(float(bscan_defaults.stop_freq_mhz))
|
owner._bscan_stop_freq_mhz.setValue(float(bscan_defaults.stop_freq_mhz))
|
||||||
|
|
||||||
|
owner._bscan_subtract_mean_ascan = QCheckBox("Subtract mean A-scan")
|
||||||
|
owner._bscan_subtract_mean_ascan.setChecked(bool(bscan_defaults.subtract_mean_ascan))
|
||||||
|
|
||||||
bscan_page = _build_processing_mode_page(
|
bscan_page = _build_processing_mode_page(
|
||||||
owner._processing_mode_pages,
|
owner._processing_mode_pages,
|
||||||
[
|
[
|
||||||
@@ -143,8 +146,9 @@ def build_processing_group(owner) -> QGroupBox:
|
|||||||
("Gain", owner._bscan_gain),
|
("Gain", owner._bscan_gain),
|
||||||
("Start MHz", owner._bscan_start_freq_mhz),
|
("Start MHz", owner._bscan_start_freq_mhz),
|
||||||
("Stop MHz", owner._bscan_stop_freq_mhz),
|
("Stop MHz", owner._bscan_stop_freq_mhz),
|
||||||
|
owner._bscan_subtract_mean_ascan,
|
||||||
],
|
],
|
||||||
split_index=3,
|
split_index=4,
|
||||||
)
|
)
|
||||||
owner._processing_mode_pages.addWidget(bscan_page)
|
owner._processing_mode_pages.addWidget(bscan_page)
|
||||||
|
|
||||||
@@ -314,6 +318,7 @@ def build_processing_group(owner) -> QGroupBox:
|
|||||||
owner._bscan_gain.valueChanged.connect(owner._on_processing_live_settings_changed)
|
owner._bscan_gain.valueChanged.connect(owner._on_processing_live_settings_changed)
|
||||||
owner._bscan_start_freq_mhz.valueChanged.connect(owner._on_processing_live_settings_changed)
|
owner._bscan_start_freq_mhz.valueChanged.connect(owner._on_processing_live_settings_changed)
|
||||||
owner._bscan_stop_freq_mhz.valueChanged.connect(owner._on_processing_live_settings_changed)
|
owner._bscan_stop_freq_mhz.valueChanged.connect(owner._on_processing_live_settings_changed)
|
||||||
|
owner._bscan_subtract_mean_ascan.toggled.connect(owner._on_processing_live_settings_changed)
|
||||||
owner._gpr_input_positions_input.editingFinished.connect(owner._on_processing_live_settings_changed)
|
owner._gpr_input_positions_input.editingFinished.connect(owner._on_processing_live_settings_changed)
|
||||||
owner._gpr_output_positions_input.editingFinished.connect(owner._on_processing_live_settings_changed)
|
owner._gpr_output_positions_input.editingFinished.connect(owner._on_processing_live_settings_changed)
|
||||||
owner._gpr_min_depth_m.valueChanged.connect(owner._on_processing_live_settings_changed)
|
owner._gpr_min_depth_m.valueChanged.connect(owner._on_processing_live_settings_changed)
|
||||||
|
|||||||
@@ -14,14 +14,14 @@ if str(PROJECT_ROOT) not in sys.path:
|
|||||||
sys.path.insert(0, str(PROJECT_ROOT))
|
sys.path.insert(0, str(PROJECT_ROOT))
|
||||||
|
|
||||||
from python_app.gui.app_window import AppWindow
|
from python_app.gui.app_window import AppWindow
|
||||||
from python_app.gui.theme import apply_dark_theme
|
from python_app.gui.theme import apply_light_theme
|
||||||
|
|
||||||
|
|
||||||
def main() -> int:
|
def main() -> int:
|
||||||
"""Run Qt event loop and show main radar control window."""
|
"""Run Qt event loop and show main radar control window."""
|
||||||
app = QApplication(sys.argv)
|
app = QApplication(sys.argv)
|
||||||
apply_dark_theme(app)
|
apply_light_theme(app)
|
||||||
pg.setConfigOptions(antialias=True, foreground="#dbe4f1")
|
pg.setConfigOptions(antialias=True, background="#ffffff", foreground="#334155")
|
||||||
window = AppWindow(PROJECT_ROOT)
|
window = AppWindow(PROJECT_ROOT)
|
||||||
window.showMaximized()
|
window.showMaximized()
|
||||||
return app.exec()
|
return app.exec()
|
||||||
|
|||||||
+34
-34
@@ -6,19 +6,19 @@ from PyQt6.QtGui import QColor, QPalette
|
|||||||
from PyQt6.QtWidgets import QApplication, QStyleFactory
|
from PyQt6.QtWidgets import QApplication, QStyleFactory
|
||||||
|
|
||||||
|
|
||||||
_DARK_STYLESHEET = """
|
_LIGHT_STYLESHEET = """
|
||||||
QMainWindow, QDialog {
|
QMainWindow, QDialog {
|
||||||
background-color: #0f131a;
|
background-color: #f3f6fb;
|
||||||
}
|
}
|
||||||
|
|
||||||
QWidget {
|
QWidget {
|
||||||
color: #e7edf7;
|
color: #1f2937;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
QGroupBox {
|
QGroupBox {
|
||||||
background-color: #151b24;
|
background-color: #ffffff;
|
||||||
border: 1px solid #263142;
|
border: 1px solid #d7dee8;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
margin-top: 14px;
|
margin-top: 14px;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
@@ -28,39 +28,39 @@ QGroupBox::title {
|
|||||||
subcontrol-origin: margin;
|
subcontrol-origin: margin;
|
||||||
left: 10px;
|
left: 10px;
|
||||||
padding: 0 6px;
|
padding: 0 6px;
|
||||||
color: #9fb4ce;
|
color: #526277;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
QPushButton {
|
QPushButton {
|
||||||
background-color: #1d2735;
|
background-color: #f8fafc;
|
||||||
border: 1px solid #314055;
|
border: 1px solid #cad4e1;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
padding: 7px 12px;
|
padding: 7px 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
QPushButton:hover {
|
QPushButton:hover {
|
||||||
background-color: #243246;
|
background-color: #eef3f9;
|
||||||
}
|
}
|
||||||
|
|
||||||
QPushButton:pressed {
|
QPushButton:pressed {
|
||||||
background-color: #1a2432;
|
background-color: #e4ebf4;
|
||||||
}
|
}
|
||||||
|
|
||||||
QPushButton:checked {
|
QPushButton:checked {
|
||||||
background-color: #2f7ee6;
|
background-color: #2f7ee6;
|
||||||
border-color: #5d88bd;
|
border-color: #2f7ee6;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
QPushButton:checked:hover {
|
QPushButton:checked:hover {
|
||||||
background-color: #3c89ee;
|
background-color: #3b8bf4;
|
||||||
}
|
}
|
||||||
|
|
||||||
QPushButton:disabled {
|
QPushButton:disabled {
|
||||||
color: #6b7d95;
|
color: #98a4b3;
|
||||||
background-color: #151d27;
|
background-color: #f3f5f8;
|
||||||
border-color: #232e3d;
|
border-color: #dde4ec;
|
||||||
}
|
}
|
||||||
|
|
||||||
QPushButton#settingsToggleButton {
|
QPushButton#settingsToggleButton {
|
||||||
@@ -83,11 +83,11 @@ QTextEdit,
|
|||||||
QComboBox,
|
QComboBox,
|
||||||
QSpinBox,
|
QSpinBox,
|
||||||
QDoubleSpinBox {
|
QDoubleSpinBox {
|
||||||
background-color: #101721;
|
background-color: #ffffff;
|
||||||
border: 1px solid #2e3b4e;
|
border: 1px solid #c9d4e1;
|
||||||
border-radius: 7px;
|
border-radius: 7px;
|
||||||
padding: 5px 8px;
|
padding: 5px 8px;
|
||||||
selection-background-color: #2f7ee6;
|
selection-background-color: #cfe3ff;
|
||||||
}
|
}
|
||||||
|
|
||||||
QLineEdit:focus,
|
QLineEdit:focus,
|
||||||
@@ -96,7 +96,7 @@ QTextEdit:focus,
|
|||||||
QComboBox:focus,
|
QComboBox:focus,
|
||||||
QSpinBox:focus,
|
QSpinBox:focus,
|
||||||
QDoubleSpinBox:focus {
|
QDoubleSpinBox:focus {
|
||||||
border: 1px solid #5d88bd;
|
border: 1px solid #2f7ee6;
|
||||||
}
|
}
|
||||||
|
|
||||||
QTextEdit#runtimeLogBox {
|
QTextEdit#runtimeLogBox {
|
||||||
@@ -116,34 +116,34 @@ QScrollArea {
|
|||||||
}
|
}
|
||||||
|
|
||||||
QLabel#statusLabel {
|
QLabel#statusLabel {
|
||||||
color: #94b4d9;
|
color: #35507a;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
padding: 2px 1px;
|
padding: 2px 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
QLabel#hintLabel {
|
QLabel#hintLabel {
|
||||||
color: #7f94af;
|
color: #6c7b8d;
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
def apply_dark_theme(app: QApplication) -> None:
|
def apply_light_theme(app: QApplication) -> None:
|
||||||
"""Apply a minimal modern dark theme shared by all windows."""
|
"""Apply a minimal light theme shared by all windows."""
|
||||||
app.setStyle(QStyleFactory.create("Fusion"))
|
app.setStyle(QStyleFactory.create("Fusion"))
|
||||||
|
|
||||||
palette = QPalette()
|
palette = QPalette()
|
||||||
palette.setColor(QPalette.ColorRole.Window, QColor("#0f131a"))
|
palette.setColor(QPalette.ColorRole.Window, QColor("#f3f6fb"))
|
||||||
palette.setColor(QPalette.ColorRole.WindowText, QColor("#e7edf7"))
|
palette.setColor(QPalette.ColorRole.WindowText, QColor("#1f2937"))
|
||||||
palette.setColor(QPalette.ColorRole.Base, QColor("#101721"))
|
palette.setColor(QPalette.ColorRole.Base, QColor("#ffffff"))
|
||||||
palette.setColor(QPalette.ColorRole.AlternateBase, QColor("#151b24"))
|
palette.setColor(QPalette.ColorRole.AlternateBase, QColor("#eef3f8"))
|
||||||
palette.setColor(QPalette.ColorRole.ToolTipBase, QColor("#151b24"))
|
palette.setColor(QPalette.ColorRole.ToolTipBase, QColor("#ffffff"))
|
||||||
palette.setColor(QPalette.ColorRole.ToolTipText, QColor("#e7edf7"))
|
palette.setColor(QPalette.ColorRole.ToolTipText, QColor("#1f2937"))
|
||||||
palette.setColor(QPalette.ColorRole.Text, QColor("#e7edf7"))
|
palette.setColor(QPalette.ColorRole.Text, QColor("#1f2937"))
|
||||||
palette.setColor(QPalette.ColorRole.Button, QColor("#1d2735"))
|
palette.setColor(QPalette.ColorRole.Button, QColor("#f8fafc"))
|
||||||
palette.setColor(QPalette.ColorRole.ButtonText, QColor("#e7edf7"))
|
palette.setColor(QPalette.ColorRole.ButtonText, QColor("#1f2937"))
|
||||||
palette.setColor(QPalette.ColorRole.BrightText, QColor("#ffffff"))
|
palette.setColor(QPalette.ColorRole.BrightText, QColor("#ffffff"))
|
||||||
palette.setColor(QPalette.ColorRole.Link, QColor("#5d88bd"))
|
palette.setColor(QPalette.ColorRole.Link, QColor("#2f7ee6"))
|
||||||
palette.setColor(QPalette.ColorRole.Highlight, QColor("#2f7ee6"))
|
palette.setColor(QPalette.ColorRole.Highlight, QColor("#2f7ee6"))
|
||||||
palette.setColor(QPalette.ColorRole.HighlightedText, QColor("#ffffff"))
|
palette.setColor(QPalette.ColorRole.HighlightedText, QColor("#ffffff"))
|
||||||
app.setPalette(palette)
|
app.setPalette(palette)
|
||||||
app.setStyleSheet(_DARK_STYLESHEET)
|
app.setStyleSheet(_LIGHT_STYLESHEET)
|
||||||
|
|||||||
@@ -152,6 +152,12 @@ def gui_profile_from_dict(payload: dict[str, Any]) -> GuiProfileModel:
|
|||||||
gui.processing.bscan.stop_freq_mhz,
|
gui.processing.bscan.stop_freq_mhz,
|
||||||
"gui.processing.bscan",
|
"gui.processing.bscan",
|
||||||
),
|
),
|
||||||
|
subtract_mean_ascan=_optional_bool(
|
||||||
|
bscan_object,
|
||||||
|
"subtract_mean_ascan",
|
||||||
|
gui.processing.bscan.subtract_mean_ascan,
|
||||||
|
"gui.processing.bscan",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
gpr=GuiGprStateModel(
|
gpr=GuiGprStateModel(
|
||||||
input_positions=_optional_string(
|
input_positions=_optional_string(
|
||||||
@@ -359,6 +365,7 @@ def gui_profile_to_dict(model: GuiProfileModel) -> dict[str, Any]:
|
|||||||
"gain": gui.processing.bscan.gain,
|
"gain": gui.processing.bscan.gain,
|
||||||
"start_freq_mhz": gui.processing.bscan.start_freq_mhz,
|
"start_freq_mhz": gui.processing.bscan.start_freq_mhz,
|
||||||
"stop_freq_mhz": gui.processing.bscan.stop_freq_mhz,
|
"stop_freq_mhz": gui.processing.bscan.stop_freq_mhz,
|
||||||
|
"subtract_mean_ascan": gui.processing.bscan.subtract_mean_ascan,
|
||||||
},
|
},
|
||||||
"gpr": {
|
"gpr": {
|
||||||
"input_positions": gui.processing.gpr.input_positions,
|
"input_positions": gui.processing.gpr.input_positions,
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ class GuiBscanStateModel:
|
|||||||
gain: float = 1.0
|
gain: float = 1.0
|
||||||
start_freq_mhz: float = 100.0
|
start_freq_mhz: float = 100.0
|
||||||
stop_freq_mhz: float = 8800.0
|
stop_freq_mhz: float = 8800.0
|
||||||
|
subtract_mean_ascan: bool = False
|
||||||
|
|
||||||
|
|
||||||
@dataclass(slots=True)
|
@dataclass(slots=True)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ from python_app.models.run_config_schema import (
|
|||||||
GprRxGeometryModel,
|
GprRxGeometryModel,
|
||||||
GprTxGeometryModel,
|
GprTxGeometryModel,
|
||||||
PreprocessAssetModel,
|
PreprocessAssetModel,
|
||||||
|
PreprocessNotchModel,
|
||||||
RunConfigModel,
|
RunConfigModel,
|
||||||
)
|
)
|
||||||
from python_app.models.run_config_validation import load_ring_payload, load_switch_payload, validate_gpr_model
|
from python_app.models.run_config_validation import load_ring_payload, load_switch_payload, validate_gpr_model
|
||||||
@@ -137,6 +138,18 @@ def run_config_from_dict(payload: dict[str, Any]) -> RunConfigModel:
|
|||||||
_as_dict(s11_preprocess_payload.get("reference"), "preprocess.s11.reference"),
|
_as_dict(s11_preprocess_payload.get("reference"), "preprocess.s11.reference"),
|
||||||
model.preprocess.s11.reference,
|
model.preprocess.s11.reference,
|
||||||
)
|
)
|
||||||
|
notch_payload = _as_dict(preprocess_payload.get("notch"), "preprocess.notch")
|
||||||
|
model.preprocess.notch = PreprocessNotchModel(
|
||||||
|
enabled=bool(notch_payload.get("enabled", model.preprocess.notch.enabled)),
|
||||||
|
taper_width_hz=float(notch_payload.get("taper_width_hz", model.preprocess.notch.taper_width_hz)),
|
||||||
|
taper_type=str(notch_payload.get("taper_type", model.preprocess.notch.taper_type)),
|
||||||
|
bands_hz=[],
|
||||||
|
)
|
||||||
|
bands_payload = notch_payload.get("bands_hz", [])
|
||||||
|
if isinstance(bands_payload, list):
|
||||||
|
for band in bands_payload:
|
||||||
|
if isinstance(band, (list, tuple)) and len(band) == 2:
|
||||||
|
model.preprocess.notch.bands_hz.append((float(band[0]), float(band[1])))
|
||||||
|
|
||||||
model.gpr.mode = str(gpr_payload.get("mode", model.gpr.mode))
|
model.gpr.mode = str(gpr_payload.get("mode", model.gpr.mode))
|
||||||
model.gpr.relative_permittivity = float(
|
model.gpr.relative_permittivity = float(
|
||||||
@@ -282,6 +295,12 @@ def run_config_to_dict(model: RunConfigModel) -> dict[str, Any]:
|
|||||||
"bundle_path": model.preprocess.s11.reference.bundle_path,
|
"bundle_path": model.preprocess.s11.reference.bundle_path,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"notch": {
|
||||||
|
"enabled": model.preprocess.notch.enabled,
|
||||||
|
"bands_hz": [[low_hz, high_hz] for low_hz, high_hz in model.preprocess.notch.bands_hz],
|
||||||
|
"taper_width_hz": model.preprocess.notch.taper_width_hz,
|
||||||
|
"taper_type": model.preprocess.notch.taper_type,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
"gpr": {
|
"gpr": {
|
||||||
"mode": model.gpr.mode,
|
"mode": model.gpr.mode,
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ from python_app.models.run_config_schema import (
|
|||||||
GprTxGeometryModel,
|
GprTxGeometryModel,
|
||||||
LocatorServerRuntimeModel,
|
LocatorServerRuntimeModel,
|
||||||
PreprocessAssetModel,
|
PreprocessAssetModel,
|
||||||
|
PreprocessNotchModel,
|
||||||
PreprocessModel,
|
PreprocessModel,
|
||||||
RadarModel,
|
RadarModel,
|
||||||
RadarSweepModel,
|
RadarSweepModel,
|
||||||
@@ -33,6 +34,7 @@ __all__ = [
|
|||||||
"GprTxGeometryModel",
|
"GprTxGeometryModel",
|
||||||
"LocatorServerRuntimeModel",
|
"LocatorServerRuntimeModel",
|
||||||
"PreprocessAssetModel",
|
"PreprocessAssetModel",
|
||||||
|
"PreprocessNotchModel",
|
||||||
"PreprocessModel",
|
"PreprocessModel",
|
||||||
"RadarModel",
|
"RadarModel",
|
||||||
"RadarSweepModel",
|
"RadarSweepModel",
|
||||||
|
|||||||
@@ -132,12 +132,23 @@ class S11PreprocessModel:
|
|||||||
reference: PreprocessAssetModel = field(default_factory=PreprocessAssetModel)
|
reference: PreprocessAssetModel = field(default_factory=PreprocessAssetModel)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(slots=True)
|
||||||
|
class PreprocessNotchModel:
|
||||||
|
"""Optional frequency-domain notch filter applied after calibration and reference subtraction."""
|
||||||
|
|
||||||
|
enabled: bool = False
|
||||||
|
bands_hz: list[tuple[float, float]] = field(default_factory=list)
|
||||||
|
taper_width_hz: float = 40_000_000.0
|
||||||
|
taper_type: str = "cosine"
|
||||||
|
|
||||||
|
|
||||||
@dataclass(slots=True)
|
@dataclass(slots=True)
|
||||||
class PreprocessModel:
|
class PreprocessModel:
|
||||||
"""Selected preprocessing artifacts for live acquisition."""
|
"""Selected preprocessing artifacts for live acquisition."""
|
||||||
|
|
||||||
s21: S21PreprocessModel = field(default_factory=S21PreprocessModel)
|
s21: S21PreprocessModel = field(default_factory=S21PreprocessModel)
|
||||||
s11: S11PreprocessModel = field(default_factory=S11PreprocessModel)
|
s11: S11PreprocessModel = field(default_factory=S11PreprocessModel)
|
||||||
|
notch: PreprocessNotchModel = field(default_factory=PreprocessNotchModel)
|
||||||
|
|
||||||
|
|
||||||
@dataclass(slots=True)
|
@dataclass(slots=True)
|
||||||
|
|||||||
@@ -32,4 +32,4 @@
|
|||||||
"gpr_background_mean_count": 10,
|
"gpr_background_mean_count": 10,
|
||||||
"history_command_seq": 0,
|
"history_command_seq": 0,
|
||||||
"history_command": "none"
|
"history_command": "none"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -108,6 +108,18 @@
|
|||||||
"set_name": "",
|
"set_name": "",
|
||||||
"bundle_path": ""
|
"bundle_path": ""
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"notch": {
|
||||||
|
"enabled": true,
|
||||||
|
"bands_hz": [
|
||||||
|
[790000000.0, 850000000.0],
|
||||||
|
[1790000000.0, 1890000000.0],
|
||||||
|
[1970000000.0, 2050000000.0],
|
||||||
|
[2080000000.0, 2560000000.0],
|
||||||
|
[2650000000.0, 2700000000.0]
|
||||||
|
],
|
||||||
|
"taper_width_hz": 40000000.0,
|
||||||
|
"taper_type": "cosine"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"gpr": {
|
"gpr": {
|
||||||
@@ -169,4 +181,4 @@
|
|||||||
"slot_size_bytes": 2097152
|
"slot_size_bytes": 2097152
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,11 @@ class NpzStore(StoreApi):
|
|||||||
self._root_dir = root_dir
|
self._root_dir = root_dir
|
||||||
self._root_dir.mkdir(parents=True, exist_ok=True)
|
self._root_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _vna_json_output_dir(output_root_dir: Path, output_stem: str) -> Path:
|
||||||
|
"""Return per-export directory for VNA history JSON files."""
|
||||||
|
return output_root_dir / output_stem
|
||||||
|
|
||||||
def save_set(self, kind: str, radar_key: str, set_name: str, collection: SweepCollection) -> None:
|
def save_set(self, kind: str, radar_key: str, set_name: str, collection: SweepCollection) -> None:
|
||||||
"""Persist named preprocess set as NPZ and metadata JSON."""
|
"""Persist named preprocess set as NPZ and metadata JSON."""
|
||||||
set_dir = self._set_dir(kind, radar_key)
|
set_dir = self._set_dir(kind, radar_key)
|
||||||
@@ -217,7 +222,9 @@ class NpzStore(StoreApi):
|
|||||||
output_stem = sanitize_path_component(output_stem)
|
output_stem = sanitize_path_component(output_stem)
|
||||||
|
|
||||||
output_root_dir.mkdir(parents=True, exist_ok=True)
|
output_root_dir.mkdir(parents=True, exist_ok=True)
|
||||||
output_path = output_root_dir / f"{output_stem}_vna_bscan_history.json"
|
output_dir = self._vna_json_output_dir(output_root_dir, output_stem)
|
||||||
|
output_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
output_path = output_dir / f"{output_stem}_vna_bscan_history.json"
|
||||||
if output_path.exists():
|
if output_path.exists():
|
||||||
raise FileExistsError(f"Output JSON file already exists: {output_path}")
|
raise FileExistsError(f"Output JSON file already exists: {output_path}")
|
||||||
|
|
||||||
@@ -253,6 +260,7 @@ class NpzStore(StoreApi):
|
|||||||
summary["channel"] = str(channel)
|
summary["channel"] = str(channel)
|
||||||
summary["primary_stage"] = str(primary_stage)
|
summary["primary_stage"] = str(primary_stage)
|
||||||
summary["output_stem"] = output_stem
|
summary["output_stem"] = output_stem
|
||||||
|
summary["output_dir"] = str(output_dir)
|
||||||
summary["output_path"] = str(output_path)
|
summary["output_path"] = str(output_path)
|
||||||
return output_path, summary
|
return output_path, summary
|
||||||
|
|
||||||
@@ -275,6 +283,8 @@ class NpzStore(StoreApi):
|
|||||||
output_stem = output_name.strip() or datetime.utcnow().strftime("snapshot_%Y%m%d_%H%M%S")
|
output_stem = output_name.strip() or datetime.utcnow().strftime("snapshot_%Y%m%d_%H%M%S")
|
||||||
output_stem = sanitize_path_component(output_stem)
|
output_stem = sanitize_path_component(output_stem)
|
||||||
output_root_dir.mkdir(parents=True, exist_ok=True)
|
output_root_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
output_dir = self._vna_json_output_dir(output_root_dir, output_stem)
|
||||||
|
output_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
selected_raw, selected_preprocessed, selected_results, selection_summary = select_aligned_histories(
|
selected_raw, selected_preprocessed, selected_results, selection_summary = select_aligned_histories(
|
||||||
raw_history,
|
raw_history,
|
||||||
@@ -296,7 +306,7 @@ class NpzStore(StoreApi):
|
|||||||
output_paths: list[Path] = []
|
output_paths: list[Path] = []
|
||||||
payloads: list[dict[str, Any]] = []
|
payloads: list[dict[str, Any]] = []
|
||||||
for input_index, output_index in combos:
|
for input_index, output_index in combos:
|
||||||
output_path = output_root_dir / (
|
output_path = output_dir / (
|
||||||
f"{output_stem}_i{input_index}_o{output_index}_{channel}_vna_bscan_history.json"
|
f"{output_stem}_i{input_index}_o{output_index}_{channel}_vna_bscan_history.json"
|
||||||
)
|
)
|
||||||
if output_path.exists():
|
if output_path.exists():
|
||||||
@@ -330,6 +340,7 @@ class NpzStore(StoreApi):
|
|||||||
summary["channel"] = str(channel)
|
summary["channel"] = str(channel)
|
||||||
summary["primary_stage"] = str(primary_stage)
|
summary["primary_stage"] = str(primary_stage)
|
||||||
summary["output_stem"] = output_stem
|
summary["output_stem"] = output_stem
|
||||||
|
summary["output_dir"] = str(output_dir)
|
||||||
summary["output_paths"] = [str(path) for path in output_paths]
|
summary["output_paths"] = [str(path) for path in output_paths]
|
||||||
return output_paths, summary
|
return output_paths, summary
|
||||||
|
|
||||||
|
|||||||
@@ -93,6 +93,18 @@
|
|||||||
"set_name": "",
|
"set_name": "",
|
||||||
"bundle_path": ""
|
"bundle_path": ""
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"notch": {
|
||||||
|
"enabled": true,
|
||||||
|
"bands_hz": [
|
||||||
|
[790000000.0, 850000000.0],
|
||||||
|
[1790000000.0, 1890000000.0],
|
||||||
|
[1970000000.0, 2050000000.0],
|
||||||
|
[2080000000.0, 2560000000.0],
|
||||||
|
[2650000000.0, 2700000000.0]
|
||||||
|
],
|
||||||
|
"taper_width_hz": 40000000.0,
|
||||||
|
"taper_type": "cosine"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"gpr": {
|
"gpr": {
|
||||||
|
|||||||
Reference in New Issue
Block a user