Skip to content

ENH: Add CAT12 interfaces #3310

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

Merged
merged 27 commits into from
Jun 1, 2021
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
533850b
Added cat12 interface.
mfmachado Mar 1, 2021
eb80b27
Updated .zenodo.json.
mfmachado Mar 1, 2021
f109163
Added interfaces to extract measures from the surface using CAT12.
mfmachado Mar 8, 2021
d8ca991
Improved documentation of the surface measures extraction.
mfmachado Mar 9, 2021
c906740
- Added examples to the segmentation.
mfmachado Mar 9, 2021
2885499
Improved documentation.
mfmachado Mar 9, 2021
98f118d
Added outputs to ExtractROIBasedSurfaceMeasures interface.
mfmachado Mar 9, 2021
7713f78
Added files to test the interfaces.
mfmachado Mar 9, 2021
44b8605
Added files to test the interfaces.
mfmachado Mar 9, 2021
1bed8ed
Minor fix
mfmachado Mar 9, 2021
d18eb1e
Merge remote-tracking branch 'origin/interface/cat12' into interface/…
mfmachado Mar 9, 2021
aa41da6
Bug fix in the output of ROIMeasures.
mfmachado Mar 15, 2021
dd152ee
Bug fix bias corrected file.
mfmachado Mar 31, 2021
ea72f5e
Update nipype/interfaces/cat12/preprocess.py
mfmachado Apr 1, 2021
89d3059
Added more variable descriptions.
mfmachado Apr 29, 2021
285eb05
Merge remote-tracking branch 'upstream/master' into interface/cat12
effigies Apr 30, 2021
04718ac
STY: black
effigies Apr 30, 2021
8a44b46
TEST: make specs
effigies Apr 30, 2021
e40fcb6
Correct TPM and dartel shooting template format;
mfmachado May 30, 2021
920b879
Added format utils.
mfmachado May 30, 2021
4f2511f
Merge branch 'master' into interface/cat12
mfmachado May 31, 2021
56a02f2
Reformatted preprocess.py.
mfmachado May 31, 2021
8a0ccae
Updated test_auto_CAT12Segment.py.
mfmachado May 31, 2021
cdcdba9
DOC: Add CAT12 to interfaces list
effigies May 31, 2021
20629a0
RF: Move format_utils to base
effigies Jun 1, 2021
fc4f82d
ENH: Add cat12 interfaces to __init__
effigies Jun 1, 2021
16f62fc
DOC: Minor cleanups
effigies Jun 1, 2021
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
5 changes: 5 additions & 0 deletions .zenodo.json
Original file line number Diff line number Diff line change
Expand Up @@ -807,6 +807,11 @@
"affiliation": "MIT, HMS",
"name": "Ghosh, Satrajit",
"orcid": "0000-0002-5312-6729"
},
{
"affiliation": "CIBIT, UC",
"name": "Machado, Fátima",
"orcid": "0000-0001-8878-1750"
}
],
"keywords": [
Expand Down
Empty file.
355 changes: 355 additions & 0 deletions nipype/interfaces/cat12/preprocess.py

Large diffs are not rendered by default.

224 changes: 224 additions & 0 deletions nipype/interfaces/cat12/surface.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
import os
from pathlib import Path

from traits.trait_base import _Undefined

from nipype.interfaces.base import File, InputMultiPath, TraitedSpec, traits
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have the isdefined function.

Suggested change
from traits.trait_base import _Undefined
from nipype.interfaces.base import File, InputMultiPath, TraitedSpec, traits
from nipype.interfaces.base import File, InputMultiPath, TraitedSpec, traits, isdefined

from nipype.interfaces.spm import SPMCommand
from nipype.interfaces.spm.base import SPMCommandInputSpec
from nipype.utils.filemanip import split_filename


class ExtractAdditionalSurfaceParametersInputSpec(SPMCommandInputSpec):
left_central_surfaces = InputMultiPath(File(exists=True), field="data_surf",
desc="Left and central surfaces files", mandatory=True, copyfile=False)
surface_files = InputMultiPath(File(exists=True),
desc="All surface files", mandatory=False, copyfile=False)

gyrification = traits.Bool(True, field="GI", usedefault=True,
desc="Extract gyrification index (GI) based on absolute mean curvature. The"
" method is described in Luders et al. Neuroimage, 29:1224-1230, 2006")
gmv = traits.Bool(True, field="gmv", usedefault=True, desc="Extract volume")
area = traits.Bool(True, field="area", usedefault=True, desc="Extract area surface")
depth = traits.Bool(False, field="SD", usedefault=True,
desc="Extract sulcus depth based on euclidian distance between the central "
"surface anf its convex hull.")
fractal_dimension = traits.Bool(False, field="FD", usedefault=True,
desc="Extract cortical complexity (fractal dimension) which is "
"described in Yotter ar al. Neuroimage, 56(3): 961-973, 2011")


class ExtractAdditionalSurfaceParametersOutputSpec(TraitedSpec):
lh_extracted_files = traits.List(File(exists=True), desc="Files of left Hemisphere extracted measures")
rh_extracted_files = traits.List(File(exists=True), desc="Files of right Hemisphere extracted measures")

lh_gyrification = traits.List(File(exists=True), desc="Gyrification of left Hemisphere")
rh_gyrification = traits.List(File(exists=True), desc="Gyrification of right Hemisphere")

lh_gmv = traits.List(File(exists=True), desc="Grey matter volume of left Hemisphere")
rh_gmv = traits.List(File(exists=True), desc="Grey matter volume of right Hemisphere")

lh_area = traits.List(File(exists=True), desc="Area of left Hemisphere")
rh_area = traits.List(File(exists=True), desc="Area of right Hemisphere")

lh_depth = traits.List(File(exists=True), desc="Depth of left Hemisphere")
rh_depth = traits.List(File(exists=True), desc="Depth of right Hemisphere")

lh_fractaldimension = traits.List(File(exists=True), desc="Fractal Dimension of left Hemisphere")
rh_fractaldimension = traits.List(File(exists=True), desc="Fractal Dimension of right Hemisphere")


class ExtractAdditionalSurfaceParameters(SPMCommand):
"""
Additional surface parameters can be extracted that can be used for statistical analysis, such as:

* Central surfaces
* Surface area
* Surface GM volume
* Gyrification Index
* Sulcus depth
* Toro's gyrification index
* Shaer's local gyrification index
* Laplacian gyrification indeces
* Addicional surfaces
* Measure normalization
* Lazy processing

http://www.neuro.uni-jena.de/cat12/CAT12-Manual.pdf#page=53

Examples
--------
>>> # Set the left surface files, both will be processed
>>> lh_path_central = 'lh.central.structural.gii'
>>> # Put here all surface files generated by CAT12 Segment, this is only required if the this approach is putted in
>>> surf_files = ['lh.sphere.reg.structural.gii', 'rh.sphere.reg.structural.gii', 'lh.sphere.structural.gii', 'rh.sphere.structural.gii', 'rh.central.structural.gii', 'lh.pbt.structural', 'rh.pbt.structural']
>>> extract_additional_measures = ExtractAdditionalSurfaceParameters(left_central_surfaces=lh_path_central, surface_files=surf_files)
>>> extract_additional_measures.run() # doctest: +SKIP

"""
input_spec = ExtractAdditionalSurfaceParametersInputSpec
output_spec = ExtractAdditionalSurfaceParametersOutputSpec

def __init__(self, **inputs):
_local_version = SPMCommand().version
if _local_version and "12." in _local_version:
self._jobtype = "tools"
self._jobname = "cat.stools.surfextract"

super().__init__(**inputs)

def _list_outputs(self):
outputs = self._outputs().get()

names_outputs = [(self.inputs.gyrification, 'gyrification'), (self.inputs.gmv, 'gmv'),
(self.inputs.area, 'area'), (self.inputs.depth, 'depth'),
(self.inputs.fractal_dimension, 'fractaldimension')]

for filename in self.inputs.left_central_surfaces:
pth, base, ext = split_filename(filename)
# The first part of the filename is rh.central or lh.central
original_filename = base.split(".", 2)[-1]
for i, (extracted_parameter, parameter_name) in enumerate(names_outputs):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused index i.

Suggested change
for i, (extracted_parameter, parameter_name) in enumerate(names_outputs):
for extracted_parameter, parameter_name in names_outputs:

if extracted_parameter:
for hemisphere in ["rh", "lh"]:
all_files_hemisphere = hemisphere + '_extracted_files'
name_hemisphere = hemisphere + "_" + parameter_name
if isinstance(outputs[name_hemisphere], _Undefined):
outputs[name_hemisphere] = []
if isinstance(outputs[all_files_hemisphere], _Undefined):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if isinstance(outputs[all_files_hemisphere], _Undefined):
if not isdefined(outputs[all_files_hemisphere]):

outputs[all_files_hemisphere] = []
generated_filename = ".".join([hemisphere, parameter_name, original_filename])
outputs[name_hemisphere].append(os.path.join(pth, generated_filename))

# Add all hemisphere files into one list, this is important because only the left hemisphere
# files are used as input in the Surface ROI Tools, fpr instance.
outputs[all_files_hemisphere].append(os.path.join(pth, generated_filename))

return outputs

def _format_arg(self, opt, spec, val):
if opt == "left_central_surfaces":
return Cell2Str(val)
return super(ExtractAdditionalSurfaceParameters, self)._format_arg(opt, spec, val)


class ExtractROIBasedSurfaceMeasuresInputSpec(SPMCommandInputSpec):
# Only these files are given as input, yet the right hemisphere (rh) files should also be on the processing
# directory.

surface_files = InputMultiPath(File(exists=True), desc="Surface data files. This variable should be a list "
"with all", mandatory=False, copyfile=False)
lh_roi_atlas = InputMultiPath(File(exists=True), field="rdata", desc="(Left) ROI Atlas. These are the ROI's ",
mandatory=True, copyfile=False)

rh_roi_atlas = InputMultiPath(File(exists=True), desc="(Right) ROI Atlas. These are the ROI's ",
mandatory=False, copyfile=False)

lh_surface_measure = InputMultiPath(File(exists=True), field="cdata", desc="(Left) Surface data files. ",
mandatory=True, copyfile=False)
rh_surface_measure = InputMultiPath(File(exists=True), desc="(Right) Surface data files.",
mandatory=False, copyfile=False)


class ExtractROIBasedSurfaceMeasuresOutputSpec(TraitedSpec):
label_files = traits.List(File(exists=True), desc="Files with the measures extracted for ROIs.")


class ExtractROIBasedSurfaceMeasures(SPMCommand):
"""
Extract ROI-based surface values
While ROI-based values for VBM (volume) data are automatically saved in the <em>label</em> folder as XML file it is
necessary to additionally extract these values for surface data (except for thickness which is automatically
extracted during segmentation). This has to be done after preprocessing the data and creating cortical surfaces.

You can extract ROI-based values for cortical thickness but also for any other surface parameter that was extracted
using the Extract Additional Surface Parameters such as volume, area, depth, gyrification and fractal dimension.


http://www.neuro.uni-jena.de/cat12/CAT12-Manual.pdf#page=53

Examples
--------
>>> # Template surface files
>>> lh_atlas = 'lh.aparc_a2009s.freesurfer.annot'
>>> rh_atlas = 'rh.aparc_a2009s.freesurfer.annot'
>>> surf_files = ['lh.sphere.reg.structural.gii', 'rh.sphere.reg.structural.gii', 'lh.sphere.structural.gii', 'rh.sphere.structural.gii', 'lh.central.structural.gii', 'rh.central.structural.gii', 'lh.pbt.structural', 'rh.pbt.structural']
>>> lh_measure = 'lh.area.structural'
>>> extract_additional_measures = ExtractROIBasedSurfaceMeasures(surface_files=surf_files, lh_surface_measure=lh_measure, lh_roi_atlas=lh_atlas, rh_roi_atlas=rh_atlas)
>>> extract_additional_measures.run() # doctest: +SKIP


"""

input_spec = ExtractROIBasedSurfaceMeasuresInputSpec
output_spec = ExtractROIBasedSurfaceMeasuresOutputSpec

def __init__(self, **inputs):
_local_version = SPMCommand().version
if _local_version and "12." in _local_version:
self._jobtype = "tools"
self._jobname = "cat.stools.surf2roi"

SPMCommand.__init__(self, **inputs)

def _format_arg(self, opt, spec, val):
if opt == "lh_surface_measure":
return NestedCell(val)
elif opt == "lh_roi_atlas":
return Cell2Str(val)

return super(ExtractROIBasedSurfaceMeasures, self)._format_arg(opt, spec, val)

def _list_outputs(self):
outputs = self._outputs().get()

pth, base, ext = split_filename(self.inputs.lh_surface_measure[0])

outputs["label_files"] = [str(label) for label in Path(pth).glob("label/*") if label.is_file()]
return outputs


class Cell:
def __init__(self, arg):
self.arg = arg

def to_string(self):
if isinstance(self.arg, list):
v = '\n'.join([f"'{el}'" for el in self.arg])
else:
v = self.arg
return v


class NestedCell(Cell):

def __str__(self):
return "{{%s}}" % self.to_string()


class Cell2Str(Cell):

def __str__(self):
"""Convert input to appropriate format for cat12
"""
return "{%s}" % self.to_string()
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.