This commit is contained in:
awe
2026-03-25 18:54:59 +03:00
parent fa4870c56c
commit 64e66933e4
14 changed files with 350 additions and 42 deletions

View File

@ -65,14 +65,23 @@ def set_calibration_base_value(index: int, value: float) -> np.ndarray:
def calibrate_freqs(sweep: Mapping[str, Any]) -> SweepData:
"""Return a sweep copy with calibrated and resampled frequency axis."""
freqs = np.asarray(sweep["F"], dtype=np.float64).copy()
values = np.asarray(sweep["I"], dtype=np.float64).copy()
values_in = np.asarray(sweep["I"]).reshape(-1)
values = np.asarray(
values_in,
dtype=np.complex128 if np.iscomplexobj(values_in) else np.float64,
).copy()
coeffs = np.asarray(CALIBRATION_C, dtype=np.float64)
if freqs.size > 0:
freqs = coeffs[0] + coeffs[1] * freqs + coeffs[2] * (freqs * freqs)
if freqs.size >= 2:
freqs_cal = np.linspace(float(freqs[0]), float(freqs[-1]), freqs.size, dtype=np.float64)
values_cal = np.interp(freqs_cal, freqs, values).astype(np.float64)
if np.iscomplexobj(values):
values_real = np.interp(freqs_cal, freqs, values.real.astype(np.float64, copy=False))
values_imag = np.interp(freqs_cal, freqs, values.imag.astype(np.float64, copy=False))
values_cal = (values_real + (1j * values_imag)).astype(np.complex64)
else:
values_cal = np.interp(freqs_cal, freqs, values).astype(np.float64)
else:
freqs_cal = freqs.copy()
values_cal = values.copy()

View File

@ -24,6 +24,21 @@ def _finite_freq_bounds(freqs: Optional[np.ndarray]) -> Optional[Tuple[float, fl
return f_min, f_max
def _coerce_sweep_array(sweep: np.ndarray) -> np.ndarray:
values = np.asarray(sweep).reshape(-1)
if np.iscomplexobj(values):
return np.asarray(values, dtype=np.complex64)
return np.asarray(values, dtype=np.float32)
def _interp_signal(x_uniform: np.ndarray, x_known: np.ndarray, y_known: np.ndarray) -> np.ndarray:
if np.iscomplexobj(y_known):
real = np.interp(x_uniform, x_known, np.asarray(y_known.real, dtype=np.float64))
imag = np.interp(x_uniform, x_known, np.asarray(y_known.imag, dtype=np.float64))
return (real + (1j * imag)).astype(np.complex64)
return np.interp(x_uniform, x_known, np.asarray(y_known, dtype=np.float64)).astype(np.float32)
def prepare_fft_segment(
sweep: np.ndarray,
freqs: Optional[np.ndarray],
@ -34,8 +49,10 @@ def prepare_fft_segment(
if take_fft <= 0:
return None
sweep_seg = np.asarray(sweep[:take_fft], dtype=np.float32)
fallback = np.nan_to_num(sweep_seg, nan=0.0).astype(np.float32, copy=False)
sweep_arr = _coerce_sweep_array(sweep)
sweep_seg = sweep_arr[:take_fft]
fallback_dtype = np.complex64 if np.iscomplexobj(sweep_seg) else np.float32
fallback = np.nan_to_num(sweep_seg, nan=0.0).astype(fallback_dtype, copy=False)
if freqs is None:
return fallback, take_fft
@ -59,7 +76,7 @@ def prepare_fft_segment(
return fallback, take_fft
x_uniform = np.linspace(float(x_unique[0]), float(x_unique[-1]), take_fft, dtype=np.float64)
resampled = np.interp(x_uniform, x_unique, y_unique).astype(np.float32)
resampled = _interp_signal(x_uniform, x_unique, y_unique)
return resampled, take_fft
@ -94,18 +111,20 @@ def build_symmetric_ifft_spectrum(
fft_seg, take_fft = prepared
if take_fft != band_len:
fft_seg = np.asarray(fft_seg[:band_len], dtype=np.float32)
fft_dtype = np.complex64 if np.iscomplexobj(fft_seg) else np.float32
fft_seg = np.asarray(fft_seg[:band_len], dtype=fft_dtype)
if fft_seg.size < band_len:
padded = np.zeros((band_len,), dtype=np.float32)
padded = np.zeros((band_len,), dtype=fft_dtype)
padded[: fft_seg.size] = fft_seg
fft_seg = padded
window = np.hanning(band_len).astype(np.float32)
band = np.nan_to_num(fft_seg, nan=0.0).astype(np.float32, copy=False) * window
band_dtype = np.complex64 if np.iscomplexobj(fft_seg) else np.float32
band = np.nan_to_num(fft_seg, nan=0.0).astype(band_dtype, copy=False) * window
spectrum = np.zeros((int(fft_len),), dtype=np.float32)
spectrum = np.zeros((int(fft_len),), dtype=band_dtype)
spectrum[pos_idx] = band
spectrum[neg_idx] = band[::-1]
spectrum[neg_idx] = np.conj(band[::-1]) if np.iscomplexobj(band) else band[::-1]
return spectrum
@ -137,16 +156,18 @@ def build_positive_only_centered_ifft_spectrum(
fft_seg, take_fft = prepared
if take_fft != band_len:
fft_seg = np.asarray(fft_seg[:band_len], dtype=np.float32)
fft_dtype = np.complex64 if np.iscomplexobj(fft_seg) else np.float32
fft_seg = np.asarray(fft_seg[:band_len], dtype=fft_dtype)
if fft_seg.size < band_len:
padded = np.zeros((band_len,), dtype=np.float32)
padded = np.zeros((band_len,), dtype=fft_dtype)
padded[: fft_seg.size] = fft_seg
fft_seg = padded
window = np.hanning(band_len).astype(np.float32)
band = np.nan_to_num(fft_seg, nan=0.0).astype(np.float32, copy=False) * window
band_dtype = np.complex64 if np.iscomplexobj(fft_seg) else np.float32
band = np.nan_to_num(fft_seg, nan=0.0).astype(band_dtype, copy=False) * window
spectrum = np.zeros((int(fft_len),), dtype=np.float32)
spectrum = np.zeros((int(fft_len),), dtype=band_dtype)
spectrum[pos_idx] = band
return spectrum
@ -168,7 +189,8 @@ def _compute_fft_mag_row_direct(
return np.full((bins,), np.nan, dtype=np.float32)
fft_seg, take_fft = prepared
fft_in = np.zeros((FFT_LEN,), dtype=np.float32)
fft_dtype = np.complex64 if np.iscomplexobj(fft_seg) else np.float32
fft_in = np.zeros((FFT_LEN,), dtype=fft_dtype)
window = np.hanning(take_fft).astype(np.float32)
fft_in[:take_fft] = fft_seg * window
spec = np.fft.ifft(fft_in)

View File

@ -150,18 +150,24 @@ def resample_envelope(envelope: np.ndarray, width: int) -> np.ndarray:
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_arr = np.asarray(raw, dtype=np.float32).reshape(-1)
raw_in = np.asarray(raw).reshape(-1)
raw_dtype = np.complex64 if np.iscomplexobj(raw_in) else np.float32
raw_arr = np.asarray(raw_in, dtype=raw_dtype).reshape(-1)
if raw_arr.size == 0:
return raw_arr.copy()
env = resample_envelope(envelope, raw_arr.size)
out = np.full_like(raw_arr, np.nan, dtype=np.float32)
out = np.full(raw_arr.shape, np.nan + 0j if np.iscomplexobj(raw_arr) else np.nan, dtype=raw_dtype)
den_eps = np.float32(1e-9)
valid = np.isfinite(raw_arr) & np.isfinite(env)
if np.any(valid):
with np.errstate(divide="ignore", invalid="ignore"):
denom = env[valid] + np.where(env[valid] >= 0.0, den_eps, -den_eps)
out[valid] = raw_arr[valid] / denom
if np.iscomplexobj(out):
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)
return np.nan_to_num(out, nan=np.nan, posinf=np.nan, neginf=np.nan)