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

NF: volume computation function for nifti masks #952

Merged
merged 9 commits into from
Oct 20, 2020
45 changes: 45 additions & 0 deletions nibabel/cmdline/stats.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#!python
# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*-
# vi: set ft=python sts=4 ts=4 sw=4 et:
### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
#
# See COPYING file distributed along with the NiBabel package for the
# copyright and license terms.
#
### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
"""
Compute image statistics
"""

import argparse
from nibabel.loadsave import load
from nibabel.imagestats import mask_volume, count_nonzero_voxels


def _get_parser():
"""Return command-line argument parser."""
p = argparse.ArgumentParser(description=__doc__)
p.add_argument("infile",
help="Neuroimaging volume to compute statistics on.")
p.add_argument("-V", "--Volume", action="store_true", required=False,
help="Compute mask volume of a given mask image.")
p.add_argument("--units", default="mm3", required=False,
choices=("mm3", "vox"), help="Preferred output units")
return p


def main(args=None):
"""Main program function."""
parser = _get_parser()
opts = parser.parse_args(args)
from_img = load(opts.infile)

if opts.Volume:
if opts.units == 'mm3':
computed_volume = mask_volume(from_img)
elif opts.units == 'vox':
computed_volume = count_nonzero_voxels(from_img)
else:
raise ValueError(f'{opts.units} is not a valid unit. Choose "mm3" or "vox".')
print(computed_volume)
return 0
36 changes: 36 additions & 0 deletions nibabel/cmdline/tests/test_stats.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#!python
# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*-
# vi: set ft=python sts=4 ts=4 sw=4 et:
### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
#
# See COPYING file distributed along with the NiBabel package for the
# copyright and license terms.
#
### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##

from io import StringIO
import sys
import numpy as np

from nibabel.loadsave import save
from nibabel.cmdline.stats import main
from nibabel import Nifti1Image


def test_volume(tmpdir, capsys):
mask_data = np.zeros((20, 20, 20), dtype='u1')
mask_data[5:15, 5:15, 5:15] = 1
img = Nifti1Image(mask_data, np.eye(4))

infile = tmpdir / "input.nii"
save(img, infile)

args = (f"{infile} --Volume")
main(args.split())
vol_mm3 = capsys.readouterr()
args = (f"{infile} --Volume --units vox")
main(args.split())
vol_vox = capsys.readouterr()

assert float(vol_mm3[0]) == 1000.0
assert int(vol_vox[0]) == 1000
66 changes: 66 additions & 0 deletions nibabel/imagestats.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*-
# vi: set ft=python sts=4 ts=4 sw=4 et:
### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
#
# See COPYING file distributed along with the NiBabel package for the
# copyright and license terms.
#
### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
"""
Functions for computing image statistics
"""

import numpy as np
JulianKlug marked this conversation as resolved.
Show resolved Hide resolved
from nibabel.imageclasses import spatial_axes_first


def count_nonzero_voxels(img):
"""
Count number of non-zero voxels
JulianKlug marked this conversation as resolved.
Show resolved Hide resolved

Parameters
----------
img : ``SpatialImage``
All voxels of the mask should be of value 1, background should have value 0.

Returns
-------
count : int
Number of non-zero voxels

"""
return np.count_nonzero(img.dataobj)


def mask_volume(img):
""" Compute volume of mask image.
JulianKlug marked this conversation as resolved.
Show resolved Hide resolved

Equivalent to "fslstats /path/file.nii -V"

Parameters
----------
img : ``SpatialImage``
All voxels of the mask should be of value 1, background should have value 0.


Returns
-------
volume : float
Volume of mask expressed in mm3.

Examples
--------
>>> import numpy as np
>>> import nibabel as nb
>>> mask_data = np.zeros((20, 20, 20), dtype='u1')
>>> mask_data[5:15, 5:15, 5:15] = 1
>>> nb.imagestats.mask_volume(nb.Nifti1Image(mask_data, np.eye(4)))
1000.0
"""
if not spatial_axes_first(img):
raise ValueError("Cannot calculate voxel volume for image with unknown spatial axes")
voxel_volume_mm3 = np.prod(img.header.get_zooms()[:3])
mask_volume_vx = count_nonzero_voxels(img)
mask_volume_mm3 = mask_volume_vx * voxel_volume_mm3

return mask_volume_mm3
28 changes: 28 additions & 0 deletions nibabel/tests/test_imagestats.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*-
# vi: set ft=python sts=4 ts=4 sw=4 et:
### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
#
# See COPYING file distributed along with the NiBabel package for the
# copyright and license terms.
#
### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
""" Tests for image statistics """

import numpy as np

from .. import imagestats
from .. import Nifti1Image


def test_mask_volume():
# Test mask volume computation

mask_data = np.zeros((20, 20, 20), dtype='u1')
mask_data[5:15, 5:15, 5:15] = 1
img = Nifti1Image(mask_data, np.eye(4))

vol_mm3 = imagestats.mask_volume(img)
vol_vox = imagestats.count_nonzero_voxels(img)

assert vol_mm3 == 1000.0
assert vol_vox == 1000
1 change: 1 addition & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ console_scripts =
nib-ls=nibabel.cmdline.ls:main
nib-dicomfs=nibabel.cmdline.dicomfs:main
nib-diff=nibabel.cmdline.diff:main
nib-stats=nibabel.cmdline.stats:main
nib-nifti-dx=nibabel.cmdline.nifti_dx:main
nib-tck2trk=nibabel.cmdline.tck2trk:main
nib-trk2tck=nibabel.cmdline.trk2tck:main
Expand Down