Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support preload=False for the new EEGLAB single .set format #13096

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 82 additions & 5 deletions mne/io/eeglab/_eeglab.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -71,13 +71,90 @@ 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",
Comment on lines +83 to +84
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a list available (like an EEGLAB API documentation) that describes or at least simply but exhaustively enumerates these fields? If yes, it would be very valuable to link to that in a comment here, or in the docstr of the EEGLAB reading function. If not, I expect this bit of code to be relatively volatile, as we would always need an EEGLAB expert to help us fixing this function.

"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)
27 changes: 8 additions & 19 deletions mne/io/eeglab/eeglab.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,18 @@
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":
raise NotImplementedError(
"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(f"The file extension must be .set or .fdt, not {fmt}")
basedir = op.dirname(fname)
data_fname = op.join(basedir, dataname)
if not op.exists(data_fname):
Expand All @@ -68,10 +69,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"
Expand Down Expand Up @@ -302,8 +303,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

Expand Down Expand Up @@ -420,8 +419,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
Expand All @@ -447,7 +444,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"
Expand All @@ -472,14 +469,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:
Expand Down
Loading