Skip to content

Commit e251d98

Browse files
ENH read in data based on sample information, add exclude_fname_patterns
1 parent 6efcb3b commit e251d98

File tree

1 file changed

+59
-43
lines changed

1 file changed

+59
-43
lines changed

mne/io/neuralynx/neuralynx.py

Lines changed: 59 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import numpy as np
2+
import glob
3+
import os
24

35
from ..._fiff.meas_info import create_info
46
from ..._fiff.utils import _mult_cal_one
@@ -7,8 +9,8 @@
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
3436
class 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

Comments
 (0)