-
Notifications
You must be signed in to change notification settings - Fork 258
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
Changes from 6 commits
d7c7069
73bff0f
57f7723
e15bb8d
7412ba9
d357eae
93532de
2331e9d
35beaba
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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() | ||
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 |
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use the There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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))) | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We don't need to touch this file. |
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 | ||
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 | ||
------- | ||
non zero voxel volume: int | ||
JulianKlug marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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 | ||
------- | ||
mask_volume_mm3: float | ||
JulianKlug marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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 |
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 | ||
|
||
JulianKlug marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
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 | ||
|
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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!
There was a problem hiding this comment.
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.