Find number of voxels in ROIs - Python

Hi Neurostars,

I am using the Schaefer-Yeo 400 atlas (2018) and I was wondering if anyone has an easy implementation to find the number of voxels in each of these ROIs across 300 subjects. I can do this by looping over 400 individual ROI masks for each subject, but that seems very inefficient and I would be grateful for a more elegant solution. Does anyone have a good nipype/nilearn solution?

Many thanks!
Joff

Considering your atlas has ROIs encoded as integers in a NIfTI object:

roi_indices, roi_counts = np.unique(schaefer_yeo.get_data(),
                                    return_counts=True)

I don’t see why the number of voxels would change across subjects for an atlas, though. So perhaps I’m misinterpreting the question.

Hi Dangom,

Thanks for this, I noticed a few rois with null values because of imperfect registration probably due to susceptibility distortion or movement. Do you know what tool I can use to extract voxel info from all ROIs in one go? Essentially, I am trying to do something like NiftiLabelsMasker but at the voxel level, or like NiftiMasker but extracting all voxel time-series for all ROIs rather than one at a time.

You can cast your subject’s masked mean EPI to boolean, use NiftiLabelsMasker to fit_transform it, and that will give you a number between 0 and 1 for each ROI which corresponds to the ratio of non-zero voxels to all voxels. You multiply that by the number of voxels in each ROI (as in my previous answer) and you will have the number of voxels within each mask that are not 0.

Not tested:

sub_mean = image.mean_img("subject_fmri.nii.gz")
sub_mean_mask = image.math_img("i.astype(bool).astype(int)", i=sub_mean)
masker = NiftiLabelsMasker(schaefer_yeo)
voxel_ratios = masker.fit_transform([sub_mean_mask])
n_voxels = voxel_ratios * roi_counts

Brilliant! Thank you very much for this. I’ll test this out and get back to you

Hi Dangom, this seems to almost be working. The only thing is that voxel-ratios is binary rather than a continuous value between 0 and 1. This is because the sub_mean_mask gives binary values. Do you know how can I adjust this to get a ratio?

Many thanks,
Joff

add a 1. * to the math img:

sub_mean_mask = image.math_img("1. * i.astype(bool)", i=sub_mean)

Also, roi_counts will count the background 0 too, so make sure to account for that.
And maybe test on an example where you know the voxel counts, just to make sure the code is doing what you expect.

Thank you Dan, that seems to be working perfectly!

Full example for interested readers:

count number of voxels per ROI in the atlas

import nibabel as nib
import numpy as np
atlas = ‘/path/to/atlas/file’
atlas_img = nib.load(atlas)
roi_indices, roi_counts = np.unique(atlas_img.get_data(),
return_counts=True)

from nilearn import image, masking
from nilearn.input_data import NiftiMasker, NiftiLabelsMasker
import os.path
import glob

fmriprep_dir = '/path/to/fmriprep
fmri_files = sorted(glob.glob(fmriprep_dir + ‘/sub*/func’ + /*fmri_file.nii.gz))

for subject in fmri_files:
sub_id = os.path.basename(subject)[:7]

# mean EPI
sub_mean = image.mean_img(subject)

# cast mean EPI to boolean
sub_mean_mask = image.math_img("1. * i.astype(bool)", i=sub_mean)

# transform to atlas
masker = NiftiLabelsMasker(atlas_img)
voxel_ratios = masker.fit_transform([sub_mean_mask])

# multiple by the number of voxels in each ROI
n_voxels = voxel_ratios[0,:] * roi_counts[1:]
print(sub_id)
print(n_voxels)