Repository: mne-tools/mne-bids-pipeline

Issue:
In sensor/apply_ica, we should not apply the ICA rejection threshold to the data before rejecting components
We have a setting `ica_reject` to remove bad epochs before fitting ICA. The epochs we feed into ICA are separate from the epochs we want to clean: they're high-pass filtered and they have `drop_bad(reject=ica_reject)` applied. This ensures ICA can be fit on epochs without flux / voltage spikes etc

Now in the `apply_ica` step, when we load the "actual" epochs we wish to clean, instead of simply applying ICA, we first call `drop_bad(reject=ica_reject)` too. In my opinion this is a bug, as normally we should be assuming that applying ICA may at least remove some of those high-amplitude artifacts. But if we reject those epochs before cleaning them via ICA, they're gone forever.

https://github.com/mne-tools/mne-bids-pipeline/blob/1bc1e8226d70b10b6d3a76d69d355cd906775b39/mne_bids_pipeline/steps/preprocessing/_07a_apply_ica.py#L102-L115

I think we should remove the `drop_bad()` call here. Any remaining large-amplitude artifacts after ICA cleaning will be caught in the following step, `preprocessing/ptp_reject`.

In an EEG dataset I'm currently working with, applying the PTP rejection threshold before applying ICA leads to a loss of many epochs that could have been saved through ICA

cc @agramfort 


Discussion:
Agreed @hoechenberger feel free to fix it

Resolving patch:
diff --git a/mne_bids_pipeline/_config.py b/mne_bids_pipeline/_config.py
--- a/mne_bids_pipeline/_config.py
+++ b/mne_bids_pipeline/_config.py
@@ -1235,13 +1235,20 @@
 # ~~~~~~~~~~~~~~~~~~~~~~
 ica_reject: Optional[Dict[str, float]] = None
 """
-Peak-to-peak amplitude limits to exclude epochs from ICA fitting.
-
-This allows you to remove strong transient artifacts, which could negatively
-affect ICA performance.
+Peak-to-peak amplitude limits to exclude epochs from ICA fitting. Epochs exceeding these
+limits will be excluded from ICA fitting. This allows you to remove strong transient
+artifacts, which could negatively affect ICA performance.
 
 This will also be applied to ECG and EOG epochs created during preprocessing.
 
+???+ info 
+    This setting is applied only to the epochs that are used for **fitting** ICA. The
+    goal is to make it easier for ICA to produce a good decomposition. After fitting,
+    ICA is applied to the epochs to be analyzed, usually with one or more components
+    removed (as to remove artifacts). But even after ICA cleaning, some epochs may still
+    contain large-amplitude artifacts. Those epochs can then be rejected by using
+    the [`reject`][mne_bids_pipeline._config.reject] parameter.
+
 The BIDS Pipeline will automatically try to detect EOG and ECG artifacts in
 your data, and remove them. For this to work properly, it is recommended
 to **not** specify rejection thresholds for EOG and ECG channels here –
diff --git a/mne_bids_pipeline/steps/preprocessing/_07a_apply_ica.py b/mne_bids_pipeline/steps/preprocessing/_07a_apply_ica.py
--- a/mne_bids_pipeline/steps/preprocessing/_07a_apply_ica.py
+++ b/mne_bids_pipeline/steps/preprocessing/_07a_apply_ica.py
@@ -28,7 +28,6 @@
 )
 from ..._logging import gen_log_kwargs, logger
 from ..._parallel import parallel_func, get_parallel_backend
-from ..._reject import _get_reject
 from ..._report import _open_report, _agg_backend
 from ..._run import failsafe_run, _update_for_splits, save_logs, _prep_out_files
 
@@ -93,21 +92,13 @@ def apply_ica(
     tsv_data = pd.read_csv(in_files.pop("components"), sep="\t")
     ica.exclude = tsv_data.loc[tsv_data["status"] == "bad", "component"].to_list()
 
-    # Load epochs to reject ICA components.
+    # Load epochs.
     msg = f'Input: {in_files["epochs"].basename}'
     logger.info(**gen_log_kwargs(message=msg))
     msg = f'Output: {out_files["epochs"].basename}'
     logger.info(**gen_log_kwargs(message=msg))
 
     epochs = mne.read_epochs(in_files.pop("epochs"), preload=True)
-    ica_reject = _get_reject(
-        subject=subject,
-        session=session,
-        reject=cfg.ica_reject,
-        ch_types=cfg.ch_types,
-        param="ica_reject",
-    )
-    epochs.drop_bad(ica_reject)
 
     # Now actually reject the components.
     msg = f'Rejecting ICs: {", ".join([str(ic) for ic in ica.exclude])}'


Test patch: