nipy / nibabel

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

Suggestion: raise exception if slope or inter has been set if saving nibabel image to Analyze format #643

Open sohrabtowfighi opened 6 years ago

sohrabtowfighi commented 6 years ago

I work with both nifti and analyze format. My image object was a Nifti1Image, but the filepath to which I was saving had the '.hdr' extension, so nibabel saved as Analyze format. When I reloaded the image, the scaling was not applied, and I had no idea what was going on. Maybe consider this for a future version.

effigies commented 6 years ago

If you have a Nifti1Image and save to *.hdr, it should save as a Nifti1Pair... What specific code did you use to save it, and was your goal actually to have an Analyze image instead of NIfTI?

sohrabtowfighi commented 6 years ago

I used nib.save(img, img_path) to save the image.

I wanted to scale the image, move the original image file somewhere for safe keeping, then save the new image to where the old image file was, so presumably I wanted to save as the same format as the original file, but I did not realize that the slope and intercept are not valid attributes for the original file format.

effigies commented 6 years ago

I see. So you started with an AnalyzeImage, not a Nifti1Image?

I believe Analyze images are valid NIfTI-1 images, so you should be able to do the following:

import nibabel as nb

img = nb.Nifti1Pair.from_filename(img_path)
...

I've verified that a Nifti1Image saved to a out.hdr filename will produce something that will be interpreted as a Nifti1Pair:

In [1]: import nibabel as nb

In [2]: import numpy as np

In [3]: nb.save(nb.Nifti1Image(np.zeros((5,5,5), dtype=np.uint8), np.eye(4)), 'out.hdr')

In [4]: nb.load('out.hdr')
Out[4]: <nibabel.nifti1.Nifti1Pair at 0x111a35320>

Also, I would generally not directly modify the scale and intercept, but rather, modify the values, and allow the writing routine to select appropriate scales and intercepts. It's harder to end up in a situation with surprising values. Is there a specific reason for re-scaling in this way?

sohrabtowfighi commented 6 years ago

Im not super sure if it was hdr or img anymore.

Regarding modifying signal intensities ditectly, I tried modifying the values directly before then got an error saying the array needs scaling but cannot scale. In addition the documentation shows an example with setting the slope and intercept so I had no idea that it's not the best approach.

Anyways, if the image being saved does not support intercept and slope but the image has a slope and intercept that is not None or m=1,b=0 (as given by the get_slope_inter method) then it makes sense that it raise exception.

effigies commented 6 years ago

I'm sorry if I'm being obtuse, but perhaps it would be clearer if you could give replicating code that you think ought to have raised the exception you're asking for.

Regarding modifying signal intensities ditectly, I tried modifying the values directly before then got an error saying the array needs scaling but cannot scale.

This sounds like the error you're requesting. What did you then do, that you expected would change the slope and intercept? set_slope_inter ought to have failed for an AnalyzeImage:

https://github.com/nipy/nibabel/blob/5c9dfffc33b13046751c686587ea1791dd164656/nibabel/analyze.py#L799-L800

And would loading the file as a Nifti1Pair before modifying the values resolve your issue?

sohrabtowfighi commented 6 years ago

I called set_slope_inter against a Nifti1Image, which I then saved as an AnalyzeImage or Nifti1Pair

The call to set_slope_inter worked fine. When I subsequently called get_slope_inter against the Nifti1Image, I got the slope and intercept I assigned, but when I saved it, I saved it with a filepath that was not ending in '.nii' so nibabel saved it in a different format, without telling me that the slope and intercept would be discarded.

sohrabtowfighi commented 6 years ago
                old_img = nb.load(image_cbv[0])
                affine, did_change = r.correct_origin(dc(old_img.affine))
                new_img = nb.Nifti1Image(dc(new_image_data_cbv), affine)
                new_img = intensity_correction.transform(new_img, 10) # <- in here I use set_slope_inter
                # move the old faulty image to the faulty cases directory
                move_image(image_path, new_img_path)
                # in the original data directory, replace the faulty image
                # with the one we generated 
                image_path = replace_ext(image_path, 'nii') # <- if this line is commented, issue arises
                nb.save(new_img, image_path)
effigies commented 6 years ago

To reproduce, I'll need to know the definitions of:

I assume move_image is just os.rename, possibly with a os.makedirs thrown in to make sure there's a target directory?

And I can guess how replace_ext works.

sohrabtowfighi commented 6 years ago

Use this as a minimum working example.

import nibabel as nb
from copy import deepcopy as dc 
import os

nii_file = "C:/Users/sohra/Desktop/1010_brain_mr_02.nii"
img_file = nii_file[:-4] + '.img'
img = nb.load(nii_file)
nii = nb.Nifti1Image(dc(img.get_data()), dc(img.affine), dc(img.header))
nii.header.set_slope_inter(10,2)
nb.save(nii, img_file)    
nii.header.get_slope_inter()
nii.get_data().max()
new = nb.load(img_file)
new.get_data().max()