nipy / nibabel

Python package to access a cacophony of neuro-imaging file formats
http://nipy.org/nibabel/
Other
654 stars 258 forks source link

wrong minc header #712

Open ltetrel opened 5 years ago

ltetrel commented 5 years ago

Hello all,

So I am trying to convert .mnc images to .nii with nibabel, here is my code :

import nibabel as nib

(...)

# loading the original image
nibImg = nib.load(srcFilePath)
nibAffine = np.array(nibImg.affine)
nibData = np.array(nibImg.dataobj)

# inverting the data axes and rotating the affine to match nii space
nibAffine[0:3, 0:3] = nibAffine[0:3, 0:3] @ rotZ(np.pi/2) @ rotY(np.pi) @ rotX(np.pi/2)
nibData = nibData.T
nibData = np.swapaxes(nibData, 0, 1)

# creating the nifti image
niftiImg = nib.Nifti1Image(nibData, nibAffine, nibImg.header)

The problem is that the header nib.minc2.Minc2Image.header does nto have proper TR information :

In [270]: nibImg.header.get_zooms()
Out[270]: (4.000000000000021, 4.000000000000001, 4.0, 4.0)

When it should be (using mincheader):

acquisition:repetition_time = 2. ;

I started investigated and apparently it has something to do with https://github.com/nipy/nibabel/blob/8b1a1b298cd85b09aaeb97813f1fec650672f546/nibabel/spatialimages.py#L511

Thank you for your work,

effigies commented 5 years ago

Hi @ltetrel., thanks for the report. I'm not sure that's the issue, as the affine should only be setting the first three zooms. If you're familiar with the MINC formats (or at least MINC2), would you care to dig into this and submit a pull request? I suspect the issue is here:

https://github.com/nipy/nibabel/blob/5ab94146d569dc905bfbbb5b7a4ae4b190bbd133/nibabel/minc1.py#L95-L99

It's not clear to me that we're looking for TR at all. We'll need to find how that's represented in each format, and possibly use two different approaches in MINC1 and MINC2 to return the TR.

ltetrel commented 5 years ago

Hi @effigies,

I am not really familiar with this format sorry.. For now it seems to work with the little trick that I posted, but maybe I am missing something..

Thx,

effigies commented 5 years ago

Hi @ltetrel, sorry to be so slow getting back to this one. Sorry to be a bit obtuse, but I'm not sure what little trick you posted that fixed the issue for you. Could you be very explicit?

ltetrel commented 5 years ago

Hi @effigies, Sorry if my issue was not clear, it has been a while so I will try to be as more clear as possible :)

Actually my main problem was that I had a wrong header information (at least affine and repetition time) when loading a .minc image into nibabel, that "break" my conversion to .nifti.

  1. The affine seemed to be rotated, so I added a little piece of code to reorientate the affine into a good landmark. For that I compared inside a viewer the original volume (.minc), with the transformed volume (.nii) to find the good transformation to realign.> I also had to swap some axes but not sure if this is really an issue of loading .minc or just the original format ?
    
    def rot_x(alpha):
    return np.array([[1, 0, 0]
                     , [0, np.cos(alpha), np.sin(alpha)]
                     , [0, -np.sin(alpha), np.cos(alpha)]])

def rot_y(alpha): return np.array([[np.cos(alpha), 0, -np.sin(alpha)] , [0, 1, 0] , [np.sin(alpha), 0, np.cos(alpha)]])

def rot_z(alpha): return np.array([[np.cos(alpha), np.sin(alpha), 0] , [-np.sin(alpha), np.cos(alpha), 0] , [0, 0, 1]])

nib_affine[0:3, 0:3] = nib_affine[0:3, 0:3] @ rot_z(np.pi/2) @ rot_y(np.pi) @ rot_x(np.pi/2) nib_data = nib_data.T nib_data = np.swapaxes(nib_data, 0, 1) nifti_img = nib.Nifti1Image(nib_data, nib_affine, nib_img.header)

2. The loaded repetition time seemed to be wrong

In [270]: nibImg.header.get_zooms() Out[270]: (4.000000000000021, 4.000000000000001, 4.0, 4.0)

When it should be (with minc toolkit):

acquisition:repetition_time = 2. ;

So in this case I had to manually overwrite the repetition time:

nifti_img.header.set_xyzt_units(xyz="mm", t="sec") zooms = np.array(nifti_img.header.get_zooms()) zooms[3] = self._config["repetitionTimeInSec"] nifti_img.header.set_zooms(zooms)

effigies commented 5 years ago

Sorry for being so slow to get back to this.

Looking at the file format reference, it indicates that acquisition:repetition_time is optional, but the time dimension variable would have a mandatory step attribute, and this is almost certainly what we would use. Is it possible that there's an issue with your MINC file?

If you're sure that it's well-formed, would you be willing to share it (with data zeroed-out and truncated to, say, 5 volumes) so we could make it part of the test suite?

ltetrel commented 5 years ago

Hi @effigies,

I don't know if there is an issue with this file since I am not a frequent user of minc files, I just can say that I have no artifacts when I see it, and the header seems normal. So I will send you two sample volumes from ADNI that I zero-filled with mincmath -mult -constant 0 Resting_out.mnc. I am not sure how to get just 5 volumes from a minc files so I didn't do it.

So to test it, I think the idea would be to compare the output from mincheader with the output from nibabel after loading the file. I will mail you and cc a T1 file and a bold one,

hope this will help,

effigies commented 5 years ago

Hi @ltetrel, you can send an attachment directly to this username @ gmail. GitHub will strip attachments.

crocodoyle commented 5 years ago

I'm pretty sure that there are multiple conventions used by minc files and that's the source of a lot of the confusion. I tried to dig into this a while ago, and it is possible to have a variable step size per dimension (not just time), and spatial dimensions (1mm per voxel, say), sometimes have a giant array of [1, 1, 1, 1, 1, 1, ..., 1]. Sometimes they just have a single value (1). I think many tools do not quite respect the official minc spec as well

effigies commented 5 years ago

Does the standard specify an order of precedence? If so, we can work with that. It's not an easy thing to read through. A battery of example files and the correct interpretation of them would go a long way.