Skip to content

dspk.synapse.detection

Spike detection: threshold crossings, Teager–Kaiser energy (NEO) and matched filtering on data in memory, spatial de-duplication across neighbouring channels, and the streaming detector over a whole recording (detect_recording, as dspk detect).

Detections are dicts of arrays sorted by time: samples (sample index), channels, amplitudes (µV at the extremum).

deduplicate One spike per event seen on several neighbouring channels.
detect_recording Detects spikes over a whole recording, as dspk detect does.
heights Detection height of every channel.
matched_filter Matched-filter detection.
neo Peaks of the Teager–Kaiser energy (NEO) beyond threshold * sigma(energy) on each channel.
prototype A biphasic spike shape.
threshold Threshold crossings.
deduplicate(
spikes: dict,
probe: Probe,
sample_rate: float,
*,
radius_um: float = DEFAULT_RADIUS_UM,
window_ms: float = 0.5,
) -> dict

One spike per event seen on several neighbouring channels: the largest stays.

Parameters

  • spikes : dict of numpy.ndarray
    samples, channels and amplitudes, as the detectors return.
  • probe : Probe
    The probe, for distances between channels.
  • sample_rate : float
    Sample rate in Hz.
  • radius_um : float, default DEFAULT_RADIUS_UM (150.0)
    Spikes on channels closer than this…
  • window_ms : float, default 0.5
    …and closer in time than this are one event.

Returns

  • dict of np.ndarray — The spikes kept, plus participating: the channels each event was seen on.
detect_recording(
recording,
probe: Probe,
*,
band: tuple[float, float] = DEFAULT_BAND,
car: bool = False,
threshold: float = DEFAULT_RECORDING_THRESHOLD,
refractory_ms: float = DEFAULT_REFRACTORY_MS,
polarity: str = 'negative',
spacing: str = 'locally_exclusive',
radius_um: float = DEFAULT_RADIUS_UM,
k_neighbors: int = DEFAULT_K_NEIGHBORS,
pre_ms: float = DEFAULT_PRE_MS,
post_ms: float = DEFAULT_POST_MS,
sinc_shift: bool = True,
locate: bool = True,
batch_s: float = DEFAULT_BATCH_S,
calibration_s: float = DEFAULT_CALIBRATION_S,
calibration_chunks: int = DEFAULT_CALIBRATION_CHUNKS,
runtime: str | None = None,
) -> tuple[Sorting, dict]

Detects spikes over a whole recording, as dspk detect does.

The recording is read in place in batch_s batches: optional CAR, zero-phase band-pass, noise calibrated on chunks spread over the recording, threshold crossings, spatial de-duplication, snippets and per-channel templates.

Parameters

  • recording : dspk.io.Recording
    The recording.
  • probe : Probe
    Its probe.
  • band : float, float, default DEFAULT_BAND
    Band-pass (low, high) in Hz.
  • car : bool, default False
    Subtract the common average first.
  • threshold : float, default DEFAULT_RECORDING_THRESHOLD (5.0)
    Height in units of each channel’s noise.
  • refractory_ms : float, default DEFAULT_REFRACTORY_MS (1.0)
    Spikes on one channel stay more than this apart.
  • polarity : {"negative", "positive", "both"}, default 'negative'
    Which extrema count.
  • spacing : {"locally_exclusive", "scipy"}, default 'locally_exclusive'
    Which of two nearer spikes stays (see threshold).
  • radius_um : float, default DEFAULT_RADIUS_UM (150.0)
    De-duplication radius.
  • k_neighbors : int, default DEFAULT_K_NEIGHBORS (4)
    Channels per snippet (the spike’s nearest).
  • pre_ms : float, default DEFAULT_PRE_MS (1.0)
    Snippet span around each spike.
  • post_ms : float, default DEFAULT_POST_MS (2.0)
    Snippet span around each spike.
  • sinc_shift : bool, default True
    Realign each snippet to its sub-sample trough.
  • locate : bool, default True
    Locate every spike: the centre of mass of its snippet’s heights over its k_neighbors.
  • batch_s : float, default DEFAULT_BATCH_S (10.0)
    Seconds read and processed at a time.
  • calibration_s : float, default DEFAULT_CALIBRATION_S (5.0)
    Seconds of signal the noise is measured on.
  • calibration_chunks : int, default DEFAULT_CALIBRATION_CHUNKS (5)
    Pieces those seconds are spread over.
  • runtime : str, default None
    A runtime from runtime.available; by default the current one.

Returns

  • Sorting — One unit per channel; spikes()["locations"] holds the locations (NaN without locate).
  • dict — sigmas_uv (noise per channel), raw_crossings, spikes, channel_counts.

Raises

  • ValueError: For an invalid option.
  • OSError: When the recording cannot be read.
  • RuntimeError: When the runtime has no device.

Examples

>>> import dspk
>>> rec = dspk.io.open("session.bin")
>>> probe = dspk.synapse.io.Probe.preset("neuropixels1")
>>> sorting, summary = dspk.synapse.detection.detect_recording(rec, probe)
heights(
sigmas,
threshold: float = DEFAULT_THRESHOLD,
) -> np.ndarray

Detection height of every channel: threshold * sigma.

Parameters

  • sigmas : array_like
    Each channel’s noise (channels,).
  • threshold : float, default DEFAULT_THRESHOLD (4.5)
    Height in units of noise.

Returns

  • np.ndarray — (channels,); inf where sigma is not positive (the channel never detects).
matched_filter(
data,
sample_rate: float,
*,
prototype_ms: float = 1.0,
prototype_=None,
threshold: float = DEFAULT_THRESHOLD,
refractory_ms: float = DEFAULT_REFRACTORY_MS,
spacing: str = 'locally_exclusive',
) -> dict

Matched-filter detection: correlation with a spike shape, peaks beyond threshold * sigma.

Only peaks where the signal is negative count.

Parameters

  • data : array_like
    Signal (channels, samples) in µV, converted to float32 on the host.
  • sample_rate : float
    Sample rate in Hz.
  • prototype_ms : float, default 1.0
    Length of the default shape (prototype), in ms.
  • prototype_ : array_like, default None
    Your own shape, instead of the default.
  • threshold : float, default DEFAULT_THRESHOLD (4.5)
    Height in units of each channel’s correlation noise.
  • refractory_ms : float, default DEFAULT_REFRACTORY_MS (1.0)
    Spikes on one channel stay more than this apart.
  • spacing : {"locally_exclusive", "scipy"}, default 'locally_exclusive'
    Which of two nearer spikes stays. "locally_exclusive": a spike stays unless a larger one is nearer, which chunked detection reproduces exactly. "scipy": as scipy.signal.find_peaks with distance.

Returns

  • dict of np.ndarray — samples (sample index), channels and amplitudes (µV at the extremum), sorted by time.
neo(
data,
sample_rate: float,
*,
threshold: float = DEFAULT_NEO_THRESHOLD,
refractory_ms: float = DEFAULT_REFRACTORY_MS,
spacing: str = 'locally_exclusive',
) -> dict

Peaks of the Teager–Kaiser energy (NEO) beyond threshold * sigma(energy) on each channel.

The energy is large where the signal is both large and fast, so it favours spikes over slow waves.

Parameters

  • data : array_like
    Signal (channels, samples) in µV, converted to float32 on the host.
  • sample_rate : float
    Sample rate in Hz.
  • threshold : float, default DEFAULT_NEO_THRESHOLD (8.0)
    Height in units of each channel’s energy noise.
  • refractory_ms : float, default DEFAULT_REFRACTORY_MS (1.0)
    Spikes on one channel stay more than this apart.
  • spacing : {"locally_exclusive", "scipy"}, default 'locally_exclusive'
    Which of two nearer spikes stays. "locally_exclusive": a spike stays unless a larger one is nearer, which chunked detection reproduces exactly. "scipy": as scipy.signal.find_peaks with distance.

Returns

  • dict of np.ndarray — samples (sample index), channels and amplitudes (µV at the extremum), sorted by time.
prototype(samples: int) -> np.ndarray

A biphasic spike shape: zero mean, unit norm.

Parameters

  • samples : int
    Length.

Returns

  • np.ndarray — (samples,), float32.
threshold(
data,
sample_rate: float,
*,
threshold: float = DEFAULT_THRESHOLD,
polarity: str = 'negative',
refractory_ms: float = DEFAULT_REFRACTORY_MS,
sigmas=None,
spacing: str = 'locally_exclusive',
) -> dict

Threshold crossings: local extrema beyond threshold * sigma on each channel.

Parameters

  • data : array_like
    Signal (channels, samples) in µV, converted to float32 on the host.
  • sample_rate : float
    Sample rate in Hz.
  • threshold : float, default DEFAULT_THRESHOLD (4.5)
    Height in units of each channel’s noise.
  • polarity : {"negative", "positive", "both"}, default 'negative'
    Which extrema count.
  • refractory_ms : float, default DEFAULT_REFRACTORY_MS (1.0)
    Spikes on one channel stay more than this apart.
  • sigmas : array_like, default None
    Each channel’s noise (channels,); by default estimated as median(|x|) / 0.6745 (ops.math.channel_noise_std).
  • spacing : {"locally_exclusive", "scipy"}, default 'locally_exclusive'
    Which of two nearer spikes stays. "locally_exclusive": a spike stays unless a larger one is nearer, which chunked detection reproduces exactly. "scipy": as scipy.signal.find_peaks with distance.

Returns

  • dict of np.ndarray — samples (sample index), channels and amplitudes (µV at the extremum), sorted by time.

Raises

  • ValueError: For an unknown polarity or spacing, or sigmas of another length.

Examples

>>> import numpy as np
>>> import dspk
>>> x = np.random.default_rng(0).standard_normal((4, 30_000)).astype(np.float32) * 10
>>> x[1, 15_000:15_010] -= 120 # one spike
>>> spikes = dspk.synapse.detection.threshold(x, 30_000.0)
>>> int(spikes["channels"][0]), int(spikes["samples"][0]) # the spike's most negative sample
(1, 15004)
Name Type Value Description
DEFAULT_BAND tuple[float, float] (300.0, 6000.0)
DEFAULT_BATCH_S float 10.0
DEFAULT_CALIBRATION_CHUNKS int 5
DEFAULT_CALIBRATION_S float 5.0
DEFAULT_K_NEIGHBORS int 4
DEFAULT_NEO_THRESHOLD float 8.0
DEFAULT_POST_MS float 2.0
DEFAULT_PRE_MS float 1.0
DEFAULT_RADIUS_UM float 150.0
DEFAULT_RECORDING_THRESHOLD float 5.0
DEFAULT_REFRACTORY_MS float 1.0
DEFAULT_THRESHOLD float 4.5