Spaces:
Running
Running
| # Authors: The MNE-Python contributors. | |
| # License: BSD-3-Clause | |
| # Copyright the MNE-Python contributors. | |
| import os.path as op | |
| import time | |
| from pathlib import Path | |
| import numpy as np | |
| from scipy.io import loadmat | |
| from ..._fiff.meas_info import create_info | |
| from ...channels import make_standard_montage | |
| from ...epochs import EpochsArray | |
| from ...utils import _check_pandas_installed, logger, verbose | |
| from ..utils import _do_path_update, _downloader_params, _get_path, _log_time_size | |
| # root url for LIMO files | |
| root_url = "https://files.de-1.osf.io/v1/resources/52rea/providers/osfstorage/" | |
| def data_path( | |
| subject, path=None, force_update=False, update_path=None, *, verbose=None | |
| ): | |
| """Get path to local copy of LIMO dataset URL. | |
| This is a low-level function useful for getting a local copy of the | |
| remote LIMO dataset :footcite:`Rousselet2016`. The complete dataset is | |
| available at datashare.is.ed.ac.uk/. | |
| Parameters | |
| ---------- | |
| subject : int | |
| Subject to download. Must be of :class:`ìnt` in the range from 1 | |
| to 18 (inclusive). | |
| path : None | str | |
| Location of where to look for the LIMO data storing directory. | |
| If None, the environment variable or config parameter | |
| ``MNE_DATASETS_LIMO_PATH`` is used. If it doesn't exist, the | |
| "~/mne_data" directory is used. If the LIMO dataset | |
| is not found under the given path, the data | |
| will be automatically downloaded to the specified folder. | |
| force_update : bool | |
| Force update of the dataset even if a local copy exists. | |
| update_path : bool | None | |
| If True, set the MNE_DATASETS_LIMO_PATH in mne-python | |
| config to the given path. If None, the user is prompted. | |
| %(verbose)s | |
| Returns | |
| ------- | |
| path : str | |
| Local path to the given data file. | |
| Notes | |
| ----- | |
| For example, one could do: | |
| >>> from mne.datasets import limo | |
| >>> limo.data_path(subject=1, path=os.getenv('HOME') + '/datasets') # doctest:+SKIP | |
| This would download the LIMO data file to the 'datasets' folder, | |
| and prompt the user to save the 'datasets' path to the mne-python config, | |
| if it isn't there already. | |
| References | |
| ---------- | |
| .. footbibliography:: | |
| """ # noqa: E501 | |
| import pooch | |
| t0 = time.time() | |
| downloader = pooch.HTTPDownloader(**_downloader_params()) | |
| # local storage patch | |
| config_key = "MNE_DATASETS_LIMO_PATH" | |
| name = "LIMO" | |
| subj = f"S{subject}" | |
| path = _get_path(path, config_key, name) | |
| base_path = op.join(path, "MNE-limo-data") | |
| subject_path = op.join(base_path, subj) | |
| # the remote URLs are in the form of UUIDs: | |
| urls = dict( | |
| S18={ | |
| "Yr.mat": "5cf839833a4d9500178a6ff8", | |
| "LIMO.mat": "5cf83907e650a2001ad592e4", | |
| }, | |
| S17={ | |
| "Yr.mat": "5cf838e83a4d9500168aeb76", | |
| "LIMO.mat": "5cf83867a542b80019c87602", | |
| }, | |
| S16={ | |
| "Yr.mat": "5cf83857e650a20019d5778f", | |
| "LIMO.mat": "5cf837dc3a4d9500188a64fe", | |
| }, | |
| S15={ | |
| "Yr.mat": "5cf837cce650a2001ad591e8", | |
| "LIMO.mat": "5cf83758a542b8001ac7d11d", | |
| }, | |
| S14={ | |
| "Yr.mat": "5cf837493a4d9500198a938f", | |
| "LIMO.mat": "5cf836e4a542b8001bc7cc53", | |
| }, | |
| S13={ | |
| "Yr.mat": "5cf836d23a4d9500178a6df7", | |
| "LIMO.mat": "5cf836543a4d9500168ae7cb", | |
| }, | |
| S12={ | |
| "Yr.mat": "5cf83643d4c7d700193e5954", | |
| "LIMO.mat": "5cf835193a4d9500178a6c92", | |
| }, | |
| S11={ | |
| "Yr.mat": "5cf8356ea542b8001cc81517", | |
| "LIMO.mat": "5cf834f7d4c7d700163daab8", | |
| }, | |
| S10={ | |
| "Yr.mat": "5cf833b0e650a20019d57454", | |
| "LIMO.mat": "5cf83204e650a20018d59eb2", | |
| }, | |
| S9={ | |
| "Yr.mat": "5cf83201a542b8001cc811cf", | |
| "LIMO.mat": "5cf8316c3a4d9500168ae13b", | |
| }, | |
| S8={ | |
| "Yr.mat": "5cf8326ce650a20017d60373", | |
| "LIMO.mat": "5cf8316d3a4d9500198a8dc5", | |
| }, | |
| S7={ | |
| "Yr.mat": "5cf834a03a4d9500168ae59b", | |
| "LIMO.mat": "5cf83069e650a20017d600d7", | |
| }, | |
| S6={ | |
| "Yr.mat": "5cf830e6a542b80019c86a70", | |
| "LIMO.mat": "5cf83057a542b80019c869ca", | |
| }, | |
| S5={ | |
| "Yr.mat": "5cf8115be650a20018d58041", | |
| "LIMO.mat": "5cf80c0bd4c7d700193e213c", | |
| }, | |
| S4={ | |
| "Yr.mat": "5cf810c9a542b80019c8450a", | |
| "LIMO.mat": "5cf80bf83a4d9500198a6eb4", | |
| }, | |
| S3={ | |
| "Yr.mat": "5cf80c55d4c7d700163d8f52", | |
| "LIMO.mat": "5cf80bdea542b80019c83cab", | |
| }, | |
| S2={ | |
| "Yr.mat": "5cde827123fec40019e01300", | |
| "LIMO.mat": "5cde82682a50c4001677c259", | |
| }, | |
| S1={ | |
| "Yr.mat": "5d6d3071536cf5001a8b0c78", | |
| "LIMO.mat": "5d6d305f6f41fc001a3151d8", | |
| }, | |
| ) | |
| # these can't be in the registry file (mne/data/dataset_checksums.txt) | |
| # because of filename duplication | |
| hashes = dict( | |
| S18={ | |
| "Yr.mat": "md5:87f883d442737971a80fc0a35d057e51", | |
| "LIMO.mat": "md5:8b4879646f65d7876fa4adf2e40162c5", | |
| }, | |
| S17={ | |
| "Yr.mat": "md5:7b667ec9eefd7a9996f61ae270e295ee", | |
| "LIMO.mat": "md5:22eaca4e6fad54431fd61b307fc426b8", | |
| }, | |
| S16={ | |
| "Yr.mat": "md5:c877afdb4897426421577e863a45921a", | |
| "LIMO.mat": "md5:86672d7afbea1e8c39305bc3f852c8c2", | |
| }, | |
| S15={ | |
| "Yr.mat": "md5:eea9e0140af598fefc08c886a6f05de5", | |
| "LIMO.mat": "md5:aed5cb71ddbfd27c6a3ac7d3e613d07f", | |
| }, | |
| S14={ | |
| "Yr.mat": "md5:8bd842cfd8588bd5d32e72fdbe70b66e", | |
| "LIMO.mat": "md5:1e07d1f36f2eefad435a77530daf2680", | |
| }, | |
| S13={ | |
| "Yr.mat": "md5:d7925d2af7288b8a5186dfb5dbb63d34", | |
| "LIMO.mat": "md5:ba891015d2f9e447955fffa9833404ca", | |
| }, | |
| S12={ | |
| "Yr.mat": "md5:0e1d05beaa4bf2726e0d0671b78fe41e", | |
| "LIMO.mat": "md5:423fd479d71097995b6614ecb11df9ad", | |
| }, | |
| S11={ | |
| "Yr.mat": "md5:1b0016fb9832e43b71f79c1992fcbbb1", | |
| "LIMO.mat": "md5:1a281348c2a41ee899f42731d30cda70", | |
| }, | |
| S10={ | |
| "Yr.mat": "md5:13c66f60e241b9a9cc576eaf1b55a417", | |
| "LIMO.mat": "md5:3c4b41e221eb352a21bbef1a7e006f06", | |
| }, | |
| S9={ | |
| "Yr.mat": "md5:3ae1d9c3a1d9325deea2f2dddd1ab507", | |
| "LIMO.mat": "md5:5e204e2a4bcfe4f535b4b1af469b37f7", | |
| }, | |
| S8={ | |
| "Yr.mat": "md5:7e9adbca4e03d8d7ce8ea07ccecdc8fd", | |
| "LIMO.mat": "md5:88313c21d34428863590e586b2bc3408", | |
| }, | |
| S7={ | |
| "Yr.mat": "md5:6b5290a6725ecebf1022d5d2789b186d", | |
| "LIMO.mat": "md5:8c769219ebc14ce3f595063e84bfc0a9", | |
| }, | |
| S6={ | |
| "Yr.mat": "md5:420c858a8340bf7c28910b7b0425dc5d", | |
| "LIMO.mat": "md5:9cf4e1a405366d6bd0cc6d996e32fd63", | |
| }, | |
| S5={ | |
| "Yr.mat": "md5:946436cfb474c8debae56ffb1685ecf3", | |
| "LIMO.mat": "md5:241fac95d3a79d2cea081391fb7078bd", | |
| }, | |
| S4={ | |
| "Yr.mat": "md5:c8216af78ac87b739e86e57b345cafdd", | |
| "LIMO.mat": "md5:8e10ef36c2e075edc2f787581ba33459", | |
| }, | |
| S3={ | |
| "Yr.mat": "md5:ff02e885b65b7b807146f259a30b1b5e", | |
| "LIMO.mat": "md5:59b5fb3a9749003133608b5871309e2c", | |
| }, | |
| S2={ | |
| "Yr.mat": "md5:a4329022e57fd07ceceb7d1735fd2718", | |
| "LIMO.mat": "md5:98b284b567f2dd395c936366e404f2c6", | |
| }, | |
| S1={ | |
| "Yr.mat": "md5:076c0ae78fb71d43409c1877707df30e", | |
| "LIMO.mat": "md5:136c8cf89f8f111a11f531bd9fa6ae69", | |
| }, | |
| ) | |
| # create the download manager | |
| fetcher = pooch.create( | |
| path=subject_path, | |
| base_url="", | |
| version=None, # Data versioning is decoupled from MNE-Python version. | |
| registry=hashes[subj], | |
| urls={key: f"{root_url}{uuid}" for key, uuid in urls[subj].items()}, | |
| retry_if_failed=2, # 2 retries = 3 total attempts | |
| ) | |
| # use our logger level for pooch's logger too | |
| pooch.get_logger().setLevel(logger.getEffectiveLevel()) | |
| # fetch the data | |
| sz = 0 | |
| for fname in ("LIMO.mat", "Yr.mat"): | |
| destination = Path(subject_path, fname) | |
| if destination.exists(): | |
| if force_update: | |
| destination.unlink() | |
| else: | |
| continue | |
| if sz == 0: # log once | |
| logger.info("Downloading LIMO data") | |
| # fetch the remote file (if local file missing or has hash mismatch) | |
| fetcher.fetch(fname=fname, downloader=downloader) | |
| sz += destination.stat().st_size | |
| # update path in config if desired | |
| _do_path_update(path, update_path, config_key, name) | |
| if sz > 0: | |
| _log_time_size(t0, sz) | |
| return base_path | |
| def load_data(subject, path=None, force_update=False, update_path=None, verbose=None): | |
| """Fetch subjects epochs data for the LIMO data set. | |
| Parameters | |
| ---------- | |
| subject : int | |
| Subject to use. Must be of class ìnt in the range from 1 to 18. | |
| path : str | |
| Location of where to look for the LIMO data. | |
| If None, the environment variable or config parameter | |
| ``MNE_DATASETS_LIMO_PATH`` is used. If it doesn't exist, the | |
| "~/mne_data" directory is used. | |
| force_update : bool | |
| Force update of the dataset even if a local copy exists. | |
| update_path : bool | None | |
| If True, set the MNE_DATASETS_LIMO_PATH in mne-python | |
| config to the given path. If None, the user is prompted. | |
| %(verbose)s | |
| Returns | |
| ------- | |
| epochs : instance of Epochs | |
| The epochs. | |
| """ # noqa: E501 | |
| pd = _check_pandas_installed() | |
| # subject in question | |
| if isinstance(subject, int) and 1 <= subject <= 18: | |
| subj = f"S{subject}" | |
| else: | |
| raise ValueError("subject must be an int in the range from 1 to 18") | |
| # set limo path, download and decompress files if not found | |
| limo_path = data_path(subject, path, force_update, update_path) | |
| # -- 1) import .mat files | |
| # epochs info | |
| fname_info = op.join(limo_path, subj, "LIMO.mat") | |
| data_info = loadmat(fname_info) | |
| # number of epochs per condition | |
| design = data_info["LIMO"]["design"][0][0]["X"][0][0] | |
| data_info = data_info["LIMO"]["data"][0][0][0][0] | |
| # epochs data | |
| fname_eeg = op.join(limo_path, subj, "Yr.mat") | |
| data = loadmat(fname_eeg) | |
| # -- 2) get epochs information from structure | |
| # sampling rate | |
| sfreq = data_info["sampling_rate"][0][0] | |
| # tmin and tmax | |
| tmin = data_info["start"][0][0] | |
| # create events matrix | |
| sample = np.arange(len(design)) | |
| prev_id = np.zeros(len(design)) | |
| ev_id = design[:, 1] | |
| events = np.array([sample, prev_id, ev_id]).astype(int).T | |
| # event ids, such that Face B == 1 | |
| event_id = {"Face/A": 0, "Face/B": 1} | |
| # -- 3) extract channel labels from LIMO structure | |
| # get individual labels | |
| labels = data_info["chanlocs"]["labels"] | |
| labels = [label for label, *_ in labels[0]] | |
| # get montage | |
| montage = make_standard_montage("biosemi128") | |
| # add external electrodes (e.g., eogs) | |
| ch_names = montage.ch_names + ["EXG1", "EXG2", "EXG3", "EXG4"] | |
| # match individual labels to labels in montage | |
| found_inds = [ind for ind, name in enumerate(ch_names) if name in labels] | |
| missing_chans = [name for name in ch_names if name not in labels] | |
| assert labels == [ch_names[ind] for ind in found_inds] | |
| # -- 4) extract data from subjects Yr structure | |
| # data is stored as channels x time points x epochs | |
| # data['Yr'].shape # <-- see here | |
| # transpose to epochs x channels time points | |
| data = np.transpose(data["Yr"], (2, 0, 1)) | |
| # initialize data in expected order | |
| temp_data = np.empty((data.shape[0], len(ch_names), data.shape[2])) | |
| # copy over the non-missing data | |
| for source, target in enumerate(found_inds): | |
| # avoid copy when fancy indexing | |
| temp_data[:, target, :] = data[:, source, :] | |
| # data to V (to match MNE's format) | |
| data = temp_data / 1e6 | |
| # create list containing channel types | |
| types = ["eog" if ch.startswith("EXG") else "eeg" for ch in ch_names] | |
| # -- 5) Create custom info for mne epochs structure | |
| # create info | |
| info = create_info(ch_names, sfreq, types).set_montage(montage) | |
| # get faces and noise variables from design matrix | |
| event_list = list(events[:, 2]) | |
| faces = ["B" if event else "A" for event in event_list] | |
| noise = list(design[:, 2]) | |
| # create epochs metadata | |
| metadata = {"face": faces, "phase-coherence": noise} | |
| metadata = pd.DataFrame(metadata) | |
| # -- 6) Create custom epochs array | |
| epochs = EpochsArray( | |
| data, info, events, tmin, event_id, metadata=metadata, verbose=False | |
| ) | |
| epochs.info["bads"] = missing_chans # missing channels are marked as bad. | |
| return epochs | |