From d7c706975e8fc8c6a2fac9d83c2f006c236b4c6c Mon Sep 17 00:00:00 2001
From: Julian Klug <tensu.wave@gmail.com>
Date: Wed, 9 Sep 2020 23:52:38 +0200
Subject: [PATCH 1/9] Added a volume computation function

Compute volume of mask image.
Equivalent to "fslstats /path/file.nii -V"
---
 nibabel/funcs.py | 31 +++++++++++++++++++++++++++++++
 1 file changed, 31 insertions(+)

diff --git a/nibabel/funcs.py b/nibabel/funcs.py
index df5eb0e96f..37eda9d66e 100644
--- a/nibabel/funcs.py
+++ b/nibabel/funcs.py
@@ -215,3 +215,34 @@ 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)))
+
+
+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 nf
+    >>> img = nf.load(path) # path is contains a path to an example nifti mask
+    >>> mask_volume(img)
+    50.3021
+    """
+    header = img.header
+    _, vx, vy, vz, _, _, _, _ = header['pixdim']
+    voxel_volume_mm3 = vx * vy * vz
+    mask = img.get_fdata()
+    mask_volume_vx = np.sum(mask)
+    mask_volume_mm3 = mask_volume_vx * voxel_volume_mm3
+
+    return mask_volume_mm3

From 73bff0ff6742ae4e4e04f33dcbe64f6d710f98ae Mon Sep 17 00:00:00 2001
From: Julian Klug <tensu.wave@gmail.com>
Date: Sun, 18 Oct 2020 10:20:45 +0300
Subject: [PATCH 2/9] Added commandline and tests for imagestats volume
 computation

---
 bin/nib-stats.py                    | 18 +++++++++
 nibabel/cmdline/stats.py            | 39 ++++++++++++++++++++
 nibabel/cmdline/tests/test_stats.py | 33 +++++++++++++++++
 nibabel/funcs.py                    | 30 ---------------
 nibabel/imagestats.py               | 57 +++++++++++++++++++++++++++++
 nibabel/tests/test_imagestats.py    | 29 +++++++++++++++
 6 files changed, 176 insertions(+), 30 deletions(-)
 create mode 100644 bin/nib-stats.py
 create mode 100644 nibabel/cmdline/stats.py
 create mode 100644 nibabel/cmdline/tests/test_stats.py
 create mode 100644 nibabel/imagestats.py
 create mode 100644 nibabel/tests/test_imagestats.py

diff --git a/bin/nib-stats.py b/bin/nib-stats.py
new file mode 100644
index 0000000000..4544b415aa
--- /dev/null
+++ b/bin/nib-stats.py
@@ -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()
diff --git a/nibabel/cmdline/stats.py b/nibabel/cmdline/stats.py
new file mode 100644
index 0000000000..81aded8041
--- /dev/null
+++ b/nibabel/cmdline/stats.py
@@ -0,0 +1,39 @@
+#!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
+
+
+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,
+                   help="Preferred output units of {mm3, vox}. Defaults to mm3")
+    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:
+        computed_volume = mask_volume(from_img, opts.units)
+        print(computed_volume)
+        return computed_volume
diff --git a/nibabel/cmdline/tests/test_stats.py b/nibabel/cmdline/tests/test_stats.py
new file mode 100644
index 0000000000..4fa8a14094
--- /dev/null
+++ b/nibabel/cmdline/tests/test_stats.py
@@ -0,0 +1,33 @@
+#!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.
+#
+### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
+
+import unittest
+
+import pytest
+
+from nibabel.testing import test_data
+from nibabel.cmdline.stats import main
+from nibabel.optpkg import optional_package
+
+_, have_scipy, _ = optional_package('scipy.ndimage')
+needs_scipy = unittest.skipUnless(have_scipy, 'These tests need scipy')
+
+
+def test_volume():
+    infile = test_data(fname="anatomical.nii")
+    args = (f"{infile} --Volume")
+    vol_mm3 = main(args.split())
+    args = (f"{infile} --Volume --units vox")
+    vol_vox = main(args.split())
+
+    assert float(vol_mm3) == 2273328656.0
+    assert float(vol_vox) == 284166082.0
+
+
diff --git a/nibabel/funcs.py b/nibabel/funcs.py
index 37eda9d66e..e12acee57d 100644
--- a/nibabel/funcs.py
+++ b/nibabel/funcs.py
@@ -216,33 +216,3 @@ def _aff_is_diag(aff):
     rzs_aff = aff[:3, :3]
     return np.allclose(rzs_aff, np.diag(np.diag(rzs_aff)))
 
-
-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 nf
-    >>> img = nf.load(path) # path is contains a path to an example nifti mask
-    >>> mask_volume(img)
-    50.3021
-    """
-    header = img.header
-    _, vx, vy, vz, _, _, _, _ = header['pixdim']
-    voxel_volume_mm3 = vx * vy * vz
-    mask = img.get_fdata()
-    mask_volume_vx = np.sum(mask)
-    mask_volume_mm3 = mask_volume_vx * voxel_volume_mm3
-
-    return mask_volume_mm3
diff --git a/nibabel/imagestats.py b/nibabel/imagestats.py
new file mode 100644
index 0000000000..c6b855ae8e
--- /dev/null
+++ b/nibabel/imagestats.py
@@ -0,0 +1,57 @@
+# 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
+
+
+def mask_volume(img, units='mm3'):
+    """ 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.
+
+    units : string {"mm3", "vox"}, optional
+        Unit of the returned mask volume. Defaults to "mm3".
+
+    Returns
+    -------
+    mask_volume_vx: float
+        Volume of mask expressed in voxels.
+
+    or
+
+    mask_volume_mm3: float
+        Volume of mask expressed in mm3.
+
+    Examples
+    --------
+    >>> import nibabel as nf
+    >>> img = nf.load(path) # path is contains a path to an example nifti mask
+    >>> mask_volume(img)
+    50.3021
+    """
+    header = img.header
+    _, vx, vy, vz, _, _, _, _ = header['pixdim']
+    voxel_volume_mm3 = vx * vy * vz
+    mask = img.get_fdata()
+    mask_volume_vx = np.sum(mask)
+    mask_volume_mm3 = mask_volume_vx * voxel_volume_mm3
+
+    if units == 'vox':
+        return mask_volume_vx
+    elif units == 'mm3':
+        return mask_volume_mm3
+    else:
+        raise ValueError(f'{units} is not a valid unit. Choose "mm3" or "vox".')
diff --git a/nibabel/tests/test_imagestats.py b/nibabel/tests/test_imagestats.py
new file mode 100644
index 0000000000..918bf38fc3
--- /dev/null
+++ b/nibabel/tests/test_imagestats.py
@@ -0,0 +1,29 @@
+# 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 nibabel.testing import test_data
+from nibabel.loadsave import load
+
+import pytest
+
+
+def test_mask_volume():
+    # Test mask volume computation
+    infile = test_data(fname="anatomical.nii")
+    img = load(infile)
+    vol_mm3 = imagestats.mask_volume(img)
+    vol_vox = imagestats.mask_volume(img, units='vox')
+
+    assert float(vol_mm3) == 2273328656.0
+    assert float(vol_vox) == 284166082.0
+

From 57f7723ca46d964e710ad5cefbf2bcd40c096563 Mon Sep 17 00:00:00 2001
From: Julian Klug <tensu.wave@gmail.com>
Date: Sun, 18 Oct 2020 10:41:02 +0300
Subject: [PATCH 3/9] Fix style errors - blank lines - variable declaration in
 example

---
 nibabel/imagestats.py | 1 +
 1 file changed, 1 insertion(+)

diff --git a/nibabel/imagestats.py b/nibabel/imagestats.py
index c6b855ae8e..1877c1fff4 100644
--- a/nibabel/imagestats.py
+++ b/nibabel/imagestats.py
@@ -38,6 +38,7 @@ def mask_volume(img, units='mm3'):
     Examples
     --------
     >>> import nibabel as nf
+    >>> path = 'path/to/nifti/mask.nii'
     >>> img = nf.load(path) # path is contains a path to an example nifti mask
     >>> mask_volume(img)
     50.3021

From e15bb8d1c43ca6d20996bcbd022195b7a77029c7 Mon Sep 17 00:00:00 2001
From: Julian Klug <tensu.wave@gmail.com>
Date: Sun, 18 Oct 2020 19:43:34 +0200
Subject: [PATCH 4/9] Apply suggestions from code review: Removing unused code

Co-authored-by: Chris Markiewicz <effigies@gmail.com>
---
 nibabel/cmdline/stats.py            | 4 ++--
 nibabel/cmdline/tests/test_stats.py | 9 ---------
 2 files changed, 2 insertions(+), 11 deletions(-)

diff --git a/nibabel/cmdline/stats.py b/nibabel/cmdline/stats.py
index 81aded8041..e7e4348003 100644
--- a/nibabel/cmdline/stats.py
+++ b/nibabel/cmdline/stats.py
@@ -24,7 +24,7 @@ def _get_parser():
     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,
-                   help="Preferred output units of {mm3, vox}. Defaults to mm3")
+                   choices=("mm3", "vox"), help="Preferred output units")
     return p
 
 def main(args=None):
@@ -36,4 +36,4 @@ def main(args=None):
     if opts.Volume:
         computed_volume = mask_volume(from_img, opts.units)
         print(computed_volume)
-        return computed_volume
+        return 0
diff --git a/nibabel/cmdline/tests/test_stats.py b/nibabel/cmdline/tests/test_stats.py
index 4fa8a14094..dd14044486 100644
--- a/nibabel/cmdline/tests/test_stats.py
+++ b/nibabel/cmdline/tests/test_stats.py
@@ -8,16 +8,8 @@
 #
 ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
 
-import unittest
-
-import pytest
-
 from nibabel.testing import test_data
 from nibabel.cmdline.stats import main
-from nibabel.optpkg import optional_package
-
-_, have_scipy, _ = optional_package('scipy.ndimage')
-needs_scipy = unittest.skipUnless(have_scipy, 'These tests need scipy')
 
 
 def test_volume():
@@ -30,4 +22,3 @@ def test_volume():
     assert float(vol_mm3) == 2273328656.0
     assert float(vol_vox) == 284166082.0
 
-

From 7412ba9789d431297ddee02db4403aa14bd57d2d Mon Sep 17 00:00:00 2001
From: Julian Klug <tensu.wave@gmail.com>
Date: Sun, 18 Oct 2020 19:55:12 +0200
Subject: [PATCH 5/9] Apply suggestions from code review: Use header.get_zooms
 instead of header.pixdim

Co-authored-by: Chris Markiewicz <effigies@gmail.com>
---
 nibabel/imagestats.py | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/nibabel/imagestats.py b/nibabel/imagestats.py
index 1877c1fff4..9b4356ca8a 100644
--- a/nibabel/imagestats.py
+++ b/nibabel/imagestats.py
@@ -11,6 +11,7 @@
 """
 
 import numpy as np
+from nibabel.imageclasses import spatial_axes_first
 
 
 def mask_volume(img, units='mm3'):
@@ -43,11 +44,10 @@ def mask_volume(img, units='mm3'):
     >>> mask_volume(img)
     50.3021
     """
-    header = img.header
-    _, vx, vy, vz, _, _, _, _ = header['pixdim']
-    voxel_volume_mm3 = vx * vy * vz
-    mask = img.get_fdata()
-    mask_volume_vx = np.sum(mask)
+    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 = np.count_nonzero(img.dataobj)
     mask_volume_mm3 = mask_volume_vx * voxel_volume_mm3
 
     if units == 'vox':

From d357eae8d70d53f0f6c2d41265fafc38a3cbac17 Mon Sep 17 00:00:00 2001
From: Julian Klug <tensu.wave@gmail.com>
Date: Mon, 19 Oct 2020 01:11:02 +0300
Subject: [PATCH 6/9] Split Mask_volume into a function for voxels and a
 function for volume in mm3 - use a mask-like volume for testing

---
 nibabel/cmdline/stats.py            |  9 ++++--
 nibabel/cmdline/tests/test_stats.py | 39 ++++++++++++++++++++------
 nibabel/imagestats.py               | 43 +++++++++++++++--------------
 nibabel/tests/test_imagestats.py    | 16 ++++++-----
 setup.cfg                           |  1 +
 5 files changed, 71 insertions(+), 37 deletions(-)

diff --git a/nibabel/cmdline/stats.py b/nibabel/cmdline/stats.py
index e7e4348003..0ea4617936 100644
--- a/nibabel/cmdline/stats.py
+++ b/nibabel/cmdline/stats.py
@@ -13,7 +13,7 @@
 
 import argparse
 from nibabel.loadsave import load
-from nibabel.imagestats import mask_volume
+from nibabel.imagestats import mask_volume, count_nonzero_voxels
 
 
 def _get_parser():
@@ -34,6 +34,11 @@ def main(args=None):
     from_img = load(opts.infile)
 
     if opts.Volume:
-        computed_volume = mask_volume(from_img, opts.units)
+        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
diff --git a/nibabel/cmdline/tests/test_stats.py b/nibabel/cmdline/tests/test_stats.py
index dd14044486..2a3e43a4d5 100644
--- a/nibabel/cmdline/tests/test_stats.py
+++ b/nibabel/cmdline/tests/test_stats.py
@@ -8,17 +8,40 @@
 #
 ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
 
-from nibabel.testing import test_data
+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
 
 
-def test_volume():
-    infile = test_data(fname="anatomical.nii")
+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")
-    vol_mm3 = main(args.split())
+    with Capturing() as vol_mm3:
+        main(args.split())
     args = (f"{infile} --Volume --units vox")
-    vol_vox = main(args.split())
-
-    assert float(vol_mm3) == 2273328656.0
-    assert float(vol_vox) == 284166082.0
+    with Capturing() as vol_vox:
+        main(args.split())
 
+    assert float(vol_mm3[0]) == 1000.0
+    assert int(vol_vox[0]) == 1000
\ No newline at end of file
diff --git a/nibabel/imagestats.py b/nibabel/imagestats.py
index 9b4356ca8a..20422fee54 100644
--- a/nibabel/imagestats.py
+++ b/nibabel/imagestats.py
@@ -13,8 +13,23 @@
 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, units='mm3'):
+def mask_volume(img):
     """ Compute volume of mask image.
     Equivalent to "fslstats /path/file.nii -V"
 
@@ -23,36 +38,24 @@ def mask_volume(img, units='mm3'):
     img : ``SpatialImage``
         All voxels of the mask should be of value 1, background should have value 0.
 
-    units : string {"mm3", "vox"}, optional
-        Unit of the returned mask volume. Defaults to "mm3".
 
     Returns
     -------
-    mask_volume_vx: float
-        Volume of mask expressed in voxels.
-
-    or
-
     mask_volume_mm3: float
         Volume of mask expressed in mm3.
 
     Examples
     --------
-    >>> import nibabel as nf
-    >>> path = 'path/to/nifti/mask.nii'
-    >>> img = nf.load(path) # path is contains a path to an example nifti mask
-    >>> mask_volume(img)
-    50.3021
+    >>> 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 = np.count_nonzero(img.dataobj)
+    mask_volume_vx = count_nonzero_voxels(img)
     mask_volume_mm3 = mask_volume_vx * voxel_volume_mm3
 
-    if units == 'vox':
-        return mask_volume_vx
-    elif units == 'mm3':
-        return mask_volume_mm3
-    else:
-        raise ValueError(f'{units} is not a valid unit. Choose "mm3" or "vox".')
+    return mask_volume_mm3
diff --git a/nibabel/tests/test_imagestats.py b/nibabel/tests/test_imagestats.py
index 918bf38fc3..d8ef2018c1 100644
--- a/nibabel/tests/test_imagestats.py
+++ b/nibabel/tests/test_imagestats.py
@@ -11,19 +11,21 @@
 import numpy as np
 
 from .. import imagestats
-from nibabel.testing import test_data
-from nibabel.loadsave import load
+from .. import Nifti1Image
 
 import pytest
 
 
 def test_mask_volume():
     # Test mask volume computation
-    infile = test_data(fname="anatomical.nii")
-    img = load(infile)
+
+    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.mask_volume(img, units='vox')
+    vol_vox = imagestats.count_nonzero_voxels(img)
 
-    assert float(vol_mm3) == 2273328656.0
-    assert float(vol_vox) == 284166082.0
+    assert vol_mm3 == 1000.0
+    assert vol_vox == 1000
 
diff --git a/setup.cfg b/setup.cfg
index c2bf604f94..141a37caec 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -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

From 93532dedbb547597bbc29444bd67e5068b5a9752 Mon Sep 17 00:00:00 2001
From: Julian Klug <tensu.wave@gmail.com>
Date: Mon, 19 Oct 2020 09:12:41 +0300
Subject: [PATCH 7/9] Fix failing docstring

---
 nibabel/imagestats.py | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/nibabel/imagestats.py b/nibabel/imagestats.py
index 20422fee54..74506a07ff 100644
--- a/nibabel/imagestats.py
+++ b/nibabel/imagestats.py
@@ -46,10 +46,11 @@ def mask_volume(img):
 
     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))
+    >>> nb.imagestats.mask_volume(nb.Nifti1Image(mask_data, np.eye(4)))
     1000.0
     """
     if not spatial_axes_first(img):

From 2331e9d33677cb7fb14eec84d925b7dd18cce740 Mon Sep 17 00:00:00 2001
From: Julian Klug <tensu.wave@gmail.com>
Date: Mon, 19 Oct 2020 22:10:13 +0200
Subject: [PATCH 8/9] Apply suggestions from code review

Co-authored-by: Chris Markiewicz <effigies@gmail.com>
---
 nibabel/imagestats.py            | 6 ++++--
 nibabel/tests/test_imagestats.py | 3 ---
 2 files changed, 4 insertions(+), 5 deletions(-)

diff --git a/nibabel/imagestats.py b/nibabel/imagestats.py
index 74506a07ff..3af2d8a089 100644
--- a/nibabel/imagestats.py
+++ b/nibabel/imagestats.py
@@ -16,6 +16,7 @@
 def count_nonzero_voxels(img):
     """
     Count number of non-zero voxels
+
     Parameters
     ----------
     img : ``SpatialImage``
@@ -23,7 +24,7 @@ def count_nonzero_voxels(img):
 
     Returns
     -------
-    non zero voxel volume: int
+    count : int
         Number of non-zero voxels
 
     """
@@ -31,6 +32,7 @@ def count_nonzero_voxels(img):
 
 def mask_volume(img):
     """ Compute volume of mask image.
+
     Equivalent to "fslstats /path/file.nii -V"
 
     Parameters
@@ -41,7 +43,7 @@ def mask_volume(img):
 
     Returns
     -------
-    mask_volume_mm3: float
+    volume : float
         Volume of mask expressed in mm3.
 
     Examples
diff --git a/nibabel/tests/test_imagestats.py b/nibabel/tests/test_imagestats.py
index d8ef2018c1..e104013ddd 100644
--- a/nibabel/tests/test_imagestats.py
+++ b/nibabel/tests/test_imagestats.py
@@ -13,8 +13,6 @@
 from .. import imagestats
 from .. import Nifti1Image
 
-import pytest
-
 
 def test_mask_volume():
     # Test mask volume computation
@@ -28,4 +26,3 @@ def test_mask_volume():
 
     assert vol_mm3 == 1000.0
     assert vol_vox == 1000
-

From 35beabab98e6ab2a924fd91055c70277bf91cf7b Mon Sep 17 00:00:00 2001
From: Julian Klug <tensu.wave@gmail.com>
Date: Tue, 20 Oct 2020 00:34:49 +0300
Subject: [PATCH 9/9] Style issues, use capsys

---
 bin/nib-stats.py                    | 18 ------------------
 nibabel/cmdline/stats.py            |  1 +
 nibabel/cmdline/tests/test_stats.py | 21 +++++----------------
 nibabel/funcs.py                    |  1 -
 nibabel/imagestats.py               |  2 ++
 5 files changed, 8 insertions(+), 35 deletions(-)
 delete mode 100644 bin/nib-stats.py

diff --git a/bin/nib-stats.py b/bin/nib-stats.py
deleted file mode 100644
index 4544b415aa..0000000000
--- a/bin/nib-stats.py
+++ /dev/null
@@ -1,18 +0,0 @@
-#!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()
diff --git a/nibabel/cmdline/stats.py b/nibabel/cmdline/stats.py
index 0ea4617936..91b9f7c104 100644
--- a/nibabel/cmdline/stats.py
+++ b/nibabel/cmdline/stats.py
@@ -27,6 +27,7 @@ def _get_parser():
                    choices=("mm3", "vox"), help="Preferred output units")
     return p
 
+
 def main(args=None):
     """Main program function."""
     parser = _get_parser()
diff --git a/nibabel/cmdline/tests/test_stats.py b/nibabel/cmdline/tests/test_stats.py
index 2a3e43a4d5..1ceac90231 100644
--- a/nibabel/cmdline/tests/test_stats.py
+++ b/nibabel/cmdline/tests/test_stats.py
@@ -17,18 +17,7 @@
 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
-
-
-def test_volume(tmpdir):
+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))
@@ -37,11 +26,11 @@ def test_volume(tmpdir):
     save(img, infile)
 
     args = (f"{infile} --Volume")
-    with Capturing() as vol_mm3:
-        main(args.split())
+    main(args.split())
+    vol_mm3 = capsys.readouterr()
     args = (f"{infile} --Volume --units vox")
-    with Capturing() as vol_vox:
-        main(args.split())
+    main(args.split())
+    vol_vox = capsys.readouterr()
 
     assert float(vol_mm3[0]) == 1000.0
     assert int(vol_vox[0]) == 1000
\ No newline at end of file
diff --git a/nibabel/funcs.py b/nibabel/funcs.py
index e12acee57d..df5eb0e96f 100644
--- a/nibabel/funcs.py
+++ b/nibabel/funcs.py
@@ -215,4 +215,3 @@ 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)))
-
diff --git a/nibabel/imagestats.py b/nibabel/imagestats.py
index 3af2d8a089..4520d7f612 100644
--- a/nibabel/imagestats.py
+++ b/nibabel/imagestats.py
@@ -13,6 +13,7 @@
 import numpy as np
 from nibabel.imageclasses import spatial_axes_first
 
+
 def count_nonzero_voxels(img):
     """
     Count number of non-zero voxels
@@ -30,6 +31,7 @@ def count_nonzero_voxels(img):
     """
     return np.count_nonzero(img.dataobj)
 
+
 def mask_volume(img):
     """ Compute volume of mask image.