Skip to content

Commit 01a094d

Browse files
Decode Persyst and Nihon Kohden data in cache-sized blocks (#14251)
1 parent fb4dc39 commit 01a094d

3 files changed

Lines changed: 39 additions & 34 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Speed up :func:`mne.io.read_raw_persyst` and :func:`mne.io.read_raw_nihon` by decoding data in cache-sized blocks rather than materializing the whole request, by `Bruno Aristimunha`_.

mne/io/nihon/nihon.py

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,10 @@ def _map_ch_to_specs(ch_name, chan_labels_upper):
418418
return out
419419

420420

421+
# decode in cache-sized blocks rather than one huge one (1.8x on a 106 MB file)
422+
_BLOCK_BYTES = 1024**2
423+
424+
421425
@fill_doc
422426
class RawNihon(BaseRaw):
423427
"""Raw object from a Nihon Kohden EEG file.
@@ -566,13 +570,20 @@ def _read_segment_file(self, data, idx, fi, start, stop, cals, mult):
566570
rel_start = start - ends[start_block - 1]
567571
start_offset = datastart + rel_start * n_channels * 2
568572

573+
# Decode a few MB at a time: each step below builds a temporary the
574+
# size of the block, so reading the whole request at once pushes
575+
# them all out of cache.
576+
n_times = stop - start
577+
n_block = max(1, _BLOCK_BYTES // 2 // n_channels)
569578
with open(self.filenames[fi], "rb") as fid:
570-
to_read = (stop - start) * n_channels
571579
fid.seek(start_offset)
572-
block_data = np.fromfile(fid, "<u2", to_read) + 0x8000
573-
block_data = block_data.astype(np.int16)
574-
block_data = block_data.reshape(n_channels, -1, order="F")
575-
block_data = block_data[:-1] * cal # cast to float64
576-
block_data += offsets
577-
block_data *= gains
578-
_mult_cal_one(data, block_data, idx, cals, mult)
580+
for sample_start in range(0, n_times, n_block):
581+
n_read = min(n_block, n_times - sample_start)
582+
block_data = np.fromfile(fid, "<u2", n_read * n_channels) + 0x8000
583+
block_data = block_data.astype(np.int16)
584+
block_data = block_data.reshape(n_channels, -1, order="F")
585+
block_data = block_data[:-1] * cal # cast to float64
586+
block_data += offsets
587+
block_data *= gains
588+
data_view = data[:, sample_start : sample_start + n_read]
589+
_mult_cal_one(data_view, block_data, idx, cals, mult)

mne/io/persyst/persyst.py

Lines changed: 19 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
from ..._fiff.constants import FIFF
1515
from ..._fiff.meas_info import create_info
16-
from ..._fiff.utils import _mult_cal_one
16+
from ..._fiff.utils import _read_segments_file
1717
from ...annotations import Annotations
1818
from ...utils import _check_fname, fill_doc, logger, verbose, warn
1919
from ..base import BaseRaw
@@ -55,6 +55,11 @@ def read_raw_persyst(
5555
return RawPersyst(fname, preload, verbose)
5656

5757

58+
# read in cache-sized blocks rather than one huge one (2.3x on a 107 MB file);
59+
# see _read_segments_file() for why a smaller block is faster
60+
_BLOCK_BYTES = 16 * 1024**2
61+
62+
5863
@fill_doc
5964
class RawPersyst(BaseRaw):
6065
"""Raw object from a Persyst file.
@@ -267,31 +272,19 @@ def _read_segment_file(self, data, idx, fi, start, stop, cals, mult):
267272
binary files. In addition, it stores the calibration to convert
268273
data to uV in the lay file.
269274
"""
270-
dtype = self._raw_extras[fi]["dtype"]
271-
n_chs = self._raw_extras[fi]["n_chs"]
272-
dat_fname = self.filenames[fi]
273-
274-
# compute samples count based on start and stop
275-
time_length_samps = stop - start
276-
277-
# read data from .dat file into array of correct size, then calibrate
278-
# records = recnum rows x inf columns
279-
count = time_length_samps * n_chs
280-
281-
# seek the dat file
282-
with open(dat_fname, "rb") as dat_file_ID:
283-
# allow offset to occur
284-
dat_file_ID.seek(n_chs * dtype.itemsize * start, 1)
285-
286-
# read in the actual record starting at possibly offset
287-
record = np.fromfile(dat_file_ID, dtype=dtype, count=count)
288-
289-
# chs * rows
290-
# cast as float32; more than enough precision
291-
record = np.reshape(record, (n_chs, -1), order="F").astype(np.float32)
292-
293-
# calibrate to convert to V and handle mult
294-
_mult_cal_one(data, record, idx, cals, mult)
275+
_read_segments_file(
276+
self,
277+
data,
278+
idx,
279+
fi,
280+
start,
281+
stop,
282+
cals,
283+
mult,
284+
dtype=self._raw_extras[fi]["dtype"],
285+
n_channels=self._raw_extras[fi]["n_chs"],
286+
max_block_bytes=_BLOCK_BYTES,
287+
)
295288

296289

297290
def _get_subjectinfo(patient_dict):

0 commit comments

Comments
 (0)