Source code for neuroconv.tools.signal_processing
import numpy as np
[docs]
def get_rising_frames_from_ttl(trace: np.ndarray, threshold: float | None = None) -> np.ndarray:
"""
Return the frame indices for rising events in a TTL pulse.
Parameters
----------
trace : numpy.ndarray
A TTL signal.
threshold : float, optional
The threshold used to distinguish on/off states in the trace.
The mean of the trace is used by default.
Returns
-------
rising_frames : numpy.ndarray
The frame indices of rising events.
"""
flattened_trace = np.ravel(trace) # Shapes like (1, x, 1, 1) might result from slicing patterns and are allowed
if np.max(trace.shape) != flattened_trace.shape[0]: # TODO: when 3.7 dropped, use math.prod to avoid overflow
raise ValueError(f"This function expects a one-dimensional array! Received shape of {trace.shape}.")
threshold = np.mean(trace) if threshold is None else threshold
sign = np.sign(flattened_trace - threshold)
diff = np.diff(sign)
rising_frames = np.where(diff > 0)[0] + 1
return rising_frames
[docs]
def get_falling_frames_from_ttl(trace: np.ndarray, threshold: float | None = None) -> np.ndarray:
"""
Return the frame indices for falling events in a TTL pulse.
Parameters
----------
trace : numpy.ndarray
A TTL signal.
threshold : float, optional
The threshold used to distinguish on/off states in the trace.
The mean of the trace is used by default.
Returns
-------
falling_frames : numpy.ndarray
The frame indices of falling events.
"""
flattened_trace = np.ravel(trace) # Shapes like (1, x, 1, 1) might result from slicing patterns and are allowed
if np.max(trace.shape) != flattened_trace.shape[0]: # TODO: when 3.7 dropped, use math.prod to avoid overflow
raise ValueError(f"This function expects a one-dimensional array! Received shape of {trace.shape}.")
threshold = np.mean(trace) if threshold is None else threshold
sign = np.sign(flattened_trace - threshold)
diff = np.diff(sign)
falling_frames = np.where(diff < 0)[0] + 1
return falling_frames
def _run_length_encode_labels(
labels: np.ndarray,
timestamps: np.ndarray,
frame_period: float | None = None,
) -> list[tuple[float, float, int]]:
"""
Run-length-encode a per-frame integer label array into labeled time intervals.
A label at frame ``k`` occupies ``[timestamps[k], timestamps[k] + frame_period)``, so a maximal
run of identical labels over frames ``[i, j]`` becomes one interval
``[timestamps[i], timestamps[j] + frame_period]``. For a regular series the stop time of one
interval equals the start time of the next, giving a gapless single-label partition.
Parameters
----------
labels : numpy.ndarray
1D array of per-frame integer labels (e.g. behavioral motif/syllable ids, threshold states).
timestamps : numpy.ndarray
1D array of frame times in seconds, the same length as ``labels``.
frame_period : float, optional
Duration of a single frame in seconds. Defaults to the median inter-frame interval of
``timestamps``.
Returns
-------
list of tuple of (float, float, int)
One ``(start_time, stop_time, label)`` tuple per run, in time order.
"""
labels = np.asarray(labels)
timestamps = np.asarray(timestamps)
if frame_period is None:
frame_period = float(np.median(np.diff(timestamps)))
boundaries = np.flatnonzero(np.diff(labels)) + 1
run_starts = np.concatenate(([0], boundaries))
run_ends = np.concatenate((boundaries, [labels.size])) # exclusive frame index
return [
(
float(timestamps[start_index]),
float(timestamps[end_index - 1] + frame_period),
int(labels[start_index]),
)
for start_index, end_index in zip(run_starts, run_ends)
]