A bug in an FSL function we are using has stripped our resulting NIfTIs of the correct repetition time information (TR, entry [3]
in the header). It says 1.0 but should be 1.5.
We have confirmation from the FSL developers that manually changing the TR inside the NIfTIs is fine.
To do this in nibabel
, I did:
def set_tr(img, tr):
header = img.header.copy()
zooms = header.get_zooms()[:3] + (tr,)
header.set_zooms(zooms)
return img.__class__(img.get_fdata().copy(), img.affine, header)
niifiles = glob.glob('*.nii.gz')
for niifile in niifiles:
img = nb.load(niifile)
fixed_img = set_tr(img, 1.5)
fixed_img.to_filename(niifile)
This correctly sets the repetition time in the header. This is adapted from the response by @effigies here: BIDS Validator error: TR mismatch between NIFTI header and JSON file, and BIDS Validator is somehow finding TR=0. Best solution?
I’ve got two questions:
- Can you confirm that this is 100% correct? I admit that I’m not sure I understand the differences between
get_data()
and get_fdata()
well enough to be confident about the usage.
- The rewritten files have different file sizes (e.g. one went from 624.1MB to 692.9MB). What is going on here? Could there be a problem?
Thanks for the immediate response, great community!
Unfortunately the assertion assert np.allclose(reload.get_fdata(), img.get_fdata())
fails, so the code snippet I added up there could be unsafe.
Can you recommend any way around this? I only want to change this one number in the NIfTI header. Any change to the data is unacceptable here.
Ah, sorry, I’ve been slow to get back to this.
To ensure the data block is identical, you need to give nibabel direct instructions about how to scale the data.
import nibabel as nb
def strict_load(fname):
""" Load an image that will write out the same as the input image.
"""
orig = nb.load(fname)
strict_img = orig.__class__(np.array(orig.dataobj.get_unscaled()), orig.affine, orig.header)
strict_img.header.set_slope_inter(orig.dataobj.slope, orig.dataobj.inter)
return strict_img
This should (read: I haven’t tested) produce an image that will write the exact same values and scale factors back to disk. Note that calling get_fdata()
on an image returned by this function will not return scaled data.
1 Like
Thanks for the response. Sorry for being slow to return here.
Would it be possible to explain what you are doing in the strict_load()
function?
.get_fdata()
does indeed not result in the same matrix, so the assert is not working when I am only replacing the nb.load
functions by the strict_load
function. I do expect .get_fdata()
to result in the same values. The assert does work when I use the deprecated .get_data()
, which is confusing because (as far as I understand) .get_fdata()
should do nothing more than lead to strict float return types.
Could it be that the set_tr
function should actually use the .get_unscaled()
function?:
def set_tr(img, tr):
header = img.header.copy()
zooms = header.get_zooms()[:3] + (tr,)
header.set_zooms(zooms)
return img.__class__(np.array(img.dataobj.get_unscaled()), img.affine, header)
Btw, this part: np.array(img.dataobj.get_unscaled())
does not work because dataobj
is a numpy array and Nifti1Image
does not have the function .get_unscaled()
(version 3.2.1 of nibabel).
Where is my mistake in understanding?
I’ve done these header changes manually with FSL 6.0
fsledithd
now. There is a very old entry on the FSL
mailing list where a similar issue (writing the wrong TR / dt) is mentioned, and where using fsledithd
to correct it if necessary is recommended.
While the nifti appears to be rewritten in the process too, it seems safer to go this route as there are less vectors of failure than when I’m coming up with my own script (although it would have been best to automate this).
Ah, sorry for failing to get back to you on this… I think I had something half-written that I was wanting to test before posting, but now I can’t find it.
1 Like