6565import os
6666import re
6767import warnings
68- import math
6968
7069import numpy as np
7170import quantities as pq
@@ -131,6 +130,10 @@ class BlackrockRawIO(BaseRawIO):
131130 2.2 and 2.3 are automatically extracted and the data set is split into different
132131 segments.
133132
133+ * The times of spikes and events are compared with `t_start` and `t_stop` as float64 seconds, and
134+ `t_stop` is exclusive. To include a spike at `t_stop`, such as the last of a segment, pass
135+ `t_stop + 1e-5`.
136+
134137 * The Blackrock data format consists not of a single file, but a set of
135138 different files. This constructor associates itself with a set of files
136139 that constitute a common data set. By default, all files belonging to
@@ -826,18 +829,15 @@ def _get_blackrock_timestamps(self, block_index, seg_index, i_start, i_stop, str
826829 def _spike_count (self , block_index , seg_index , unit_index ):
827830 channel_id , unit_id = self .internal_unit_ids [unit_index ]
828831
829- all_spikes = self .nev_data ["Spikes" ][0 ]
830- mask = (all_spikes ["packet_id" ] == channel_id ) & (all_spikes ["unit_class_nb" ] == unit_id )
831- if self ._nb_segment == 1 :
832- # very fast
833- nb = int (np .sum (mask ))
834- else :
835- # must clip in time time range
836- timestamp = all_spikes [mask ]["timestamp" ]
837- sl = self ._get_timestamp_slice (timestamp , seg_index , None , None )
838- timestamp = timestamp [sl ]
839- nb = timestamp .size
840- return nb
832+ all_spikes , event_segment_ids = self .nev_data ["Spikes" ]
833+ # Selected by segment id, as _get_spike_timestamps does, so the count always equals
834+ # the number of timestamps it returns.
835+ mask = (
836+ (all_spikes ["packet_id" ] == channel_id )
837+ & (all_spikes ["unit_class_nb" ] == unit_id )
838+ & (event_segment_ids == seg_index )
839+ )
840+ return int (np .sum (mask ))
841841
842842 def _get_spike_timestamps (self , block_index , seg_index , unit_index , t_start , t_stop ):
843843 channel_id , unit_id = self .internal_unit_ids [unit_index ]
@@ -859,27 +859,27 @@ def _get_spike_timestamps(self, block_index, seg_index, unit_index, t_start, t_s
859859 return timestamp
860860
861861 def _get_timestamp_slice (self , timestamp , seg_index , t_start , t_stop ):
862- if self ._nb_segment > 1 :
863- # we must clip event in seg time limits
864- if t_start is None :
865- t_start = self ._seg_t_starts [seg_index ]
866- if t_stop is None :
867- t_stop = self ._seg_t_stops [seg_index ] + 1 / float (self ._nev_basic_header ["timestamp_resolution" ])
868-
869- if t_start is None :
870- ind_start = None
871- else :
872- ts = math .ceil (t_start * self ._nev_basic_header ["timestamp_resolution" ])
873- ind_start = np .searchsorted (timestamp , ts )
862+ """
863+ Slice of `timestamp` (one segment's, in file order) within [t_start, t_stop), in seconds.
874864
875- if t_stop is None :
876- ind_stop = None
877- else :
878- ts = int (t_stop * self ._nev_basic_header ["timestamp_resolution" ])
879- ind_stop = np .searchsorted (timestamp , ts ) # +1
865+ The segment itself is already selected by segment id, so None means no limit.
880866
881- sl = slice (ind_start , ind_stop )
882- return sl
867+ The bounds are compared with the times neo reports (the timestamps in float64 seconds), so a
868+ time obtained from neo, such as `segment_t_start`, selects the item it came from. Converting the
869+ bounds to ticks instead does not work at the 1 ns resolution of PTP files: a float64 number of
870+ seconds (about 1.75e9) is only precise to about 240 ns, so the converted bound can fall a few
871+ ticks past the item and drop it, and np.searchsorted compares uint64 with a Python int as float64.
872+
873+ `t_stop` is exclusive. To include an item at `t_stop`, such as the last of a segment,
874+ pass `t_stop + 1e-5` (10 us, shorter than the interval between samples at 30 kHz and longer
875+ than the precision of float64 seconds).
876+ """
877+ if t_start is None and t_stop is None :
878+ return slice (None )
879+ times = timestamp .astype ("float64" ) / self ._nev_basic_header ["timestamp_resolution" ]
880+ ind_start = None if t_start is None else np .searchsorted (times , t_start , side = "left" )
881+ ind_stop = None if t_stop is None else np .searchsorted (times , t_stop , side = "left" )
882+ return slice (ind_start , ind_stop )
883883
884884 def _rescale_spike_timestamp (self , spike_timestamps , dtype ):
885885 spike_times = spike_timestamps .astype (dtype )
@@ -924,17 +924,9 @@ def _event_count(self, block_index, seg_index, event_channel_index):
924924 else :
925925 events_data , event_segment_ids = self .nev_data ["NonNeural" ]
926926 ev_dict = self ._nonneural_evdicts [self ._nev_spec ](events_data )[name ]
927+ # Selected by segment id, as _get_event_timestamps does.
927928 mask = ev_dict ["mask" ] & (event_segment_ids == seg_index )
928- if self ._nb_segment == 1 :
929- # very fast
930- nb = int (np .sum (mask ))
931- else :
932- # must clip in time time range
933- timestamp = events_data [ev_dict ["mask" ]]["timestamp" ]
934- sl = self ._get_timestamp_slice (timestamp , seg_index , None , None )
935- timestamp = timestamp [sl ]
936- nb = timestamp .size
937- return nb
929+ return int (np .sum (mask ))
938930
939931 def _get_event_timestamps (self , block_index , seg_index , event_channel_index , t_start , t_stop ):
940932 name = self .header ["event_channels" ]["name" ][event_channel_index ]
0 commit comments