Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BUG] Several functions under the image module do not carry over the header information to the output #4393

Open
9 of 16 tasks
man-shu opened this issue Apr 23, 2024 · 0 comments · May be fixed by #4397
Open
9 of 16 tasks
Labels
Bug for bug reports

Comments

@man-shu
Copy link
Contributor

man-shu commented Apr 23, 2024

Is there an existing issue for this?

  • I have searched the existing issues

Operating system

  • Linux
  • Mac
  • Windows

Operating system version

Mac OS Version 14.4.1 "sonoma"

Python version

  • 3.12
  • 3.11
  • 3.10
  • 3.9
  • 3.8

nilearn version

0.10.4

Expected behavior

This issue was initially reported in #2645 for math_img and was fixed in #4337 with a copy_header_from parameter. The usage is explained in detail in #4392.

I have identified other functions with the same issue since then:

  • crop_img
  • mean_img
  • threshold_img
  • binarize_img
  • resample_img
  • reorder_img
  • remove the check for number of dimensions in math_img when copy_header_from is not None, if new_img_like can handle it (explained below)

The fix could be simple - all of these functions use new_img_like to create the output nifti object with the copy_header parameter set to False, which can just be changed to True.

However, a common thing in all these functions is that they all somehow manipulate the data values/affine/dimensions of the input, which in turn reflects in the header info of the result image. So this makes copying the header a bit more complicated depending on what the function does.

As shown below for new_img_like, the header copying from new_img_like does not handle these cases well and returns wrong headers. So I think it would be worthwhile to update new_img_like first.

EDIT: As it turns out, I wasn't passing the affine into new_img_like below. I have updated the code now. Once I do this, it does seem to give the correct TR and voxel dimensions.

So new_img_like seems to handle well the changes in affine at least for resample_img. I will go over each function and use case now.

Current behavior & error messages

# header of the input_img
print(input_img.header)
# note the dim and pixdim values
<class 'nibabel.nifti1.Nifti1Header'> object, endian='<'
sizeof_hdr      : 348
data_type       : b''
db_name         : b''
extents         : 0
session_error   : 0
regular         : b'r'
dim_info        : 48
dim             : [  4  61  73  61 176   1   1   1]
intent_p1       : 0.0
intent_p2       : 0.0
intent_p3       : 0.0
intent_code     : none
datatype        : float32
bitpix          : 32
slice_start     : 0
pixdim          : [-1.  3.  3.  3.  2.  0.  0.  0.]
vox_offset      : 0.0
scl_slope       : nan
scl_inter       : nan
slice_end       : 60
slice_code      : unknown
xyzt_units      : 10
cal_max         : 0.0
cal_min         : 0.0
slice_duration  : 0.0
toffset         : 0.0
glmax           : 0
glmin           : 0
descrip         : b''
aux_file        : b''
qform_code      : scanner
sform_code      : scanner
quatern_b       : -0.0
quatern_c       : 1.0
quatern_d       : 0.0
qoffset_x       : 90.0
qoffset_y       : -126.0
qoffset_z       : -72.0
srow_x          : [-3. -0. -0. 90.]
srow_y          : [  -0.    3.   -0. -126.]
srow_z          : [  0.   0.   3. -72.]
intent_name     : b''
magic           : b'n+1'
# %% header of the result_img with default header
print(result_img.header)
# has correct pixdim[1:4] but wrong TR (pixdim[4])
# Several other fields don't match the input either
<class 'nibabel.nifti1.Nifti1Header'> object, endian='<'
sizeof_hdr      : 348
data_type       : b''
db_name         : b''
extents         : 0
session_error   : 0
regular         : b''
dim_info        : 0
dim             : [  4  31  37  31 176   1   1   1]
intent_p1       : 0.0
intent_p2       : 0.0
intent_p3       : 0.0
intent_code     : none
datatype        : float32
bitpix          : 32
slice_start     : 0
pixdim          : [1. 6. 6. 6. 1. 1. 1. 1.]
vox_offset      : 0.0
scl_slope       : nan
scl_inter       : nan
slice_end       : 0
slice_code      : unknown
xyzt_units      : 0
cal_max         : 0.0
cal_min         : 0.0
slice_duration  : 0.0
toffset         : 0.0
glmax           : 0
glmin           : 0
descrip         : b''
aux_file        : b''
qform_code      : unknown
sform_code      : aligned
quatern_b       : 0.0
quatern_c       : 0.0
quatern_d       : 0.0
qoffset_x       : -90.0
qoffset_y       : -126.0
qoffset_z       : -72.0
srow_x          : [  6.   0.   0. -90.]
srow_y          : [   0.    6.    0. -126.]
srow_z          : [  0.   0.   6. -72.]
intent_name     : b''
magic           : b'n+1'
# %% header copied via new_img_like
print(result_img_with_header.header)
# has correct pixdim[1:4] and TR (pixdim[4])
# All other important fields are appropriately updated too
<class 'nibabel.nifti1.Nifti1Header'> object, endian='<'
sizeof_hdr      : 348
data_type       : b''
db_name         : b''
extents         : 0
session_error   : 0
regular         : b'r'
dim_info        : 48
dim             : [  4  31  37  31 176   1   1   1]
intent_p1       : 0.0
intent_p2       : 0.0
intent_p3       : 0.0
intent_code     : none
datatype        : float32
bitpix          : 32
slice_start     : 0
pixdim          : [1. 6. 6. 6. 2. 1. 1. 1.]
vox_offset      : 0.0
scl_slope       : nan
scl_inter       : nan
slice_end       : 60
slice_code      : unknown
xyzt_units      : 10
cal_max         : 18028.748
cal_min         : 0.0
slice_duration  : 0.0
toffset         : 0.0
glmax           : 0
glmin           : 0
descrip         : b''
aux_file        : b''
qform_code      : unknown
sform_code      : aligned
quatern_b       : 0.0
quatern_c       : 0.0
quatern_d       : 0.0
qoffset_x       : -90.0
qoffset_y       : -126.0
qoffset_z       : -72.0
srow_x          : [  6.   0.   0. -90.]
srow_y          : [   0.    6.    0. -126.]
srow_z          : [  0.   0.   6. -72.]
intent_name     : b''
magic           : b'n+1'

Steps and code to reproduce bug

# code to see above output

# %% import stuff
from nilearn import datasets
from nilearn.image import load_img, resample_img, new_img_like
import numpy as np

# %% get a sample dataset
dataset = datasets.fetch_adhd(n_subjects=1)
input_img = load_img(dataset.func[0])
# header of the input_img
print(input_img.header)
# note the dim and pixdim values

# %% downsample to 6mm isotropic,
# we expect the pixdim[1:4] to change to 6
# rest should remain the same
result_img = resample_img(input_img, target_affine=np.diag((6, 6, 6)))

# %% header of the result_img with default header
print(result_img.header)
# has correct pixdim[1:4] but wrong TR (pixdim[4])
# Several other fields don't match the input either

# %% explicitly copy the header with new_img_like from input_img to result_img
result_img_with_header = new_img_like(
    ref_niimg=input_img,
    data=result_img.get_fdata(),
    affine=result_img.affine,
    copy_header=True,
)

# %% header copied via new_img_like
print(result_img_with_header.header)
# has correct pixdim[1:4] and TR (pixdim[4])
# All other important fields are appropriately updated too
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug for bug reports
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant