11import numpy as np
2+ import glob
3+ import os
24
35from ..._fiff .meas_info import create_info
46from ..._fiff .utils import _mult_cal_one
79
810
911@fill_doc
10- def read_raw_neuralynx (fname , preload = False , verbose = None ):
11- """Reader for an Neuralynx files.
12+ def read_raw_neuralynx (fname , preload = False , verbose = None , exclude_fname_patterns : list = None ):
13+ """Reader for Neuralynx files.
1214
1315 Parameters
1416 ----------
@@ -27,24 +29,35 @@ def read_raw_neuralynx(fname, preload=False, verbose=None):
2729 --------
2830 mne.io.Raw : Documentation of attributes and methods of RawNeuralynx.
2931 """
30- return RawNeuralynx (fname , preload , verbose )
32+ return RawNeuralynx (fname , preload , verbose , exclude_fname_patterns )
3133
3234
3335@fill_doc
3436class RawNeuralynx (BaseRaw ):
3537 """RawNeuralynx class."""
3638
3739 @verbose
38- def __init__ (self , fname , preload = False , verbose = None ):
40+ def __init__ (self , fname , preload = False , verbose = None , exclude_fname_patterns : list = None ):
3941 _soft_import ("neo" , "Reading NeuralynxIO files" , strict = True )
4042 from neo .io import NeuralynxIO
4143
4244 fname = _check_fname (fname , "read" , True , "fname" , need_dir = True )
4345
4446 logger .info (f"Checking files in { fname } " )
4547
48+ # construct a list of filenames to ignore
49+ exclude_fnames = None
50+ if exclude_fname_patterns :
51+ exclude_fnames = []
52+ for pattern in exclude_fname_patterns :
53+ fnames = glob .glob (os .path .join (fname , pattern ))
54+ fnames = [os .path .basename (fname ) for fname in fnames ]
55+ exclude_fnames .extend (fnames )
56+
57+ logger .info ("Ignoring .ncs files:\n " + "\n " .join (exclude_fnames ))
58+
4659 # get basic file info
47- nlx_reader = NeuralynxIO (dirname = fname )
60+ nlx_reader = NeuralynxIO (dirname = fname , exclude_filename = exclude_fnames )
4861
4962 info = create_info (
5063 ch_types = "seeg" ,
@@ -75,64 +88,67 @@ def __init__(self, fname, preload=False, verbose=None):
7588 last_samps = [n_total_samples - 1 ],
7689 filenames = [fname ],
7790 preload = preload ,
78- raw_extras = [dict (smp2seg = sample2segment )],
91+ raw_extras = [dict (smp2seg = sample2segment , exclude_fnames = exclude_fnames )],
7992 )
8093
8194 def _read_segment_file (self , data , idx , fi , start , stop , cals , mult ):
8295 """Read a chunk of raw data."""
8396 from neo .io import NeuralynxIO
8497
85- nlx_reader = NeuralynxIO (dirname = self ._filenames [fi ])
98+ nlx_reader = NeuralynxIO (dirname = self ._filenames [fi ], exclude_filename = self . _raw_extras [ 0 ][ "exclude_fnames" ] )
8699 neo_block = nlx_reader .read (lazy = True )
87- sr = nlx_reader .header ["signal_channels" ]["sampling_rate" ][0 ]
88100
89101 # check that every segment has 1 associated neo.AnalogSignal() object
90102 # (not sure what multiple analogsignals per neo.Segment would mean)
91103 assert sum ([len (segment .analogsignals ) for segment in neo_block [0 ].segments ]) == len (neo_block [0 ].segments )
92104
93- # gather the start and stop times (in sec) for all signals in segments (assumes 1 signal per segment)
94- # shape = (n_segments, 2) where 2nd dim is (start_time, stop_time)
95- start_stop_times = np .array (
96- [(signal .t_start .item (), signal .t_stop .item ())
97- for segment in neo_block [0 ].segments
98- for signal in segment .analogsignals
105+ # collect sizes of each segment
106+ segment_sizes = np .array (
107+ [nlx_reader .get_signal_size (0 , segment_id )
108+ for segment_id in range (len (neo_block [0 ].segments ))
99109 ]
100110 )
101111
102- # get times (in sec) for the first and last segment
103- start_time = start / sr
104- stop_time = stop / sr
105-
106- first_seg = self ._raw_extras [0 ]["smp2seg" ][start ] # segment index for the first sample
107- last_seg = self ._raw_extras [0 ]["smp2seg" ][stop - 1 ] # segment index for the last sample
108-
109- # now select only segments between the one that containst the start sample
112+ # construct a (n_segments, 2) array of the first and last
113+ # sample index for each segment relative to the start of the recording
114+ seg_starts = [0 ] # first chunk starts at sample 0
115+ seg_stops = [segment_sizes [0 ]- 1 ]
116+ for i in range (1 , len (segment_sizes )):
117+ ons_new = seg_stops [i - 1 ] + 1 # current chunk starts one sample after the previous one
118+ seg_starts .append (ons_new )
119+ off_new = seg_stops [i - 1 ] + segment_sizes [i ] # the last sample is len(chunk) samples after the previous ended
120+ seg_stops .append (off_new )
121+
122+ start_stop_samples = np .stack (
123+ [np .array (seg_starts ),
124+ np .array (seg_stops )]
125+ ).T
126+
127+ first_seg = self ._raw_extras [0 ]["smp2seg" ][start ] # segment containing start sample
128+ last_seg = self ._raw_extras [0 ]["smp2seg" ][stop - 1 ] # segment containing stop sample
129+
130+ # select all segments between the one that contains the start sample
110131 # and the one that contains the stop sample
111- sel_times = start_stop_times [first_seg :last_seg + 1 , :]
132+ sel_samples_global = start_stop_samples [first_seg :last_seg + 1 , :]
133+
134+ # express end samples relative to segment onsets
135+ # to be used for slicing the arrays below
136+ sel_samples_local = sel_samples_global .copy ()
137+ sel_samples_local [0 :- 1 , 1 ] = sel_samples_global [0 :- 1 , 1 ] - sel_samples_global [0 :- 1 , 0 ]
138+ sel_samples_local [1 ::, 0 ] = 0 # now set the start sample for all segments after the first to 0
112139
113- # if we're reading in later than first sample in first segment
114- # or earlier than the last sample in last segment
115- # we need to adjust the start and stop times accordingly
116- if start_time > sel_times [0 , 0 ]:
117- sel_times [0 , 0 ] = start_time
118- if stop_time < sel_times [- 1 , - 1 ]:
119- sel_times [- 1 , 1 ] = stop_time
120-
121- # now load the data arrays via neo.Segment.Analogsignal.load()
122- # only from the selected segments and channels
140+ sel_samples_local [0 , 0 ] = start - sel_samples_global [0 , 0 ] # express start sample relative to segment onset
141+ sel_samples_local [- 1 , - 1 ] = (stop - 1 ) - sel_samples_global [- 1 , 0 ] # express stop sample relative to segment onset
142+
143+ # now load data from selected segments/channels via
144+ # neo.Segment.AnalogSignal.load()
123145 all_data = np .concatenate (
124- [
125- signal .load (
126- channel_indexes = idx , time_slice = (time [0 ], time [- 1 ])
127- ).magnitude
128- for seg , time in zip (
129- bl [0 ].segments [first_seg : last_seg + 1 ], sel_times
130- )
131- for signal in seg .analogsignals
132- ]
146+ [signal .load (channel_indexes = idx ).magnitude [samples [0 ]:samples [- 1 ] + 1 , :]
147+ for seg , samples in zip (neo_block [0 ].segments [first_seg :last_seg + 1 ], sel_samples_local )
148+ for signal in seg .analogsignals ]
133149 ).T
134- all_data *= 1e-6 # Convert uV to V
135150
151+ all_data *= 1e-6 # Convert uV to V
136152 n_channels = len (nlx_reader .header ["signal_channels" ]["name" ])
137153 block = np .zeros ((n_channels , stop - start ), dtype = data .dtype )
138154 block [idx ] = all_data # shape = (n_channels, n_samples)
0 commit comments