Repository: mne-tools/mne-bids-pipeline

Issue:
Generate head surfaces in our FreeSurfer step to save time when we're later running the pipeline
Currently we generate the surfaces during the BEM step if they don't exist; but it would make sense to already produce them as part of the FS step. Also will make it easier to run coreg before running the pipeline.


Discussion:
Why not but I would allow to compute the bem surfaces for already available recons. So it would be 2 scripts in the anatomy batch of steps
Oh I was only referring to `mne.bem.make_scalp_surfaces()` here, which we currently run in `01-make_bem_surfaces.py` if the surfaces cannot be found. 
yes but it's still something you may want to do from an existing freesurfer
recon.

>

> yes but it's still something you may want to do from an existing freesurfer recon.

Yes, this is already implemented in `main`! We simply do it "on demand" during the source processing steps. Are you suggesting that we should have a second anatomy script that does this (and only this)? Might be a bit overkill but I can have a look

Resolving patch:
diff --git a/run.py b/run.py
--- a/run.py
+++ b/run.py
@@ -49,7 +49,10 @@
 
 REPORT_SCRIPTS = ('01-make_reports.py',)
 
-FREESURFER_SCRIPTS = ('recon_all.py',)
+FREESURFER_SCRIPTS = (
+    '01-recon_all.py',
+    '02-coreg_surfaces.py'
+)
 
 SCRIPT_BASE_DIR = pathlib.Path(__file__).parent / 'scripts'
 
@@ -214,7 +217,7 @@ def process(config: PathLike,
         script_paths = [*SCRIPT_PATHS['init'], *script_paths]
 
     for script_path in script_paths:
-        step_name = script_path.name.replace('.py', '')[3:]
+        step_name = f'{script_path.parent.name}/{script_path.name}'
         logger.info(f'Now running: {step_name}')
         _run_script(script_path, config, root_dir, subject, session, task, run,
                     n_jobs)
diff --git a/scripts/freesurfer/recon_all.py b/scripts/freesurfer/01-recon_all.py
similarity index 100%
rename from scripts/freesurfer/recon_all.py
rename to scripts/freesurfer/01-recon_all.py
diff --git a/scripts/freesurfer/02-coreg_surfaces.py b/scripts/freesurfer/02-coreg_surfaces.py
new file mode 100644
--- /dev/null
+++ b/scripts/freesurfer/02-coreg_surfaces.py
@@ -0,0 +1,62 @@
+#!/usr/bin/env python
+
+from pathlib import Path
+import logging
+from typing import Union
+
+import mne.bem
+from mne.utils import BunchConst
+from mne.parallel import parallel_func
+
+import config
+
+PathLike = Union[str, Path]
+logger = logging.getLogger('mne-bids-pipeline')
+fs_bids_app = Path(__file__).parent / 'contrib' / 'run.py'
+
+
+def make_coreg_surfaces(
+    cfg: BunchConst,
+    subject: str
+) -> None:
+    """Create head surfaces for use with MNE-Python coregistration tools."""
+    fs_subject = config.get_fs_subject(subject)
+    subject_str = f'sub-{subject}' if subject != 'fsaverage' else 'fsaverage'
+    logger.info(
+        f'Creating scalp surfaces for coregistration, '
+        f'subject: {subject_str} (FreeSurfer subject: {fs_subject})'
+    )
+
+    mne.bem.make_scalp_surfaces(
+        subject=fs_subject,
+        subjects_dir=cfg.subjects_dir,
+        force=True,
+        overwrite=True
+    )
+
+
+def get_config() -> BunchConst:
+    cfg = BunchConst(
+        subjects_dir=config.get_fs_subjects_dir()
+    )
+    return cfg
+
+
+def main():
+    # Ensure we're also processing fsaverage if present
+    subjects = config.get_subjects()
+    if (Path(config.get_fs_subjects_dir()) / 'fsaverage').exists():
+        subjects.append('fsaverage')
+
+    parallel, run_func, _ = parallel_func(make_coreg_surfaces,
+                                          n_jobs=config.get_n_jobs())
+
+    parallel(
+        run_func(
+            get_config(), subject
+        ) for subject in subjects
+    )
+
+
+if __name__ == '__main__':
+    main()


Test patch:
diff --git a/tests/configs/config_ds000248_coreg_surfaces.py b/tests/configs/config_ds000248_coreg_surfaces.py
new file mode 100644
--- /dev/null
+++ b/tests/configs/config_ds000248_coreg_surfaces.py
@@ -0,0 +1,9 @@
+"""
+MNE Sample Data: Head surfaces from FreeSurfer surfaces for coregistration step
+"""
+
+study_name = 'ds000248'
+bids_root = '~/mne_data/ds000248_coreg_surfaces'
+subjects = ['01']
+conditions = ['Auditory']
+ch_types = ['meg']
diff --git a/tests/datasets.py b/tests/datasets.py
--- a/tests/datasets.py
+++ b/tests/datasets.py
@@ -117,6 +117,19 @@ class DATASET_OPTIONS_T(TypedDict):
             'derivatives/freesurfer/subjects/sub-01/mri/aparc.a2009s+aseg.mgz'
         ],
     },
+    'ds000248_coreg_surfaces': {
+        'git': '',
+        'openneuro': 'ds000248',
+        'osf': '',
+        'web': '',
+        'include': ['derivatives/freesurfer/subjects/sub-01'],
+        'exclude': [
+            'derivatives/freesurfer/subjects/sub-01/mri/aparc+aseg.mgz',
+            'derivatives/freesurfer/subjects/sub-01/mri/aparc.DKTatlas+aseg.mgz',  # noqa: E501
+            'derivatives/freesurfer/subjects/sub-01/mri/aparc.DKTatlas+aseg.mgz',  # noqa: E501
+            'derivatives/freesurfer/subjects/sub-01/mri/aparc.a2009s+aseg.mgz'
+        ],
+    },
     'ds000248_no_mri': {
         'git': '',
         'openneuro': 'ds000248',
diff --git a/tests/run_tests.py b/tests/run_tests.py
--- a/tests/run_tests.py
+++ b/tests/run_tests.py
@@ -87,6 +87,13 @@ class TestOptionsT(TypedDict):
         'task': None,
         'env': {}
     },
+    'ds000248_coreg_surfaces': {
+        'dataset': 'ds000248_coreg_surfaces',
+        'config': 'config_ds000248_coreg_surfaces.py',
+        'steps': ('freesurfer/coreg_surfaces',),
+        'task': None,
+        'env': {}
+    },
     'ds000248_no_mri': {
         'dataset': 'ds000248_no_mri',
         'config': 'config_ds000248_no_mri.py',