Skip to content

Commit f3fbd5d

Browse files
committed
nodepipeline : skip chunks when no peaks inside
1 parent 161efc4 commit f3fbd5d

2 files changed

Lines changed: 114 additions & 63 deletions

File tree

src/spikeinterface/core/node_pipeline.py

Lines changed: 103 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,9 @@ def get_trace_margin(self):
103103
def get_dtype(self):
104104
return base_peak_dtype
105105

106+
def get_peak_slice(self, segment_index, start_frame, end_frame, ):
107+
# not needed for PeakDetector
108+
raise NotImplementedError
106109

107110
# this is used in sorting components
108111
class PeakDetector(PeakSource):
@@ -127,11 +130,18 @@ def get_trace_margin(self):
127130
def get_dtype(self):
128131
return base_peak_dtype
129132

130-
def compute(self, traces, start_frame, end_frame, segment_index, max_margin):
131-
# get local peaks
133+
def get_peak_slice(self, segment_index, start_frame, end_frame, max_margin):
132134
sl = self.segment_slices[segment_index]
133135
peaks_in_segment = self.peaks[sl]
134136
i0, i1 = np.searchsorted(peaks_in_segment["sample_index"], [start_frame, end_frame])
137+
return i0, i1
138+
139+
def compute(self, traces, start_frame, end_frame, segment_index, max_margin, peak_slice):
140+
# get local peaks
141+
sl = self.segment_slices[segment_index]
142+
peaks_in_segment = self.peaks[sl]
143+
# i0, i1 = np.searchsorted(peaks_in_segment["sample_index"], [start_frame, end_frame])
144+
i0, i1 = peak_slice
135145
local_peaks = peaks_in_segment[i0:i1]
136146

137147
# make sample index local to traces
@@ -212,8 +222,7 @@ def get_trace_margin(self):
212222
def get_dtype(self):
213223
return self._dtype
214224

215-
def compute(self, traces, start_frame, end_frame, segment_index, max_margin):
216-
# get local peaks
225+
def get_peak_slice(self, segment_index, start_frame, end_frame, max_margin):
217226
sl = self.segment_slices[segment_index]
218227
peaks_in_segment = self.peaks[sl]
219228
if self.include_spikes_in_margin:
@@ -222,6 +231,20 @@ def compute(self, traces, start_frame, end_frame, segment_index, max_margin):
222231
)
223232
else:
224233
i0, i1 = np.searchsorted(peaks_in_segment["sample_index"], [start_frame, end_frame])
234+
return i0, i1
235+
236+
def compute(self, traces, start_frame, end_frame, segment_index, max_margin, peak_slice):
237+
# get local peaks
238+
sl = self.segment_slices[segment_index]
239+
peaks_in_segment = self.peaks[sl]
240+
# if self.include_spikes_in_margin:
241+
# i0, i1 = np.searchsorted(
242+
# peaks_in_segment["sample_index"], [start_frame - max_margin, end_frame + max_margin]
243+
# )
244+
# else:
245+
# i0, i1 = np.searchsorted(peaks_in_segment["sample_index"], [start_frame, end_frame])
246+
i0, i1 = peak_slice
247+
225248
local_peaks = peaks_in_segment[i0:i1]
226249

227250
# make sample index local to traces
@@ -525,64 +548,79 @@ def _compute_peak_pipeline_chunk(segment_index, start_frame, end_frame, worker_c
525548
nodes = worker_ctx["nodes"]
526549

527550
recording_segment = recording._recording_segments[segment_index]
528-
traces_chunk, left_margin, right_margin = get_chunk_with_margin(
529-
recording_segment, start_frame, end_frame, None, max_margin, add_zeros=True
530-
)
531-
532-
# compute the graph
533-
pipeline_outputs = {}
534-
for node in nodes:
535-
node_parents = node.parents if node.parents else list()
536-
node_input_args = tuple()
537-
for parent in node_parents:
538-
parent_output = pipeline_outputs[parent]
539-
parent_outputs_tuple = parent_output if isinstance(parent_output, tuple) else (parent_output,)
540-
node_input_args += parent_outputs_tuple
541-
if isinstance(node, PeakDetector):
542-
# to handle compatibility peak detector is a special case
543-
# with specific margin
544-
# TODO later when in master: change this later
545-
extra_margin = max_margin - node.get_trace_margin()
546-
if extra_margin:
547-
trace_detection = traces_chunk[extra_margin:-extra_margin]
551+
node0 = nodes[0]
552+
553+
if isinstance(node0, (SpikeRetriever, PeakRetriever)):
554+
# in this case PeakSource could have no peaks and so no need to load traces just skip
555+
peak_slice = i0, i1 = node0.get_peak_slice(segment_index, start_frame, end_frame, max_margin)
556+
load_trace_and_compute = i0 < i1
557+
else:
558+
# PeakDetector always need traces
559+
load_trace_and_compute = True
560+
561+
if load_trace_and_compute:
562+
traces_chunk, left_margin, right_margin = get_chunk_with_margin(
563+
recording_segment, start_frame, end_frame, None, max_margin, add_zeros=True
564+
)
565+
# compute the graph
566+
pipeline_outputs = {}
567+
for node in nodes:
568+
node_parents = node.parents if node.parents else list()
569+
node_input_args = tuple()
570+
for parent in node_parents:
571+
parent_output = pipeline_outputs[parent]
572+
parent_outputs_tuple = parent_output if isinstance(parent_output, tuple) else (parent_output,)
573+
node_input_args += parent_outputs_tuple
574+
if isinstance(node, PeakDetector):
575+
# to handle compatibility peak detector is a special case
576+
# with specific margin
577+
# TODO later when in master: change this later
578+
extra_margin = max_margin - node.get_trace_margin()
579+
if extra_margin:
580+
trace_detection = traces_chunk[extra_margin:-extra_margin]
581+
else:
582+
trace_detection = traces_chunk
583+
node_output = node.compute(trace_detection, start_frame, end_frame, segment_index, max_margin)
584+
# set sample index to local
585+
node_output[0]["sample_index"] += extra_margin
586+
elif isinstance(node, PeakSource):
587+
node_output = node.compute(traces_chunk, start_frame, end_frame, segment_index, max_margin, peak_slice)
548588
else:
549-
trace_detection = traces_chunk
550-
node_output = node.compute(trace_detection, start_frame, end_frame, segment_index, max_margin)
551-
# set sample index to local
552-
node_output[0]["sample_index"] += extra_margin
553-
elif isinstance(node, PeakSource):
554-
node_output = node.compute(traces_chunk, start_frame, end_frame, segment_index, max_margin)
555-
else:
556-
# TODO later when in master: change the signature of all nodes (or maybe not!)
557-
node_output = node.compute(traces_chunk, *node_input_args)
558-
pipeline_outputs[node] = node_output
559-
560-
# propagate the output
561-
pipeline_outputs_tuple = tuple()
562-
for node in nodes:
563-
# handle which buffer are given to the output
564-
# this is controlled by node.return_output being a bool or tuple of bool
565-
out = pipeline_outputs[node]
566-
if isinstance(out, tuple):
567-
if isinstance(node.return_output, bool) and node.return_output:
568-
pipeline_outputs_tuple += out
569-
elif isinstance(node.return_output, tuple):
570-
for flag, e in zip(node.return_output, out):
571-
if flag:
572-
pipeline_outputs_tuple += (e,)
573-
else:
574-
if isinstance(node.return_output, bool) and node.return_output:
575-
pipeline_outputs_tuple += (out,)
576-
elif isinstance(node.return_output, tuple):
577-
# this should not apppend : maybe a checker somewhere before ?
578-
pass
589+
# TODO later when in master: change the signature of all nodes (or maybe not!)
590+
node_output = node.compute(traces_chunk, *node_input_args)
591+
pipeline_outputs[node] = node_output
592+
593+
# propagate the output
594+
pipeline_outputs_tuple = tuple()
595+
for node in nodes:
596+
# handle which buffer are given to the output
597+
# this is controlled by node.return_output being a bool or tuple of bool
598+
out = pipeline_outputs[node]
599+
if isinstance(out, tuple):
600+
if isinstance(node.return_output, bool) and node.return_output:
601+
pipeline_outputs_tuple += out
602+
elif isinstance(node.return_output, tuple):
603+
for flag, e in zip(node.return_output, out):
604+
if flag:
605+
pipeline_outputs_tuple += (e,)
606+
else:
607+
if isinstance(node.return_output, bool) and node.return_output:
608+
pipeline_outputs_tuple += (out,)
609+
elif isinstance(node.return_output, tuple):
610+
# this should not apppend : maybe a checker somewhere before ?
611+
pass
612+
613+
if isinstance(nodes[0], PeakDetector):
614+
# the first out element is the peak vector
615+
# we need to go back to absolut sample index
616+
pipeline_outputs_tuple[0]["sample_index"] += start_frame - left_margin
617+
618+
return pipeline_outputs_tuple
579619

580-
if isinstance(nodes[0], PeakDetector):
581-
# the first out element is the peak vector
582-
# we need to go back to absolut sample index
583-
pipeline_outputs_tuple[0]["sample_index"] += start_frame - left_margin
620+
else:
621+
# the gather will skip this output and not concatenate it
622+
return
584623

585-
return pipeline_outputs_tuple
586624

587625

588626
class GatherToMemory:
@@ -595,6 +633,9 @@ def __init__(self):
595633
self.tuple_mode = None
596634

597635
def __call__(self, res):
636+
if res is None:
637+
return
638+
598639
if self.tuple_mode is None:
599640
# first loop only
600641
self.tuple_mode = isinstance(res, tuple)
@@ -655,6 +696,9 @@ def __init__(self, folder, names, npy_header_size=1024, exist_ok=False):
655696
self.final_shapes.append(None)
656697

657698
def __call__(self, res):
699+
if res is None:
700+
return
701+
658702
if self.tuple_mode is None:
659703
# first loop only
660704
self.tuple_mode = isinstance(res, tuple)

src/spikeinterface/core/tests/test_node_pipeline.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,12 @@ def test_run_node_pipeline(cache_folder_creation):
8383
extremum_channel_inds = get_template_extremum_channel(sorting_analyzer, peak_sign="neg", outputs="index")
8484

8585
peaks = sorting_to_peaks(sorting, extremum_channel_inds, spike_peak_dtype)
86+
print(peaks.size)
8687

8788
peak_retriever = PeakRetriever(recording, peaks)
89+
# this test when no spikes in last chunks
90+
peak_retriever_few = PeakRetriever(recording, peaks[:peaks.size//2])
91+
8892
# channel index is from template
8993
spike_retriever_T = SpikeRetriever(
9094
sorting, recording, channel_from_template=True, extremum_channel_inds=extremum_channel_inds
@@ -100,7 +104,7 @@ def test_run_node_pipeline(cache_folder_creation):
100104
)
101105

102106
# test with 3 differents first nodes
103-
for loop, peak_source in enumerate((peak_retriever, spike_retriever_T, spike_retriever_S)):
107+
for loop, peak_source in enumerate((peak_retriever, peak_retriever_few, spike_retriever_T, spike_retriever_S)):
104108
# one step only : squeeze output
105109
nodes = [
106110
peak_source,
@@ -139,10 +143,12 @@ def test_run_node_pipeline(cache_folder_creation):
139143

140144
num_peaks = peaks.shape[0]
141145
num_channels = recording.get_num_channels()
142-
assert waveforms_rms.shape[0] == num_peaks
146+
if peak_source != peak_retriever_few:
147+
assert waveforms_rms.shape[0] == num_peaks
143148
assert waveforms_rms.shape[1] == num_channels
144149

145-
assert waveforms_rms.shape[0] == num_peaks
150+
if peak_source != peak_retriever_few:
151+
assert waveforms_rms.shape[0] == num_peaks
146152
assert waveforms_rms.shape[1] == num_channels
147153

148154
# gather npy mode
@@ -186,4 +192,5 @@ def test_run_node_pipeline(cache_folder_creation):
186192

187193

188194
if __name__ == "__main__":
189-
test_run_node_pipeline()
195+
folder = Path("./cache_folder/core")
196+
test_run_node_pipeline(folder)

0 commit comments

Comments
 (0)