Skip to content

Build the NwbSortingExtractor spike vector with a single bulk read#4662

Open
h-mayorquin wants to merge 5 commits into
SpikeInterface:mainfrom
h-mayorquin:make_nwb_sorting_lazy
Open

Build the NwbSortingExtractor spike vector with a single bulk read#4662
h-mayorquin wants to merge 5 commits into
SpikeInterface:mainfrom
h-mayorquin:make_nwb_sorting_lazy

Conversation

@h-mayorquin

Copy link
Copy Markdown
Contributor

NwbSortingExtractor now overrides _compute_and_cache_spike_vector to build the whole spike vector from one read of the Units table instead of the generic per-unit loop it inherits from BaseSorting. An NWB (Neurodata Without Borders) Units table stores every unit's spikes in a single flat spike_times dataset grouped by unit, with the per-unit boundaries in spike_times_index, so the entire table can be read in one shot and each spike's unit index recovered from those boundaries. The generic builder instead reads one spike train per unit, which is one streamed read per unit; for a remote or streamed file that is the difference between a single request and one request per unit. Construction stays lazy as before: nothing is read until the spike vector is first requested.

The seconds-to-samples conversion stays on the segment, shared by the per-unit and bulk paths through _times_to_frames, and the override only assembles and sorts the structured array, so its output is byte-for-byte identical to the generic builder. Correctness is therefore already exercised by the existing NWB (Neurodata Without Borders) roundtrip tests, and a comment on the override records why it should not be reverted to the generic per-unit path.

@h-mayorquin h-mayorquin self-assigned this Jul 8, 2026
@h-mayorquin h-mayorquin added extractors Related to extractors module performance Performance issues/improvements Edinburgh hackathon 2026 PRs from Edinburgh hackathon 2026 labels Jul 8, 2026

@zm711 zm711 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couple questions.

# Stable sort by sample_index keeps unit_index ordering on ties, matching the way the generic
# BaseSorting builder constructs the vector (unit order within a sample).
order = np.argsort(sample_indices, stable=True)
n = sample_indices.size

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe n_spikes to be more explicit?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, good point.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

num_spikes


# Stable sort by sample_index keeps unit_index ordering on ties, matching the way the generic
# BaseSorting builder constructs the vector (unit order within a sample).
order = np.argsort(sample_indices, stable=True)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The data isn't presorted in NWB? Or can't be guaranteed to be sorted?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we can't guarantee that I added a comment.

return self._time_vector[frame]
return frame / self._sampling_frequency + self._t_start

def _times_to_frames(self, spike_times) -> np.ndarray:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in nwb terminology do you say frames instead of samples?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, nwb favors samples. Do you think it should be samples here? I thought about it and I remember that the get_traces uses frame terminology but maybe we were moving away from it?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I vote for samples. The spike vector uses spikes["sample_index"] and samples is found across the codebase more often than frames.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I changed this.

"""
spike_times = np.asarray(self.spike_times_data[:], dtype="float64")
# boundaries[u]:boundaries[u + 1] is the slice of spike_times belonging to unit u.
boundaries = np.concatenate(([0], np.asarray(self.spike_times_index_data[:], dtype="int64")))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems super fancy and I'm having a hard time following this. So you add a 0 to the beginning then take the diff to get boundaries? Could you give me the dumbed down version of why this works?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually yes, this is kind of complicated. Mmm, let's discuss it tomorrow and we probably should add documentation in hdmf that we c an link for this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We discussed this in person and I added a comment.

@chrishalcrow

Copy link
Copy Markdown
Member

On some real data: https://dandiarchive.org/dandiset/000939/0.260512.1701/files?location=sub-A1808&page=1
Building the spike vector took
Main: 106 seconds
This PR: 7.5 seconds
!!!

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

Labels

Edinburgh hackathon 2026 PRs from Edinburgh hackathon 2026 extractors Related to extractors module performance Performance issues/improvements

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants