Skip to content
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
from nibabel.imageclasses import spatial_axes_first


def count_nonzero_voxels(img):
"""
Count number of non-zero voxels

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.

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