Repository: mne-tools/mne-bids-pipeline

Issue:
Allow specification of custom channels for EOG / ECG detection
@dnacombo has some HD-EEG data without a dedicated EOG channel, but there are channels close to the eyes that most certainly captured EOG artifacts (we haven't looked yet, but I would be surprised if that wasn't the case). Anyway, we'll need a config option to specify those channels so the SSP & ICA artifact detection algorithms can make use of them. Same for ECG.


Discussion:

Resolving patch:
diff --git a/config.py b/config.py
--- a/config.py
+++ b/config.py
@@ -213,6 +213,41 @@
     ```
 """
 
+eog_channels: Optional[Iterable[str]] = None
+"""
+Specify EOG channel to use, or create virtual EOG channels.
+
+Allows the specification of custom channel names that shall be used as
+(virtual) EOG channels. For example, say you recorded EEG **without** dedicated
+EOG electrodes, but with some EEG electrodes placed close to the eyes, e.g.
+Fp1 and Fp2. These channels would be expected to have captured large quantities
+of ocular activity, and you might want to use them as "virtual" EOG channels,
+while also including them in the EEG analysis. By default, MNE won't know that
+these channels are suitable for recovering EOG, and hence won't be able to
+perform tasks like automated blink removal, unless a "true" EOG sensor is
+present in the data as well. Speficying channel names here allows MNE to find
+the respective EOG signals.
+
+You can specify one or multiple channel names. Each will be treated as if it
+were a dedicated EOG channel, without excluding it from any other analyses.
+
+If ``None``, only "true" EOG channels will be used for EOG recovery.
+
+If there are multiple true EOG channels in your data, and you only specify
+a subset of them here, only this subset will be used during processing.
+
+???+ example "Example"
+    Treat ``Fp1`` as virtual EOG channel:
+    ```python
+    eog_channels = ['Fp1']
+    ```
+
+    Treat ``Fp1`` and ``Fp2`` as virtual EOG channels:
+    ```python
+    eog_channels = ['Fp1', 'Fp2']
+    ```
+"""
+
 Bipolar_T = Tuple[str, str, str]
 eeg_bipolar_channels: Iterable[Bipolar_T] = []
 """
diff --git a/scripts/preprocessing/04a-run_ica.py b/scripts/preprocessing/04a-run_ica.py
--- a/scripts/preprocessing/04a-run_ica.py
+++ b/scripts/preprocessing/04a-run_ica.py
@@ -199,13 +199,20 @@ def detect_ecg_artifacts(ica, raw, subject, session, report):
 def detect_eog_artifacts(ica, raw, subject, session, report):
     pick_eog = mne.pick_types(raw.info, meg=False, eeg=False, ecg=False,
                               eog=True)
-    if pick_eog.any():
+    if config.eog_channels:
+        assert all([ch_name in raw.ch_names
+                    for ch_name in config.eog_channels])
+        ch_name = ','.join(config.eog_channels)
+    else:
+        ch_name = None
+
+    if pick_eog.any() or config.eog_channels:
         msg = 'Performing automated EOG artifact detection …'
         logger.info(gen_log_message(message=msg, step=4, subject=subject,
                                     session=session))
 
         # Do not reject epochs based on amplitude.
-        eog_epochs = create_eog_epochs(raw, reject=None,
+        eog_epochs = create_eog_epochs(raw, ch_name=ch_name, reject=None,
                                        baseline=(None, -0.2),
                                        tmin=-0.5, tmax=0.5)
         eog_evoked = eog_epochs.average()
diff --git a/scripts/preprocessing/04b-run_ssp.py b/scripts/preprocessing/04b-run_ssp.py
--- a/scripts/preprocessing/04b-run_ssp.py
+++ b/scripts/preprocessing/04b-run_ssp.py
@@ -60,8 +60,16 @@ def run_ssp(subject, session=None):
     msg = 'Computing SSPs for EOG'
     logger.debug(gen_log_message(message=msg, step=4, subject=subject,
                                  session=session))
-    eog_projs, eog_events = compute_proj_eog(raw, n_grad=1, n_mag=1,
-                                             n_eeg=1, average=True)
+    if config.eog_channels:
+        assert all([ch_name in raw.ch_names
+                    for ch_name in config.eog_channels])
+        ch_name = ','.join(config.eog_channels)
+    else:
+        ch_name = None
+
+    eog_projs, eog_events = compute_proj_eog(raw, ch_name=ch_name,
+                                             n_grad=1, n_mag=1, n_eeg=1,
+                                             average=True)
 
     mne.write_proj(proj_fname_out, eog_projs + ecg_projs)
 


Test patch: