lina-usc / pylossless

🧠 EEG Processing pipeline that annotates continuous data
https://pylossless.readthedocs.io/en/latest/
MIT License
25 stars 10 forks source link

TypeError: write_raw_bids() got an unexpected keyword argument 'events_data'" #175

Closed christian-oreilly closed 1 week ago

christian-oreilly commented 1 week ago

Error reported by @Deepa-Tilwani on Discord.

{
    "name": "TypeError",
    "message": "write_raw_bids() got an unexpected keyword argument 'events_data'",
    "stack": "---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[10], line 19
     16 bids_path_args = [{'subject': subject, 'session': '01', \"task\": \"narative\"} for subject in df_paths.index]
     18 if recompute:
---> 19     bids_paths = ll.bids.convert_dataset_to_bids(import_fct, import_args, bids_path_args,
     20                                                  bids_root=path_eegp / \"bids_dataset\", overwrite=True)
     21 else:
     22     bids_paths = ll.bids.get_dataset_bids_path(bids_path_args, bids_root=path_eegp / \"bids_dataset\")

File ~/Documents/Projects/ABC/.venv/lib/python3.10/site-packages/pylossless/bids.py:140, in convert_dataset_to_bids(import_funcs, import_args, bids_path_args, datatype, bids_root, import_events, **write_kwargs)
    135 bids_paths = []
    136 for import_kwargs, bids_path_kwargs, func in zip(
    137     import_args, bids_path_args, import_funcs
    138 ):
    139     bids_paths.append(
--> 140         convert_recording_to_bids(
    141             func,
    142             import_kwargs,
    143             bids_path_kwargs,
    144             datatype=datatype,
    145             bids_root=bids_root,
    146             import_events=import_events,
    147             **write_kwargs
    148         )
    149     )
    151 return bids_paths

File ~/Documents/Projects/ABC/.venv/lib/python3.10/site-packages/pylossless/bids.py:80, in convert_recording_to_bids(import_func, import_kwargs, bids_path_kwargs, datatype, bids_root, import_events, **write_kwargs)
     77 if \"allow_preload\" not in write_kwargs:
     78     write_kwargs[\"allow_preload\"] = True
---> 80 write_raw_bids(
     81     raw, bids_path=bids_path, events_data=events, event_id=event_id, **write_kwargs
     82 )
     84 return bids_path

TypeError: write_raw_bids() got an unexpected keyword argument 'events_data'"
}
christian-oreilly commented 1 week ago

I think MNE-BIDS renamed the events_data arguments to events in MNE-BIDS version 0.11+. @Deepa-Tilwani Could you check if you rename events_data to events on line 80 of the bids.py file if it works fine?

christian-oreilly commented 1 week ago

Could you check if you rename events_data to events on line 80 of the bids.py file if it works fine?

Confirmed.

scott-huberty commented 1 week ago

Thanks @christian-oreilly Seems you are right 🙏 . Maybe we just pin to MNE-BIDS>=0.11? it was released 2 years ago.

Nevermind.. We already pin to MNE-BIDS 0.14. As @christian-oreilly said, looks like we are lacking a test for convert_dataset_to_bids

christian-oreilly commented 1 week ago

Yeah. I'll fish some old code. I was using this in the README some time back... so maybe this can be put in a test with the PR solving this.

christian-oreilly commented 1 week ago

Fetched from the history...

BIDSification

PyLossless provides some functions to help the user import non-BIDS recordings. Since the code to import datasets recorded in different formats and with different properties can vary much from one project to the next, the user must provide a function that can load and return a raw object along with the standard MNE events array and event_id dictionary. For example, in the case of our dataset

# Example of importing function
import tempfile
def egi_import_fct(path_in, stim_channel):
    # read in a file
    raw = mne.io.read_raw_egi(path_in, preload=True)
    # events and event IDs for events sidecar
    events = mne.find_events(raw, stim_channel=['STI 014'])
    event_id = raw.event_id
    # MNE-BIDS doesn't currently support RawMFF objects.
    with tempfile.TemporaryDirectory() as temp_dir:
        raw.save(Path(temp_dir) / "tmp_raw.fif")
        # preload=True is important since this file is volatile
        raw = mne.io.read_raw_fif(Path(temp_dir) / 'tmp_raw.fif', preload=True)
    # we only want EEG channels in the channels sidecar
    raw.pick_types(eeg=True, stim=False)
    raw.rename_channels({'E129': 'Cz'})  # to make compatible with montage
    return raw, events, event_id

Then, the dataset can be converted to BIDS as follows

import_args = [{"stim_channel": 'STI 014', "path_in": './sub-s004-ses_07_task-MMN_20220218_022348.mff'},
               {"stim_channel": 'STI 014', "path_in": './sub-s004-ses_07_task-MMN_20220218_022348.mff'}]
bids_path_args = [{'subject': '001', 'run': '01', 'session': '01', "task": "mmn"},
                  {'subject': '002', 'run': '01', 'session': '01', "task": "mmn"}]
bids_paths = ll.bids.convert_to_bids(egi_import_fct, import_args, bids_path_args, overwrite=True)

Note that, in this case, we used twice the same input file just to demonstrate how this function can be used for multiple recordings. In practice, a user may want to have this information stored in CSV files that can be readily used. For example, if we create such files for the demonstration:

import pandas as pd
pd.DataFrame(import_args).to_csv("import_args.csv", index=False)
pd.DataFrame(bids_path_args).to_csv("bids_path_args.csv", index=False)

Now, regardless of how such files have been produced (e.g., from Excel), these can be used directly to process the whole dataset:

import_args = list(pd.read_csv("import_args.csv").T.to_dict().values())
bids_path_args = list(pd.read_csv("bids_path_args.csv").T.to_dict().values())
bids_paths = ll.bids.convert_to_bids(egi_import_fct, import_args, bids_path_args, overwrite=True)
pipeline.run_dataset(bids_paths)
christian-oreilly commented 1 week ago

@scott-huberty I think the only challenge is that we don't have such a test file in the repo. Apparently, that file was also not part of the repo at this point in time... I am not sure if it was ever included.

christian-oreilly commented 1 week ago

I did find it in some old code archive on my local computer, though. That file was 761.1 MB. Maybe it can be cropped to a more reasonable size (~1MB?) and included? I suppose this was one of our internal Q1k piloting on you or me.

scott-huberty commented 1 week ago

@christian-oreilly Maybe we can just use one of MNE's test files to create a "dummy" dataset (repeatedly using one file) like your previous example did?

import mne

raw_fname = mne.datasets.testing.data_path() / "EGI" / "test_egi.mff"

...
christian-oreilly commented 1 week ago

Yeah... I ended up doing something like that. I copied a snippet from another test and used

    testing_path = mne.datasets.testing.data_path()
    fname = testing_path / "EDF" / "test_edf_overlapping_annotations.edf"

Test written and successful locally.