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

[FIX] Headers not carried over by math_img #4337

Merged
merged 16 commits into from Apr 8, 2024
Merged

Conversation

man-shu
Copy link
Contributor

@man-shu man-shu commented Mar 21, 2024

Changes proposed in this pull request:

  • [x] copy last image's header in the result image (i.e. if the call is math_img("img1+img2", img1, img2), the result would have img2's header)
  • copy user-specified image's header in the result image
  • [x] check if the images have the same TRs, if not throw the "Input images cannot be compared..." error
  • check if the number of dimensions is the same between that user-specified image and the result image. Checks for affine and shapes already exist.
  • add a short example to demo the usage of the new parameter
  • [ ] test the exceptions and equality of TRs
  • test for the error when the number dimensions are not the same
  • test for equality of header (or just the TR) when the correct copy_header_from is passed
  • test for the error when a non-existent variable is passed in copy_header_from
  • test for default header (or TR being 1.0) when the copy_header_from is None
  • test for cases where data values are changed, that changes the cal_max and cal_min fields in the header, but the rest remains the same.

Copy link
Contributor

👋 @man-shu Thanks for creating a PR!

Until this PR is ready for review, you can include the [WIP] tag in its title, or leave it as a github draft.

Please make sure it is compliant with our contributing guidelines. In particular, be sure it checks the boxes listed below.

  • PR has an interpretable title.
  • PR links to Github issue with mention Closes #XXXX (see our documentation on PR structure)
  • Code is PEP8-compliant (see our documentation on coding style)
  • Changelog or what's new entry in doc/changes/latest.rst (see our documentation on PR structure)

For new features:

  • There is at least one unit test per new function / class (see our documentation on testing)
  • The new feature is demoed in at least one relevant example.

For bug fixes:

  • There is at least one test that would fail under the original bug conditions.

We will review it as quick as possible, feel free to ping us with questions if needed.

@bthirion
Copy link
Member

Maybe also include resampling functions in this PR ?

@man-shu
Copy link
Contributor Author

man-shu commented Mar 22, 2024

I ran into a use case where one could remove the time dimension in the expression to math_img, for example, by taking a mean over time:

from nilearn import image
from pathlib import Path
import numpy as np
from nibabel import Nifti1Image
from nilearn._utils.data_gen import generate_fake_fmri

def generate_fake_fmri_with_tr(tr=1.0):
    # Generate a fake fmri image
    img, mask = generate_fake_fmri()
    # Change the TR of the image
    header = img.header.copy()
    header["pixdim"][4] = tr
    img = Nifti1Image(img.get_fdata(), img.affine, header)
    return img, mask

img1, mask = generate_fake_fmri_with_tr(2)
img2, mask = generate_fake_fmri_with_tr()

# check the TRs of the two images
print(img1.header.get_zooms()[3])
print(img2.header.get_zooms()[3])

# mean over time dimension and subtract the two images
result = image.math_img(
    "np.mean(img1, axis=-1) - np.mean(img2, axis=-1)", img1=img1, img2=img2
)
# will return a ValueError that TRs are different (if you switch to this branch)

In this case, it does not make sense to throw an error if img1 and img2 have different TRs.
Plus, copying the header from either input image to result would be invalid.

Any ideas on how we could separate out such events? @bthirion @Remi-Gau

@man-shu
Copy link
Contributor Author

man-shu commented Mar 22, 2024

Seems like a job for regex to me..

@bthirion
Copy link
Member

Oops. It looks very hard to me to handle properly all cases To me his would justify not to copy the whole header by default...

@man-shu
Copy link
Contributor Author

man-shu commented Mar 25, 2024

I agree. The solution I am trying to implement uses:

  • regex to identify individual operations (from numpy, math)
  • doing eval on each of them
  • then another eval on the remaining basic mathematical operations (* / + -)

Will try to push it soon. But this might be difficult to maintain in long-term, given the complexity of regex.

Same issues would apply to resample_img also, which only handles spatial resampling as of now.
So comparing time resolutions doesn't seem useful to me.

Speaking of resampling only handling spatial resampling we should probably clarify that in the docs.

@bthirion
Copy link
Member

Maybe just add an argument to copy the header information, that would be False by default, that people could use at their own risk. Demoing the feature would be relevant.

@man-shu
Copy link
Contributor Author

man-shu commented Apr 2, 2024

I think it would also be useful to allow the users to select which img's header to copy.

@man-shu
Copy link
Contributor Author

man-shu commented Apr 2, 2024

So now the user can specify which image to copy the header from. As long as this image and the result image have same dimensions, it will copy the header:

from nilearn import image
from nibabel import Nifti1Image
from nilearn._utils.data_gen import generate_fake_fmri


def generate_fake_fmri_with_tr(tr=1.0):

    # Generate a fake fmri image
    img, mask = generate_fake_fmri()

    # Change the TR of the image
    header = img.header.copy()
    header["pixdim"][4] = tr
    img = Nifti1Image(img.get_fdata(), img.affine, header)
    return img, mask


img1, mask = generate_fake_fmri_with_tr(2)
img2, mask = generate_fake_fmri_with_tr()

# check the TRs of the two images
print(img1.header.get_zooms())
print(img2.header.get_zooms())

# try adding the two images
img_sum = image.math_img(
    "np.mean(img1, axis=-1) - np.mean(img2, axis=-1)",
    img1=img1,
    img2=img2,
    copy_header_from="img1",
)
# will throw Value error: The result of the formula has a different number of dimensions than the image to copy the header from.


# user could instead mean the images before subtraction
img1_mean = image.mean_img(img1)
img2_mean = image.mean_img(img2)
img_sum = image.math_img(
    "img1 - img2", img1=img1_mean, img2=img2_mean, copy_header_from="img1"
)
# this will work

Copy link

codecov bot commented Apr 2, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 92.15%. Comparing base (abb80ff) to head (634ee6d).
Report is 46 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4337      +/-   ##
==========================================
+ Coverage   91.85%   92.15%   +0.30%     
==========================================
  Files         144      143       -1     
  Lines       16419    16501      +82     
  Branches     3434     3465      +31     
==========================================
+ Hits        15082    15207     +125     
+ Misses        792      749      -43     
  Partials      545      545              
Flag Coverage Δ
macos-latest_3.10_test_plotting 91.95% <100.00%> (?)
macos-latest_3.11_test_plotting 91.95% <100.00%> (+0.09%) ⬆️
macos-latest_3.12_test_plotting 91.95% <100.00%> (?)
macos-latest_3.9_test_plotting 91.91% <100.00%> (?)
ubuntu-latest_3.10_test_plotting 91.95% <100.00%> (+0.09%) ⬆️
ubuntu-latest_3.11_test_plotting 91.95% <100.00%> (?)
ubuntu-latest_3.12_test_plotting 91.95% <100.00%> (?)
ubuntu-latest_3.12_test_pre 91.95% <100.00%> (?)
ubuntu-latest_3.8_test_min 68.84% <100.00%> (?)
ubuntu-latest_3.8_test_plot_min 91.61% <100.00%> (?)
ubuntu-latest_3.8_test_plotting 91.91% <100.00%> (?)
ubuntu-latest_3.9_test_plotting 91.91% <100.00%> (?)
windows-latest_3.10_test_plotting 91.92% <100.00%> (?)
windows-latest_3.11_test_plotting 91.92% <100.00%> (?)
windows-latest_3.12_test_plotting 91.92% <100.00%> (?)
windows-latest_3.8_test_plotting 91.88% <100.00%> (?)
windows-latest_3.9_test_plotting 91.89% <100.00%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@man-shu
Copy link
Contributor Author

man-shu commented Apr 4, 2024

I think this PR is big enough already. I will make a separate one to handle resample_img issues.

@man-shu man-shu marked this pull request as ready for review April 4, 2024 13:56
@Remi-Gau
Copy link
Collaborator

Remi-Gau commented Apr 4, 2024

Still at a conference. Don't have time for an in depth review just yet, but you may want to check why a test is failing with the oldest dependency.

@man-shu
Copy link
Contributor Author

man-shu commented Apr 4, 2024

Yeah, it's a failure in doctest. I am downloading a dataset in the short example right under the reference to math_img function, that seems to fail for some reason...

@man-shu man-shu marked this pull request as draft April 4, 2024 16:32
Copy link
Member

@bthirion bthirion left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM overall.
Thx !

nilearn/image/tests/test_image.py Outdated Show resolved Hide resolved
@man-shu
Copy link
Contributor Author

man-shu commented Apr 5, 2024

Ok, so @Remi-Gau and I found that the doctest is failing because I am trying to fetch a dataset in the docstring example, and that is currently not possible in the testing runs on the CI.

So writing a full example to demo the copy_header_from functionality would be the solution. Additionally, we can go into full details of what works, and what does not in that example.

I looked into other full examples on math_img but describing this feature in those examples looks a bit out of the scope.

So, I am now writing a separate standalone example for this.

@Remi-Gau
Copy link
Collaborator

Remi-Gau commented Apr 8, 2024

So, I am now writing a separate standalone example for this.

maybe we can have this in a separate PR, so we can already merge the fix?

nilearn/conftest.py Outdated Show resolved Hide resolved
@man-shu man-shu marked this pull request as ready for review April 8, 2024 10:43
Copy link
Collaborator

@Remi-Gau Remi-Gau left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually we probably want to update the changelog before merging

@man-shu
Copy link
Contributor Author

man-shu commented Apr 8, 2024

Actually we probably want to update the changelog before merging

Good to know. I wasn't doing that in previous PRs. Done now!

@man-shu man-shu requested a review from Remi-Gau April 8, 2024 12:46
@@ -49,3 +49,5 @@ Changes

- :bdg-dark:`Code` Use red to blue color map in the GLM reports (:gh:`4266` by `Rémi Gau`_).
- :bdg-dark:`Code` :class:`nilearn.maskers.NiftiSpheresMasker` will throw warnings if the ``labels`` passed to it is not a :obj:`list` of :obj:`str`, or if the number of items in the list of labels does not match the number of regions in the label image (:gh:`4274` by `Rémi Gau`_).

- :bdg-dark:`Code` Copy headers from user-specified image to the result of :func:`nilearn.image.math_img` (:gh:`4337` by `Himanshu Aggarwal`_).
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it just a change or actually a fix?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was a bit confused about that, too.

But here's my thinking behind tagging it as a change:
The default behavior is still what was initially reported. We are just giving the user an option to copy the header if they want.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK makes sense: I got confused because it was closing an issue filed as bug, so I would have seen this as a fix.

that's fine then.

@Remi-Gau
Copy link
Collaborator

Remi-Gau commented Apr 8, 2024

@bthirion had already approved so I will got ahead and merge this.

@Remi-Gau Remi-Gau merged commit c153676 into nilearn:main Apr 8, 2024
34 checks passed
@man-shu man-shu deleted the fix/2645 branch April 8, 2024 14:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

math_img loses header information
3 participants