@@ -96,6 +96,8 @@ def _parse_header(self):
96
96
else :
97
97
event_stream_names = []
98
98
99
+ self ._num_of_signal_streams = len (sig_stream_names )
100
+
99
101
# first loop to reassign stream by "stream_index" instead of "stream_name"
100
102
self ._sig_streams = {}
101
103
self ._evt_streams = {}
@@ -121,45 +123,75 @@ def _parse_header(self):
121
123
# signals zone
122
124
# create signals channel map: several channel per stream
123
125
signal_channels = []
126
+
124
127
for stream_index , stream_name in enumerate (sig_stream_names ):
125
- # stream_index is the index in vector sytream names
128
+ # stream_index is the index in vector stream names
126
129
stream_id = str (stream_index )
127
130
buffer_id = stream_id
128
131
info = self ._sig_streams [0 ][0 ][stream_index ]
129
132
new_channels = []
130
133
for chan_info in info ["channels" ]:
131
134
chan_id = chan_info ["channel_name" ]
135
+
136
+ units = chan_info ["units" ]
137
+ if units == "" :
138
+ # When units are not provided they are microvolts for neural channels and volts for ADC channels
139
+ # See https://open-ephys.github.io/gui-docs/User-Manual/Recording-data/Binary-format.html#continuous
140
+ units = "uV" if "ADC" not in chan_id else "V"
141
+
142
+ # Special cases for stream
132
143
if "SYNC" in chan_id and not self .load_sync_channel :
133
144
# the channel is removed from stream but not the buffer
134
145
stream_id = ""
135
- if chan_info ["units" ] == "" :
136
- # in some cases for some OE version the unit is "", but the gain is to "uV"
137
- units = "uV"
138
- else :
139
- units = chan_info ["units" ]
146
+
147
+ if "ADC" in chan_id :
148
+ # These are non-neural channels and their stream should be separated
149
+ # We defined their stream_id as the stream_index of neural data plus the number of neural streams
150
+ # This is to not break backwards compatbility with the stream_id numbering
151
+ stream_id = str (stream_index + len (sig_stream_names ))
152
+
153
+ gain = float (chan_info ["bit_volts" ])
154
+ sampling_rate = float (info ["sample_rate" ])
155
+ offset = 0.0
140
156
new_channels .append (
141
157
(
142
158
chan_info ["channel_name" ],
143
159
chan_id ,
144
- float ( info [ "sample_rate" ]) ,
160
+ sampling_rate ,
145
161
info ["dtype" ],
146
162
units ,
147
- chan_info [ "bit_volts" ] ,
148
- 0.0 ,
163
+ gain ,
164
+ offset ,
149
165
stream_id ,
150
166
buffer_id ,
151
167
)
152
168
)
153
169
signal_channels .extend (new_channels )
170
+
154
171
signal_channels = np .array (signal_channels , dtype = _signal_channel_dtype )
155
172
156
173
signal_streams = []
157
174
signal_buffers = []
158
- for stream_index , stream_name in enumerate (sig_stream_names ):
159
- stream_id = str (stream_index )
160
- buffer_id = str (stream_index )
161
- signal_buffers .append ((stream_name , buffer_id ))
175
+
176
+ unique_streams_ids = np .unique (signal_channels ["stream_id" ])
177
+ for stream_id in unique_streams_ids :
178
+ # Handle special case of Synch channel having stream_id empty
179
+ if stream_id == "" :
180
+ continue
181
+ stream_index = int (stream_id )
182
+ # Neural signal
183
+ if stream_index < self ._num_of_signal_streams :
184
+ stream_name = sig_stream_names [stream_index ]
185
+ buffer_id = stream_id
186
+ # We add the buffers here as both the neural and the ADC channels are in the same buffer
187
+ signal_buffers .append ((stream_name , buffer_id ))
188
+ else : # This names the ADC streams
189
+ neural_stream_index = stream_index - self ._num_of_signal_streams
190
+ neural_stream_name = sig_stream_names [neural_stream_index ]
191
+ stream_name = f"{ neural_stream_name } _ADC"
192
+ buffer_id = str (neural_stream_index )
162
193
signal_streams .append ((stream_name , stream_id , buffer_id ))
194
+
163
195
signal_streams = np .array (signal_streams , dtype = _signal_stream_dtype )
164
196
signal_buffers = np .array (signal_buffers , dtype = _signal_buffer_dtype )
165
197
@@ -192,10 +224,49 @@ def _parse_header(self):
192
224
"SYNC channel is not present in the recording. " "Set load_sync_channel to False"
193
225
)
194
226
195
- if has_sync_trace and not self .load_sync_channel :
196
- self ._stream_buffer_slice [stream_id ] = slice (None , - 1 )
227
+
228
+
229
+ # Check if ADC and non-ADC channels are contiguous
230
+ is_channel_adc = ["ADC" in ch ["channel_name" ] for ch in info ["channels" ]]
231
+ if any (is_channel_adc ):
232
+ first_adc_index = is_channel_adc .index (True )
233
+ non_adc_channels_after_adc_channels = [not is_adc for is_adc in is_channel_adc [first_adc_index :]]
234
+ if any (non_adc_channels_after_adc_channels ):
235
+ raise ValueError (
236
+ "Interleaved ADC and non-ADC channels are not supported. "
237
+ "ADC channels must be contiguous. Open an issue in python-neo to request this feature."
238
+ )
239
+
240
+ # Find sync channel and verify it's the last channel
241
+ sync_index = next (
242
+ (index for index , ch in enumerate (info ["channels" ]) if ch ["channel_name" ].endswith ("_SYNC" )),
243
+ None ,
244
+ )
245
+ if sync_index is not None and sync_index != num_channels - 1 :
246
+ raise ValueError (
247
+ "SYNC channel must be the last channel in the buffer. Open an issue in python-neo to request this feature."
248
+ )
249
+
250
+ neural_channels = [ch for ch in info ["channels" ] if "ADC" not in ch ["channel_name" ]]
251
+ adc_channels = [ch for ch in info ["channels" ] if "ADC" in ch ["channel_name" ]]
252
+ num_neural_channels = len (neural_channels )
253
+ num_adc_channels = len (adc_channels )
254
+
255
+ if num_adc_channels == 0 :
256
+ if has_sync_trace and not self .load_sync_channel :
257
+ self ._stream_buffer_slice [stream_id ] = slice (None , - 1 )
258
+ else :
259
+ self ._stream_buffer_slice [stream_id ] = None
197
260
else :
198
- self ._stream_buffer_slice [stream_id ] = None
261
+ stream_id_neural = stream_id
262
+ stream_id_non_neural = str (int (stream_id ) + self ._num_of_signal_streams )
263
+
264
+ self ._stream_buffer_slice [stream_id_neural ] = slice (0 , num_neural_channels )
265
+
266
+ if has_sync_trace and not self .load_sync_channel :
267
+ self ._stream_buffer_slice [stream_id_non_neural ] = slice (num_neural_channels , - 1 )
268
+ else :
269
+ self ._stream_buffer_slice [stream_id_non_neural ] = slice (num_neural_channels , None )
199
270
200
271
# events zone
201
272
# channel map: one channel one stream
@@ -375,17 +446,32 @@ def _parse_header(self):
375
446
seg_ann = bl_ann ["segments" ][seg_index ]
376
447
377
448
# array annotations for signal channels
378
- for stream_index , stream_name in enumerate (sig_stream_names ):
449
+ for stream_index , stream_name in enumerate (self . header [ "signal_streams" ][ "name" ] ):
379
450
sig_ann = seg_ann ["signals" ][stream_index ]
380
- info = self ._sig_streams [block_index ][seg_index ][stream_index ]
381
- has_sync_trace = self ._sig_streams [block_index ][seg_index ][stream_index ]["has_sync_trace" ]
451
+ if stream_index < self ._num_of_signal_streams :
452
+ _sig_stream_index = stream_index
453
+ is_neural_stream = True
454
+ else :
455
+ _sig_stream_index = stream_index - self ._num_of_signal_streams
456
+ is_neural_stream = False
457
+ info = self ._sig_streams [block_index ][seg_index ][_sig_stream_index ]
458
+ has_sync_trace = self ._sig_streams [block_index ][seg_index ][_sig_stream_index ]["has_sync_trace" ]
459
+
460
+ for key in ("identifier" , "history" , "source_processor_index" , "recorded_processor_index" ):
461
+ if key in info ["channels" ][0 ]:
462
+ values = np .array ([chan_info [key ] for chan_info in info ["channels" ]])
382
463
383
- for k in ("identifier" , "history" , "source_processor_index" , "recorded_processor_index" ):
384
- if k in info ["channels" ][0 ]:
385
- values = np .array ([chan_info [k ] for chan_info in info ["channels" ]])
386
464
if has_sync_trace :
387
465
values = values [:- 1 ]
388
- sig_ann ["__array_annotations__" ][k ] = values
466
+
467
+ neural_channels = [ch for ch in info ["channels" ] if "ADC" not in ch ["channel_name" ]]
468
+ num_neural_channels = len (neural_channels )
469
+ if is_neural_stream :
470
+ values = values [:num_neural_channels ]
471
+ else :
472
+ values = values [num_neural_channels :]
473
+
474
+ sig_ann ["__array_annotations__" ][key ] = values
389
475
390
476
# array annotations for event channels
391
477
# use other possible data in _possible_event_stream_names
@@ -429,7 +515,12 @@ def _channels_to_group_id(self, channel_indexes):
429
515
return group_id
430
516
431
517
def _get_signal_t_start (self , block_index , seg_index , stream_index ):
432
- t_start = self ._sig_streams [block_index ][seg_index ][stream_index ]["t_start" ]
518
+ if stream_index < self ._num_of_signal_streams :
519
+ _sig_stream_index = stream_index
520
+ else :
521
+ _sig_stream_index = stream_index - self ._num_of_signal_streams
522
+
523
+ t_start = self ._sig_streams [block_index ][seg_index ][_sig_stream_index ]["t_start" ]
433
524
return t_start
434
525
435
526
def _spike_count (self , block_index , seg_index , unit_index ):
0 commit comments