@@ -96,6 +96,8 @@ def _parse_header(self):
9696 else :
9797 event_stream_names = []
9898
99+ self ._num_of_signal_streams = len (sig_stream_names )
100+
99101 # first loop to reassign stream by "stream_index" instead of "stream_name"
100102 self ._sig_streams = {}
101103 self ._evt_streams = {}
@@ -121,45 +123,75 @@ def _parse_header(self):
121123 # signals zone
122124 # create signals channel map: several channel per stream
123125 signal_channels = []
126+
124127 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
126129 stream_id = str (stream_index )
127130 buffer_id = stream_id
128131 info = self ._sig_streams [0 ][0 ][stream_index ]
129132 new_channels = []
130133 for chan_info in info ["channels" ]:
131134 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
132143 if "SYNC" in chan_id and not self .load_sync_channel :
133144 # the channel is removed from stream but not the buffer
134145 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
140156 new_channels .append (
141157 (
142158 chan_info ["channel_name" ],
143159 chan_id ,
144- float ( info [ "sample_rate" ]) ,
160+ sampling_rate ,
145161 info ["dtype" ],
146162 units ,
147- chan_info [ "bit_volts" ] ,
148- 0.0 ,
163+ gain ,
164+ offset ,
149165 stream_id ,
150166 buffer_id ,
151167 )
152168 )
153169 signal_channels .extend (new_channels )
170+
154171 signal_channels = np .array (signal_channels , dtype = _signal_channel_dtype )
155172
156173 signal_streams = []
157174 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 )
162193 signal_streams .append ((stream_name , stream_id , buffer_id ))
194+
163195 signal_streams = np .array (signal_streams , dtype = _signal_stream_dtype )
164196 signal_buffers = np .array (signal_buffers , dtype = _signal_buffer_dtype )
165197
@@ -192,10 +224,49 @@ def _parse_header(self):
192224 "SYNC channel is not present in the recording. " "Set load_sync_channel to False"
193225 )
194226
195- if has_sync_trace and not self .load_sync_channel :
196- self ._stream_buffer_slice [stream_id ] = slice (None , - 1 )
227+ # Check if ADC and non-ADC channels are contiguous
228+ is_channel_adc = ["ADC" in ch ["channel_name" ] for ch in info ["channels" ]]
229+ if any (is_channel_adc ):
230+ first_adc_index = is_channel_adc .index (True )
231+ non_adc_channels_after_adc_channels = [
232+ not is_adc for is_adc in is_channel_adc [first_adc_index :]
233+ ]
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
197260 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 )
199270
200271 # events zone
201272 # channel map: one channel one stream
@@ -375,17 +446,32 @@ def _parse_header(self):
375446 seg_ann = bl_ann ["segments" ][seg_index ]
376447
377448 # 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" ] ):
379450 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" ]])
382463
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" ]])
386464 if has_sync_trace :
387465 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
389475
390476 # array annotations for event channels
391477 # use other possible data in _possible_event_stream_names
@@ -429,7 +515,12 @@ def _channels_to_group_id(self, channel_indexes):
429515 return group_id
430516
431517 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" ]
433524 return t_start
434525
435526 def _spike_count (self , block_index , seg_index , unit_index ):
0 commit comments