Skip to content

Commit 261d671

Browse files
authored
Merge pull request #3312 from alejoe91/fix-waveforms-backcompatibility
Add dtype in load_waveforms and analyzer.is_filtered()
2 parents 9238023 + 5461e43 commit 261d671

5 files changed

Lines changed: 78 additions & 16 deletions

File tree

src/spikeinterface/core/recording_tools.py

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -929,7 +929,9 @@ def get_rec_attributes(recording):
929929
return rec_attributes
930930

931931

932-
def do_recording_attributes_match(recording1, recording2_attributes) -> bool:
932+
def do_recording_attributes_match(
933+
recording1: "BaseRecording", recording2_attributes: bool, check_dtype: bool = True
934+
) -> tuple[bool, str]:
933935
"""
934936
Check if two recordings have the same attributes
935937
@@ -939,22 +941,43 @@ def do_recording_attributes_match(recording1, recording2_attributes) -> bool:
939941
The first recording object
940942
recording2_attributes : dict
941943
The recording attributes to test against
944+
check_dtype : bool, default: True
945+
If True, check if the recordings have the same dtype
942946
943947
Returns
944948
-------
945949
bool
946950
True if the recordings have the same attributes
951+
str
952+
A string with the exception message with the attributes that do not match
947953
"""
948954
recording1_attributes = get_rec_attributes(recording1)
949955
recording2_attributes = deepcopy(recording2_attributes)
950956
recording1_attributes.pop("properties")
951957
recording2_attributes.pop("properties")
952958

953-
return (
954-
np.array_equal(recording1_attributes["channel_ids"], recording2_attributes["channel_ids"])
955-
and recording1_attributes["sampling_frequency"] == recording2_attributes["sampling_frequency"]
956-
and recording1_attributes["num_channels"] == recording2_attributes["num_channels"]
957-
and recording1_attributes["num_samples"] == recording2_attributes["num_samples"]
958-
and recording1_attributes["is_filtered"] == recording2_attributes["is_filtered"]
959-
and recording1_attributes["dtype"] == recording2_attributes["dtype"]
960-
)
959+
attributes_match = True
960+
non_matching_attrs = []
961+
962+
if not np.array_equal(recording1_attributes["channel_ids"], recording2_attributes["channel_ids"]):
963+
non_matching_attrs.append("channel_ids")
964+
if not recording1_attributes["sampling_frequency"] == recording2_attributes["sampling_frequency"]:
965+
non_matching_attrs.append("sampling_frequency")
966+
if not recording1_attributes["num_channels"] == recording2_attributes["num_channels"]:
967+
non_matching_attrs.append("num_channels")
968+
if not recording1_attributes["num_samples"] == recording2_attributes["num_samples"]:
969+
non_matching_attrs.append("num_samples")
970+
# dtype is optional
971+
if "dtype" in recording1_attributes and "dtype" in recording2_attributes:
972+
if check_dtype:
973+
if not recording1_attributes["dtype"] == recording2_attributes["dtype"]:
974+
non_matching_attrs.append("dtype")
975+
976+
if len(non_matching_attrs) > 0:
977+
attributes_match = False
978+
exception_str = f"Recordings do not match in the following attributes: {non_matching_attrs}"
979+
else:
980+
attributes_match = True
981+
exception_str = ""
982+
983+
return attributes_match, exception_str

src/spikeinterface/core/sortinganalyzer.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -616,7 +616,7 @@ def load_from_zarr(cls, folder, recording=None, storage_options=None):
616616

617617
return sorting_analyzer
618618

619-
def set_temporary_recording(self, recording: BaseRecording):
619+
def set_temporary_recording(self, recording: BaseRecording, check_dtype: bool = True):
620620
"""
621621
Sets a temporary recording object. This function can be useful to temporarily set
622622
a "cached" recording object that is not saved in the SortingAnalyzer object to speed up
@@ -628,12 +628,17 @@ def set_temporary_recording(self, recording: BaseRecording):
628628
----------
629629
recording : BaseRecording
630630
The recording object to set as temporary recording.
631+
check_dtype : bool, default: True
632+
If True, check that the dtype of the temporary recording is the same as the original recording.
631633
"""
632634
# check that recording is compatible
633-
assert do_recording_attributes_match(recording, self.rec_attributes), "Recording attributes do not match."
634-
assert np.array_equal(
635-
recording.get_channel_locations(), self.get_channel_locations()
636-
), "Recording channel locations do not match."
635+
attributes_match, exception_str = do_recording_attributes_match(
636+
recording, self.rec_attributes, check_dtype=check_dtype
637+
)
638+
if not attributes_match:
639+
raise ValueError(exception_str)
640+
if not np.array_equal(recording.get_channel_locations(), self.get_channel_locations()):
641+
raise ValueError("Recording channel locations do not match.")
637642
if self._recording is not None:
638643
warnings.warn("SortingAnalyzer recording is already set. The current recording is temporarily replaced.")
639644
self._temporary_recording = recording
@@ -1026,6 +1031,9 @@ def has_temporary_recording(self) -> bool:
10261031
def is_sparse(self) -> bool:
10271032
return self.sparsity is not None
10281033

1034+
def is_filtered(self) -> bool:
1035+
return self.rec_attributes["is_filtered"]
1036+
10291037
def get_sorting_provenance(self):
10301038
"""
10311039
Get the original sorting if possible otherwise return None

src/spikeinterface/core/tests/test_recording_tools.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
get_channel_distances,
1818
get_noise_levels,
1919
order_channels_by_depth,
20+
do_recording_attributes_match,
21+
get_rec_attributes,
2022
)
2123

2224

@@ -300,6 +302,35 @@ def test_order_channels_by_depth():
300302
assert np.array_equal(order_2d[::-1], order_2d_fliped)
301303

302304

305+
def test_do_recording_attributes_match():
306+
recording = NoiseGeneratorRecording(
307+
num_channels=2, durations=[10.325, 3.5], sampling_frequency=30_000, strategy="tile_pregenerated"
308+
)
309+
rec_attributes = get_rec_attributes(recording)
310+
do_match, _ = do_recording_attributes_match(recording, rec_attributes)
311+
assert do_match
312+
313+
rec_attributes = get_rec_attributes(recording)
314+
rec_attributes["sampling_frequency"] = 1.0
315+
do_match, exc = do_recording_attributes_match(recording, rec_attributes)
316+
assert not do_match
317+
assert "sampling_frequency" in exc
318+
319+
# check dtype options
320+
rec_attributes = get_rec_attributes(recording)
321+
rec_attributes["dtype"] = "int16"
322+
do_match, exc = do_recording_attributes_match(recording, rec_attributes)
323+
assert not do_match
324+
assert "dtype" in exc
325+
do_match, exc = do_recording_attributes_match(recording, rec_attributes, check_dtype=False)
326+
assert do_match
327+
328+
# check missing dtype
329+
rec_attributes.pop("dtype")
330+
do_match, exc = do_recording_attributes_match(recording, rec_attributes)
331+
assert do_match
332+
333+
303334
if __name__ == "__main__":
304335
# Create a temporary folder using the standard library
305336
import tempfile

src/spikeinterface/core/tests/test_sortinganalyzer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ def test_SortingAnalyzer_tmp_recording(dataset):
141141
recording_sliced = recording.channel_slice(recording.channel_ids[:-1])
142142

143143
# wrong channels
144-
with pytest.raises(AssertionError):
144+
with pytest.raises(ValueError):
145145
sorting_analyzer.set_temporary_recording(recording_sliced)
146146

147147

src/spikeinterface/exporters/to_phy.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ def export_to_phy(
167167
f.write(f"dtype = '{dtype_str}'\n")
168168
f.write(f"offset = 0\n")
169169
f.write(f"sample_rate = {fs}\n")
170-
f.write(f"hp_filtered = {sorting_analyzer.recording.is_filtered()}")
170+
f.write(f"hp_filtered = {sorting_analyzer.is_filtered()}")
171171

172172
# export spike_times/spike_templates/spike_clusters
173173
# here spike_labels is a remapping to unit_index

0 commit comments

Comments
 (0)