Skip to content

Commit c7c36fb

Browse files
committed
First Pass at extracting Events from SpikeGLX
1 parent e023577 commit c7c36fb

File tree

1 file changed

+61
-1
lines changed

1 file changed

+61
-1
lines changed

neo/rawio/spikeglxrawio.py

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ def __init__(self, dirname='', load_sync_channel=False, load_channel_location=Fa
7878
self.dirname = dirname
7979
self.load_sync_channel = load_sync_channel
8080
self.load_channel_location = load_channel_location
81+
self._use_direct_evt_timestamps = None
8182

8283
def _source_name(self):
8384
return self.dirname
@@ -136,6 +137,22 @@ def _parse_header(self):
136137

137138
# No events
138139
event_channels = []
140+
# This is true only in case of 'nidq' stream
141+
for stream_name in stream_names:
142+
if 'nidq' in stream_name:
143+
info = self.signals_info_dict[0, stream_name]
144+
if len(info['digital_channels']) > 0:
145+
# add event channels
146+
for local_chan in info['digital_channels']:
147+
chan_name = local_chan
148+
chan_id = f'{stream_name}#{chan_name}'
149+
event_channels.append((chan_name, chan_id, 'event'))
150+
# add events_memmap
151+
data = np.memmap(info['bin_file'], dtype='int16', mode='r', offset=0, order='C')
152+
data = data.reshape(-1, info['num_chan'])
153+
# The digital word is usually the last channel, after all the individual analog channels
154+
extracted_word = data[:,len(info['analog_channels'])]
155+
self._events_memmap = np.unpackbits(extracted_word.astype(np.uint8)[:,None], axis=1)
139156
event_channels = np.array(event_channels, dtype=_event_channel_dtype)
140157

141158
# No spikes
@@ -233,6 +250,38 @@ def _get_analogsignal_chunk(self, block_index, seg_index, i_start, i_stop,
233250
raw_signals = memmap[slice(i_start, i_stop), channel_selection]
234251

235252
return raw_signals
253+
254+
def _event_count(self, event_channel_idx, block_index=None, seg_index=None):
255+
timestamps, _, _ = self._get_event_timestamps(block_index, seg_index, event_channel_index,
256+
None, None)
257+
return timestamps.size
258+
259+
def _get_event_timestamps(self, block_index, seg_index, event_channel_index, t_start=None, t_stop=None):
260+
timestamps, durations, labels = [], None, []
261+
info = self.signals_info_dict[0, 'nidq'] # There are no events that are not in the nidq stream
262+
dig_ch = info['digital_channels']
263+
if len(dig_ch) > 0:
264+
event_data = self._events_memmap
265+
channel = dig_ch[event_channel_index]
266+
ch_idx = 7 - int(channel[2:]) # They are in the reverse order
267+
this_stream = event_data[:,ch_idx]
268+
this_rising = np.where(np.diff(this_stream)==1)[0] + 1
269+
this_falling = np.where(np.diff(this_stream)==-1)[0] + 1
270+
if len(this_rising) > 0:
271+
timestamps.extend(this_rising)
272+
labels.extend([channel + ' ON']*len(this_rising))
273+
if len(this_falling) > 0:
274+
timestamps.extend(this_falling)
275+
labels.extend([channel + ' OFF']*len(this_falling))
276+
return np.asarray(timestamps), np.asarray(durations), np.asarray(labels)
277+
278+
def _rescale_event_timestamp(self, event_timestamps, dtype, event_channel_index):
279+
info = self.signals_info_dict[0, 'nidq'] # There are no events that are not in the nidq stream
280+
if not self._use_direct_evt_timestamps:
281+
event_times = event_timestamps.astype(dtype) / float(info['sampling_rate'])
282+
else: # Does this ever happen?
283+
event_times = event_timestamps.astype(dtype)
284+
return event_times
236285

237286

238287
def scan_files(dirname):
@@ -452,5 +501,16 @@ def extract_stream_info(meta_file, meta):
452501
info['channel_gains'] = channel_gains
453502
info['channel_offsets'] = np.zeros(info['num_chan'])
454503
info['has_sync_trace'] = has_sync_trace
504+
505+
if 'nidq' in device:
506+
info['digital_channels'] = []
507+
info['analog_channels'] = [channel for channel in info['channel_names'] if not channel.startswith('XD')]
508+
# Digital/event channels are encoded within the digital word, so that will need more handling
509+
for item in meta['niXDChans1'].split(','):
510+
if ':' in item:
511+
start, end = map(int, item.split(':'))
512+
info['digital_channels'].extend([f"XD{i}" for i in range(start, end+1)])
513+
else:
514+
info['digital_channels'].append(f"XD{int(item)}")
455515

456-
return info
516+
return info

0 commit comments

Comments
 (0)