Repository: SpikeInterface/spikeinterface
Pull request #3942 (2025-05-21 14:59:18)

Add informative assertions to `slice_time`
This is so the user knows that they are out of the time range of the recording. This would have saved me time when solving this:
https://github.com/catalystneuro/neuroconv/issues/1355

And I think is useful overall.


Patch:
diff --git a/src/spikeinterface/core/baserecording.py b/src/spikeinterface/core/baserecording.py
index 83146951c..5e77cb402 100644
--- a/src/spikeinterface/core/baserecording.py
+++ b/src/spikeinterface/core/baserecording.py
@@ -541,10 +541,10 @@ class BaseRecording(BaseRecordingSnippets):
         else:
             segments_to_shift = (segment_index,)
 
-        for idx in segments_to_shift:
-            rs = self._recording_segments[idx]
+        for segment_index in segments_to_shift:
+            rs = self._recording_segments[segment_index]
 
-            if self.has_time_vector(segment_index=idx):
+            if self.has_time_vector(segment_index=segment_index):
                 rs.time_vector += shift
             else:
                 new_start_time = 0 + shift if rs.t_start is None else rs.t_start + shift
@@ -781,11 +781,37 @@ class BaseRecording(BaseRecordingSnippets):
         BaseRecording
             A new recording object with only samples between start_time and end_time
         """
+        num_segments = self.get_num_segments()
+        assert (
+            num_segments == 1
+        ), f"Time slicing is only supported for single segment recordings. Found {num_segments} segments."
+
+        t_start = self.get_start_time()
+        t_end = self.get_end_time()
+
+        if start_time is not None:
+            t_start = self.get_start_time()
+            t_start_too_early = start_time < t_start
+            if t_start_too_early:
+                raise ValueError(f"start_time {start_time} is before the start time {t_start} of the recording.")
+            t_start_too_late = start_time > t_end
+            if t_start_too_late:
+                raise ValueError(f"start_time {start_time} is after the end time {t_end} of the recording.")
+            start_frame = self.time_to_sample_index(start_time)
+        else:
+            start_frame = None
 
-        assert self.get_num_segments() == 1, "Time slicing is only supported for single segment recordings."
+        if end_time is not None:
+            t_end_too_early = end_time < t_start
+            if t_end_too_early:
+                raise ValueError(f"end_time {end_time} is before the start time {t_start} of the recording.")
 
-        start_frame = self.time_to_sample_index(start_time) if start_time else None
-        end_frame = self.time_to_sample_index(end_time) if end_time else None
+            t_end_too_late = end_time > t_end
+            if t_end_too_late:
+                raise ValueError(f"end_time {end_time} is after the end time {t_end} of the recording.")
+            end_frame = self.time_to_sample_index(end_time)
+        else:
+            end_frame = None
 
         return self.frame_slice(start_frame=start_frame, end_frame=end_frame)
 
diff --git a/src/spikeinterface/core/generate.py b/src/spikeinterface/core/generate.py
index ed18b815d..cb8f1983c 100644
--- a/src/spikeinterface/core/generate.py
+++ b/src/spikeinterface/core/generate.py
@@ -33,7 +33,7 @@ def generate_recording(
     set_probe: bool | None = True,
     ndim: int | None = 2,
     seed: int | None = None,
-) -> NumpySorting:
+) -> BaseRecording:
     """
     Generate a lazy recording object.
     Useful for testing API and algos.


Test patch:
diff --git a/src/spikeinterface/core/tests/test_baserecording.py b/src/spikeinterface/core/tests/test_baserecording.py
index 7d7ce52f2..d0637e92e 100644
--- a/src/spikeinterface/core/tests/test_baserecording.py
+++ b/src/spikeinterface/core/tests/test_baserecording.py
@@ -412,6 +412,27 @@ def test_time_slice():
     assert np.allclose(sliced_recording_times.get_traces(), sliced_recording_frames.get_traces())
 
 
+def test_out_of_range_time_slice():
+    recording = generate_recording(durations=[0.100])  # duration = 0.1 s
+    recording.shift_times(1.0)  # shifts start time to 1.0 s, end time to 1.1 s
+
+    # start_time before recording
+    with pytest.raises(ValueError, match="start_time .* is before the start time"):
+        recording.time_slice(start_time=0, end_time=None)
+
+    # end_time before start of recording
+    with pytest.raises(ValueError, match="end_time .* is before the start time"):
+        recording.time_slice(start_time=None, end_time=0.5)
+
+    # start_time after end of recording
+    with pytest.raises(ValueError, match="start_time .* is after the end time"):
+        recording.time_slice(start_time=2.0, end_time=None)
+
+    # end_time after end of recording
+    with pytest.raises(ValueError, match="end_time .* is after the end time"):
+        recording.time_slice(start_time=None, end_time=2.0)
+
+
 def test_time_slice_with_time_vector():
 
     # Case with time vector