Source code for pykrait.trace_analysis.filtering
import numpy as np
from math import pi
from scipy.signal import fftconvolve
from scipy.ndimage import gaussian_filter1d
def _blackman_sinc_filter_kernel(kernel_window: int, cutoff_freq: float) -> list[float]:
"""Generates a Blackman windowed sinc filter kernel
:param kernel_window: window size for the kernel, must be even
:type kernel_window: int
:param cutoff_freq: cut-off frequency, oscillations of lower frequencies will be blocked
:type cutoff_freq: float
:return: returns the filter kernel as a list of floats
:rtype: list
See the pyBOAT paper (https://www.biorxiv.org/content/10.1101/2020.04.29.067744v2) and Chapter 16 of the The Scientist and Engineer's Guide to Digital Signal Processing (https://www.dspguide.com/ch16/1.htm) for more details.
"""
if kernel_window % 2 != 0:
raise ValueError("Filter kernel window must be even")
kernel = np.zeros(kernel_window + 1, dtype=float)
midpoint = kernel_window / 2
for n in range(kernel_window + 1):
if n == midpoint:
val = 2 * pi * cutoff_freq
else:
val = np.sin(2 * pi * cutoff_freq * (n - midpoint)) / (n - midpoint)
# Apply Blackman window
val *= (
0.42
- 0.5 * np.cos(2 * pi * n / kernel_window)
+ 0.08 * np.cos(4 * pi * n / kernel_window)
)
kernel[n] = val
kernel /= np.sum(kernel)
return kernel
def _convolve_signals_with_kernel(
signals: np.ndarray, filter_kernel: list
) -> np.ndarray:
"""Convolve a 2D array of signals with a filter kernel using FFT convolution.
:param signals: 2D numpy array of shape (T x n_rois)
:type signals: np.ndarray
:param filter_kernel: kernel to convolve with, must be odd length
:type filter_kernel: list
:raises ValueError: raises a ValueError if the filter kernel length is not odd
:return: 2D numpy array of convolved signals in the shape (T x n_rois)
:rtype: np.ndarray
"""
kernel_length = len(filter_kernel)
if kernel_length % 2 == 0:
raise ValueError("Filter kernel length must be odd")
# Convert to 1D array if needed
filter_kernel = np.asarray(filter_kernel).flatten()
# Pad signals along time axis (axis=0)
left_padding = signals[kernel_length - 1 : 0 : -1, :]
right_padding = signals[-2 : -kernel_length - 1 : -1, :]
padded_signals = np.concatenate([left_padding, signals, right_padding], axis=0)
# Apply convolution along axis=0 (time)
convolved = fftconvolve(
padded_signals, filter_kernel[:, None], mode="valid", axes=0
)
# Extract center portion to match input shape
start = (kernel_length - 1) // 2
end = start + signals.shape[0]
result = convolved[start:end, :]
return result
[docs]
def detrend_with_sinc_filter(
signals: np.ndarray, cutoff_period: float, sampling_interval: float
) -> np.ndarray:
"""Applies a Blackman-windowd sinc filter to a 2D array of signals to detrend them. Frequencies lower than the cutoff frequency will be blocked, while higher frequencies will pass through unaffected.
See the pyBOAT paper (https://www.biorxiv.org/content/10.1101/2020.04.29.067744v2) and Chapter 16 of the The Scientist and Engineer's Guide to Digital Signal Processing (https://www.dspguide.com/ch16/1.htm) for more details.
:param signals: 2D numpy array of shape (T x n_rois)
:type signals: np.ndarray
:param cutoff_period: cutoff period in [time units], lower frequencies will be blocked
:type cutoff_period: float
:param sampling_interval: sampling interval in [time units] of the signals
:type sampling_interval: float
:return: returns the detrended signals as a 2D numpy array of shape (T x n_rois)
:rtype: np.ndarray
"""
signal_length, _ = signals.shape
kernel_window = signal_length - 1
if kernel_window % 2 != 0:
kernel_window -= 1 # Make kernel window even
cutoff_frequency = (
sampling_interval / cutoff_period
) # Cutoff freq in sampling units
filter_kernel = _blackman_sinc_filter_kernel(kernel_window, cutoff_frequency)
smoothed_signals = _convolve_signals_with_kernel(signals, filter_kernel)
detrended_signals = signals - smoothed_signals
return detrended_signals
[docs]
def smooth_gauss(signal: np.ndarray, sigma: float) -> np.ndarray:
"""Applies a Gaussian smoothing filter to the 2D input signal.
:param signal: 2D numpy array of shape (T x n_rois)
:type signal: np.ndarray
:param sigma: Standard deviation for the Gaussian kernel.
:type sigma: float
:return: The smoothed signal of shape (T x n_rois).
:rtype: np.ndarray
"""
if sigma <= 0:
return signal
return gaussian_filter1d(signal, sigma, axis=0)