Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Correct handling of time in plot_traces #3393

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/spikeinterface/core/frameslicerecording.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ def __init__(self, parent_recording, start_frame=None, end_frame=None):
if start_frame is None:
start_frame = 0
else:
assert 0 <= start_frame < parent_size
assert (
0 <= start_frame < parent_size
), f"'start_frame' must be fewer than number of samples in parent: {parent_size}"
alejoe91 marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit confusing in the case where start_frame is less than 0. It should just be two different checks:

assert 0 <= start_frame, "Requested `start_frame` is negative (requested timestamp may be earlier than the first timestamp in the recording)"
assert start_frame < parent_size, f"`start_frame` must be fewer than number of samples in parent: {parent_size}"

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about saying this too, but I vaguely remember we discussed (and documented) not allowing negative slicing so I wasn't sure if this point was moot or not. It can't hurt to be more explicit though.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I get this error trying to run recording_car.time_slice(0,1) when the recording starts around t0=15 seconds, I'm just assuming it's giving a negative start_frame. If it's somehow failing with start_frame > parent _size, that seems like a different issue

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No I agree that they are different problems. Although your error should likely be handled in the time_slice function since saying you have negative frames/samples isn't super helpful. @h-mayorquin wrote the time_slice right? so maybe that should be dealt with at the time level instead? What do you think Alessio and Heberto? If you give a bad time you should just know it right away in my opinion rather than getting a nonsensical frame?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dividing the asssertion in two each one with a message revealing what is the problem is good. Having specific assertions with proper messages in the time_slice is better. I also think all of that can be done in another PR as it is not really central to this.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. Thanks Heberto.


if end_frame is None:
end_frame = parent_size
Expand Down
23 changes: 17 additions & 6 deletions src/spikeinterface/widgets/traces.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,10 @@ def __init__(

if not rec0.has_time_vector(segment_index=segment_index):
times = None
t_start = 0
t_end = rec0.get_duration(segment_index=segment_index)
t_start = rec0.sample_index_to_time(0, segment_index=segment_index)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that this first conditional can be completely removed now, as get_times() will always return a time array that will incorporate whether either a time vector, t_start attribute, or no time information set on the recording.

If times is None, then the times will be generated on the fly in _get_trace_list but this uses essentially the same code that is now implemented centrally in get_times().

I think this is also the case here. If the second case in the conditional can also be used and the first case removed, I think that times=None option could be removed from _get_trace_list.

So, basically this would centralise some of the time-related computations, away from the widget and out to the recording. But, maybe it will be necessary to check the frame computations are handled the same as there is some clipping done to them in one of the conditionals I linked. I think they should work as before as the times are converted already to frames with the new methods here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was done to avoid loading the time vector if not needed. In the _get_traces_list, only a local time vector is generated on the fly

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice that makes sense

t_end = rec0.sample_index_to_time(
rec0.get_num_samples(segment_index=segment_index), segment_index=segment_index
)
else:
times = rec0.get_times(segment_index=segment_index)
t_start = times[0]
Expand All @@ -149,6 +151,11 @@ def __init__(
)
time_range[1] = t_end

if time_range[0] < t_start or time_range[1] < t_start:
raise ValueError(f"All time_range values must be greater than {t_start}")
alejoe91 marked this conversation as resolved.
Show resolved Hide resolved
if time_range[1] <= time_range[0]:
raise ValueError("time_range[1] must be greater than time_range[0]")
alejoe91 marked this conversation as resolved.
Show resolved Hide resolved

assert mode in ("auto", "line", "map"), 'Mode must be one of "auto","line", "map"'
if mode == "auto":
if len(channel_ids) <= 64:
Expand Down Expand Up @@ -673,13 +680,17 @@ def _get_trace_list(recordings, channel_ids, time_range, segment_index, return_s
assert all(
rec.has_scaleable_traces() for rec in recordings.values()
), "Some recording layers do not have scaled traces. Use `return_scaled=False`"
frame_range = np.array(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optionally, although I'm super curious about the history of this. We could start moving some of these internal variables over to sample instead of frame. Just for consistency. The function says time_to_sample_index. I think most people get the frame-sample equivalence, but it might be good to try to converge a bit more on one term for consistency. Trickiest thing is that the public function is frame_slice instead of sample_slice

[
rec0.time_to_sample_index(time_range[0], segment_index=segment_index),
rec0.time_to_sample_index(time_range[1], segment_index=segment_index),
]
)
if times is not None:
frame_range = np.searchsorted(times, time_range)
times = times[frame_range[0] : frame_range[1]]
else:
frame_range = (time_range * fs).astype("int64", copy=False)
a_max = rec0.get_num_frames(segment_index=segment_index)
frame_range = np.clip(frame_range, 0, a_max)
num_samples = rec0.get_num_samples(segment_index=segment_index)
frame_range = np.clip(frame_range, 0, num_samples)
time_range = frame_range / fs
times = np.arange(frame_range[0], frame_range[1]) / fs

Expand Down
Loading