From b6871ccb8444468881ccdc93b09134f617654a11 Mon Sep 17 00:00:00 2001 From: Dung Truong Date: Tue, 14 Jan 2025 13:14:14 -0500 Subject: [PATCH] update preload to support new single .set EEGLAB format --- mne/io/eeglab/_eeglab.py | 26 +++++++++++++++++++++----- mne/io/eeglab/eeglab.py | 29 ++++++++++------------------- 2 files changed, 31 insertions(+), 24 deletions(-) diff --git a/mne/io/eeglab/_eeglab.py b/mne/io/eeglab/_eeglab.py index 28df469fada..d32f47446a1 100644 --- a/mne/io/eeglab/_eeglab.py +++ b/mne/io/eeglab/_eeglab.py @@ -9,7 +9,7 @@ except ImportError: # scipy < 1.8 from scipy.io.matlab.mio5 import MatlabFunction from scipy.io.matlab.mio5_params import MatlabOpaque -from scipy.io import loadmat +from scipy.io import loadmat, whosmat from ...utils import _import_pymatreader_funcs @@ -71,13 +71,29 @@ def _check_for_scipy_mat_struct(data): # taken from pymatreader.utils return data -def _readmat(fname, uint16_codec=None): +def _readmat(fname, uint16_codec=None, preload=False): try: read_mat = _import_pymatreader_funcs("EEGLAB I/O") except RuntimeError: # pymatreader not installed - eeg = loadmat( - fname, squeeze_me=True, mat_dtype=False, uint16_codec=uint16_codec - ) + if preload: + eeg = loadmat( + fname, squeeze_me=True, mat_dtype=False, uint16_codec=uint16_codec + ) + else: + info_fields = ['setname', 'filename', 'filepath', 'subject', 'group', 'condition', 'session', 'comments', 'nbchan', 'trials', 'pnts', 'srate', 'xmin', 'xmax', 'times', 'icaact', 'icawinv', 'icasphere', 'icaweights', 'icachansind', 'chanlocs', 'urchanlocs', 'chaninfo', 'ref', 'event', 'urevent', 'eventdescription', 'epoch', 'epochdescription', 'reject', 'stats', 'specdata', 'specicaact', 'splinefile', 'icasplinefile', 'dipfit', 'history', 'saved', 'etc'] + eeg = loadmat( + fname, variable_names=info_fields, squeeze_me=True, mat_dtype=False, uint16_codec=uint16_codec + ) + variables = whosmat(str(fname)) + for var in variables: + if var[0] == 'data': + numeric_types = ['int8', 'int16', 'int32', 'int64', 'uint8', 'uint16', 'uint32', 'uint64', 'single', 'double'] + if var[2] in numeric_types: + # in preload=False mode and data is in .set file + eeg['data'] = str(fname) + else: + eeg['data'] = loadmat(fname, variable_names=['data'], squeeze_me=True, mat_dtype=False, uint16_codec=uint16_codec) + break return _check_for_scipy_mat_struct(eeg) else: return read_mat(fname, uint16_codec=uint16_codec) diff --git a/mne/io/eeglab/eeglab.py b/mne/io/eeglab/eeglab.py index 3aa611b4e28..9a883493a16 100644 --- a/mne/io/eeglab/eeglab.py +++ b/mne/io/eeglab/eeglab.py @@ -39,9 +39,9 @@ def _check_eeglab_fname(fname, dataname): """Check whether the filename is valid. - Check if the file extension is ``.fdt`` (older ``.dat`` being invalid) or - whether the ``EEG.data`` filename exists. If ``EEG.data`` file is absent - the set file name with .set changed to .fdt is checked. + Check if the file extension is ``.fdt`` (older ``.dat`` being invalid) + or ``.set`` (new EEGLAB format) or whether the ``EEG.data`` filename exists. + If ``EEG.data`` file is absent the set file name with .set changed to .fdt is checked. """ fmt = str(op.splitext(dataname)[-1]) if fmt == ".dat": @@ -49,7 +49,10 @@ def _check_eeglab_fname(fname, dataname): "Old data format .dat detected. Please update your EEGLAB " "version and resave the data in .fdt format" ) - + if fmt != ".set" and fmt != ".fdt": + raise ValueError( + "The file extension must be .set or .fdt, not {}".format(fmt) + ) basedir = op.dirname(fname) data_fname = op.join(basedir, dataname) if not op.exists(data_fname): @@ -68,10 +71,10 @@ def _check_eeglab_fname(fname, dataname): return data_fname -def _check_load_mat(fname, uint16_codec): +def _check_load_mat(fname, uint16_codec, preload=False): """Check if the mat struct contains 'EEG'.""" fname = _check_fname(fname, "read", True) - eeg = _readmat(fname, uint16_codec=uint16_codec) + eeg = _readmat(fname, uint16_codec=uint16_codec, preload=preload) if "ALLEEG" in eeg: raise NotImplementedError( "Loading an ALLEEG array is not supported. Please contact" @@ -302,8 +305,6 @@ def read_raw_eeglab( If 'auto', the channel names containing ``EOG`` or ``EYE`` are used. Defaults to empty tuple. %(preload)s - Note that ``preload=False`` will be effective only if the data is - stored in a separate binary file. %(uint16_codec)s %(montage_units)s @@ -420,8 +421,6 @@ class RawEEGLAB(BaseRaw): If 'auto', the channel names containing ``EOG`` or ``EYE`` are used. Defaults to empty tuple. %(preload)s - Note that preload=False will be effective only if the data is stored - in a separate binary file. %(uint16_codec)s %(montage_units)s %(verbose)s @@ -447,7 +446,7 @@ def __init__( verbose=None, ): input_fname = str(_check_fname(input_fname, "read", True, "input_fname")) - eeg = _check_load_mat(input_fname, uint16_codec) + eeg = _check_load_mat(input_fname, uint16_codec, preload) if eeg.trials != 1: raise TypeError( f"The number of trials is {eeg.trials:d}. It must be 1 for raw" @@ -472,14 +471,6 @@ def __init__( verbose=verbose, ) else: - if preload is False or isinstance(preload, str): - warn( - "Data will be preloaded. preload=False or a string " - "preload is not supported when the data is stored in " - "the .set file" - ) - # can't be done in standard way with preload=True because of - # different reading path (.set file) if eeg.nbchan == 1 and len(eeg.data.shape) == 1: n_chan, n_times = [1, eeg.data.shape[0]] else: