added scan averaging and consideration of the epsilon environmen
This commit is contained in:
@@ -35,7 +35,7 @@ VNA_PID = 0x5740 # STM32 Virtual ComPort
|
|||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# Simulator mode settings
|
# Simulator mode settings
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
USE_SIMULATOR = False # Set to True to use simulator instead of real device
|
USE_SIMULATOR = True # Set to True to use simulator instead of real device
|
||||||
SIMULATOR_SWEEP_FILE = BASE_DIR / "binary_input" / "sweep_example" / "example.json"
|
SIMULATOR_SWEEP_FILE = BASE_DIR / "binary_input" / "sweep_example" / "example.json"
|
||||||
SIMULATOR_NOISE_LEVEL = 100 # Standard deviation of Gaussian noise to add to real and imaginary parts
|
SIMULATOR_NOISE_LEVEL = 100 # Standard deviation of Gaussian noise to add to real and imaginary parts
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,21 @@
|
|||||||
{
|
{
|
||||||
"open_air": true,
|
"open_air": false,
|
||||||
|
"subtract_mean_ascan": false,
|
||||||
"axis": "abs",
|
"axis": "abs",
|
||||||
"cut": 0.2,
|
"cut": 0.546,
|
||||||
"max": 1.5,
|
"max": 0.5,
|
||||||
"gain": 1.0,
|
"gain": 1.0,
|
||||||
"start_freq": 400.0,
|
"start_freq": 400.0,
|
||||||
"stop_freq": 5170.0,
|
"stop_freq": 5170.0,
|
||||||
"clear_history": false,
|
"clear_history": false,
|
||||||
"sigma": 0.54,
|
"sigma": 0.54,
|
||||||
"border_border_m": 0.3,
|
"border_border_m": 0.09,
|
||||||
"if_normalize": false,
|
"if_normalize": true,
|
||||||
"if_draw_level": false,
|
"if_draw_level": false,
|
||||||
"detection_level": 8.0,
|
"detection_level": 8.0,
|
||||||
|
"apply_eps_correction": true,
|
||||||
|
"eps_r": 4.4,
|
||||||
|
"eps_boundary_m": 0.12,
|
||||||
"data_limit": 500,
|
"data_limit": 500,
|
||||||
"y_min": -50,
|
"y_min": -50,
|
||||||
"y_max": 40,
|
"y_max": 40,
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ class BScanProcessor(BaseProcessor):
|
|||||||
"""Return default configuration values."""
|
"""Return default configuration values."""
|
||||||
return {
|
return {
|
||||||
"open_air": False, # Toggle for reference usage
|
"open_air": False, # Toggle for reference usage
|
||||||
|
"subtract_mean_ascan": False, # Subtract mean A-scan from each sweep in B-scan
|
||||||
"axis": "abs", # "real", "abs", or "phase"
|
"axis": "abs", # "real", "abs", or "phase"
|
||||||
# "data_limitation": None, # None, "ph_only_1", "ph_only_2"
|
# "data_limitation": None, # None, "ph_only_1", "ph_only_2"
|
||||||
"cut": 0.824, # Cut parameter (meters)
|
"cut": 0.824, # Cut parameter (meters)
|
||||||
@@ -62,6 +63,9 @@ class BScanProcessor(BaseProcessor):
|
|||||||
"if_normalize" : False,
|
"if_normalize" : False,
|
||||||
"if_draw_level" : False,
|
"if_draw_level" : False,
|
||||||
"detection_level" : 5,
|
"detection_level" : 5,
|
||||||
|
"apply_eps_correction": False,
|
||||||
|
"eps_r": 4.0,
|
||||||
|
"eps_boundary_m": 0.5,
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_ui_parameters(self) -> list[UIParameter]:
|
def get_ui_parameters(self) -> list[UIParameter]:
|
||||||
@@ -75,6 +79,12 @@ class BScanProcessor(BaseProcessor):
|
|||||||
type="toggle",
|
type="toggle",
|
||||||
value=cfg["open_air"],
|
value=cfg["open_air"],
|
||||||
),
|
),
|
||||||
|
UIParameter(
|
||||||
|
name="subtract_mean_ascan",
|
||||||
|
label="Вычесть средний A-скан",
|
||||||
|
type="toggle",
|
||||||
|
value=cfg["subtract_mean_ascan"],
|
||||||
|
),
|
||||||
UIParameter(
|
UIParameter(
|
||||||
name="axis",
|
name="axis",
|
||||||
label="Ось",
|
label="Ось",
|
||||||
@@ -168,6 +178,27 @@ class BScanProcessor(BaseProcessor):
|
|||||||
value=cfg["clear_history"],
|
value=cfg["clear_history"],
|
||||||
options={"action": "Очистить накопленную историю графика"},
|
options={"action": "Очистить накопленную историю графика"},
|
||||||
),
|
),
|
||||||
|
# --- NEW: epsilon correction controls ---
|
||||||
|
UIParameter(
|
||||||
|
name="apply_eps_correction",
|
||||||
|
label="Учет ε ниже границы (новая)",
|
||||||
|
type="toggle",
|
||||||
|
value=cfg["apply_eps_correction"],
|
||||||
|
),
|
||||||
|
UIParameter(
|
||||||
|
name="eps_r",
|
||||||
|
label="εr ниже границы (новая)",
|
||||||
|
type="slider",
|
||||||
|
value=cfg["eps_r"],
|
||||||
|
options={"min": 1.0, "max": 30.0, "step": 0.1, "dtype": "float"},
|
||||||
|
),
|
||||||
|
UIParameter(
|
||||||
|
name="eps_boundary_m",
|
||||||
|
label="Граница среды (ε) (м) (новая)",
|
||||||
|
type="slider",
|
||||||
|
value=cfg["eps_boundary_m"],
|
||||||
|
options={"min": 0.0, "max": 2.5, "step": 0.01, "dtype": "float"},
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
def update_config(self, updates: dict[str, Any]) -> None:
|
def update_config(self, updates: dict[str, Any]) -> None:
|
||||||
@@ -310,8 +341,11 @@ class BScanProcessor(BaseProcessor):
|
|||||||
all_sweep_numbers = list(range(1, len(self._plot_history) + 1))
|
all_sweep_numbers = list(range(1, len(self._plot_history) + 1))
|
||||||
all_timestamps = [record["timestamp"] for record in self._plot_history]
|
all_timestamps = [record["timestamp"] for record in self._plot_history]
|
||||||
|
|
||||||
|
adjusted_time_domain = self._apply_mean_ascan_subtraction(all_time_domain)
|
||||||
|
latest_time_domain = adjusted_time_domain[-1] if adjusted_time_domain else analysis["time_data"].tolist()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"time_domain_data": analysis["time_data"].tolist(), # Latest sweep
|
"time_domain_data": latest_time_domain, # Latest sweep (after mean subtraction if enabled)
|
||||||
"distance_data": analysis["distance"].tolist(), # Latest sweep
|
"distance_data": analysis["distance"].tolist(), # Latest sweep
|
||||||
"frequency_range": analysis["freq_range"],
|
"frequency_range": analysis["freq_range"],
|
||||||
"reference_used": bool(self._config["open_air"] and reference_data is not None),
|
"reference_used": bool(self._config["open_air"] and reference_data is not None),
|
||||||
@@ -319,7 +353,7 @@ class BScanProcessor(BaseProcessor):
|
|||||||
"points_processed": int(complex_data.size),
|
"points_processed": int(complex_data.size),
|
||||||
"plot_history_count": len(self._plot_history),
|
"plot_history_count": len(self._plot_history),
|
||||||
# Full history data
|
# Full history data
|
||||||
"all_time_domain_data": all_time_domain,
|
"all_time_domain_data": adjusted_time_domain,
|
||||||
"all_distance_data": all_distance,
|
"all_distance_data": all_distance,
|
||||||
"all_sweep_numbers": all_sweep_numbers,
|
"all_sweep_numbers": all_sweep_numbers,
|
||||||
"all_timestamps": all_timestamps,
|
"all_timestamps": all_timestamps,
|
||||||
@@ -383,9 +417,12 @@ class BScanProcessor(BaseProcessor):
|
|||||||
z_values: list[float] = []
|
z_values: list[float] = []
|
||||||
|
|
||||||
z_values_square = np.zeros((len(history[0]["distance_data"]),len(history)),dtype=float)
|
z_values_square = np.zeros((len(history[0]["distance_data"]),len(history)),dtype=float)
|
||||||
|
time_series = [record["time_domain_data"] for record in history]
|
||||||
|
adjusted_time_series = self._apply_mean_ascan_subtraction(time_series)
|
||||||
|
|
||||||
for sweep_index, item in enumerate(history, start=1):
|
for sweep_index, item in enumerate(history, start=1):
|
||||||
depths = item["distance_data"]
|
depths = item["distance_data"]
|
||||||
amps = item["time_domain_data"]
|
amps = adjusted_time_series[sweep_index - 1] if sweep_index - 1 < len(adjusted_time_series) else item["time_domain_data"]
|
||||||
|
|
||||||
if self._config['if_normalize']:
|
if self._config['if_normalize']:
|
||||||
depth_mask = np.array(depths) < Y_VALUE
|
depth_mask = np.array(depths) < Y_VALUE
|
||||||
@@ -492,6 +529,25 @@ class BScanProcessor(BaseProcessor):
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
if self._config.get("apply_eps_correction", False):
|
||||||
|
eps_boundary = float(self._config.get("eps_boundary_m", 0.0))
|
||||||
|
layout["shapes"] = layout.get("shapes", []) + [
|
||||||
|
{
|
||||||
|
"type": "line",
|
||||||
|
"xref": "paper",
|
||||||
|
"yref": "y",
|
||||||
|
"x0": 0,
|
||||||
|
"x1": 1,
|
||||||
|
"y0": eps_boundary,
|
||||||
|
"y1": eps_boundary,
|
||||||
|
"line": {
|
||||||
|
"width": 2,
|
||||||
|
"dash": "dot",
|
||||||
|
"color": "#00E5FF",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
if detected_trace is not None:
|
if detected_trace is not None:
|
||||||
return {"data": [heatmap_trace,detected_trace], "layout": layout}
|
return {"data": [heatmap_trace,detected_trace], "layout": layout}
|
||||||
return {"data": [heatmap_trace], "layout": layout}
|
return {"data": [heatmap_trace], "layout": layout}
|
||||||
@@ -786,6 +842,59 @@ class BScanProcessor(BaseProcessor):
|
|||||||
depth_fallback = np.linspace(0.0, 1.0, s_array.size, dtype=float)
|
depth_fallback = np.linspace(0.0, 1.0, s_array.size, dtype=float)
|
||||||
return depth_fallback, np.abs(s_array).astype(float, copy=False)
|
return depth_fallback, np.abs(s_array).astype(float, copy=False)
|
||||||
|
|
||||||
|
def _apply_eps_depth_correction(
|
||||||
|
self,
|
||||||
|
depth_out: NDArray[np.floating],
|
||||||
|
) -> NDArray[np.floating]:
|
||||||
|
"""
|
||||||
|
NEW FEATURE:
|
||||||
|
Compress depth axis below eps_boundary_m using epsilon.
|
||||||
|
|
||||||
|
Inputs
|
||||||
|
------
|
||||||
|
depth_out:
|
||||||
|
One-way depth axis (meters). This is what you later plot on Y.
|
||||||
|
|
||||||
|
Config keys used
|
||||||
|
---------------
|
||||||
|
apply_eps_correction: bool
|
||||||
|
If False -> do nothing.
|
||||||
|
eps_r: float
|
||||||
|
Relative permittivity below boundary. Compression factor = 1/sqrt(eps_r).
|
||||||
|
eps_boundary_m: float
|
||||||
|
Depth (m) where the medium changes.
|
||||||
|
|
||||||
|
Mapping
|
||||||
|
-------
|
||||||
|
d0 = eps_boundary_m
|
||||||
|
if d <= d0: unchanged
|
||||||
|
if d > d0: d_corr = d0 + (d - d0)/sqrt(eps_r)
|
||||||
|
"""
|
||||||
|
# 1) Check toggle
|
||||||
|
if not self._config.get("apply_eps_correction", False):
|
||||||
|
return depth_out
|
||||||
|
|
||||||
|
# 2) Read eps_r
|
||||||
|
eps_r = float(self._config.get("eps_r", 1.0))
|
||||||
|
if eps_r <= 1.0:
|
||||||
|
return depth_out
|
||||||
|
|
||||||
|
# 3) Read epsilon boundary depth
|
||||||
|
d0 = float(self._config.get("eps_boundary_m", 0.0))
|
||||||
|
|
||||||
|
# 4) Boundary at/under 0 -> compress everything
|
||||||
|
if d0 <= 0.0:
|
||||||
|
return depth_out / np.sqrt(eps_r)
|
||||||
|
|
||||||
|
# 5) Copy to avoid mutating input array
|
||||||
|
depth_corr = depth_out.astype(float, copy=True)
|
||||||
|
|
||||||
|
# 6) Apply only below boundary
|
||||||
|
mask = depth_corr > d0
|
||||||
|
depth_corr[mask] = d0 + (depth_corr[mask] - d0) / np.sqrt(eps_r)
|
||||||
|
return depth_corr
|
||||||
|
|
||||||
|
|
||||||
def _apply_depth_processing(
|
def _apply_depth_processing(
|
||||||
self,
|
self,
|
||||||
depth_m: NDArray[np.floating],
|
depth_m: NDArray[np.floating],
|
||||||
@@ -824,6 +933,8 @@ class BScanProcessor(BaseProcessor):
|
|||||||
|
|
||||||
# Convert to one-way depth relative to the cut
|
# Convert to one-way depth relative to the cut
|
||||||
depth_out = (depth_win - lo) / 2.0
|
depth_out = (depth_win - lo) / 2.0
|
||||||
|
depth_out = self._apply_eps_depth_correction(depth_out)
|
||||||
|
|
||||||
|
|
||||||
# Depth-dependent gain (safe for zero depth with exponent >= 0)
|
# Depth-dependent gain (safe for zero depth with exponent >= 0)
|
||||||
with np.errstate(invalid="ignore"):
|
with np.errstate(invalid="ignore"):
|
||||||
@@ -838,6 +949,31 @@ class BScanProcessor(BaseProcessor):
|
|||||||
logger.error("Depth processing failed", error=repr(exc))
|
logger.error("Depth processing failed", error=repr(exc))
|
||||||
return depth_m, response
|
return depth_m, response
|
||||||
|
|
||||||
|
def _apply_mean_ascan_subtraction(
|
||||||
|
self,
|
||||||
|
time_domain_series: list[list[float]],
|
||||||
|
) -> list[list[float]]:
|
||||||
|
"""
|
||||||
|
Subtract mean A-scan (across sweeps) from each A-scan in the B-scan.
|
||||||
|
|
||||||
|
This operates on time-domain data after reference subtraction and IFFT.
|
||||||
|
"""
|
||||||
|
if not self._config.get("subtract_mean_ascan", False):
|
||||||
|
return time_domain_series
|
||||||
|
|
||||||
|
lengths = {len(series) for series in time_domain_series}
|
||||||
|
if len(lengths) != 1:
|
||||||
|
logger.warning(
|
||||||
|
"Mean A-scan subtraction skipped due to inconsistent lengths",
|
||||||
|
lengths=sorted(lengths),
|
||||||
|
)
|
||||||
|
return time_domain_series
|
||||||
|
|
||||||
|
data = np.asarray(time_domain_series, dtype=float)
|
||||||
|
mean_trace = np.mean(data, axis=0)
|
||||||
|
adjusted = data - mean_trace
|
||||||
|
return [row.tolist() for row in adjusted]
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
# State export override
|
# State export override
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
@@ -862,7 +998,7 @@ class BScanProcessor(BaseProcessor):
|
|||||||
all_timestamps = [record["timestamp"] for record in self._plot_history]
|
all_timestamps = [record["timestamp"] for record in self._plot_history]
|
||||||
|
|
||||||
state["plot_data"] = {
|
state["plot_data"] = {
|
||||||
"all_time_domain_data": all_time_domain,
|
"all_time_domain_data": self._apply_mean_ascan_subtraction(all_time_domain),
|
||||||
"all_distance_data": all_distance,
|
"all_distance_data": all_distance,
|
||||||
"all_sweep_numbers": all_sweep_numbers,
|
"all_sweep_numbers": all_sweep_numbers,
|
||||||
"all_timestamps": all_timestamps,
|
"all_timestamps": all_timestamps,
|
||||||
|
|||||||
Reference in New Issue
Block a user