-
Notifications
You must be signed in to change notification settings - Fork 26
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
RF/FIX: Scale all phase maps to [0, 2pi]
range
#88
Conversation
sdcflows/interfaces/fmap.py
Outdated
data -= mode(data, axis=None)[0][0] | ||
if np.allclose((data.min(), data.max()), (-np.pi, np.pi), atol=0.01): | ||
# Already in rads, but wrap to 0 - 2pi | ||
data[data < 0] += 2 * np.pi |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why only negatives?
data[data < 0] += 2 * np.pi | |
data -= data.min() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because this function sets the range to (0, 2pi), not (pi, 3pi). And it seems unwise to go precisely counter-phase by adding np.pi
to everything.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I had the suggestion above sign-flipped. I've fixed it.
This is going to wrap and scale only negatives - you don't want either (and even less only for the lower half of the histogram).
If I understand correctly, the initial range goes from -pi to pi. Adding pi will move the range to 0-2pi
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So does fieldmap correction treat (θ±π) the same? Shifting by half phase feels problematic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To be clear, this is equivalent to:
data = (data + 2 * np.pi) % (2 * np.pi)
But without bothering add, divide and round things in the (0, pi) range. It doesn't feel controversial to me. data += np.pi
does.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So does fieldmap correction treat (θ±π) the same? Shifting by half phase feels problematic.
FSL FUGUE theoretically only unwraps phase maps in the (0, 2π) range. It seems to work with (-π, π), though.
I don't know why data -= np.pi
is controversial - it just moves all values from (-π, π) to (0, 2π).
The transformation you propose will wrap negative phase values (which you definitely don't want to do).
Normally, a phase maps' histogram looks like a centered pyramid. Doing this, we are selecting the left side of the pyramid, shifting it to overlap the right side of the pyramid and finally checking no values have gone over 2pi. That will introduce another wrapping that FUGUE might or might not pick up.
EDIT: The shifted intensities will not overlap, now that I look into this more carefully, but the final distribution will be like a V, with the notch at pi.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's talk in the meeting.
sdcflows/interfaces/fmap.py
Outdated
# Scale upper tail | ||
data[data > 0] = np.pi * data[data > 0] / data[data > 0].max() | ||
# Scale upper tail | ||
data[data > 0] = np.pi * data[data > 0] / data[data > 0].max() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This (the old code) looks problematic - we shift the left side of the pyramid first, hence making it overlap with the right hand side. Operations should be done the other way around.
Okay, sorry for the noise. There is no shifting yet.
Results of live discussion: Replace mode-centering and separately rescaling positive and negative to simply scaling min/max to 0/2pi. This would affect fieldmaps from all scanners. @mattcieslak @Aksoo Your input here would be appreciated. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a couple explainers before you get to asking. :-)
@@ -663,28 +663,19 @@ def au2rads(in_file, newpath=None): | |||
"""Convert the input phase difference map in arbitrary units (a.u.) to rads.""" | |||
from scipy.stats import mode | |||
im = nb.load(in_file) | |||
data = im.get_fdata(dtype='float32') | |||
data = im.get_fdata(caching='unchanged') # Read as float64 for safety |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm extremely comfortable using double memory for one volume to keep things deterministic.
|
||
hdr.set_data_dtype(np.float32) | ||
hdr.set_xyzt_units('mm') | ||
out_file = fname_presuffix(in_file, suffix='_rads', newpath=newpath) | ||
nb.Nifti1Image(data, im.affine, hdr).to_filename(out_file) | ||
nb.Nifti1Image(data, None, hdr).to_filename(out_file) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you're keeping the affines in the header, there's no need to pass an affine in to override.
I also had doubts about mode centering. This looks simpler and cleaner. |
543786e
to
b16156c
Compare
b16156c
to
410365a
Compare
@mgxd I see the CI has gotten clogged. Naturally. If it makes life easier, I can cancel this and master and just merge this to only have one job in queue. |
Doesn't the current master CI needs to pass to upload the new version to PyPI? I'm fine with cutting corners, but would like to see a green check here prior to merging just for my peace of mind |
Okay. I'll leave this one up. Still cancelled master since I rebased this on master. We'll push to PyPI with the tag: https://circleci.com/workflow-run/52c4f144-141b-41ed-acad-e218e1b65f2a |
[0, 2pi]
range
Best reviewed: with all changes
Optimal code review plan (2 commits squashed)
|
Final decision: Instead of making
[-pi, pi]
a special case, all data will be naively scaled to[0, 2pi]
. This ensures that the source data intervals are respected, but may result in a central mode slightly off ofpi
.We judge that the simplicity and consistency outweighs any small adjustments and potential for bugs resulting from a mode-based transform with separate treatment of the left and right tail.
I have some Philips Achieva 2-echo phase/magnitude fieldmaps that come out of
dcm2niix
with a range of approximately(-pi, pi)
.This adjusts au2rads to detect that case and simply wraps the negative values to
(pi, 2pi)
.This may resolve #83.
cc @ins0mniac2