@@ -166,6 +166,9 @@ def _parse_header(self):
166166 if excl_file in filenames :
167167 filenames .remove (excl_file )
168168
169+ ncs_sampling_rates = []
170+ stream_id = - 1 # will be increased to 0 for first signal
171+
169172 for filename in filenames :
170173 filename = os .path .join (dirname , filename )
171174
@@ -191,13 +194,17 @@ def _parse_header(self):
191194
192195 chan_uid = (chan_name , chan_id )
193196 if ext == 'ncs' :
197+ if info ['sampling_rate' ] not in ncs_sampling_rates :
198+ ncs_sampling_rates .append (info ['sampling_rate' ])
199+ stream_id += 1
200+
194201 # a sampled signal channel
195202 units = 'uV'
196203 gain = info ['bit_to_microVolt' ][idx ]
197204 if info .get ('input_inverted' , False ):
198205 gain *= - 1
199206 offset = 0.
200- stream_id = 0
207+ stream_id = stream_id
201208 signal_channels .append ((chan_name , str (chan_id ), info ['sampling_rate' ],
202209 'int16' , units , gain , offset , stream_id ))
203210 self .ncs_filenames [chan_uid ] = filename
@@ -283,10 +290,8 @@ def _parse_header(self):
283290
284291 # require all sampled signals, ncs files, to have the same sampling rate
285292 if signal_channels .size > 0 :
286- sampling_rate = np .unique (signal_channels ['sampling_rate' ])
287- assert sampling_rate .size == 1
288- self ._sigs_sampling_rate = sampling_rate [0 ]
289- signal_streams = [('signals' , '0' )]
293+ names = [f'signals ({ sr } Hz)' for sr in ncs_sampling_rates ]
294+ signal_streams = list (zip (names , range (len (names ))))
290295 else :
291296 signal_streams = []
292297 signal_streams = np .array (signal_streams , dtype = _signal_stream_dtype )
@@ -301,7 +306,6 @@ def _parse_header(self):
301306 if ncsSegTimestampLimits :
302307 self ._ncs_seg_timestamp_limits = ncsSegTimestampLimits # save copy
303308 self ._nb_segment = ncsSegTimestampLimits .nb_segment
304- self ._sigs_length = ncsSegTimestampLimits .length .copy ()
305309 self ._timestamp_limits = ncsSegTimestampLimits .timestamp_limits .copy ()
306310 self ._sigs_t_start = ncsSegTimestampLimits .t_start .copy ()
307311 self ._sigs_t_stop = ncsSegTimestampLimits .t_stop .copy ()
@@ -362,19 +366,20 @@ def _parse_header(self):
362366 for seg_index in range (self ._nb_segment ):
363367 seg_annotations = bl_annotations ['segments' ][seg_index ]
364368
365- for c in range (signal_streams .size ):
369+ for stream_id in range (signal_streams .size ):
366370 # one or no signal stream
367- sig_ann = seg_annotations ['signals' ][c ]
371+ stream_ann = seg_annotations ['signals' ][stream_id ]
368372 # handle array annotations
369373 for key in signal_annotations [0 ].keys ():
370374 values = []
371- for c in range (signal_channels .size ):
372- value = signal_annotations [0 ][key ]
375+ # only collect values from channels belonging to current stream
376+ for d in np .where (signal_channels ['stream_id' ] == f'{ stream_id } ' )[0 ]:
377+ value = signal_annotations [d ][key ]
373378 values .append (value )
374379 values = np .array (values )
375380 if values .ndim == 1 :
376381 # 'InputRange': is 2D and make bugs
377- sig_ann ['__array_annotations__' ][key ] = values
382+ stream_ann ['__array_annotations__' ][key ] = values
378383
379384 for c in range (spike_channels .size ):
380385 unit_ann = seg_annotations ['spikes' ][c ]
@@ -432,7 +437,15 @@ def _segment_t_stop(self, block_index, seg_index):
432437 return self ._seg_t_stops [seg_index ] - self .global_t_start
433438
434439 def _get_signal_size (self , block_index , seg_index , stream_index ):
435- return self ._sigs_length [seg_index ]
440+ stream_id = self .header ['signal_streams' ][stream_index ]['id' ]
441+ channel_indexes = np .where (self .header ['signal_channels' ]['stream_id' ] == stream_id )[0 ]
442+
443+ if len (channel_indexes ):
444+ channel_index = channel_indexes [0 ]
445+ chan = self .header ['signal_channels' ][channel_index ]
446+ return self ._sigs_memmaps [seg_index ][(chan ['name' ], int (chan ['id' ]))]
447+ else :
448+ return None
436449
437450 def _get_signal_t_start (self , block_index , seg_index , stream_index ):
438451 return self ._sigs_t_start [seg_index ] - self .global_t_start
@@ -472,8 +485,12 @@ def _get_analogsignal_chunk(self, block_index, seg_index, i_start, i_stop,
472485 if channel_indexes is None :
473486 channel_indexes = slice (None )
474487
475- channel_ids = self .header ['signal_channels' ][channel_indexes ]['id' ].astype (int )
476- channel_names = self .header ['signal_channels' ][channel_indexes ]['name' ]
488+ stream_id = self .header ['signal_streams' ][stream_index ]['id' ]
489+ stream_mask = self .header ['signal_channels' ]['stream_id' ] == stream_id
490+
491+ # channel_streams = self.
492+ channel_ids = self .header ['signal_channels' ][stream_mask ][channel_indexes ]['id' ].astype (int )
493+ channel_names = self .header ['signal_channels' ][stream_mask ][channel_indexes ]['name' ]
477494
478495 # create buffer for samples
479496 sigs_chunk = np .zeros ((i_stop - i_start , len (channel_ids )), dtype = 'int16' )
@@ -622,9 +639,18 @@ def scan_ncs_files(self, ncs_filenames):
622639 del data
623640
624641 # Construct an inverse dictionary from NcsSections to list of associated chan_uids
625- revSectMap = dict ()
626- for k , v in chanSectMap .items ():
627- revSectMap .setdefault (v [0 ], []).append (k )
642+ # consider channels
643+ revSectMap = {}
644+ for i , (k , v ) in enumerate (chanSectMap .items ()):
645+ if i == 0 : # start initially with first Ncssections
646+ latest_sections = v [0 ]
647+ # time tolerance of +- one data package (in microsec)
648+ tolerance = 512 / min (v [0 ].sampFreqUsed , latest_sections .sampFreqUsed ) * 1e6
649+ if v [0 ].is_equivalent (latest_sections , abs_tol = tolerance ):
650+ revSectMap .setdefault (latest_sections , []).append (k )
651+ else :
652+ revSectMap [v [0 ]] = [k ]
653+ latest_sections = v [0 ]
628654
629655 # If there is only one NcsSections structure in the set of ncs files, there should only
630656 # be one entry. Otherwise this is presently unsupported.
0 commit comments