Run pyLossless on a BIDS dataset.ΒΆ

In this notebook, we will run the pyLossless pipeline on a publicly available dataset.

ImportsΒΆ

from pathlib import Path
import shutil
import pylossless as ll

Get the dataΒΆ

raw, config, bids_path = ll.datasets.load_openneuro_bids()
πŸ‘‹ Hello! This is openneuro-py 2024.2.0. Great to see you! πŸ€—

   πŸ‘‰ Please report problems 🀯 and bugs πŸͺ² at
      https://github.com/hoechenberger/openneuro-py/issues

🌍 Preparing to download ds002778 …

πŸ“ Traversing directories for ds002778 : 0 entities [00:00, ? entities/s]
πŸ“ Traversing directories for ds002778 : 7 entities [00:03,  2.33 entities/s]
πŸ“ Traversing directories for ds002778 : 8 entities [00:03,  2.54 entities/s]
πŸ“ Traversing directories for ds002778 : 14 entities [00:03,  5.58 entities/s]
πŸ“ Traversing directories for ds002778 : 17 entities [00:03,  6.88 entities/s]
πŸ“ Traversing directories for ds002778 : 20 entities [00:03,  5.40 entities/s]
πŸ“₯ Retrieving up to 19 files (5 concurrent downloads).

participants.json:   0%|          | 0.00/1.24k [00:00<?, ?B/s]


sub-pd6_ses-off_scans.tsv:   0%|          | 0.00/75.0 [00:00<?, ?B/s]


dataset_description.json: 0.00B [00:00, ?B/s]

CHANGES: 0.00B [00:00, ?B/s]


README: 0.00B [00:00, ?B/s]



participants.tsv:   0%|          | 0.00/1.62k [00:00<?, ?B/s]










sub-pd6_ses-off_task-rest_channels.tsv:   0%|          | 0.00/2.22k [00:00<?, ?B/s]


sub-pd6_ses-off_task-rest_beh.tsv:   0%|          | 0.00/10.0 [00:00<?, ?B/s]

sub-pd6_ses-off_task-rest_eeg.json:   0%|          | 0.00/471 [00:00<?, ?B/s]


sub-pd6_ses-off_task-rest_beh.json: 0.00B [00:00, ?B/s]






sub-pd6_ses-off_task-rest_eeg.bdf:   0%|          | 0.00/11.5M [00:00<?, ?B/s]

sub-pd6_ses-on_task-rest_beh.tsv:   0%|          | 0.00/10.0 [00:00<?, ?B/s]


sub-pd6_ses-off_task-rest_eeg.bdf:   6%|β–Œ         | 714k/11.5M [00:00<00:01, 7.17MB/s]

sub-pd6_ses-off_task-rest_events.tsv:   0%|          | 0.00/66.0 [00:00<?, ?B/s]


sub-pd6_ses-on_scans.tsv:   0%|          | 0.00/74.0 [00:00<?, ?B/s]



sub-pd6_ses-on_task-rest_beh.json:   0%|          | 0.00/433 [00:00<?, ?B/s]









sub-pd6_ses-off_task-rest_eeg.bdf:  12%|β–ˆβ–        | 1.39M/11.5M [00:00<00:02, 4.56MB/s]

sub-pd6_ses-on_task-rest_channels.tsv:   0%|          | 0.00/2.22k [00:00<?, ?B/s]



sub-pd6_ses-on_task-rest_events.tsv:   0%|          | 0.00/51.0 [00:00<?, ?B/s]



sub-pd6_ses-on_task-rest_eeg.json:   0%|          | 0.00/471 [00:00<?, ?B/s]



sub-pd6_ses-on_task-rest_eeg.bdf:   0%|          | 0.00/17.4M [00:00<?, ?B/s]
sub-pd6_ses-off_task-rest_eeg.bdf:  43%|β–ˆβ–ˆβ–ˆβ–ˆβ–Ž     | 4.96M/11.5M [00:00<00:00, 15.9MB/s]

sub-pd6_ses-on_task-rest_eeg.bdf:   6%|β–Œ         | 1.00M/17.4M [00:00<00:01, 10.4MB/s]
sub-pd6_ses-off_task-rest_eeg.bdf:  62%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–   | 7.16M/11.5M [00:00<00:00, 18.2MB/s]

sub-pd6_ses-on_task-rest_eeg.bdf:  16%|β–ˆβ–Œ        | 2.75M/17.4M [00:00<00:01, 15.0MB/s]
sub-pd6_ses-off_task-rest_eeg.bdf:  82%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ– | 9.43M/11.5M [00:00<00:00, 20.0MB/s]

sub-pd6_ses-on_task-rest_eeg.bdf:  29%|β–ˆβ–ˆβ–‰       | 5.09M/17.4M [00:00<00:00, 19.4MB/s]


sub-pd6_ses-on_task-rest_eeg.bdf:  54%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–Ž    | 9.32M/17.4M [00:00<00:00, 29.2MB/s]

sub-pd6_ses-on_task-rest_eeg.bdf:  79%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‰  | 13.7M/17.4M [00:00<00:00, 35.2MB/s]

                                                                                      βœ… Finished downloading ds002778.

🧠 Please enjoy your brains.

Prep the Raw objectΒΆ

This data has EOG channels that are not labeled as such. We will manually set the channel types to be β€œeog” for these channels (i.e. β€œEXG1”). We will also crop the data to 60 seconds for speed, and load the data in memory, which is required for running the pipeline.

raw.set_channel_types({ch: "eog" for ch in raw.ch_names if ch.startswith("EX")})
raw.load_data().crop(0, 60)
Reading 0 ... 97791  =      0.000 ...   190.998 secs...
General
Filename(s) sub-pd6_ses-off_task-rest_eeg.bdf
MNE object type RawEDF
Measurement date 2011-02-18 at 13:07:18 UTC
Participant sub-pd6
Experimenter mne_anonymize
Acquisition
Duration 00:01:00 (HH:MM:SS)
Sampling frequency 512.00 Hz
Time points 30,721
Channels
EEG
EOG
Stimulus
Head & sensor digitization Not available
Filters
Highpass 0.00 Hz
Lowpass 104.00 Hz


Initialize the pipelineΒΆ

The LosslessPipeline instance is the main object that will run the pipeline. It takes a file path to a Config object as input. load_openneuro_bids() returned a Config object, so we will save it to disk and pass the file path to the LosslessPipeline constructor.

config_path = Path("lossless_config.yaml")
config["filtering"]["notch_filter_args"]["freqs"] = [60]
config.save(config_path)
pipeline = ll.LosslessPipeline(config_path)

Run the pipelineΒΆ

The LosslessPipeline object has a run_with_raw() method that takes a Raw object as input. We will use the Raw object that was returned by load_openneuro_bids() with the pipeline.

 ⏩ LOSSLESS: Starting Pylossless Pipeline.
LOSSLESS: Skipping Looking for break periods between tasks
LOSSLESS: πŸ‘‡ Flagging Noisy Channels.
🧹 Epoching..
Not setting metadata
60 matching events found
No baseline correction applied
0 projection items activated
Using data from preloaded Raw for 60 events and 512 original time points ...
0 bad epochs dropped
πŸ” Detecting channels to leave out of reference.
EEG channel type selected for re-referencing
Applying a custom ('EEG',) reference.
πŸ“‹ LOSSLESS: Noisy channels: []
LOSSLESS: 🏁 Finished Flagging Noisy Channels after 0.11 seconds.
LOSSLESS: πŸ‘‡ Flagging Noisy Time periods.
🧹 Epoching..
Not setting metadata
60 matching events found
No baseline correction applied
0 projection items activated
Using data from preloaded Raw for 60 events and 512 original time points ...
0 bad epochs dropped
πŸ” Detecting channels to leave out of reference.
EEG channel type selected for re-referencing
Applying a custom ('EEG',) reference.
πŸ“‹ LOSSLESS: 1.0 second(s) flagged as BAD_LL_noisy
LOSSLESS: 🏁 Finished Flagging Noisy Time periods after 0.11 seconds.
LOSSLESS: πŸ‘‡ Filtering.
Filtering raw data in 1 contiguous segment
Setting up band-pass filter from 1 - 1e+02 Hz

FIR filter parameters
---------------------
Designing a one-pass, zero-phase, non-causal bandpass filter:
- Windowed time-domain design (firwin) method
- Hamming window with 0.0194 passband ripple and 53 dB stopband attenuation
- Lower passband edge: 1.00
- Lower transition bandwidth: 1.00 Hz (-6 dB cutoff frequency: 0.50 Hz)
- Upper passband edge: 100.00 Hz
- Upper transition bandwidth: 25.00 Hz (-6 dB cutoff frequency: 112.50 Hz)
- Filter length: 1691 samples (3.303 s)

[Parallel(n_jobs=1)]: Done  17 tasks      | elapsed:    0.0s
Filtering raw data in 1 contiguous segment
Setting up band-stop filter from 59 - 61 Hz

FIR filter parameters
---------------------
Designing a one-pass, zero-phase, non-causal bandstop filter:
- Windowed time-domain design (firwin) method
- Hamming window with 0.0194 passband ripple and 53 dB stopband attenuation
- Lower passband edge: 59.35
- Lower transition bandwidth: 0.50 Hz (-6 dB cutoff frequency: 59.10 Hz)
- Upper passband edge: 60.65 Hz
- Upper transition bandwidth: 0.50 Hz (-6 dB cutoff frequency: 60.90 Hz)
- Filter length: 3381 samples (6.604 s)

[Parallel(n_jobs=1)]: Done  17 tasks      | elapsed:    0.0s
LOSSLESS: 🏁 Finished Filtering after 0.15 seconds.
LOSSLESS: πŸ‘‡ Flagging uncorrelated channels.
🧹 Epoching..
Not setting metadata
60 matching events found
No baseline correction applied
0 projection items activated
Using data from preloaded Raw for 60 events and 512 original time points ...
1 bad epochs dropped
πŸ” Detecting channels to leave out of reference.
EEG channel type selected for re-referencing
Applying a custom ('EEG',) reference.

  0%|          | 0/32 [00:00<?, ?it/s]
 19%|β–ˆβ–‰        | 6/32 [00:00<00:00, 55.85it/s]
 38%|β–ˆβ–ˆβ–ˆβ–Š      | 12/32 [00:00<00:00, 56.47it/s]
 56%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‹    | 18/32 [00:00<00:00, 56.68it/s]
 75%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–Œ  | 24/32 [00:00<00:00, 56.23it/s]
 94%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–| 30/32 [00:00<00:00, 56.28it/s]
100%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| 32/32 [00:00<00:00, 56.33it/s]
πŸ“‹ LOSSLESS: Uncorrelated channels: []
LOSSLESS: 🏁 Finished Flagging uncorrelated channels after 0.71 seconds.
LOSSLESS: πŸ‘‡ Flagging Bridged channels.
πŸ“‹ LOSSLESS: Bridged channels: ['PO3' 'O1' 'Oz' 'O2' 'PO4']
LOSSLESS: 🏁 Finished Flagging Bridged channels after 0.01 seconds.
LOSSLESS: πŸ‘‡ Flagging the rank channel.
πŸ“‹ LOSSLESS: Rank channel: ['Fp2']
LOSSLESS: 🏁 Finished Flagging the rank channel after 0.00 seconds.
LOSSLESS: πŸ‘‡ Flagging Uncorrelated epochs.
🧹 Epoching..
Not setting metadata
60 matching events found
No baseline correction applied
0 projection items activated
Using data from preloaded Raw for 60 events and 512 original time points ...
1 bad epochs dropped
πŸ” Detecting channels to leave out of reference.
EEG channel type selected for re-referencing
Applying a custom ('EEG',) reference.

  0%|          | 0/26 [00:00<?, ?it/s]
 23%|β–ˆβ–ˆβ–Ž       | 6/26 [00:00<00:00, 56.91it/s]
 46%|β–ˆβ–ˆβ–ˆβ–ˆβ–Œ     | 12/26 [00:00<00:00, 57.32it/s]
 69%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‰   | 18/26 [00:00<00:00, 56.88it/s]
 92%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–| 24/26 [00:00<00:00, 56.92it/s]
100%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| 26/26 [00:00<00:00, 56.99it/s]
LOSSLESS: 🏁 Finished Flagging Uncorrelated epochs after 0.58 seconds.
LOSSLESS: πŸ‘‡ Running Initial ICA.
🧹 Epoching..
Not setting metadata
60 matching events found
No baseline correction applied
0 projection items activated
Using data from preloaded Raw for 60 events and 512 original time points ...
1 bad epochs dropped
πŸ” Detecting channels to leave out of reference.
EEG channel type selected for re-referencing
Applying a custom ('EEG',) reference.
Fitting ICA to data using 26 channels (please be patient, this may take a while)
Selecting by non-zero PCA components: 25 components
/home/docs/checkouts/readthedocs.org/user_builds/pylossless/envs/stable/lib/python3.10/site-packages/sklearn/decomposition/_fastica.py:128: ConvergenceWarning: FastICA did not converge. Consider increasing tolerance or the maximum number of iterations.
  warnings.warn(
Fitting ICA took 10.2s.
LOSSLESS: 🏁 Finished Running Initial ICA after 10.33 seconds.
LOSSLESS: πŸ‘‡ Flagging time periods with noisy IC's..
🧹 Epoching..
Not setting metadata
60 matching events found
No baseline correction applied
0 projection items activated
Using data from preloaded Raw for 60 events and 512 original time points ...
1 bad epochs dropped
πŸ” Detecting channels to leave out of reference.
EEG channel type selected for re-referencing
Applying a custom ('EEG',) reference.
πŸ“‹ LOSSLESS: 3.99 second(s) flagged as BAD_LL_noisy_ICs
LOSSLESS: 🏁 Finished Flagging time periods with noisy IC's. after 0.15 seconds.
LOSSLESS: πŸ‘‡ Running Final ICA and ICLabel..
🧹 Epoching..
Not setting metadata
60 matching events found
No baseline correction applied
0 projection items activated
Using data from preloaded Raw for 60 events and 512 original time points ...
5 bad epochs dropped
πŸ” Detecting channels to leave out of reference.
EEG channel type selected for re-referencing
Applying a custom ('EEG',) reference.
Fitting ICA to data using 26 channels (please be patient, this may take a while)
Selecting by non-zero PCA components: 25 components
Computing Extended Infomax ICA
Fitting ICA took 7.6s.
LOSSLESS: 🏁 Finished Running Final ICA and ICLabel. after 12.48 seconds.
  βœ… LOSSLESS: Pipeline completed! took 0.41 minutes.
General
Filename(s) sub-pd6_ses-off_task-rest_eeg.bdf
MNE object type RawEDF
Measurement date 2011-02-18 at 13:07:18 UTC
Participant sub-pd6
Experimenter mne_anonymize
Acquisition
Duration 00:01:00 (HH:MM:SS)
Sampling frequency 512.00 Hz
Time points 30,721
Channels
EEG
EOG
Stimulus
Head & sensor digitization 35 points
Filters
Highpass 1.00 Hz
Lowpass 100.00 Hz


View the resultsΒΆ

The LosslessPipeline object stores flagged channels and ICs in the flags attribute:

LosslessPipeline

Raw('/home/docs/checkouts/readthedocs.org/user_builds/pylossless/checkouts/stable/examples/ds002778/sub-pd6/ses-off/eeg/sub-pd6_ses-off_task-rest_eeg.bdf',)
Configlossless_config.yaml
Flagged Channels
Noisy[]
Bridged['PO3' 'O1' 'Oz' 'O2' 'PO4']
Uncorrelated[]
Flagged ICs
EOG (Eye)['ICA000', 'ICA001', 'ICA013', 'ICA017']
ECG (Heart)[]
Muscle['ICA002', 'ICA004', 'ICA005', 'ICA008']
Line Noise[]
Channel Noise[]
Flagged Times (Total)
BAD_LL_noisy1.00 s seconds
BAD_LL_uncorrelated[] seconds
BAD_LL_noisy_ICs3.99 s seconds


component annotator ic_type confidence
0 ICA000 ic_label eog 0.999322
1 ICA001 ic_label eog 0.471219
2 ICA002 ic_label muscle 0.999597
3 ICA003 ic_label brain 0.998710
4 ICA004 ic_label muscle 0.963688
5 ICA005 ic_label muscle 0.998467
6 ICA006 ic_label brain 0.999827
7 ICA007 ic_label brain 0.913452
8 ICA008 ic_label muscle 0.999979
9 ICA009 ic_label other 0.840519
10 ICA010 ic_label brain 0.994211
11 ICA011 ic_label brain 0.691695
12 ICA012 ic_label brain 0.908586
13 ICA013 ic_label eog 0.976433
14 ICA014 ic_label other 0.560021
15 ICA015 ic_label other 0.600071
16 ICA016 ic_label other 0.945662
17 ICA017 ic_label eog 0.389291
18 ICA018 ic_label brain 0.884823
19 ICA019 ic_label brain 0.970654
20 ICA020 ic_label brain 0.668933
21 ICA021 ic_label brain 0.532087
22 ICA022 ic_label brain 0.997889
23 ICA023 ic_label other 0.976598
24 ICA024 ic_label other 0.945657


Get the cleaned dataΒΆ

The LosslessPipeline by default does not modify the Raw object that is passed to it, so none of the flagged channels or ICs are removed from the Raw object yet. To get the cleaned Raw object, we need to call the apply() method. This method takes a LosslessPipeline as input, which specifies how to apply the flags to generate a new Raw object.

RejectionPolicy: |
  config_fname: None
  ch_flags_to_reject: ['noisy', 'uncorrelated', 'bridged']
  ic_flags_to_reject: ['muscle', 'ecg', 'eog', 'channel_noise', 'line_noise']
  ic_rejection_threshold: 0.3
  ch_cleaning_mode: None
  remove_flagged_ics: True

Note that we are using the default channel cleaning mode, which is None, meaning that that the flagged channels will simply be added to raw.info["bads"]. We also could have specified "interpolate", which means that the flagged channels would be interpolated using interpolate_bads().

cleaned_raw = rejection_policy.apply(pipeline)
cleaned_raw.plot()
plot 10 run pipeline
Applying ICA to Raw instance
    Transforming to ICA space (25 components)
    Zeroing out 8 ICA components
    Projecting back using 26 PCA components
Using matplotlib as 2D backend.

<MNEBrowseFigure size 800x800 with 4 Axes>

Save the PyLossless DerivativeΒΆ

Let’s save our pipeline output to disk. We need to use our BIDSPath object to set up a derivatives path to save the pipeline output to:

BIDSPath(
root: ds002778/derivatives/pylossless
datatype: eeg
basename: sub-pd6_ses-off_task-rest_eeg_ll)
Writing 'ds002778/derivatives/pylossless/README'...
Writing 'ds002778/derivatives/pylossless/participants.tsv'...
Writing 'ds002778/derivatives/pylossless/participants.json'...
Writing 'ds002778/derivatives/pylossless/sub-pd6/ses-off/eeg/sub-pd6_ses-off_space-CapTrak_electrodes.tsv'...
Writing 'ds002778/derivatives/pylossless/sub-pd6/ses-off/eeg/sub-pd6_ses-off_space-CapTrak_coordsystem.json'...
The provided raw data contains annotations, but you did not pass an "event_id" mapping from annotation descriptions to event codes. We will generate arbitrary event codes. To specify custom event codes, please pass "event_id".
Used Annotations descriptions: [np.str_('1'), np.str_('65536'), np.str_('BAD_LL_noisy'), np.str_('BAD_LL_noisy_ICs')]
Writing 'ds002778/derivatives/pylossless/sub-pd6/ses-off/eeg/sub-pd6_ses-off_task-rest_events.tsv'...
Writing 'ds002778/derivatives/pylossless/sub-pd6/ses-off/eeg/sub-pd6_ses-off_task-rest_events.json'...
Writing 'ds002778/derivatives/pylossless/dataset_description.json'...
Writing 'ds002778/derivatives/pylossless/sub-pd6/ses-off/eeg/sub-pd6_ses-off_task-rest_eeg.json'...
Writing 'ds002778/derivatives/pylossless/sub-pd6/ses-off/eeg/sub-pd6_ses-off_task-rest_channels.tsv'...
Copying data files to sub-pd6_ses-off_task-rest_eeg.edf
/home/docs/checkouts/readthedocs.org/user_builds/pylossless/checkouts/stable/pylossless/pipeline.py:1085: RuntimeWarning: Converting data files to EDF format
  mne_bids.write_raw_bids(
/home/docs/checkouts/readthedocs.org/user_builds/pylossless/checkouts/stable/pylossless/pipeline.py:1085: RuntimeWarning: EDF format requires equal-length data blocks, so 0.998 seconds of edge values were appended to all channels when writing the final block.
  mne_bids.write_raw_bids(
Writing 'ds002778/derivatives/pylossless/sub-pd6/ses-off/sub-pd6_ses-off_scans.tsv'...
Wrote ds002778/derivatives/pylossless/sub-pd6/ses-off/sub-pd6_ses-off_scans.tsv entry with eeg/sub-pd6_ses-off_task-rest_eeg.edf.
Writing ICA solution to /home/docs/checkouts/readthedocs.org/user_builds/pylossless/checkouts/stable/examples/ds002778/derivatives/pylossless/sub-pd6/ses-off/eeg/sub-pd6_ses-off_task-rest_ica1_ica.fif...
Writing ICA solution to /home/docs/checkouts/readthedocs.org/user_builds/pylossless/checkouts/stable/examples/ds002778/derivatives/pylossless/sub-pd6/ses-off/eeg/sub-pd6_ses-off_task-rest_ica2_ica.fif...

Clean upΒΆ

Total running time of the script: (0 minutes 33.126 seconds)

Gallery generated by Sphinx-Gallery