Source code for torx.analysis.smoothing.smoothing_1d_m

"""Provides smoothing functionality for 1D arrays."""
import warnings
import numpy as np
from scipy.interpolate import interp1d
from torx.decorators import autodoc_function

[docs] @autodoc_function def smooth_1d_block(array: np.ndarray, axis: np.ndarray, block_length: int): """ Smooth a 1D array by averaging over non-overlapping blocks. The array is split into consecutive blocks of block_length, each block is collapsed to its mean, and the result is linearly interpolated back onto the original axis. Neighboring blocks do not overlap, so the output is piecewise linear on the block scale and the endpoints are extrapolated rather than preserved. See smooth_1d_window for an overlapping full-resolution alternative. Block length should be picked sensibly since too small blocks have little effect and too big blocks will smooth out large scale structures. Can be used for easier interpretation of noisy signals. Parameters ---------- array : numpy.ndarray Input signal. axis : numpy.ndarray Monotonic independent variable, same shape as array. block_length : int Number of samples averaged per block. A value of 1 is a no-op. Returns ------- numpy.ndarray Smoothed signal, same shape as array. """ assert len(array.shape) == 1, "Only 1D arrays can be smoothed" assert array.shape == axis.shape, "Axis and array shapes do not match" # Check for common denominator if len(array) % block_length: blocked_array = np.zeros((len(array) // block_length + 1)) blocked_axis = np.copy(blocked_array) else: blocked_array = np.zeros((len(array) // block_length)) blocked_axis = np.copy(blocked_array) # Average array values for each block for i in range(len(blocked_array)): # The last block is possibly shorter, so take everything left. if i == len(blocked_array) - 1: blocked_array[i] = np.mean(array[i * block_length:]) blocked_axis[i] = np.mean(axis[i * block_length:]) else: block = slice(i * block_length, (i + 1) * block_length) blocked_array[i] = np.mean(array[block]) blocked_axis[i] = np.mean(axis[block]) return interp1d( blocked_axis, blocked_array, kind="linear", fill_value="extrapolate" )(axis)
[docs] @autodoc_function def smooth_1d_window(array: np.ndarray, smooth_length: int) -> np.ndarray: """ Smooth a 1D array with a sliding, overlapping averaging window. Every sample is replaced by the mean of the window centered on it, so consecutive windows overlap and the output keeps the full resolution of the input. The window length is forced odd and the window shrinks symmetrically toward both ends so the first and last points stay unchanged. Useful for de-spiking the ragged outer field-line-traced flux surfaces in the scrape-off layer. See smooth_1d_block for the non-overlapping variant. No axis is required: the window is defined in sample index space, so the input is assumed to be evenly sampled. Parameters ---------- array : numpy.ndarray Input signal. smooth_length : int Length of the averaging window. Even values are rounded down to the next odd value so the window stays symmetric about each point. A length below 3 (or an array shorter than 3) returns a copy unchanged. Returns ------- numpy.ndarray Smoothed signal, same shape as ``array``. Warns ----- UserWarning If the effective window is below 3, or the array has fewer than 3 points. Either case is a no-op, so the warning distinguishes it from smoothing that ran but had little effect. Note that an even smooth_length is rounded down first, so a value of 2 warns while 4 does not. """ y = np.asarray(array, float) n = y.size requested_length = smooth_length if smooth_length % 2 == 0: smooth_length -= 1 if smooth_length < 3: warnings.warn( f"smooth_length={requested_length} gives an effective window of " f"{smooth_length}, which is below the minimum of 3. No smoothing " "is applied and the input is returned unchanged.", UserWarning, stacklevel=2) return y.copy() if n < 3: warnings.warn( f"An array of {n} point(s) is shorter than the minimum window of " "3. No smoothing is applied and the input is returned unchanged.", UserWarning, stacklevel=2) return y.copy() half = smooth_length // 2 # Prefix sums so a window mean is a single subtraction. cumulative = np.concatenate([[0.0], np.cumsum(y)]) smoothed = y.copy() for i in range(n): # Symmetric half-window, shrinking toward both ends. m = min(i, n - 1 - i, half) if m > 0: smoothed[i] = (cumulative[i + m + 1] - cumulative[i - m]) \ / (2 * m + 1) return smoothed