Skip to content
18 changes: 18 additions & 0 deletions bin/nib-stats.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!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
"""

from nibabel.cmdline.stats import main


if __name__ == '__main__':
main()
Copy link
Member

Choose a reason for hiding this comment

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

These files are actually out-of-date. Just add an entry to the entry points:

nibabel/setup.cfg

Lines 71 to 81 in 917afab

[options.entry_points]
console_scripts =
nib-conform=nibabel.cmdline.conform:main
nib-ls=nibabel.cmdline.ls:main
nib-dicomfs=nibabel.cmdline.dicomfs:main
nib-diff=nibabel.cmdline.diff:main
nib-nifti-dx=nibabel.cmdline.nifti_dx:main
nib-tck2trk=nibabel.cmdline.tck2trk:main
nib-trk2tck=nibabel.cmdline.trk2tck:main
nib-roi=nibabel.cmdline.roi:main
parrec2nii=nibabel.cmdline.parrec2nii:main

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Great hadn't seen that!

Copy link
Member

Choose a reason for hiding this comment

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

Let's remove this file.

44 changes: 44 additions & 0 deletions nibabel/cmdline/stats.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#!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
47 changes: 47 additions & 0 deletions nibabel/cmdline/tests/test_stats.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#!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


class Capturing(list):
def __enter__(self):
self._stdout = sys.stdout
sys.stdout = self._stringio = StringIO()
return self
def __exit__(self, *args):
self.extend(self._stringio.getvalue().splitlines())
del self._stringio # free up some memory
sys.stdout = self._stdout
Copy link
Member

Choose a reason for hiding this comment

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

Use the capsys fixture..

Copy link
Contributor Author

Choose a reason for hiding this comment

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

thanks, didn't know about that one



def test_volume(tmpdir):
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")
with Capturing() as vol_mm3:
main(args.split())
args = (f"{infile} --Volume --units vox")
with Capturing() as vol_vox:
main(args.split())

assert float(vol_mm3[0]) == 1000.0
assert int(vol_vox[0]) == 1000
1 change: 1 addition & 0 deletions nibabel/funcs.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,3 +215,4 @@ def _aff_is_diag(aff):
""" Utility function returning True if affine is nearly diagonal """
rzs_aff = aff[:3, :3]
return np.allclose(rzs_aff, np.diag(np.diag(rzs_aff)))

Copy link
Member

Choose a reason for hiding this comment

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

We don't need to touch this file.

61 changes: 61 additions & 0 deletions nibabel/imagestats.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# 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
-------
non zero voxel volume: 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
-------
mask_volume_mm3: float
Volume of mask expressed in mm3.

Examples
--------
>>> 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
31 changes: 31 additions & 0 deletions nibabel/tests/test_imagestats.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# 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

import pytest


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