Skip to content

Commit 37ad1c6

Browse files
authored
Merge pull request #358 from nipreps/enh/msm
ENH: Add Multimodal Surface Matching
2 parents a45a8ea + 536cfb4 commit 37ad1c6

19 files changed

+561
-14
lines changed

.circleci/ds005_run.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ docker run -it -e FMRIPREP_DEV=1 -u $(id -u) \
1717
--skull-strip-template MNI152NLin2009cAsym:res-2 \
1818
--sloppy --mem-gb 4 \
1919
--ncpus 2 --omp-nthreads 2 -vv \
20+
--no-msm \
2021
--fs-license-file /tmp/fslicense/license.txt \
2122
--fs-subjects-dir /tmp/ds005/freesurfer \
2223
${@:1}

.circleci/ds054_run.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,6 @@ docker run --rm -it -e FMRIPREP_DEV=1 -u $(id -u) \
1717
--skull-strip-template OASIS30ANTs:res-1 \
1818
--output-spaces MNI152Lin MNI152NLin2009cAsym:res-2:res-native \
1919
--mem-gb 4 --ncpus 2 --omp-nthreads 2 -vv \
20+
--no-msm \
2021
--fs-license-file /tmp/fslicense/license.txt \
2122
${@:1}

Dockerfile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,10 @@ ENV LANG="C.UTF-8" \
228228
FSLREMOTECALL="" \
229229
FSLGECUDAQ="cuda.q"
230230

231+
# MSM
232+
RUN curl -L -H "Accept: application/octet-stream" https://api.github.com/repos/ecr05/MSM_HOCR/releases/assets/16253707 -o /usr/local/bin/msm \
233+
&& chmod +x /usr/local/bin/msm
234+
231235
# Unless otherwise specified each process should only use one thread - nipype
232236
# will handle parallelization
233237
ENV MKL_NUM_THREADS=1 \

scripts/fetch_templates.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,14 @@ def fetch_fsaverage():
7979
tpl-fsaverage/tpl-fsaverage_hemi-R_den-164k_desc-std_sphere.surf.gii
8080
tpl-fsaverage/tpl-fsaverage_hemi-L_den-164k_desc-vaavg_midthickness.shape.gii
8181
tpl-fsaverage/tpl-fsaverage_hemi-R_den-164k_desc-vaavg_midthickness.shape.gii
82+
tpl-fsaverage/tpl-fsaverage_hemi-L_den-164k_sulc.shape.gii
83+
tpl-fsaverage/tpl-fsaverage_hemi-R_den-164k_sulc.shape.gii
8284
"""
8385
template = "fsaverage"
8486

8587
tf.get(template, density="164k", suffix="dseg", extension=".tsv")
88+
tf.get(template, density='164k', desc='std', suffix='sphere', extension='.surf.gii')
89+
tf.get(template, density='164k', suffix='sulc', extension='.shape.gii')
8690

8791

8892
def fetch_all():

smriprep/__about__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@
2424
Base module variables
2525
"""
2626

27-
from ._version import __version__
27+
try:
28+
from ._version import __version__
29+
except ImportError: # pragma: no cover
30+
__version__ = "0+unknown"
2831

2932
__copyright__ = "Copyright 2019, Center for Reproducible Neuroscience, Stanford University"
3033
__credits__ = [

smriprep/cli/run.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,12 @@ def get_parser():
214214
dest="hires",
215215
help="disable sub-millimeter (hires) reconstruction",
216216
)
217+
g_surfs.add_argument(
218+
"--no-msm",
219+
action="store_false",
220+
dest="msm_sulc",
221+
help="Disable Multimodal Surface Matching surface registration."
222+
)
217223
g_surfs_xor = g_surfs.add_mutually_exclusive_group()
218224

219225
g_surfs_xor.add_argument(
@@ -567,6 +573,7 @@ def build_workflow(opts, retval):
567573
layout=layout,
568574
longitudinal=opts.longitudinal,
569575
low_mem=opts.low_mem,
576+
msm_sulc=opts.msm_sulc,
570577
omp_nthreads=omp_nthreads,
571578
output_dir=str(output_dir),
572579
run_uuid=run_uuid,

smriprep/conftest.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,16 @@
11
import os
22

3+
import pytest
4+
import numpy
5+
6+
from smriprep.data import load_resource
7+
38
os.environ['NO_ET'] = '1'
9+
10+
11+
@pytest.fixture(autouse=True)
12+
def populate_namespace(doctest_namespace, tmp_path):
13+
doctest_namespace['os'] = os
14+
doctest_namespace['np'] = numpy
15+
doctest_namespace['load'] = load_resource
16+
doctest_namespace['testdir'] = tmp_path

smriprep/data/boilerplate.bib

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,3 +322,15 @@ @article{posse_t2s
322322
volume = 42,
323323
year = 1999
324324
}
325+
326+
@article{msm,
327+
author = {Emma C. Robinson and Saad Jbabdi and Matthew F. Glasser and Jesper Andersson and Gregory C. Burgess and Michael P. Harms and Stephen M. Smith and David C. Van Essen and Mark Jenkinson},
328+
doi = {10.1016/j.neuroimage.2014.05.069},
329+
year = 2014,
330+
month = {oct},
331+
volume = {100},
332+
pages = {414-426},
333+
title = {{MSM}: A new flexible framework for Multimodal Surface Matching},
334+
url = {https://doi.org/10.1016/j.neuroimage.2014.05.069},
335+
journal = {NeuroImage}
336+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
--simval=3,2,2,2
2+
--sigma_in=0,0,0,0
3+
--sigma_ref=0,0,0,0
4+
--lambda=0,10,7.5,7.5
5+
--it=50,10,15,15
6+
--opt=AFFINE,DISCRETE,DISCRETE,DISCRETE
7+
--CPgrid=6,2,3,4
8+
--SGgrid=6,4,5,6
9+
--datagrid=6,4,5,6
10+
--regoption=3
11+
--regexp=2
12+
--dopt=HOCR
13+
--VN
14+
--rescaleL
15+
--triclique
16+
--k_exponent=2
17+
--bulkmod=1.6
18+
--shearmod=0.4

smriprep/interfaces/msm.py

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
from pathlib import Path
2+
3+
from nipype.interfaces.base import (
4+
CommandLine,
5+
CommandLineInputSpec,
6+
File,
7+
TraitedSpec,
8+
traits,
9+
)
10+
11+
12+
class MSMInputSpec(CommandLineInputSpec):
13+
in_mesh = File(
14+
exists=True,
15+
mandatory=True,
16+
argstr="--inmesh=%s",
17+
desc="input mesh (available formats: VTK, ASCII, GIFTI). Needs to be a sphere",
18+
)
19+
out_base = File(
20+
name_source=["in_mesh"],
21+
name_template="%s_msm",
22+
argstr="--out=%s",
23+
desc="output basename",
24+
)
25+
reference_mesh = File(
26+
exists=True,
27+
argstr="--refmesh=%s",
28+
desc="reference mesh (available formats: VTK, ASCII, GIFTI). Needs to be a sphere."
29+
"If not included algorithm assumes reference mesh is equivalent input",
30+
)
31+
in_data = File(
32+
exists=True,
33+
argstr="--indata=%s",
34+
desc="scalar or multivariate data for input - can be ASCII (.asc,.dpv,.txt) "
35+
"or GIFTI (.func.gii or .shape.gii)",
36+
)
37+
reference_data = File(
38+
exists=True,
39+
argstr="--refdata=%s",
40+
desc="scalar or multivariate data for reference - can be ASCII (.asc,.dpv,.txt) "
41+
"or GIFTI (.func.gii or .shape.gii)",
42+
)
43+
transformed_mesh = File(
44+
exists=True,
45+
argstr="--trans=%s",
46+
desc="Transformed source mesh (output of a previous registration). "
47+
"Use this to initiliase the current registration.",
48+
)
49+
in_register = File(
50+
exists=True,
51+
argstr="--in_register=%s",
52+
desc="Input mesh at data resolution. Used to resample data onto input mesh if data "
53+
"is supplied at a different resolution. Note this mesh HAS to be in alignment with "
54+
"either the input_mesh of (if supplied) the transformed source mesh. "
55+
"Use with supreme caution.",
56+
)
57+
in_weight = File(
58+
exists=True,
59+
argstr="--inweight=%s",
60+
desc="cost function weighting for input - weights data in these vertices when calculating "
61+
"similarity (ASCII or GIFTI). Can be multivariate provided dimension equals that of data",
62+
)
63+
reference_weight = File(
64+
exists=True,
65+
argstr="--refweight=%s",
66+
desc="cost function weighting for reference - weights data in these vertices when "
67+
"calculating similarity (ASCII or GIFTI). Can be multivariate provided dimension "
68+
"equals that of data",
69+
)
70+
output_format = traits.Enum(
71+
"GIFTI",
72+
"VTK",
73+
"ASCII",
74+
"ASCII_MAT",
75+
argstr="--format=%s",
76+
desc="format of output files",
77+
)
78+
config_file = File(
79+
exists=True,
80+
argstr="--conf=%s",
81+
desc="configuration file",
82+
)
83+
levels = traits.Int(
84+
argstr="--levels=%d",
85+
desc="number of resolution levels (default = number of resolution levels specified "
86+
"by --opt in config file)",
87+
)
88+
smooth_output_sigma = traits.Int(
89+
argstr="--smoothout=%d",
90+
desc="smooth tranformed output with this sigma (default=0)",
91+
)
92+
verbose = traits.Bool(
93+
argstr="--verbose",
94+
desc="switch on diagnostic messages",
95+
)
96+
97+
98+
class MSMOutputSpec(TraitedSpec):
99+
warped_mesh = File(
100+
desc="the warped input mesh (i.e., new vertex locations - this capture the warp field, "
101+
"much like a _warp.nii.gz file would for volumetric warps created by FNIRT)."
102+
)
103+
downsampled_warped_mesh = File(
104+
desc="a downsampled version of the warped_mesh where the resolution of this mesh will "
105+
"be equivalent to the resolution of the final datamesh"
106+
)
107+
warped_data = File(
108+
desc="the input data passed through the MSM warp and projected onto the target surface"
109+
)
110+
111+
112+
class MSM(CommandLine):
113+
"""
114+
MSM (Multimodal Surface Matching) is a tool for registering cortical surfaces.
115+
The tool has been developed and tested using FreeSurfer extracted surfaces.
116+
However, in principle the tool with work with any cortical surface extraction method provided
117+
the surfaces can be mapped to the sphere.
118+
The key advantage of the method is that alignment may be driven using a wide variety of
119+
univariate (sulcal depth, curvature, myelin), multivariate (Task fMRI, or Resting State
120+
Networks) or multimodal (combinations of folding, myelin and fMRI) feature sets.
121+
122+
The main MSM tool is currently run from the command line using the program ``msm``.
123+
This enables fast alignment of spherical cortical surfaces by utilising a fast discrete
124+
optimisation framework (FastPD Komodakis 2007), which significantly reduces the search
125+
space of possible deformations for each vertex, and allows flexibility with regards to the
126+
choice of similarity metric used to match the images.
127+
128+
>>> msm = MSM(
129+
... config_file=load('msm/MSMSulcStrainFinalconf'),
130+
... in_mesh='sub-01_hemi-L_sphere.surf.gii',
131+
... reference_mesh='tpl-fsaverage_hemi-L_den-164k_desc-std_sphere.surf.gii',
132+
... in_data='sub-01_hemi-L_sulc.shape.gii',
133+
... reference_data='tpl-fsaverage_hemi-L_den-164k_sulc.shape.gii',
134+
... out_base='L.',
135+
... )
136+
>>> msm.cmdline # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
137+
'msm --conf=.../MSMSulcStrainFinalconf \
138+
--indata=sub-01_hemi-L_sulc.shape.gii \
139+
--inmesh=sub-01_hemi-L_sphere.surf.gii \
140+
--out=L. \
141+
--refdata=tpl-fsaverage_hemi-L_den-164k_sulc.shape.gii \
142+
--refmesh=tpl-fsaverage_hemi-L_den-164k_desc-std_sphere.surf.gii'
143+
144+
"""
145+
146+
input_spec = MSMInputSpec
147+
output_spec = MSMOutputSpec
148+
_cmd = "msm"
149+
150+
def _list_outputs(self):
151+
from nipype.utils.filemanip import split_filename
152+
153+
outputs = self._outputs().get()
154+
out_base = self.inputs.out_base or split_filename(self.inputs.in_mesh)[1]
155+
cwd = Path.cwd()
156+
outputs['warped_mesh'] = str(cwd / (out_base + 'sphere.reg.surf.gii'))
157+
outputs['downsampled_warped_mesh'] = str(cwd / (out_base + 'sphere.LR.reg.surf.gii'))
158+
outputs['warped_data'] = str(cwd / (out_base + 'transformed_and_reprojected.func.gii'))
159+
return outputs

0 commit comments

Comments
 (0)