Skip to content

Commit bc1c704

Browse files
committed
Improve do_recording_attributes_match impelmentation, errors, and tests
1 parent df23822 commit bc1c704

5 files changed

Lines changed: 107 additions & 20 deletions

File tree

src/spikeinterface/core/recording_tools.py

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from __future__ import annotations
22
from copy import deepcopy
3-
from typing import Literal
3+
from typing import Literal, Tuple
44
import warnings
55
from pathlib import Path
66
import os
@@ -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_is_filtered: bool = True, check_dtype: bool = True
934+
) -> Tuple[bool, str]:
933935
"""
934936
Check if two recordings have the same attributes
935937
@@ -939,22 +941,52 @@ 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_is_filtered : bool, default: True
945+
If True, check if the recordings are filtered
946+
check_dtype : bool, default: True
947+
If True, check if the recordings have the same dtype
942948
943949
Returns
944950
-------
945951
bool
946952
True if the recordings have the same attributes
953+
str
954+
A string with the an exception message with attributes that do not match
947955
"""
948956
recording1_attributes = get_rec_attributes(recording1)
949957
recording2_attributes = deepcopy(recording2_attributes)
950958
recording1_attributes.pop("properties")
951959
recording2_attributes.pop("properties")
952960

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-
)
961+
attributes_match = True
962+
non_matching_attrs = []
963+
964+
if not np.array_equal(recording1_attributes["channel_ids"], recording2_attributes["channel_ids"]):
965+
attributes_match = False
966+
non_matching_attrs.append("channel_ids")
967+
if not recording1_attributes["sampling_frequency"] == recording2_attributes["sampling_frequency"]:
968+
attributes_match = False
969+
non_matching_attrs.append("sampling_frequency")
970+
if not recording1_attributes["num_channels"] == recording2_attributes["num_channels"]:
971+
attributes_match = False
972+
non_matching_attrs.append("num_channels")
973+
if not recording1_attributes["num_samples"] == recording2_attributes["num_samples"]:
974+
attributes_match = False
975+
non_matching_attrs.append("num_samples")
976+
if check_is_filtered:
977+
if not recording1_attributes["is_filtered"] == recording2_attributes["is_filtered"]:
978+
attributes_match = False
979+
non_matching_attrs.append("is_filtered")
980+
# dtype is optional
981+
if "dtype" in recording1_attributes and "dtype" in recording2_attributes:
982+
if check_dtype:
983+
if not recording1_attributes["dtype"] == recording2_attributes["dtype"]:
984+
attributes_match = False
985+
non_matching_attrs.append("dtype")
986+
987+
if len(non_matching_attrs) > 0:
988+
exception_str = f"Recordings do not match in the following attributes: {non_matching_attrs}"
989+
else:
990+
exception_str = ""
991+
992+
return attributes_match, exception_str

src/spikeinterface/core/sortinganalyzer.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -608,7 +608,9 @@ def load_from_zarr(cls, folder, recording=None):
608608

609609
return sorting_analyzer
610610

611-
def set_temporary_recording(self, recording: BaseRecording):
611+
def set_temporary_recording(
612+
self, recording: BaseRecording, check_is_filtered: bool = True, check_dtype: bool = True
613+
):
612614
"""
613615
Sets a temporary recording object. This function can be useful to temporarily set
614616
a "cached" recording object that is not saved in the SortingAnalyzer object to speed up
@@ -620,12 +622,19 @@ def set_temporary_recording(self, recording: BaseRecording):
620622
----------
621623
recording : BaseRecording
622624
The recording object to set as temporary recording.
625+
check_is_filtered : bool, default: True
626+
If True, check that the temporary recording is filtered in the same way as the original recording.
627+
check_dtype : bool, default: True
628+
If True, check that the dtype of the temporary recording is the same as the original recording.
623629
"""
624630
# check that recording is compatible
625-
assert do_recording_attributes_match(recording, self.rec_attributes), "Recording attributes do not match."
626-
assert np.array_equal(
627-
recording.get_channel_locations(), self.get_channel_locations()
628-
), "Recording channel locations do not match."
631+
attributes_match, exception_str = do_recording_attributes_match(
632+
recording, self.rec_attributes, check_is_filtered=check_is_filtered, check_dtype=check_dtype
633+
)
634+
if not attributes_match:
635+
raise ValueError(exception_str)
636+
if not np.array_equal(recording.get_channel_locations(), self.get_channel_locations()):
637+
raise ValueError("Recording channel locations do not match.")
629638
if self._recording is not None:
630639
warnings.warn("SortingAnalyzer recording is already set. The current recording is temporarily replaced.")
631640
self._temporary_recording = recording

src/spikeinterface/core/tests/test_recording_tools.py

Lines changed: 41 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,45 @@ 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 is_filtered options
320+
rec_attributes = get_rec_attributes(recording)
321+
rec_attributes["is_filtered"] = not rec_attributes["is_filtered"]
322+
323+
do_match, exc = do_recording_attributes_match(recording, rec_attributes)
324+
assert not do_match
325+
assert "is_filtered" in exc
326+
do_match, exc = do_recording_attributes_match(recording, rec_attributes, check_is_filtered=False)
327+
assert do_match
328+
329+
# check dtype options
330+
rec_attributes = get_rec_attributes(recording)
331+
rec_attributes["dtype"] = "int16"
332+
do_match, exc = do_recording_attributes_match(recording, rec_attributes)
333+
assert not do_match
334+
assert "dtype" in exc
335+
do_match, exc = do_recording_attributes_match(recording, rec_attributes, check_dtype=False)
336+
assert do_match
337+
338+
# check missing dtype
339+
rec_attributes.pop("dtype")
340+
do_match, exc = do_recording_attributes_match(recording, rec_attributes)
341+
assert do_match
342+
343+
303344
if __name__ == "__main__":
304345
# Create a temporary folder using the standard library
305346
import tempfile

src/spikeinterface/core/tests/test_sortinganalyzer.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,9 +141,18 @@ 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

147+
# test with different is_filtered
148+
recording_filt = recording.clone()
149+
recording_filt.annotate(is_filtered=False)
150+
with pytest.raises(ValueError):
151+
sorting_analyzer.set_temporary_recording(recording_filt)
152+
153+
# thest with additional check_is_filtered
154+
sorting_analyzer.set_temporary_recording(recording_filt, check_is_filtered=False)
155+
147156

148157
def _check_sorting_analyzers(sorting_analyzer, original_sorting, cache_folder):
149158

src/spikeinterface/core/waveforms_extractor_backwards_compatibility.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -446,10 +446,6 @@ def _read_old_waveforms_extractor_binary(folder, sorting):
446446
else:
447447
rec_attributes["probegroup"] = None
448448

449-
if "dtype" not in rec_attributes:
450-
warnings.warn("dtype not found in rec_attributes. Setting to float32")
451-
rec_attributes["dtype"] = "float32"
452-
453449
# recording
454450
recording = None
455451
if (folder / "recording.json").exists():

0 commit comments

Comments
 (0)