adctoolbox.aout.analyze_error_spectrum 源代码

"""Error spectrum analysis.

Analyzes the spectrum of the fitting error signal directly to reveal
frequency components and error characteristics.
"""

import matplotlib.pyplot as plt

from adctoolbox.spectrum import analyze_spectrum
from adctoolbox.fundamentals.fit_sine_4param import fit_sine_4param
from adctoolbox.aout._fit_diagnostics import extract_fit_diagnostics

[文档] def analyze_error_spectrum(signal, fs=1, frequency=None, create_plot: bool = True, ax=None, title: str = None, max_iterations: int = 1, tolerance: float = 1e-9, return_fit: bool = False, max_scale_range=None): """ Compute error spectrum directly from the error signal. This function fits an ideal sine to the signal, computes the error, and analyzes the spectrum of the error signal (not envelope). Parameters ---------- signal : np.ndarray ADC output signal (1D array) fs : float, default=1 Sampling frequency in Hz frequency : float, optional Normalized frequency (0-0.5). If None, auto-detected create_plot : bool, default=True If True, plot the error spectrum on current axes ax : matplotlib.axes.Axes, optional Axes to plot on. If None, uses current axes (plt.gca()) title : str, optional Title for the plot. If None, no title is set max_iterations : int, default=1 Frequency-refinement iterations passed to fit_sine_4param. tolerance : float, default=1e-9 Frequency-refinement convergence threshold passed to fit_sine_4param. return_fit : bool, default=False If True, include scalar sine-fit diagnostics under result['fit']. max_scale_range : float or tuple/list, optional Full-scale reference passed to analyze_spectrum for residual scaling. If None, the residual is self-normalized, which is useful for viewing frequency fingerprints but does not preserve ADC full-scale dBFS meaning. Pass the ADC range, for example (0, 1), to report the residual spectrum relative to ADC full scale. Returns ------- result : dict Dictionary containing spectrum analysis results: - 'enob': Effective Number of Bits - 'sndr_db': Signal-to-Noise and Distortion Ratio (dB) - 'sfdr_db': Spurious-Free Dynamic Range (dB) - 'snr_db': Signal-to-Noise Ratio (dB) - 'thd_db': Total Harmonic Distortion (dB) - 'sig_pwr_dbfs': Signal power (dBFS) - 'noise_floor_dbfs': Noise floor (dBFS) - 'error_signal': Error signal (signal - fitted sine) - 'error_spectrum_scale': 'residual' when max_scale_range is None, otherwise 'adc_full_scale' - 'error_spectrum_max_scale_range': Full-scale reference used for residual spectrum analysis - 'fit': Optional sine-fit diagnostics when return_fit=True Notes ----- - Error = signal - ideal_sine (fitted using fit_sine_4param) - Analyzes spectrum of error directly (no envelope extraction) - Reveals frequency components in the error signal - With max_scale_range=None, the residual is normalized to its own peak range. Pass an ADC full-scale range when dBFS, noise floor, or NSD should retain engineering full-scale meaning. """ # Fit ideal sine to extract reference fit_kwargs = {"max_iterations": max_iterations, "tolerance": tolerance} if frequency is None: fit_result = fit_sine_4param(signal, **fit_kwargs) else: fit_result = fit_sine_4param(signal, frequency_estimate=frequency, **fit_kwargs) sig_ideal = fit_result['fitted_signal'] # Compute error error_signal = signal - sig_ideal error_scale = "residual" if max_scale_range is None else "adc_full_scale" ylabel = "Error Spectrum (dB, residual FS)" if max_scale_range is None else "Error Spectrum (dBFS)" # Analyze error spectrum directly (not envelope) if create_plot: # Use provided axes or set current axes if ax is not None: plt.sca(ax) result = analyze_spectrum( error_signal, fs=fs, max_scale_range=max_scale_range, show_label=False, max_harmonic=5, ) plt.xlabel("Frequency (Hz)") plt.ylabel(ylabel) plt.grid(True, alpha=0.3) # Set title if provided if title is not None: plt.gca().set_title(title, fontsize=10, fontweight='bold') else: # Analyze without plotting import matplotlib backend_orig = matplotlib.get_backend() matplotlib.use('Agg') # Non-interactive backend result = analyze_spectrum( error_signal, fs=fs, max_scale_range=max_scale_range, show_label=False, max_harmonic=5, ) plt.close() matplotlib.use(backend_orig) # Restore original backend # Add error signal to results result['error_signal'] = error_signal result['error_spectrum_scale'] = error_scale result['error_spectrum_max_scale_range'] = max_scale_range if return_fit: result['fit'] = extract_fit_diagnostics(fit_result) return result