complex calib add
This commit is contained in:
@ -8,12 +8,15 @@ 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,
|
||||
recalculate_calibration_c,
|
||||
save_calib_envelope,
|
||||
save_complex_calibration,
|
||||
set_calibration_base_value,
|
||||
)
|
||||
from rfg_adc_plotter.processing.fft import (
|
||||
@ -30,6 +33,8 @@ from rfg_adc_plotter.processing.formatting import (
|
||||
)
|
||||
from rfg_adc_plotter.processing.normalization import (
|
||||
build_calib_envelopes,
|
||||
fit_complex_calibration_to_width,
|
||||
normalize_by_complex_calibration,
|
||||
normalize_by_envelope,
|
||||
normalize_by_calib,
|
||||
)
|
||||
@ -42,6 +47,7 @@ from rfg_adc_plotter.processing.peaks import (
|
||||
__all__ = [
|
||||
"build_calib_envelopes",
|
||||
"build_calib_envelope",
|
||||
"build_complex_calibration_curve",
|
||||
"calibrate_freqs",
|
||||
"compute_auto_ylim",
|
||||
"compute_distance_axis",
|
||||
@ -55,13 +61,17 @@ __all__ = [
|
||||
"get_calibration_base",
|
||||
"get_calibration_coeffs",
|
||||
"load_calib_envelope",
|
||||
"load_complex_calibration",
|
||||
"load_fft_background",
|
||||
"fit_complex_calibration_to_width",
|
||||
"normalize_by_complex_calibration",
|
||||
"normalize_by_envelope",
|
||||
"normalize_by_calib",
|
||||
"parse_spec_clip",
|
||||
"recalculate_calibration_c",
|
||||
"rolling_median_ref",
|
||||
"save_calib_envelope",
|
||||
"save_complex_calibration",
|
||||
"save_fft_background",
|
||||
"set_calibration_base_value",
|
||||
"subtract_fft_background",
|
||||
|
||||
@ -101,6 +101,17 @@ def build_calib_envelope(sweep: np.ndarray) -> np.ndarray:
|
||||
return np.asarray(upper, dtype=np.float32)
|
||||
|
||||
|
||||
def build_complex_calibration_curve(ch1: np.ndarray, ch2: np.ndarray) -> np.ndarray:
|
||||
"""Build a complex calibration curve as ``ch1 + 1j*ch2``."""
|
||||
ch1_arr = np.asarray(ch1, dtype=np.float32).reshape(-1)
|
||||
ch2_arr = np.asarray(ch2, dtype=np.float32).reshape(-1)
|
||||
width = min(ch1_arr.size, ch2_arr.size)
|
||||
if width <= 0:
|
||||
raise ValueError("Complex calibration source is empty")
|
||||
curve = ch1_arr[:width].astype(np.complex64) + (1j * ch2_arr[:width].astype(np.complex64))
|
||||
return validate_complex_calibration_curve(curve)
|
||||
|
||||
|
||||
def validate_calib_envelope(envelope: np.ndarray) -> np.ndarray:
|
||||
"""Validate a saved calibration envelope payload."""
|
||||
values = np.asarray(envelope, dtype=np.float32).reshape(-1)
|
||||
@ -111,6 +122,16 @@ def validate_calib_envelope(envelope: np.ndarray) -> np.ndarray:
|
||||
return values
|
||||
|
||||
|
||||
def validate_complex_calibration_curve(curve: np.ndarray) -> np.ndarray:
|
||||
"""Validate a saved complex calibration payload."""
|
||||
values = np.asarray(curve).reshape(-1)
|
||||
if values.size == 0:
|
||||
raise ValueError("Complex calibration curve is empty")
|
||||
if not np.issubdtype(values.dtype, np.number):
|
||||
raise ValueError("Complex calibration curve must be numeric")
|
||||
return np.asarray(values, dtype=np.complex64)
|
||||
|
||||
|
||||
def _normalize_calib_path(path: str | Path) -> Path:
|
||||
out = Path(path).expanduser()
|
||||
if out.suffix.lower() != ".npy":
|
||||
@ -131,3 +152,18 @@ def load_calib_envelope(path: str | Path) -> np.ndarray:
|
||||
normalized_path = _normalize_calib_path(path)
|
||||
loaded = np.load(normalized_path, allow_pickle=False)
|
||||
return validate_calib_envelope(loaded)
|
||||
|
||||
|
||||
def save_complex_calibration(path: str | Path, curve: np.ndarray) -> str:
|
||||
"""Persist a complex calibration curve as a .npy file and return the final path."""
|
||||
normalized_path = _normalize_calib_path(path)
|
||||
values = validate_complex_calibration_curve(curve)
|
||||
np.save(normalized_path, values.astype(np.complex64, copy=False))
|
||||
return str(normalized_path)
|
||||
|
||||
|
||||
def load_complex_calibration(path: str | Path) -> np.ndarray:
|
||||
"""Load and validate a complex calibration curve from a .npy file."""
|
||||
normalized_path = _normalize_calib_path(path)
|
||||
loaded = np.load(normalized_path, allow_pickle=False)
|
||||
return validate_complex_calibration_curve(loaded)
|
||||
|
||||
@ -148,6 +148,57 @@ def resample_envelope(envelope: np.ndarray, width: int) -> np.ndarray:
|
||||
return np.interp(x_dst, x_src[finite], values[finite]).astype(np.float32)
|
||||
|
||||
|
||||
def fit_complex_calibration_to_width(calib: np.ndarray, width: int) -> np.ndarray:
|
||||
"""Fit a complex calibration curve to the signal width via trim/pad with ones."""
|
||||
target_width = int(width)
|
||||
if target_width <= 0:
|
||||
return np.zeros((0,), dtype=np.complex64)
|
||||
|
||||
values = np.asarray(calib, dtype=np.complex64).reshape(-1)
|
||||
if values.size <= 0:
|
||||
return np.ones((target_width,), dtype=np.complex64)
|
||||
if values.size == target_width:
|
||||
return values.astype(np.complex64, copy=True)
|
||||
if values.size > target_width:
|
||||
return np.asarray(values[:target_width], dtype=np.complex64)
|
||||
|
||||
out = np.ones((target_width,), dtype=np.complex64)
|
||||
out[: values.size] = values
|
||||
return out
|
||||
|
||||
|
||||
def normalize_by_complex_calibration(
|
||||
signal: np.ndarray,
|
||||
calib: np.ndarray,
|
||||
eps: float = 1e-9,
|
||||
) -> np.ndarray:
|
||||
"""Normalize complex signal by a complex calibration curve with zero protection."""
|
||||
sig_arr = np.asarray(signal, dtype=np.complex64).reshape(-1)
|
||||
if sig_arr.size <= 0:
|
||||
return sig_arr.copy()
|
||||
|
||||
calib_fit = fit_complex_calibration_to_width(calib, sig_arr.size)
|
||||
eps_abs = max(abs(float(eps)), 1e-12)
|
||||
denom = np.asarray(calib_fit, dtype=np.complex64).copy()
|
||||
safe_denom = (
|
||||
np.isfinite(denom.real)
|
||||
& np.isfinite(denom.imag)
|
||||
& (np.abs(denom) >= eps_abs)
|
||||
)
|
||||
if np.any(~safe_denom):
|
||||
denom[~safe_denom] = np.complex64(1.0 + 0.0j)
|
||||
|
||||
out = np.full(sig_arr.shape, np.nan + 0j, dtype=np.complex64)
|
||||
valid_sig = np.isfinite(sig_arr.real) & np.isfinite(sig_arr.imag)
|
||||
if np.any(valid_sig):
|
||||
with np.errstate(divide="ignore", invalid="ignore"):
|
||||
out[valid_sig] = sig_arr[valid_sig] / denom[valid_sig]
|
||||
|
||||
out_real = np.nan_to_num(out.real, nan=np.nan, posinf=np.nan, neginf=np.nan)
|
||||
out_imag = np.nan_to_num(out.imag, nan=np.nan, posinf=np.nan, neginf=np.nan)
|
||||
return (out_real + (1j * out_imag)).astype(np.complex64, copy=False)
|
||||
|
||||
|
||||
def normalize_by_envelope(raw: np.ndarray, envelope: np.ndarray) -> np.ndarray:
|
||||
"""Normalize a sweep by an envelope with safe resampling and zero protection."""
|
||||
raw_in = np.asarray(raw).reshape(-1)
|
||||
|
||||
Reference in New Issue
Block a user