-
Notifications
You must be signed in to change notification settings - Fork 262
BF - only update header if affine not close #90
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
BF - only update header if affine not close #90
Conversation
Discussion at nipy#55. Because affine in nifti1 stored as float32, using == to check if affine has changed would give false positives, comparing to float64 input. Brendan Moloney kindly suggested allclose fix.
Hi Matthew, This function is meant to handle two use cases:
I considered some other variants on this function, for example one that raises an error if the affine cannot be set. Let me know what you think. On a tangent, should we let people create an image with an integer array as the affine? Bago |
@MrBago - I see a couple of issues with the set_affine method:
|
I'll fix to code to handle _affine is None better, thanks for catching that. I threw this together really quickly so I'm totally open to changing it (and writing tests) before doing a pull request, but I think something like this would be useful. The nifti sform and qform fields cannot store any 4x4 matrix so there is no way to guarantee that what's in the affine will end up on disk. You must do img.get_affine()[:] = hdr.get_best_affine() to ensure that the image is self-consistent, ie that the affine in self.get_affine() will make it to disk, especially if you're trying to create an image with sform=0. Because the current implementation requires self-consistency in order to preserve the sform_code and qform_code on save it seems especially useful to have a function that can update the affine, ensure self-consistency and potentially inform the user if their affine was changed in the process. I'm not opposed to creating similar methods for other image classes if people think they would be useful, but one thing at a time please. |
@matthew-brett - I have played around with this a bit. Seems good to me. |
Here's a compare view, just because I spent a few minutes finding how to do it: MrBago/nibabel@matthew-brett:update-header-allclose...MrBago:update-header-allclose I would like to avoid a set_affine method especially just for the Nifti images. Specifically, I want to simplify the interface to use an I'm also a bit uncomfortable about having a function change signature between image types, as I think this one must. To be clear about your usecase - you want to be able to set the sform and the affine in one call? And you want to be warned if the affine changed as it was being set? In practice I think the affine can only (allclose) change if code for sform is 0, and for qform is non-zero. What do you think should happen for sform code = 0, qform code = 0? How about the following counterproposal? Two methods unique to nifti:
with a return value as for your function? |
@MrBago - Isn't the 3x4 sform matrix sufficient for all practical uses? Maybe we could have 'set_sform' raise an exception if the affine uses the last row. Similarly 'set_qform' could raise an exception/warning if the affine transform in non-rigid and the code is not "Unknown". |
We don't need to call it set_affine, I can see why that might be an issue. I want a method such shat img.SomeMethod(affine) returns an image so that img.affine == img.header.get_best_affine(). I think this is useful because when we save to disk img.affine get's lost and it might be useful to know that your image has the same affine now as it will have later when someone reads it from disk. Only the first three rows of the img.affine matrix get saved in the sform fileds so a nifti image can can "self inconsistent" even if sform_code=1. For example:
I'm not sure why someone would do this, maybe by mistake. I think adding something like I believe that this type of method can happly co-exist with a img.affine property. |
@matthew-brett, I misunderstood your counter-proposal. |
@matthew-brett - One issue I see with your counter proposal:
Edit -> got that backwards, you would lose the sform transform (and potentially both codes) in this case. |
On Mon, Mar 12, 2012 at 4:05 PM, moloney
Well - my plan was for 'update_affine' == True (the default) to update So, if the sform is defined, then
will result in no change to the affine. Have I thought that through correctly? |
Is this kind of what you had in mind Matthew? |
Ah, I see now. Yes I think this should work. Would the boolean return value signify whether the qform/sform was correctly set in the header, or does it signify whether the affine was set in the object? If the sform is defined then setting the qform will never set the object's affine, but it could still potentially be set correctly (without any information lost) in the header. |
On Mon, Mar 12, 2012 at 5:34 PM, moloney
Good point - I guess it would be 'correctly set in header' because |
On Mon, Mar 12, 2012 at 5:32 PM, MrBago
Yes - although Brendan is right about the allclose comparing the input Also, do you think it would be reasonable to allow None for the Would you consider crafting some tests and submitting a pull request? |
On Mon, Mar 12, 2012 at 9:03 PM, Matthew Brett [email protected] wrote:
It also occurs to me that this might be confusing: img = Nifti1Image(np.zeros((2,3,4)), np.diag([2,3,4,1])) What would be the affine after that? Whatever was in the qform? What |
I don't see a problem with this this specific case.
In general if the user sets the sform_code to 0, I think the onus on the user to either set a qform or the pixdim. Otherwise the existing qfrom/pixdim in the header should be used I think. One question I have is, should the pixdim get updated by set_sform if qform is 0? I didn't completely follow the discussion about comparing the input to the the header. In the example the input is compared to header.get_sform()/get_qform(), it's not clear to me what the alternative would be. |
@MrBago - There are two questions the user may have when using the proposed set_qform / set_sform:
In you example implementation, the return value answers question 1. If we are to go this route, I would vote for answering question 2 with the return value and answering question 1 with an exception or warning. |
A possible alternative to the 'update_affine' argument to set_qform / set_sform would be to allow the user to explicitly choose the source for the image affine (either the sform or the qform) using a method like 'choose_affine_source'. If this is never called then the behavior would be the same as it currently is (prefer the sform, fall back to the qform). If it is called then the img.get_affine() and img.update_header() would always get or set the choosen header field. Then the set_sform / set_qform methods can just return True or False as they currently do in MrBago's example implementation. I think this would also make it easier to work with images that have two different affines. |
I would rather avoid the I'll draft up some tests to see if they cover the use-cases OK, and push them to this pull request. Maybe that will make the use-cases clearer. |
Ya I think you're right Matthew. I believe we talked about something like |
OK - I will push this one, and start another branch for the sform / qform draft |
BF - only update header if affine not close A temporary fix for float32 / float64 problem discovered by MrBago.
Discussion at #55. Because affine
in nifti1 stored as float32, using == to check if affine has changed
would give false positives, comparing to float64 input. Brendan Moloney
kindly suggested allclose fix.