Visitar URL original
Blackrock sorting: spike frames are truncated, so ~2% of spikes land one sample early · Issue #4910 · SpikeInterface/spikeinterface · GitHub
Skip to content

Blackrock sorting: spike frames are truncated, so ~2% of spikes land one sample early #4910

Open

Description

@JamesBelanger

NeoSortingSegment.get_unit_spike_train turns Neo's spike times back into frames with

spike_frames = ((spike_times_seconds - t_start) * self._sampling_frequency).astype("int64")

(src/spikeinterface/extractors/neoextractors/neobaseextractor.py, line 647 on main). For Blackrock, Neo converts the integer .nev ticks to seconds, and this line converts them back. Float round-off leaves some products just under an integer, for example 30936 / 30000 * 30000 == 30935.999999999996, and astype truncates, so those spikes move one sample earlier.

Reproduce with blackrock/blackrock_2_1/l101210-001.nev from ephy_testing_data. Its timestamp resolution is 30 kHz, the same as sampling_frequency, so frames should equal ticks exactly:

import numpy as np
from spikeinterface.extractors import read_blackrock_sorting

sorting = read_blackrock_sorting(file_path="blackrock/blackrock_2_1/l101210-001.nev", sampling_frequency=30000.0)
neo = sorting.neo_reader

diffs, units_off = [], 0
for i, unit_id in enumerate(sorting.unit_ids):
    ticks = neo.get_spike_timestamps(block_index=0, seg_index=0, spike_channel_index=i).astype(np.int64)
    d = sorting.get_unit_spike_train(unit_id) - ticks
    diffs.append(d)
    units_off += bool(np.any(d))
d = np.concatenate(diffs)
values, counts = np.unique(d, return_counts=True)
print(f"spikes: {d.size}, frame - tick: { {int(v): int(c) for v, c in zip(values, counts)} }")
print(f"units with at least one shifted spike: {units_off} of {len(sorting.unit_ids)}")
spikes: 14210, frame - tick: {-1: 272, 0: 13938}
units with at least one shifted spike: 107 of 218

So 272 of 14,210 spikes (1.9%) come out one sample early, and none late. Over tick values 0 to 109,225, 2.3% truncate this way.

Affected readers: those with neo_returns_frames = False, which in 0.105.4 are Blackrock and MEArec. NeuroConv's BlackrockSortingInterface goes through this path, so NWB units tables written from .nev files carry the shift.

Suggested fix:

spike_frames = np.round((spike_times_seconds - t_start) * self._sampling_frequency).astype("int64")

With that change all 14,210 spikes match the .nev ticks exactly.

Environment: Windows 11, Python 3.12.13, spikeinterface 0.105.4 (the line is unchanged on main), neo 0.14.6, numpy 2.5.4.

I found this while checking a NeuroConv conversion against the raw files: https://github.com/JamesBelanger/blackrock-nwb-roundtrip (script: repro/si_spike_truncation.py).

Activity

  1. added
    bugSomething isn't working
    extractorsRelated to extractors module
    on Nov 5, 2026
  2. JamesBelanger commented on Nov 5, 2026

    @JamesBelanger
    Author

    Correction to the scope in the issue: MEArec should not change. Its spike times are continuous, and MEArec places spikes in its recordings with (st.times * fs).astype("int") (recordinggenerator.py#L1140), so truncation is the right conversion there. The np.round change I suggested would move 376 of the 747 spikes in mearec_test_10s.h5 one frame late.

    The bug is Blackrock-only, where Neo returns integer clock ticks. I have a branch that adds a round_spike_frames class attribute (default False), set only on BlackrockSortingExtractor, with a test against the raw .nev timestamps. Happy to open it as a PR if that approach works for you.

  3. h-mayorquin commented on Nov 5, 2026

    @h-mayorquin
    Contributor

    Thanks for reporting this, I am on the middle of fixing a lot of Blarock bugs and I will address this one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingextractorsRelated to extractors module

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions