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

ENH: Add CAT12 interfaces #3310

merged 27 commits into from
Jun 1, 2021

Conversation

mfmachado
Copy link
Contributor

@mfmachado mfmachado commented Mar 9, 2021

Summary

Fixes #2783 .

List of changes proposed in this PR (pull-request)

Acknowledgment

  • (Mandatory) I acknowledge that this contribution will be available under the Apache 2 license.

@codecov
Copy link

codecov bot commented Mar 9, 2021

Codecov Report

Merging #3310 (16f62fc) into master (12d06fd) will increase coverage by 0.03%.
The diff coverage is 70.90%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #3310      +/-   ##
==========================================
+ Coverage   65.01%   65.05%   +0.03%     
==========================================
  Files         302      306       +4     
  Lines       40035    40279     +244     
  Branches     5291     5320      +29     
==========================================
+ Hits        26029    26202     +173     
- Misses      12935    13006      +71     
  Partials     1071     1071              
Flag Coverage Δ
unittests 64.82% <69.67%> (+0.02%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
nipype/interfaces/cat12/base.py 45.45% <45.45%> (ø)
nipype/interfaces/cat12/surface.py 65.93% <65.93%> (ø)
nipype/interfaces/cat12/preprocess.py 75.71% <75.71%> (ø)
nipype/interfaces/cat12/__init__.py 100.00% <100.00%> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 12d06fd...16f62fc. Read the comment docs.

@mfmachado mfmachado changed the title ENH (enhancement) Interface/cat12 [ENH] Interface/cat12 Mar 9, 2021
@mfmachado mfmachado marked this pull request as ready for review March 9, 2021 15:37
@mfmachado mfmachado changed the title [ENH] Interface/cat12 ENH Interface/cat12 Mar 9, 2021
@mfmachado mfmachado changed the title ENH Interface/cat12 ENH: Interface/cat12 Mar 9, 2021
Copy link
Member

@effigies effigies left a comment

Choose a reason for hiding this comment

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

I had a look through preprocess.py, but haven't done the other yet. Don't forget to make specs.

Comment on lines 2 to 3
import traits
from nipype.interfaces.base import InputMultiPath, TraitedSpec, isdefined
Copy link
Member

Choose a reason for hiding this comment

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

We use nipype.interfaces.base to override some aspects of traits. I would suggest the following:

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hi @effigies sorry for the late reply, I only had the time to correct you reviews today. I do not known what you mean by make specs...

Comment on lines 7 to 8
from traits.trait_types import Int, File
from traits.trait_types import List
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
from traits.trait_types import Int, File
from traits.trait_types import List

Comment on lines 12 to 40
"""
CAT12: Segmentation
This toolbox is an extension to the default segmentation in SPM12, but uses a completely different segmentation
approach.
The segmentation approach is based on an Adaptive Maximum A Posterior (MAP) technique without the need for a priori
information about tissue probabilities. That is, the Tissue Probability Maps (TPM) are not used constantly in the
sense of the classical Unified Segmentation approach (Ashburner et. al. 2005), but just for spatial normalization.
The following AMAP estimation is adaptive in the sense that local variations of the parameters (i.e., means and
variance) are modeled as slowly varying spatial functions (Rajapakse et al. 1997). This not only accounts for
intensity inhomogeneities but also for other local variations of intensity.
Additionally, the segmentation approach uses a Partial Volume Estimation (PVE) with a simplified mixed model of at
most two tissue types (Tohka et al. 2004). We start with an initial segmentation into three pure classes: gray
matter (GM), white matter (WM), and cerebrospinal fluid (CSF) based on the above described AMAP estimation. The
initial segmentation is followed by a PVE of two additional mixed classes: GM-WM and GM-CSF. This results in an
estimation of the amount (or fraction) of each pure tissue type present in every voxel (as single voxels - given by
Another important extension to the SPM12 segmentation is the integration of the Dartel or Geodesic Shooting
registration into the toolbox by an already existing Dartel/Shooting template in MNI space. This template was
derived from 555 healthy control subjects of the IXI-database (http://www.brain-development.org) and provides the
several Dartel or Shooting iterations. Thus, for the majority of studies the creation of sample-specific templates
is not necessary anymore and is mainly recommended for children data.'};

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

Examples
--------
>>> path_mr = 'structural.nii'
>>> cat = CAT12Segment(in_files=path_mr)
>>> cat.run() # doctest: +SKIP
"""
Copy link
Member

Choose a reason for hiding this comment

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

This docstring should go on CAT12Segment, not CAT12SegmentInputSpec.

Comment on lines 45 to 49
help_tpm = 'Tissue Probability Map. Select the tissue probability image that includes 6 tissue probability ' \
'classes for (1) grey matter, (2) white matter, (3) cerebrospinal fluid, (4) bone, (5) non-brain soft ' \
'tissue, and (6) the background. CAT uses the TPM only for the initial SPM segmentation.'
tpm = InputMultiPath(ImageFileSPM(exists=True), field="tpm", desc=help_tpm, mandatory=False,
copyfile=False)
Copy link
Member

Choose a reason for hiding this comment

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

For variables that are not traits, we use _ as a prefix to mark them as private. This will help declutter tab-completion in IPython or similar environments.

Suggested change
help_tpm = 'Tissue Probability Map. Select the tissue probability image that includes 6 tissue probability ' \
'classes for (1) grey matter, (2) white matter, (3) cerebrospinal fluid, (4) bone, (5) non-brain soft ' \
'tissue, and (6) the background. CAT uses the TPM only for the initial SPM segmentation.'
tpm = InputMultiPath(ImageFileSPM(exists=True), field="tpm", desc=help_tpm, mandatory=False,
copyfile=False)
_help_tpm = 'Tissue Probability Map. Select the tissue probability image that includes 6 tissue probability ' \
'classes for (1) grey matter, (2) white matter, (3) cerebrospinal fluid, (4) bone, (5) non-brain soft ' \
'tissue, and (6) the background. CAT uses the TPM only for the initial SPM segmentation.'
tpm = InputMultiPath(ImageFileSPM(exists=True), field="tpm", desc=_help_tpm, mandatory=False,
copyfile=False)

And so on for other traits.

Comment on lines 51 to 52
n_jobs = traits.trait_types.Int(1, usedefault=True, mandatory=True, field="nproc", desc="Number of threads")
use_prior = traits.trait_types.Str(field="useprior", usedefault=True)
Copy link
Member

Choose a reason for hiding this comment

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

Based on changed imports:

Suggested change
n_jobs = traits.trait_types.Int(1, usedefault=True, mandatory=True, field="nproc", desc="Number of threads")
use_prior = traits.trait_types.Str(field="useprior", usedefault=True)
n_jobs = traits.Int(1, usedefault=True, mandatory=True, field="nproc", desc="Number of threads")
use_prior = Str(field="useprior", usedefault=True)

And can you add a description to use_prior?

else:
outputs[outfield] = fname_presuffix(f, prefix=prefix, suffix="_rigid")

if isdefined(self.inputs.save_bias_corrected) and self.inputs.save_bias_corrected:
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 isdefined(self.inputs.save_bias_corrected) and self.inputs.save_bias_corrected:
if self.inputs.save_bias_corrected:

Comment on lines 339 to 341
outputs["surface_files"] = [os.path.join(os.path.join(pth, "surf"), f) for f in
os.listdir(os.path.join(pth, "surf"))
if os.path.isfile(os.path.join(os.path.join(pth, "surf"), f))]
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
outputs["surface_files"] = [os.path.join(os.path.join(pth, "surf"), f) for f in
os.listdir(os.path.join(pth, "surf"))
if os.path.isfile(os.path.join(os.path.join(pth, "surf"), f))]
outputs["surface_files"] = [str(surf) for surf in Path(pth).glob("surf/*") if surf.is_file()]

Comment on lines 343 to 344
for tidx, hemisphere in enumerate(["rh", "lh"]):
for idx, suffix in enumerate(["central", "sphere"]):
Copy link
Member

Choose a reason for hiding this comment

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

Unused indices.

Suggested change
for tidx, hemisphere in enumerate(["rh", "lh"]):
for idx, suffix in enumerate(["central", "sphere"]):
for hemisphere in ("rh", "lh"):
for suffix in ("central", "sphere"):

Comment on lines 349 to 351
outputs["report_files"] = [os.path.join(os.path.join(pth, "report"), f) for f in
os.listdir(os.path.join(pth, "report"))
if os.path.isfile(os.path.join(os.path.join(pth, "report"), f))]
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
outputs["report_files"] = [os.path.join(os.path.join(pth, "report"), f) for f in
os.listdir(os.path.join(pth, "report"))
if os.path.isfile(os.path.join(os.path.join(pth, "report"), f))]
outputs["report_files"] = [str(report) for report in Path(pth).glob("report/*") if report.is_file()]

Comment on lines 354 to 356
outputs["label_files"] = [os.path.join(os.path.join(pth, "label"), f) for f in
os.listdir(os.path.join(pth, "label"))
if os.path.isfile(os.path.join(os.path.join(pth, "label"), f))]
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
outputs["label_files"] = [os.path.join(os.path.join(pth, "label"), f) for f in
os.listdir(os.path.join(pth, "label"))
if os.path.isfile(os.path.join(os.path.join(pth, "label"), f))]
outputs["label_files"] = [str(label) for label in Path(pth).glob("label/*") if label.is_file()]

Update nipype/interfaces/cat12/surface.py
@mfmachado mfmachado requested a review from effigies April 1, 2021 16:03
copyfile=False)

n_jobs = traits.Int(1, usedefault=True, mandatory=True, field="nproc", desc="Number of threads")
use_prior = Str(field="useprior", usedefault=True)
Copy link
Member

Choose a reason for hiding this comment

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

Missing descriptions for:

  • use_prior
  • surface_measures
  • neuromorphometrics
  • lpba40
  • cobra
  • hammers
  • save_bias_corrected

Unclear descriptions:

  • own_atlas
  • gm_output_*
  • wm_output_*
  • csf_output_*
  • label_* / output_labelnative
  • las_*

@effigies
Copy link
Member

There's a Makefile with a specs entry:

nipype/Makefile

Lines 71 to 73 in 4e8f54d

specs:
@echo "Checking specs and autogenerating spec tests"
env PYTHONPATH=".:$(PYTHONPATH)" $(PYTHON) tools/checkspecs.py

If you run make specs, it will auto-generate a test for your new interface. If you don't have make installed, you can just run python tools/checkspecs.py, as it does the same thing.

@effigies effigies changed the title ENH: Interface/cat12 ENH: Add CAT12 interfaces Apr 30, 2021
@effigies
Copy link
Member

@mfmachado I went ahead and merged master, ran black and make specs on your PR to get it ready to finish. Are you good with it at this point? If so, I can aim to do a final review soon.

@effigies effigies added this to the 1.6.0 milestone Apr 30, 2021
Copy link
Member

@effigies effigies left a comment

Choose a reason for hiding this comment

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

This is basically good to go. Some small comments, only.


def _format_arg(self, opt, spec, val):
"""Convert input to appropriate format for spm"""
if opt in ["in_files"]:
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 opt in ["in_files"]:
if opt == "in_files":

Comment on lines 516 to 518
for idx, (image, prefix) in enumerate(
[("modulated", "mw"), ("dartel", "r"), ("native", "")]
):
Copy link
Member

Choose a reason for hiding this comment

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

idx is unused.

Suggested change
for idx, (image, prefix) in enumerate(
[("modulated", "mw"), ("dartel", "r"), ("native", "")]
):
for image, prefix in (("modulated", "mw"), ("dartel", "r"), ("native", "")):

Comment on lines +520 to +522
if isdefined(getattr(self.inputs, outtype)) and getattr(
self.inputs, outtype
):
Copy link
Member

Choose a reason for hiding this comment

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

Undefined is treated as False, so this can be simplified:

Suggested change
if isdefined(getattr(self.inputs, outtype)) and getattr(
self.inputs, outtype
):
if getattr(self.inputs, outtype):

Comment on lines 555 to 556
outputs[f"report"] = fname_presuffix(
f, prefix=os.path.join("report", f"cat_"), suffix=".xml", use_ext=False
Copy link
Member

Choose a reason for hiding this comment

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

Nothing to interpolate with f-strings.

Suggested change
outputs[f"report"] = fname_presuffix(
f, prefix=os.path.join("report", f"cat_"), suffix=".xml", use_ext=False
outputs["report"] = fname_presuffix(
f, prefix=os.path.join("report", "cat_"), suffix=".xml", use_ext=False

]

outputs["label_rois"] = fname_presuffix(
f, prefix=os.path.join("label", f"catROIs_"), suffix=".xml", use_ext=False
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
f, prefix=os.path.join("label", f"catROIs_"), suffix=".xml", use_ext=False
f, prefix=os.path.join("label", "catROIs_"), suffix=".xml", use_ext=False

f, prefix=os.path.join("label", f"catROIs_"), suffix=".xml", use_ext=False
)
outputs["label_roi"] = fname_presuffix(
f, prefix=os.path.join("label", f"catROI_"), suffix=".xml", use_ext=False
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
f, prefix=os.path.join("label", f"catROI_"), suffix=".xml", use_ext=False
f, prefix=os.path.join("label", "catROI_"), suffix=".xml", use_ext=False

Comment on lines 242 to 248
_dartel_help = (
"This option is to export data into a form that can be used with DARTEL. The SPM default is to "
"only apply rigid body transformation. However, a more appropriate option is to apply affine "
"transformation, because the additional scaling of the images requires less deformations to "
"non-linearly register brains to the template."
)

Copy link
Member

Choose a reason for hiding this comment

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

Unused.

Suggested change
_dartel_help = (
"This option is to export data into a form that can be used with DARTEL. The SPM default is to "
"only apply rigid body transformation. However, a more appropriate option is to apply affine "
"transformation, because the additional scaling of the images requires less deformations to "
"non-linearly register brains to the template."
)

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:

Comment on lines 4 to 6
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

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]):

@effigies effigies modified the milestones: 1.6.0, 1.6.1 May 1, 2021
@effigies effigies mentioned this pull request May 26, 2021
14 tasks
@effigies
Copy link
Member

@mfmachado I'm going to try to make a release in the next couple days. If you want to try to finish this off, I'd love to include it!

@mfmachado
Copy link
Contributor Author

Hi @effigies,

I have been super busy in the last couple of weeks, when are you going to do the release? I can try to do it this weekend.

@effigies
Copy link
Member

Happy to wait on this PR if you expect to get it in soon. If you're very busy, we can also release and wait on this until you have more time.

@mfmachado
Copy link
Contributor Author

Hi @effigies thanks for you input :)
I updated the code and corrected a bug that I detected meanwhile regarding the TPM and Shooting template. I use this code regularly, so I hope it does not have bugs.

@mfmachado
Copy link
Contributor Author

Hi @effigies all testes passed :D

Copy link
Member

@effigies effigies left a comment

Choose a reason for hiding this comment

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

Thanks! I did a few minor cleanups just to polish this off, rather than put you through another round of review. If you want to have a quick look, let me know if there's anything you disagree with. Otherwise, I'll plan to merge tomorrow.

@mfmachado
Copy link
Contributor Author

hi, yes I agree with your changes :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

CAT12 interface?
2 participants