Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FIX: Invert sulcal depth metric before passing to MSM, use HCP atlas files #383

Merged
merged 5 commits into from
Nov 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
smriprep/_version.py export-subst
*.gii binary
63 changes: 63 additions & 0 deletions smriprep/data/atlases/L.refsulc.164k_fs_LR.shape.gii

Large diffs are not rendered by default.

63 changes: 63 additions & 0 deletions smriprep/data/atlases/R.refsulc.164k_fs_LR.shape.gii

Large diffs are not rendered by default.

128 changes: 128 additions & 0 deletions smriprep/data/atlases/fsaverage.L_LR.spherical_std.164k_fs_LR.surf.gii

Large diffs are not rendered by default.

123 changes: 123 additions & 0 deletions smriprep/data/atlases/fsaverage.R_LR.spherical_std.164k_fs_LR.surf.gii

Large diffs are not rendered by default.

71 changes: 71 additions & 0 deletions smriprep/interfaces/gifti.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
"""Interfaces for manipulating GIFTI files."""
import os

import nibabel as nb
from nipype.interfaces.base import File, SimpleInterface, TraitedSpec, isdefined, traits


class InvertShapeInputSpec(TraitedSpec):
subject_id = traits.Str(desc='subject ID')
hemisphere = traits.Enum(
"L",
"R",
mandatory=True,
desc='hemisphere',
)
shape = traits.Str(desc='name of shape to invert')
shape_file = File(exists=True, mandatory=True, desc='input GIFTI file')


class InvertShapeOutputSpec(TraitedSpec):
shape_file = File(desc='output GIFTI file')


class InvertShape(SimpleInterface):
"""Prepare GIFTI shape file for use in MSMSulc

This interface mirrors the action of the following portion
of FreeSurfer2CaretConvertAndRegisterNonlinear.sh::

wb_command -set-structure ${shape_file} CORTEX_[LEFT|RIGHT]
wb_command -metric-math "var * -1" ${shape_file} -var var ${shape_file}
wb_command -set-map-names ${shape_file} -map 1 ${subject}_[L|R]_${shape}

We do not add palette information to the output file.
"""

input_spec = InvertShapeInputSpec
output_spec = InvertShapeOutputSpec

def _run_interface(self, runtime):
subject, hemi, shape = self.inputs.subject_id, self.inputs.hemisphere, self.inputs.shape

Check warning on line 41 in smriprep/interfaces/gifti.py

View check run for this annotation

Codecov / codecov/patch

smriprep/interfaces/gifti.py#L41

Added line #L41 was not covered by tests
if not isdefined(subject):
subject = 'sub-XYZ'

Check warning on line 43 in smriprep/interfaces/gifti.py

View check run for this annotation

Codecov / codecov/patch

smriprep/interfaces/gifti.py#L43

Added line #L43 was not covered by tests

img = nb.GiftiImage.from_filename(self.inputs.shape_file)

Check warning on line 45 in smriprep/interfaces/gifti.py

View check run for this annotation

Codecov / codecov/patch

smriprep/interfaces/gifti.py#L45

Added line #L45 was not covered by tests
# wb_command -set-structure
img.meta["AnatomicalStructurePrimary"] = {'L': 'CortexLeft', 'R': 'CortexRight'}[hemi]
darray = img.darrays[0]

Check warning on line 48 in smriprep/interfaces/gifti.py

View check run for this annotation

Codecov / codecov/patch

smriprep/interfaces/gifti.py#L47-L48

Added lines #L47 - L48 were not covered by tests
# wb_command -set-map-names
meta = darray.meta
meta['Name'] = f"{subject}_{hemi}_{shape}"

Check warning on line 51 in smriprep/interfaces/gifti.py

View check run for this annotation

Codecov / codecov/patch

smriprep/interfaces/gifti.py#L50-L51

Added lines #L50 - L51 were not covered by tests

# wb_command -metric-math "var * -1"
inv = -darray.data

Check warning on line 54 in smriprep/interfaces/gifti.py

View check run for this annotation

Codecov / codecov/patch

smriprep/interfaces/gifti.py#L54

Added line #L54 was not covered by tests

darray = nb.gifti.GiftiDataArray(

Check warning on line 56 in smriprep/interfaces/gifti.py

View check run for this annotation

Codecov / codecov/patch

smriprep/interfaces/gifti.py#L56

Added line #L56 was not covered by tests
inv,
intent=darray.intent,
datatype=darray.datatype,
encoding=darray.encoding,
endian=darray.endian,
coordsys=darray.coordsys,
ordering=darray.ind_ord,
meta=meta,
)
img.darrays[0] = darray

Check warning on line 66 in smriprep/interfaces/gifti.py

View check run for this annotation

Codecov / codecov/patch

smriprep/interfaces/gifti.py#L66

Added line #L66 was not covered by tests

out_filename = os.path.join(runtime.cwd, f"{subject}.{hemi}.{shape}.native.shape.gii")
img.to_filename(out_filename)
self._results["shape_file"] = out_filename
return runtime

Check warning on line 71 in smriprep/interfaces/gifti.py

View check run for this annotation

Codecov / codecov/patch

smriprep/interfaces/gifti.py#L68-L71

Added lines #L68 - L71 were not covered by tests
50 changes: 21 additions & 29 deletions smriprep/workflows/surfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@
RefineBrainMask,
)
from niworkflows.interfaces.nitransforms import ConcatenateXFMs
import templateflow.api as tf
from ..interfaces.workbench import CreateSignedDistanceVolume


Expand Down Expand Up @@ -746,6 +745,7 @@

def init_msm_sulc_wf(*, sloppy: bool = False, name: str = 'msm_sulc_wf'):
"""Run MSMSulc registration to fsLR surfaces, per hemisphere."""
from ..interfaces.gifti import InvertShape

Check warning on line 748 in smriprep/workflows/surfaces.py

View check run for this annotation

Codecov / codecov/patch

smriprep/workflows/surfaces.py#L748

Added line #L748 was not covered by tests
from ..interfaces.msm import MSM
from ..interfaces.workbench import (
SurfaceAffineRegression,
Expand Down Expand Up @@ -789,13 +789,23 @@
name='modify_sphere',
)

# 2) Run MSMSulc
# 2) Invert sulc
# wb_command -metric-math "-1 * var" ...
invert_sulc = pe.MapNode(

Check warning on line 794 in smriprep/workflows/surfaces.py

View check run for this annotation

Codecov / codecov/patch

smriprep/workflows/surfaces.py#L794

Added line #L794 was not covered by tests
InvertShape(shape='sulc'),
iterfield=['shape_file', 'hemisphere'],
name='invert_sulc',
)
invert_sulc.inputs.hemisphere = ['L', 'R']

Check warning on line 799 in smriprep/workflows/surfaces.py

View check run for this annotation

Codecov / codecov/patch

smriprep/workflows/surfaces.py#L799

Added line #L799 was not covered by tests

# 3) Run MSMSulc
# ./msm_centos_v3 --conf=MSMSulcStrainFinalconf \
# --inmesh=${SUB}.${HEMI}.sphere_rot.native.surf.gii
# --refmesh=fsaverage.${HEMI}_LR.spherical_std.164k_fs_LR.surf.gii
# --indata=sub-${SUB}_ses-${SES}_hemi-${HEMI)_sulc.shape.gii \
# --refdata=tpl-fsaverage_hemi-${HEMI}_den-164k_sulc.shape.gii \
# --out=${HEMI}. --verbose
atlases = load_resource('atlases')

Check warning on line 808 in smriprep/workflows/surfaces.py

View check run for this annotation

Codecov / codecov/patch

smriprep/workflows/surfaces.py#L808

Added line #L808 was not covered by tests
msm_conf = load_resource(f'msm/MSMSulcStrain{"Sloppy" if sloppy else "Final"}conf')
msmsulc = pe.MapNode(
MSM(verbose=True, config_file=msm_conf),
Expand All @@ -805,42 +815,24 @@
)
msmsulc.inputs.out_base = ['lh.', 'rh.'] # To placate Path2BIDS
msmsulc.inputs.reference_mesh = [
str(
tf.get(
'fsaverage',
hemi=hemi,
density='164k',
desc='std',
suffix='sphere',
extension='.surf.gii',
)
)
for hemi in 'LR'
str(atlases / f'fsaverage.{hemi}_LR.spherical_std.164k_fs_LR.surf.gii') for hemi in 'LR'
]
msmsulc.inputs.reference_data = [
str(
tf.get(
'fsaverage',
hemi=hemi,
density='164k',
suffix='sulc',
extension='.shape.gii',
)
)
for hemi in 'LR'
str(atlases / f'{hemi}.refsulc.164k_fs_LR.shape.gii') for hemi in 'LR'
]
# fmt:off
workflow.connect([
(inputnode, regress_affine, [('sphere', 'in_surface'),
('sphere_reg_fsLR', 'target_surface')]),
(inputnode, regress_affine, [
('sphere', 'in_surface'),
('sphere_reg_fsLR', 'target_surface'),
]),
(inputnode, apply_surface_affine, [('sphere', 'in_surface')]),
(inputnode, invert_sulc, [('sulc', 'shape_file')]),
(regress_affine, apply_surface_affine, [('out_affine', 'in_affine')]),
(apply_surface_affine, modify_sphere, [('out_surface', 'in_surface')]),
(inputnode, msmsulc, [('sulc', 'in_data')]),
(modify_sphere, msmsulc, [('out_surface', 'in_mesh')]),
(invert_sulc, msmsulc, [('shape_file', 'in_data')]),
(msmsulc, outputnode, [('warped_mesh', 'sphere_reg_fsLR')]),
])
# fmt:on
]) # fmt:skip
return workflow


Expand Down
Loading