Repository: spikeinterface/spikeinterface
Issue:  `get_traces(start_frame=num_samples, end_frame=num_samples)` fails for `ProxyConcatenateRecordingSegment` 

Hi @samuelgarcia ,
I think something is wrong somewhere with TracePaddedRecordings handling of start/end frames, or in the interface with chunking

I get an error when running kilosort. The following code should be able to replicate:

```

from spikeinterface.full import NumpyRecording, concatenate_recordings, write_binary_recording
from spikeinterface.preprocessing.zero_channel_pad import TracePaddedRecording

base_traces = np.zeros((1000, 5), dtype="float64")
base_traces[:] = np.arange(1000)[:, None]
sampling_frequency = 1000
rec0 = NumpyRecording([base_traces] * 3, sampling_frequency)
rec1 = NumpyRecording([base_traces] * 2, sampling_frequency)


# concatenate
rec = concatenate_recordings([rec0, rec1])

padded_rec = TracePaddedRecording(rec, 0, 10)

write_binary_recording(padded_rec, "./padded_rec", dtype="int16")
```

> File ~/projects/ecephys_dev/spikeinterface/src/spikeinterface/core/core_tools.py:314, in write_binary_recording(recording, file_paths, dtype, add_file_extension, byte_offset, auto_cast_uint, **job_kwargs)
>     310 init_args = (recording, file_path_dict, dtype, byte_offset, cast_unsigned)
>     311 executor = ChunkRecordingExecutor(
>     312     recording, func, init_func, init_args, job_name="write_binary_recording", **job_kwargs
>     313 )
> --> 314 executor.run()
> 
> File ~/projects/ecephys_dev/spikeinterface/src/spikeinterface/core/job_tools.py:376, in ChunkRecordingExecutor.run(self)
>     374 worker_ctx = self.init_func(*self.init_args)
>     375 for segment_index, frame_start, frame_stop in all_chunks:
> --> 376     res = self.func(segment_index, frame_start, frame_stop, worker_ctx)
>     377     if self.handle_returns:
>     378         returns.append(res)
> 
> File ~/projects/ecephys_dev/spikeinterface/src/spikeinterface/core/core_tools.py:233, in _write_binary_chunk(segment_index, start_frame, end_frame, worker_ctx)
>     231 array = np.ndarray.__new__(np.ndarray, shape=shape, dtype=dtype, buffer=memmap_obj, order="C", offset=array_offset)
>     232 # apply function
> --> 233 traces = recording.get_traces(
>     234     start_frame=start_frame, end_frame=end_frame, segment_index=segment_index, cast_unsigned=cast_unsigned
>     235 )
>     236 if traces.dtype != dtype:
>     237     traces = traces.astype(dtype)
> 
> File ~/projects/ecephys_dev/spikeinterface/src/spikeinterface/core/baserecording.py:278, in BaseRecording.get_traces(self, segment_index, start_frame, end_frame, channel_ids, order, return_scaled, cast_unsigned)
>     276 channel_indices = self.ids_to_indices(channel_ids, prefer_slice=True)
>     277 rs = self._recording_segments[segment_index]
> --> 278 traces = rs.get_traces(start_frame=start_frame, end_frame=end_frame, channel_indices=channel_indices)
>     279 if order is not None:
>     280     assert order in ["C", "F"]
> 
> File ~/projects/ecephys_dev/spikeinterface/src/spikeinterface/preprocessing/zero_channel_pad.py:99, in TracePaddedRecordingSegment.get_traces(self, start_frame, end_frame, channel_indices)
>      97 # After the padding, the original traces are placed in the middle until the end of the original traces
>      98 if end_frame >= self.padding_start:
> ---> 99     original_traces = self.get_original_traces_shifted(
>     100         start_frame=start_frame,
>     101         end_frame=end_frame,
>     102         channel_indices=channel_indices,
>     103     )
>     105     padded_frames_at_the_start = max(self.padding_start - start_frame, 0)
>     106     end_of_original_traces = padded_frames_at_the_start + original_traces.shape[0]
> 
> File ~/projects/ecephys_dev/spikeinterface/src/spikeinterface/preprocessing/zero_channel_pad.py:122, in TracePaddedRecordingSegment.get_original_traces_shifted(self, start_frame, end_frame, channel_indices)
>     120 original_start_frame = max(start_frame - self.padding_start, 0)
>     121 original_end_frame = min(end_frame - self.padding_start, self.num_samples_in_original_segment)
> --> 122 original_traces = self.parent_recording_segment.get_traces(
>     123     start_frame=original_start_frame,
>     124     end_frame=original_end_frame,
>     125     channel_indices=channel_indices,
>     126 )
>     128 return original_traces
> 
> File ~/projects/ecephys_dev/spikeinterface/src/spikeinterface/core/segmentutils.py:181, in ProxyConcatenateRecordingSegment.get_traces(self, start_frame, end_frame, channel_indices)
>     175 # several case:
>     176 #  * come from one segment (i0 == i1)
>     177 #  * come from several segment (i0 < i1)
>     179 if i0 == i1:
>     180     #  one segment
> --> 181     rec_seg = self.parent_segments[i0]
>     182     seg_start = self.cumsum_length[i0]
>     183     traces = rec_seg.get_traces(start_frame - seg_start, end_frame - seg_start, channel_indices)
> 
> IndexError: list index out of range

Changed files:
src/spikeinterface/core/segmentutils.py

diff --git a/src/spikeinterface/core/segmentutils.py b/src/spikeinterface/core/segmentutils.py
index 0a87ed4d..f70c45bf 100644
--- a/src/spikeinterface/core/segmentutils.py
+++ b/src/spikeinterface/core/segmentutils.py
@@ -169,6 +169,11 @@ class ProxyConcatenateRecordingSegment(BaseRecordingSegment):
         if end_frame is None:
             end_frame = self.get_num_samples()
 
+        # # Ensures that we won't request invalid segment indices
+        if (start_frame >= self.get_num_samples()) or (end_frame <= start_frame):
+            # Return (0 * num_channels) array of correct dtype
+            return self.parent_segments[0].get_traces(0, 0, channel_indices)
+
         i0 = np.searchsorted(self.cumsum_length, start_frame, side="right") - 1
         i1 = np.searchsorted(self.cumsum_length, end_frame, side="right") - 1