diff --git a/docs/reference/commands/5ttgen.rst b/docs/reference/commands/5ttgen.rst index 950fd9c9af..3a8faa7d62 100644 --- a/docs/reference/commands/5ttgen.rst +++ b/docs/reference/commands/5ttgen.rst @@ -15,7 +15,7 @@ Usage 5ttgen algorithm [ options ] ... -- *algorithm*: Select the algorithm to be used; additional details and options become available once an algorithm is nominated. Options are: freesurfer, fsl, gif, hsvs +- *algorithm*: Select the algorithm to be used; additional details and options become available once an algorithm is nominated. Options are: deep_atropos, freesurfer, fsl, gif, hsvs Description ----------- @@ -90,6 +90,119 @@ See the Mozilla Public License v. 2.0 for more details. For more details, see http://www.mrtrix.org/. +.. _5ttgen_deep_atropos: + +5ttgen deep_atropos +=================== + +Synopsis +-------- + +Generate the 5TT image based on a Deep Atropos segmentation or probabilities image + +Usage +----- + +:: + + 5ttgen deep_atropos input output [ options ] + +- *input*: The input Deep Atropos segmentation image +- *output*: The output 5TT image + +Description +----------- + +This algorithm can accept the outputs of Deep Atropos in one of two forms. The "segmentation image" is a 3D image, of integer datatype, with indices mapping to discrete tissue classes as follows: 0: Background; 1: CSF; 2: Gray Matter; 3: White Matter; 4: Deep Gray Matter; 5: Brain Stem; 6: Cerebellum. The "probabilities images" are a set of seven 3D volumes, each corresponding to the posterior probability of one of the seven tissue classes above. These can be provided as input to this command by concatenating into a 4D image series with 7 volumes (the order of which must match that above). + +The example usages provided in this help page, which include execution of Deep Atropos itself within a Python environment, require that "ants" and "antspynet" be installed via Python's "pip"; use of the "probability images" also requires that nibabel and numpy be installed. + +Example usages +-------------- + +- *To utilise the "segmentation" image*:: + + $ python3 -c "import ants, antspynet; t1w = ants.image_read('T1w.nii.gz'); result = antspynet.deep_atropos(t1w); ants.image_write(result['segmentation_image'], 'segmentation.nii.gz')"; 5ttgen deep_atropos segmentation.nii.gz 5tt_segmentation.mif + + Because the input segmentation here is an integer image, where each voxel just contains an index corresponding to the maximal tissue class, the output 5TT image will not possess any fractional partial volumes; it will just contain the value 1.0 in whichever 5TT volume corresponds to the singular assigned tissue class. + +- *To utilise the "probability images"*:: + + $ python3 -c "import ants, antspynet, nibabel, numpy; inpath = 'T1w.nii.gz'; t1w_ants = ants.image_read(inpath); t1w_nib = nibabel.load(inpath); result = antspynet.deep_atropos(t1w_ants); prob_maps = numpy.stack([numpy.array(img.numpy()) for img in result['probability_images']], axis=-1); nibabel.save(nibabel.Nifti1Image(prob_maps, t1w_nib.affine), 'probabilities.nii.gz')"; 5ttgen deep_atropos probabilities.nii.gz 5tt_probabilities.mif + + In this use case, the posterior probabilities of these tissue classes are interpreted as partial volume fractions and imported into the derivative 5TT image appropriately. + +Options +------- + +- **-white_stem** Classify the brainstem as white matter + +Options common to all 5ttgen algorithms +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- **-nocrop** Do NOT crop the resulting 5TT image to reduce its size (keep the same dimensions as the input image) + +- **-sgm_amyg_hipp** Represent the amygdalae and hippocampi as sub-cortical grey matter in the 5TT image + +Additional standard options for Python scripts +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- **-nocleanup** do not delete intermediate files during script execution, and do not delete scratch directory at script completion. + +- **-scratch /path/to/scratch/** manually specify an existing directory in which to generate the scratch directory. + +- **-continue ScratchDir LastFile** continue the script from a previous execution; must provide the scratch directory path, and the name of the last successfully-generated file. + +Standard options +^^^^^^^^^^^^^^^^ + +- **-info** display information messages. + +- **-quiet** do not display information messages or progress status. Alternatively, this can be achieved by setting the MRTRIX_QUIET environment variable to a non-empty string. + +- **-debug** display debugging messages. + +- **-force** force overwrite of output files. + +- **-nthreads number** use this number of threads in multi-threaded applications (set to 0 to disable multi-threading). + +- **-config key value** *(multiple uses permitted)* temporarily set the value of an MRtrix config file entry. + +- **-help** display this information page and exit. + +- **-version** display version information and exit. + +References +^^^^^^^^^^ + +* Smith, R. E.; Tournier, J.-D.; Calamante, F. & Connelly, A. Anatomically-constrained tractography: Improved diffusion MRI streamlines tractography through effective use of anatomical information. NeuroImage, 2012, 62, 1924-1938 + +* Use of the ANTsX ecosystem should be accompanied by the following citation: +N.J. Tustison, P.A. Cook, A.J. Holbrook, H.J. Johnson, J. Muschelli, G.A. Devenyi, J.T. Duda, S.R. Das, N.C. Cullen, D.L. Gillen, M.A. Yassa, J.R. Stone, J.C. Gee, and B.B. Avants. The ANTsX ecosystem for quantitative biological and medical imaging. Scientific Reports, 11(1):9068 (2021), pp. 1-13. + +Tournier, J.-D.; Smith, R. E.; Raffelt, D.; Tabbara, R.; Dhollander, T.; Pietsch, M.; Christiaens, D.; Jeurissen, B.; Yeh, C.-H. & Connelly, A. MRtrix3: A fast, flexible and open software framework for medical image processing and visualisation. NeuroImage, 2019, 202, 116137 + +-------------- + + + +**Author:** Lucius S. Fekonja (lucius.fekonja[at]charite.de) and Robert E. Smith (robert.smith@florey.edu.au) + +**Copyright:** Copyright (c) 2008-2024 the MRtrix3 contributors. + +This Source Code Form is subject to the terms of the Mozilla Public +License, v. 2.0. If a copy of the MPL was not distributed with this +file, You can obtain one at http://mozilla.org/MPL/2.0/. + +Covered Software is provided under this License on an "as is" +basis, without warranty of any kind, either expressed, implied, or +statutory, including, without limitation, warranties that the +Covered Software is free of defects, merchantable, fit for a +particular purpose or non-infringing. +See the Mozilla Public License v. 2.0 for more details. + +For more details, see http://www.mrtrix.org/. + .. _5ttgen_freesurfer: 5ttgen freesurfer diff --git a/python/mrtrix3/commands/5ttgen/__init__.py b/python/mrtrix3/commands/5ttgen/__init__.py index 3f72263f4c..942601933a 100644 --- a/python/mrtrix3/commands/5ttgen/__init__.py +++ b/python/mrtrix3/commands/5ttgen/__init__.py @@ -14,4 +14,4 @@ # For more details, see http://www.mrtrix.org/. # pylint: disable=unused-variable -ALGORITHMS = ['freesurfer', 'fsl', 'gif', 'hsvs'] +ALGORITHMS = ['deep_atropos', 'freesurfer', 'fsl', 'gif', 'hsvs'] diff --git a/python/mrtrix3/commands/5ttgen/deep_atropos.py b/python/mrtrix3/commands/5ttgen/deep_atropos.py new file mode 100644 index 0000000000..a3cf5871bf --- /dev/null +++ b/python/mrtrix3/commands/5ttgen/deep_atropos.py @@ -0,0 +1,131 @@ +# Copyright (c) 2008-2024 the MRtrix3 contributors. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# Covered Software is provided under this License on an "as is" +# basis, without warranty of any kind, either expressed, implied, or +# statutory, including, without limitation, warranties that the +# Covered Software is free of defects, merchantable, fit for a +# particular purpose or non-infringing. +# See the Mozilla Public License v. 2.0 for more details. +# +# For more details, see http://www.mrtrix.org/. + +import os +from mrtrix3 import MRtrixError +from mrtrix3 import app, image, run + +def usage(base_parser, subparsers): #pylint: disable=unused-variable + parser = subparsers.add_parser('deep_atropos', parents=[base_parser]) + parser.set_author('Lucius S. Fekonja (lucius.fekonja[at]charite.de) and Robert E. Smith (robert.smith@florey.edu.au)') + parser.set_synopsis('Generate the 5TT image based on a Deep Atropos segmentation or probabilities image') + parser.add_citation('Use of the ANTsX ecosystem should be accompanied by the following citation:\n' + 'N.J. Tustison, P.A. Cook, A.J. Holbrook, H.J. Johnson, J. Muschelli, G.A. Devenyi, J.T. Duda, S.R. Das, ' + 'N.C. Cullen, D.L. Gillen, M.A. Yassa, J.R. Stone, J.C. Gee, and B.B. Avants. ' + 'The ANTsX ecosystem for quantitative biological and medical imaging. ' + 'Scientific Reports, 11(1):9068 (2021), pp. 1-13.', + is_external=True) + parser.add_description('This algorithm can accept the outputs of Deep Atropos in one of two forms. ' + 'The "segmentation image" is a 3D image, of integer datatype, ' + 'with indices mapping to discrete tissue classes as follows: ' + '0: Background; 1: CSF; 2: Gray Matter; 3: White Matter; 4: Deep Gray Matter; 5: Brain Stem; 6: Cerebellum. ' + 'The "probabilities images" are a set of seven 3D volumes, ' + 'each corresponding to the posterior probability of one of the seven tissue classes above. ' + 'These can be provided as input to this command by concatenating into a 4D image series with 7 volumes ' + '(the order of which must match that above).') + parser.add_description('The example usages provided in this help page, ' + 'which include execution of Deep Atropos itself within a Python environment, ' + 'require that "ants" and "antspynet" be installed via Python\'s "pip"; ' + 'use of the "probability images" also requires that nibabel and numpy be installed.') + parser.add_example_usage('To utilise the "segmentation" image', + 'python3 -c "import ants, antspynet; ' + 't1w = ants.image_read(\'T1w.nii.gz\'); ' + 'result = antspynet.deep_atropos(t1w); ' + 'ants.image_write(result[\'segmentation_image\'], \'segmentation.nii.gz\')"; ' + '5ttgen deep_atropos segmentation.nii.gz 5tt_segmentation.mif', + 'Because the input segmentation here is an integer image, ' + 'where each voxel just contains an index corresponding to the maximal tissue class, ' + 'the output 5TT image will not possess any fractional partial volumes; ' + 'it will just contain the value 1.0 in whichever 5TT volume corresponds to the singular assigned tissue class.') + parser.add_example_usage('To utilise the "probability images"', + 'python3 -c "import ants, antspynet, nibabel, numpy; ' + 'inpath = \'T1w.nii.gz\'; ' + 't1w_ants = ants.image_read(inpath); ' + 't1w_nib = nibabel.load(inpath); ' + 'result = antspynet.deep_atropos(t1w_ants); ' + 'prob_maps = numpy.stack([numpy.array(img.numpy()) for img in result[\'probability_images\']], axis=-1); ' + 'nibabel.save(nibabel.Nifti1Image(prob_maps, t1w_nib.affine), \'probabilities.nii.gz\')"; ' + '5ttgen deep_atropos probabilities.nii.gz 5tt_probabilities.mif', + 'In this use case, the posterior probabilities of these tissue classes are interpreted as partial volume fractions ' + 'and imported into the derivative 5TT image appropriately.') + parser.add_argument('input', + type=app.Parser.ImageIn(), + help='The input Deep Atropos segmentation image') + parser.add_argument('output', + type=app.Parser.ImageOut(), + help='The output 5TT image') + parser.add_argument('-white_stem', + action='store_true', + default=None, + help='Classify the brainstem as white matter') + +def execute(): #pylint: disable=unused-variable + if app.ARGS.sgm_amyg_hipp: + app.warn('Option -sgm_amyg_hipp has no effect on deep_atropos algorithm') + + dim = image.Header(app.ARGS.input).size() + if not(len(dim) == 3 or (len(dim) == 4 and dim[3] == 7)): + raise MRtrixError(f'Image \'{str(app.ARGS.input)}\' does not look like Deep Atropos segmentation' + f' (expected either a 3D image, or a 4D image with 7 volumes; input image is {dim})') + + run.command(['mrconvert', app.ARGS.input, 'input.mif']) + + if len(dim) == 3: + # Generate tissue-specific masks + run.command('mrcalc input.mif 2 -eq cGM.mif') + run.command('mrcalc input.mif 4 -eq input.mif 6 -eq -add sGM.mif') + run.command(f'mrcalc input.mif 3 -eq{" input.mif 5 -eq -add" if app.ARGS.white_stem else ""} WM.mif') + run.command('mrcalc input.mif 1 -eq CSF.mif') + run.command(f'mrcalc input.mif {("0 -mult" if app.ARGS.white_stem else "5 -eq")} path.mif') + else: + # Brain mask = non-brain probability is <50% + run.command('mrconvert input.mif -coord 3 0 -axes 0,1,2 - | ' + 'mrthreshold - -abs 0.5 -comparison le mask.mif') + # Need to rescale model probabilities so that, excluding the non-brain component, + # the sum across all tissues will be 1.0 + run.command('mrconvert input.mif -coord 3 1:end - | ' + 'mrmath - sum -axis 3 - | ' + 'mrcalc 1 - -div multiplier.mif') + # Generate tissue-specific probability maps + run.command('mrconvert input.mif -coord 3 2 -axes 0,1,2 cGM.mif') + run.command('mrconvert input.mif -coord 3 4,6 - | ' + 'mrmath - sum -axis 3 sGM.mif') + if app.ARGS.white_stem: + run.command('mrconvert input.mif -coord 3 3,5 - | ' + 'mrmath - sum -axis 3 WM.mif') + else: + run.command('mrconvert input.mif -coord 3 3 -axes 0,1,2 WM.mif') + run.command('mrconvert input.mif -coord 3 1 -axes 0,1,2 CSF.mif') + if app.ARGS.white_stem: + run.command('mrcalc cGM.mif 0 -mult path.mif') + else: + run.command('mrconvert input.mif -coord 3 5 -axes 0,1,2 path.mif') + + # Concatenate into the 5TT image + run.command('mrcat cGM.mif sGM.mif WM.mif CSF.mif path.mif - -axis 3 | ' + f'{"mrcalc - multiplier.mif -mult mask.mif -mult - | " if len(dim) == 4 else ""}' + 'mrconvert - combined_precrop.mif -strides +2,+3,+4,+1') + + # Apply cropping unless disabled + if app.ARGS.nocrop: + run.function(os.rename, 'combined_precrop.mif', 'result.mif') + else: + run.command('mrmath combined_precrop.mif sum - -axis 3 | ' + 'mrthreshold - - -abs 0.5 | ' + 'mrgrid combined_precrop.mif crop result.mif -mask -') + + run.command(['mrconvert', 'result.mif', app.ARGS.output], + mrconvert_keyval=app.ARGS.input, + force=app.FORCE_OVERWRITE) diff --git a/testing/CMakeLists.txt b/testing/CMakeLists.txt index ed43c4ed09..9e77b79374 100644 --- a/testing/CMakeLists.txt +++ b/testing/CMakeLists.txt @@ -16,7 +16,7 @@ include(ExternalProject) ExternalProject_Add(BinariesTestData PREFIX ${CMAKE_CURRENT_BINARY_DIR}/binaries_data GIT_REPOSITORY ${mrtrix_binaries_data_url} - GIT_TAG 2169ebc06040a0b1380017f5f2a11d6380c69922 + GIT_TAG 2169ebc06040a0b1380017f5f2a11d6380c69922 GIT_PROGRESS TRUE CONFIGURE_COMMAND "" BUILD_COMMAND "" @@ -27,7 +27,7 @@ ExternalProject_Add(BinariesTestData ExternalProject_Add(ScriptsTestData PREFIX ${CMAKE_CURRENT_BINARY_DIR}/scripts_data GIT_REPOSITORY ${mrtrix_scripts_data_url} - GIT_TAG 76f47633cd0a37e901c42320f4540ecaffd51367 + GIT_TAG 7f3dae1e1bbbb383d710c0db66f469b5f812a298 GIT_PROGRESS TRUE CONFIGURE_COMMAND "" BUILD_COMMAND "" @@ -35,11 +35,11 @@ ExternalProject_Add(ScriptsTestData LOG_DOWNLOAD ON ) -set(BINARY_DATA_DIR +set(BINARY_DATA_DIR ${CMAKE_CURRENT_BINARY_DIR}/binaries_data/src/BinariesTestData ) -set(SCRIPT_DATA_DIR +set(SCRIPT_DATA_DIR ${CMAKE_CURRENT_BINARY_DIR}/scripts_data/src/ScriptsTestData ) diff --git a/testing/scripts/CMakeLists.txt b/testing/scripts/CMakeLists.txt index 9a0b9fad51..30e5817250 100644 --- a/testing/scripts/CMakeLists.txt +++ b/testing/scripts/CMakeLists.txt @@ -27,6 +27,10 @@ function(add_bash_script_test file_path) ) endfunction() +add_bash_script_test(5ttgen/deepatropos_fromseg_default "pythonci") +add_bash_script_test(5ttgen/deepatropos_fromseg_whitestem) +add_bash_script_test(5ttgen/deepatropos_fromprob_default "pythonci") +add_bash_script_test(5ttgen/deepatropos_fromprob_whitestem) add_bash_script_test(5ttgen/freesurfer_default "pythonci") add_bash_script_test(5ttgen/freesurfer_nocrop) add_bash_script_test(5ttgen/freesurfer_piping) diff --git a/testing/scripts/tests/5ttgen/deepatropos_fromprob_default b/testing/scripts/tests/5ttgen/deepatropos_fromprob_default new file mode 100644 index 0000000000..5778dd6eb3 --- /dev/null +++ b/testing/scripts/tests/5ttgen/deepatropos_fromprob_default @@ -0,0 +1,6 @@ +#!/bin/bash +# Verify default operation of "5ttgen deep_atropos" +# where the input is the concatenation of tissue probability images +# Outcome is compared to that generated using a prior software version +5ttgen deep_atropos 5ttgen/deep_atropos/probability_images.nii.gz tmp.mif -force +testing_diff_image tmp.mif 5ttgen/deep_atropos/fromprob_default.mif.gz diff --git a/testing/scripts/tests/5ttgen/deepatropos_fromprob_whitestem b/testing/scripts/tests/5ttgen/deepatropos_fromprob_whitestem new file mode 100644 index 0000000000..2ec776a17b --- /dev/null +++ b/testing/scripts/tests/5ttgen/deepatropos_fromprob_whitestem @@ -0,0 +1,7 @@ +#!/bin/bash +# Verify default operation of "5ttgen deep_atropos" +# where the input is the concatenation of tissue probability images +# and the brain stem is allocated to WM rather than 5th volume +# Outcome is compared to that generated using a prior software version +5ttgen deep_atropos 5ttgen/deep_atropos/probability_images.nii.gz tmp.mif -white_stem -force +testing_diff_image tmp.mif 5ttgen/deep_atropos/fromprob_whitestem.mif.gz diff --git a/testing/scripts/tests/5ttgen/deepatropos_fromseg_default b/testing/scripts/tests/5ttgen/deepatropos_fromseg_default new file mode 100644 index 0000000000..d8d1133536 --- /dev/null +++ b/testing/scripts/tests/5ttgen/deepatropos_fromseg_default @@ -0,0 +1,6 @@ +#!/bin/bash +# Verify default operation of "5ttgen deep_atropos" +# where input image is the segmentation label image +# Outcome is compared to that generated using a prior software version +5ttgen deep_atropos 5ttgen/deep_atropos/segmentation_image.nii.gz tmp.mif -force +testing_diff_image tmp.mif 5ttgen/deep_atropos/fromseg_default.mif.gz diff --git a/testing/scripts/tests/5ttgen/deepatropos_fromseg_whitestem b/testing/scripts/tests/5ttgen/deepatropos_fromseg_whitestem new file mode 100644 index 0000000000..5c6994d9a1 --- /dev/null +++ b/testing/scripts/tests/5ttgen/deepatropos_fromseg_whitestem @@ -0,0 +1,7 @@ +#!/bin/bash +# Verify default operation of "5ttgen deep_atropos" +# where input image is the segmentation label image +# and the brain stem is allocated to WM rather than 5th volume +# Outcome is compared to that generated using a prior software version +5ttgen deep_atropos 5ttgen/deep_atropos/segmentation_image.nii.gz tmp.mif -white_stem -force +testing_diff_image tmp.mif 5ttgen/deep_atropos/fromseg_whitestem.mif.gz