Skip to content

Commit f3cc182

Browse files
Leonard013claude
andauthored
Fix remove_artifacts sparsity handling for median/average modes (#3290) (#4685)
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 2a430f7 commit f3cc182

2 files changed

Lines changed: 51 additions & 6 deletions

File tree

src/spikeinterface/preprocessing/remove_artifacts.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -376,7 +376,12 @@ def get_traces(self, start_frame, end_frame, channel_indices):
376376
mask = self.sparsity[label]
377377
else:
378378
mask = None
379-
artifact_duration = len(self.artifacts[label])
379+
artifact = self.artifacts[label]
380+
if mask is not None:
381+
# restrict the template to the sparse channels so that it aligns
382+
# with the sparsified traces (see issue #3290)
383+
artifact = artifact[:, mask]
384+
artifact_duration = len(artifact)
380385
if self.time_pad > 0:
381386
jitters = np.arange(-self.time_pad, self.time_pad, 1)
382387
else:
@@ -404,7 +409,7 @@ def get_traces(self, start_frame, end_frame, channel_indices):
404409
if mask is not None:
405410
trace_slice_values = trace_slice_values[:, mask]
406411

407-
artifact_slice_values = self.artifacts[label][artifact_slice]
412+
artifact_slice_values = artifact[artifact_slice]
408413

409414
norm = np.linalg.norm(trace_slice_values) * np.linalg.norm(artifact_slice_values)
410415
best_amplitudes[count] = (
@@ -435,11 +440,9 @@ def get_traces(self, start_frame, end_frame, channel_indices):
435440
best_amp = 1
436441

437442
if mask is not None:
438-
traces[trace_slice][:, mask] -= (best_amp * self.artifacts[label][artifact_slice]).astype(
439-
traces.dtype
440-
)
443+
traces[trace_slice][:, mask] -= (best_amp * artifact[artifact_slice]).astype(traces.dtype)
441444
else:
442-
traces[trace_slice] -= (best_amp * self.artifacts[label][artifact_slice]).astype(traces.dtype)
445+
traces[trace_slice] -= (best_amp * artifact[artifact_slice]).astype(traces.dtype)
443446
traces = traces[:, channel_indices]
444447

445448
return traces

src/spikeinterface/preprocessing/tests/test_remove_artifacts.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,5 +85,47 @@ def test_remove_artifacts():
8585
)
8686

8787

88+
def test_remove_artifacts_sparsity():
89+
# non-regression test for issue #3290: "median"/"average" modes with sparsity
90+
# used to raise "shapes not aligned" because the template was kept full-channel
91+
# while the traces were sparsified before the np.dot / subtraction.
92+
rec = generate_recording(num_channels=4, durations=[10.0], seed=0)
93+
rec.annotate(is_filtered=True)
94+
95+
ms = 10
96+
ms_frames = int(ms * rec.get_sampling_frequency() / 1000)
97+
98+
# two artifact labels, each removed on a different subset of channels
99+
triggers = [15000, 30000, 45000, 60000]
100+
labels = [0, 1, 0, 1]
101+
list_triggers = [triggers]
102+
list_labels = [labels]
103+
sparsity = {
104+
0: np.array([True, False, True, False]),
105+
1: np.array([False, True, False, True]),
106+
}
107+
108+
for mode in ("median", "average"):
109+
rec_rmart = remove_artifacts(
110+
rec,
111+
list_triggers,
112+
ms_before=ms,
113+
ms_after=ms,
114+
mode=mode,
115+
list_labels=list_labels,
116+
sparsity=sparsity,
117+
)
118+
for trig, label in zip(triggers, labels):
119+
mask = sparsity[label]
120+
traces_clean = rec.get_traces(start_frame=trig - ms_frames, end_frame=trig + ms_frames)
121+
# get_traces must not raise (the bug reported in issue #3290)
122+
traces = rec_rmart.get_traces(start_frame=trig - ms_frames, end_frame=trig + ms_frames)
123+
# channels outside the sparsity mask are left untouched
124+
assert np.array_equal(traces[:, ~mask], traces_clean[:, ~mask])
125+
# channels inside the sparsity mask have the artifact subtracted
126+
assert not np.allclose(traces[:, mask], traces_clean[:, mask])
127+
128+
88129
if __name__ == "__main__":
89130
test_remove_artifacts()
131+
test_remove_artifacts_sparsity()

0 commit comments

Comments
 (0)