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

♻️ Refactor niworkflow-ants for skullstripping #1403

Closed
wants to merge 15 commits into from

Conversation

shnizzedy
Copy link
Member

Fixes

(maybe) Fixes #1338 by @shnizzedy
(maybe) Fixes #1362 by @shnizzedy

Description

  • Replaces our modified copy of niworkflows-ants from niworkflows v0.10.2 with a monkeypatched (see "technical details" below) niworklfows-ants from niworkflows v1.3.2
  • Upgrades ANTs from v2.1.0 to v2.3.4

Technical details

niworkflows-ants has updated 4 times

(

1.0.0 (November 26, 2019)
[…]
ENH: Add copy_header inputs to some ANTs interfaces (#401)

1.1.9 (March 05, 2020)
[…]
FIX: replace mutable list with tuple in ANTs' workflow (#473)

1.3.0 (September 11, 2020)
[…]
MAINT: Finalize upstreaming of ANTs' interfaces to Nipype (#550)

1.3.1 (September 22, 2020)
[…]
FIX: Revision of antsBrainExtraction, better handling edge cases (#567)

)

since we reproduced their code here 15 months ago (Aug 19, 2019, niworkflows v0.10.2). This PR brings us up to date with niworkflows-ants from niworkflows v1.3.2 (Nov 5, 2020).

Monkeypatch

Up until this PR, we've been maintaining a copy of niworkflows-ants instead of importing directly from niworkflows because niworkflows requires a string name of a templateflow template

    in_template : str
        Name of the skull-stripping template ('OASIS30ANTs', 'NKI', or
        path).
        The brain template from which regions will be projected
        Anatomical template created using e.g. LPBA40 data set with
        ``buildtemplateparallel.sh`` in ANTs.
        The workflow will automatically search for a brain probability
        mask created using e.g. LPBA40 data set which have brain masks
        defined, and warped to anatomical template and
        averaged resulting in a probability image.

whereas we want to use three local paths

tpl_target_path: str
path to brain extraction template
tpl_mask_path: str
path to probabilistic brain mask
tpl_regmask_path: str
path to registration mask

Instead of updating and continuing to maintain our own copy of niworkflows/anat/ants.py, we now have our own functions get_template and get_template_specs

def get_template(in_template, raise_empty=False, label=None, suffix=None,
desc=None, **kwargs):
'''Override templateflow.api.get to support plugging
niworkflows-ants into C-PAC.
Overrides https://www.templateflow.org/python-client/master/_modules/templateflow/api.html#get # noqa: E501
Parameters
----------
in_template: dict
raise_empty: boolean, optional
Raise exception if no files were matched
Returns
-------
path: str
'''
# tpl_mask_path
if (label == 'brain' and suffix == 'probseg') or (
desc == 'brain' and suffix == 'mask'
):
return in_template['tpl_mask_path']
# tpl_regmask_path
if desc == 'BrainCerebellumExtraction' and suffix == 'mask':
return in_template['tpl_regmask_path']
def get_template_specs(in_template, template_spec=None, default_resolution=1):
'''Override niworkflows.utils.misc.get_template_specs to support
plugging niworkflows-ants into C-PAC.
Overrides https://github.com/nipreps/niworkflows/blob/df77431a26fa3f3e4b2aff790fa3b4b2c41dd650/niworkflows/utils/misc.py#L17-L77 # noqa: E501
Parameters
----------
in_template: dict
template_spec: dict or None
default_resolution: int, float, or None
Returns
-------
tpl_target_path: str
common_spec: dict
'''
return (in_template['tpl_target_path'], in_template)
with matching input and output signatures to those used in niworkflows:

get_template

def get(template, raise_empty=False, **kwargs):
    """
    Fetch one file from one particular template.

    Parameters
    ----------
    template : str
        A template identifier (e.g., ``MNI152NLin2009cAsym``).
    raise_empty : bool, optional
        Raise exception if no files were matched

    Keyword Arguments
    -----------------
    resolution: int or None
        Index to an specific spatial resolution of the template.
    suffix : str or None
        BIDS suffix
    atlas : str or None
        Name of a particular atlas
    hemi : str or None
        Hemisphere
    space : str or None
        Space template is mapped to
    density : str or None
        Surface density
    desc : str or None
        Description field

    Examples
    --------
    >>> str(get('MNI152Lin', resolution=1, suffix='T1w'))  # doctest: +ELLIPSIS
    '.../tpl-MNI152Lin/tpl-MNI152Lin_res-01_T1w.nii.gz'

    >>> str(get('MNI152Lin', resolution=2, suffix='T1w'))  # doctest: +ELLIPSIS
    '.../tpl-MNI152Lin/tpl-MNI152Lin_res-02_T1w.nii.gz'

    >>> [str(p) for p in get(
    ...     'MNI152Lin', suffix='T1w')]  # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
    ['.../tpl-MNI152Lin/tpl-MNI152Lin_res-01_T1w.nii.gz',
     '.../tpl-MNI152Lin/tpl-MNI152Lin_res-02_T1w.nii.gz']

    >>> str(get('fsLR', space=None, hemi='L',
    ...         density='32k', suffix='sphere'))  # doctest: +ELLIPSIS
    '.../tpl-fsLR_hemi-L_den-32k_sphere.surf.gii'

    >>> get('fsLR', space='madeup')
    []

    >>> get('fsLR', raise_empty=True, space='madeup')  # doctest: +IGNORE_EXCEPTION_DETAIL
    Traceback (most recent call last):
    Exception:
    ...

    """

get_template_specs

def get_template_specs(in_template, template_spec=None, default_resolution=1):
    """
    Parse template specifications

    >>> get_template_specs('MNI152NLin2009cAsym', {'suffix': 'T1w'})[1]
    {'resolution': 1}

    >>> get_template_specs('MNI152NLin2009cAsym', {'res': '2', 'suffix': 'T1w'})[1]
    {'resolution': '2'}

    >>> specs = get_template_specs('MNIInfant', {'res': '2', 'cohort': '10', 'suffix': 'T1w'})[1]
    >>> sorted(specs.items())
    [('cohort', '10'), ('resolution', '2')]

    >>> get_template_specs('MNI152NLin2009cAsym',
    ...                    {'suffix': 'T1w', 'cohort': 1})[1] # doctest: +IGNORE_EXCEPTION_DETAIL
    Traceback (most recent call last):
    RuntimeError:
    ...

    >>> get_template_specs('MNI152NLin2009cAsym',
    ...                    {'suffix': 'T1w', 'res': '1|2'})[1] # doctest: +IGNORE_EXCEPTION_DETAIL
    Traceback (most recent call last):
    RuntimeError:
    ...

    """

Replacing from ..utils.misc import get_template_specs with from CPAC.anat_preproc.ants import get_template_specs and from templateflow.api import get as get_template with from CPAC.anat_preproc.ants import get_template

spec.loader.get_source('niworkflows.anat.ants').replace(
'from ..utils.misc import get_template_specs',
'from CPAC.anat_preproc.ants import get_template_specs').replace(
'from templateflow.api import get as get_template',
'from CPAC.anat_preproc.ants import get_template'
results in the ability to otherwise just specify the version we want to include in requirements.txt
niworkflows==1.3.2
rather than having to maintain our own copy.

C-PAC Before After
init_brain_extraction_wf init_brain_extraction_wf v0.10.2 init_brain_extraction_wf v1.3.2
skullstrip_anatomical skullstrip_anatomical v0.10.2 skullstrip_anatomical v1.3.2

For comparison, here's niworkflows-ants outside of C-PAC:
init_brain_extraction_wf v1.3.2

Upgrade ANTs

Running from that upgrade resulted in

RuntimeError: Command:
antsAI -c [10,1e-06,10] -d 3 -x /ants_template/oasis/T_template0_BrainCerebellumRegistrationMask.nii.gz -m Mattes[/outputs/working/resting_preproc_17_ses-1/anat_preproc_niworkflows_ants_0/anat_preproc_niworkflows_ants_0_skullstrip/anat_skullstrip_ants/res_tmpl/T_template0_regrid.nii.gz,/outputs/working/resting_preproc_17_ses-1/anat_preproc_niworkflows_ants_0/anat_preproc_niworkflows_ants_0_skullstrip/anat_skullstrip_ants/res_target/sub-17_T1w_resample_maths_corrected_regrid.nii.gz,32,Regular,0.25] -o initialization.mat -p 0 -s [15,0.1] -g [40.0,0x40x40] -t Affine[0.1] -v 1
Standard output:
ERROR:  Invalid flag provided g
Standard error:
ERROR:  Invalid command line flags found! Aborting execution.
Return code: 1

so this PR also upgrades ANTs (to v2.3.4 from v2.1.0).

Tests

>>> from CPAC.anat_preproc.ants import init_brain_extraction_wf
>>> wf = init_brain_extraction_wf(
... '/ants_template/oasis/T_template0.nii.gz',
... '/ants_template/oasis/'
... 'T_template0_BrainCerebellumProbabilityMask.nii.gz',
... '/ants_template/oasis/'
... 'T_template0_BrainCerebellumRegistrationMask.nii.gz')
>>> wf.name
'brain_extraction_wf'
>>> wf.inputs.lap_tmpl.op1
'/ants_template/oasis/T_template0.nii.gz'
>>> wf.inputs.res_tmpl.in_file
'/ants_template/oasis/T_template0.nii.gz'
>>> getattr(wf.inputs.atropos_wf, '01_atropos').number_of_tissue_classes
3

Checklist

  • My pull request has a descriptive title (not a vague title like Update index.md).
  • My pull request targets the develop branch of the repository.
  • My commit messages follow best practices.
  • My code follows the established code style of the repository.
  • I added tests for the changes I made (if applicable).
  • I updated the changelog.
  • I added or updated documentation (if applicable).
  • I tried running the project locally and verified that there are no
    visible errors.

Developer Certificate of Origin

Developer Certificate of Origin
Developer Certificate of Origin
Version 1.1

Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
1 Letterman Drive
Suite D4700
San Francisco, CA, 94129

Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.


Developer's Certificate of Origin 1.1

By making a contribution to this project, I certify that:

(a) The contribution was created in whole or in part by me and I
    have the right to submit it under the open source license
    indicated in the file; or

(b) The contribution is based upon previous work that, to the best
    of my knowledge, is covered under an appropriate open source
    license and I have the right under that license to submit that
    work with modifications, whether created in whole or in part
    by me, under the same open source license (unless I am
    permitted to submit under a different license), as indicated
    in the file; or

(c) The contribution was provided directly to me by some other
    person who certified (a), (b) or (c) and I have not modified
    it.

(d) I understand and agree that this project and the contribution
    are public and that a record of the contribution (including all
    personal information I submit with it, including my sign-off) is
    maintained indefinitely and may be redistributed consistent with
    this project or the open source license(s) involved.

@shnizzedy
Copy link
Member Author

ANTs upgrade still in progress. Needs a newer version of ANTs than is available as an apt-install in Neurodebian (fmriprep installs ANTs from Dropbox)

@sgiavasis
Copy link
Collaborator

Great work so far!

@shnizzedy
Copy link
Member Author

We can revisit this later if we want to, but this didn't help with the issues it intended to fix, so closing unmerged for now.

@shnizzedy shnizzedy closed this Feb 5, 2021
@shnizzedy shnizzedy deleted the fix/niworkflows-ants branch November 6, 2023 19:35
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.

2 participants