I have to calculate the volume of some lesion masks and I was wondering if counting the voxels != 0 and multiplying for the dimension of a single voxel in mm³ can be a right approach.
I mean:
Load the Nifti with nibabel
Iterating x,y,z and counting the voxel != 0
Multiplying for pixmap[1] * pixmap[2] + pixmap[3] from Nifti header
In the case you have FSL installed, you can use fslstats to compute that for you.
The usage syntax would be:
$ fslstats input.nii.gz -V
Note the capital V. This will output two numbers, the first is the number of voxels greater than 0, the second is their volume in cubic millimiters. Ex:
If you’re making a general equivalent to fslstats or fslmaths, then nibabel would be a reasonable place for these. We have a number of CLI tools following the pattern nib-*.
Number of voxels are only equal to the volume when the voxels are 1 mm^3. For smaller/larger and/or anisotropic voxels, something like this would work when using nibabel:
"""Compute volume of non-zero voxels in a nifti image."""
import numpy as np
import nibabel as nb
INPUT = "/path/to/image.nii.gz"
# Load data
nii = nb.load(INPUT)
img = nii.get_fdata()
# Get voxel dimensions
voxel_dims = (nii.header["pixdim"])[1:4]
print("Voxel dimensions:")
print(" x = {} mm".format(voxel_dims[0]))
print(" y = {} mm".format(voxel_dims[1]))
print(" z = {} mm".format(voxel_dims[2]))
# Compute volume
nonzero_voxel_count = np.count_nonzero(img)
voxel_volume = np.prod(voxel_dims)
nonzero_voxel_volume = nonzero_voxel_count * voxel_volume
print("Number of non-zero voxels = {}".format(nonzero_voxel_count))
print("Volume of non-zero voxels = {} mm^3".format(nonzero_voxel_volume))
Edit: Actually I think you were wrong because img > 0 returns booleans so segmentation labels higher than 1 become True which is 1 anyway when summing. However, I would be missing the negative values so np.count_nonzero(img) or np.sum(img != 0) would still improve upon np.sum(img > 0)
Hi, I used same approach to calculate lesions. In my case, I have to register the image to MNI template. Then I found out that after registration, the real volume of the total image changed, based on np.prod(IMG.shape) * np.prod(IMG.header.get_zooms()). That means after the lesion volume after registration also changed.
Does anyone know why this happened? It doesn’t make sense that registration would change the real volume in mm^3.