-
Notifications
You must be signed in to change notification settings - Fork 301
[REVIEW] Fallback to initial registration, if BBR fails #694
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
Conversation
8793bc7
to
aaec592
Compare
ad9b29b
to
c9eb35e
Compare
This is ready for a review. It's working locally on @jdkent's dataset. Both FreeSurfer and FSL are falling back to The only thing left to do is fix cost parsing for non-BBR FLIRT so that we can compare registration performance to determine phase-encoding direction for SyN-SDC. Depends: nipreps/niworkflows#195. |
fmriprep/workflows/bold.py
Outdated
FunctionalSummary(output_spaces=output_spaces, | ||
registration='FreeSurfer' if freesurfer else 'FSL', | ||
registration_dof=bold2t1w_dof), | ||
name='summary', mem_gb=DEFAULT_MEMORY_MIN_GB) |
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.
run_without_submitting=True
perhaps?
fmriprep/workflows/util.py
Outdated
name='bbregister') | ||
|
||
transforms = pe.Node(niu.Merge(2), name='transforms') |
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.
All these new nodes should run_without_submitting=True
?
fmriprep/workflows/util.py
Outdated
|
||
fallback = any((max_trans > 2, rot > np.pi / 36, max_scale > 1.1)) | ||
|
||
return 0 if fallback else 1 |
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 not keeping the boolean type here (and downstream)?
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.
It was intended as an index, but I can reverse these. That probably would be cleaner.
fmriprep/workflows/util.py
Outdated
rot = mat2axangle(rotation_matrix)[1] | ||
max_scale = np.max(np.abs(scales)) | ||
|
||
fallback = any((max_trans > 2, rot > np.pi / 36, max_scale > 1.1)) |
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.
2mm is not too small?
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.
That might be. What seems like a reasonable translation threshold? It seems like it shouldn't be more than a couple voxels, right? (I think I picked 2 as a reasonable "1 voxel" threshold, but it was admittedly a quick and dirty guess.)
a4e1ba6
to
80565ec
Compare
fmriprep/workflows/util.py
Outdated
|
||
shift_magnitude = np.sqrt(trans.dot(trans)) # 2-norm | ||
rot = mat2axangle(rotation_matrix)[1] | ||
max_scale = np.max(np.abs(scales)) |
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.
Two changes here:
- Use the 2-norm to get total shift, not just max in any direction
- Increase shift threshold to 5mm
3a0847b
to
bf747e8
Compare
The translation differences are large, and I believe it's a function of the .mat file format.
It may be that we just need to drop the translation check altogether. |
👍 I was ok with checking only the scaling and so I remain. The center
being in a corner will also change the concept of angle, so those
parameters will not be "trustworthy" either.
…On Sep 20, 2017 07:43, "Chris Markiewicz" ***@***.***> wrote:
The translation differences are large, and I believe it's a function of
the .mat file format
<https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/FLIRT/FAQ#What_are_FLIRT_schedule_files.3F>
.
The FSL convention for transformation matrices uses an implicit centre of
transformation - that is, a point that will be unmoved by that
transformation, which is an arbitrary choice in general. This arbitrary
centre of the transformation for FSL is at the mm origin (0,0,0) which is
at the centre of the corner voxel of the image.
When using the transformation parameters from FLIRT, there is an
additional complication in that the parameters are calculated in a way that
uses a different centre convention: the centre of mass of the volume. The
effect of this is that each of the three matrices above end up with an
adjustment in the fourth column (top three elements only) that represents a
shift between the corner origin and the centre of mass, while the rest of
the matrix (first three columns) is unaffected. Once that is done the
matrices are multiplied together, as indicated above, and you get your
final matrix.
It may be that we just need to drop the translation check altogether.
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#694 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAkhxsmkMbs3C5Jvmxw3KsXZVO3kTaUjks5skSSNgaJpZM4PQSXp>
.
|
Right. I was just playing with converting to LTA, and got identical scaling parameters but quite different translation and rotation angles. |
aed0038
to
f271f3d
Compare
Looking at a lot of these, I'm not sure that there's a clear scaling threshold. Some are mostly rotations/translations, and a small rotation with a very large translation can cause as much distortion as a small(ish) translation with a large rotation. One possibility could be to have a threshold on some function of rotation and translation... I'm going to try converting to LTA, which I believe uses the RAS=(0,0,0) origin, which should at least be no worse than FSL's corner origin, and may result in more obvious thresholds. I think there's a possibility we'll need override options, no matter how we decide this, allowing users to force BBR or fallback when our heuristics fail. |
591af0d
to
f840db0
Compare
Just as an update on this, I'm definitely not getting the level of distortion @jdkent was in his original post. For the most part, the bbregister results look reasonable, while the FLIRT+BBR results are being rejected. @jdkent Do you think you could re-run that subject with the latest release? I'm a little concerned that I'm no longer replicating the issue and getting bad metrics to find cut-offs for. |
I'll re-reun with the latest release and report back |
f840db0
to
6989314
Compare
I think I'm converging on using the "norm" from ArtifactDetect, which is a kind of compromise between @chrisfilo's idea of using framewise displacement and my earlier approach of using a differential affine matrix. The norm approach took a series of motion parameters, constructed affines, and finds the norm of the displacement, which is the maximum displacement of the centers of a bounding cube, and thus takes into account rotations, translations and scales (and shears, but we're only using 9-dof transforms). With nipy/nipype#2198, I separate the norm calculation from constructing affines from the motion parameters, allowing us to directly pass the affine transform matrices. The affines in LTA format (RAS2RAS) seem to produce differences of norms that correspond intelligibly with distortion, with norm > 20 being a pretty reliable indicator of a bad BBR, and < 10 indicating good. The exact threshold isn't obvious, with some that I marked "accept" and "reject" falling on either side as I moved between 15 and 20. For now I've put 15, and I'll see what you all think. I'll package up the spreadsheet and visualizations first thing in the morning. Given that I still haven't found a clear threshold, I'm inclined to add |
60c51b6
to
e88a8a7
Compare
This is ready to review. I still like the affine difference we're taking, and think the main thing that needs to be decided is if we want a more stringent threshold. |
Alright, I ran the subject with --force-bbr and without the flag, and the results are much improved, just not identical to @effigies. I ran it with a clear PYTHONPATH and in different working directories, one thing that isn't clean is the fact that I'm using an existing freesurfer directory, and not re-rerunning recon-all. However, the function did work and improved my results, so I don't think I should be holding up this pull request any longer. |
786db5b
to
8b4ec04
Compare
@oesteban Rebased and ready for review. |
@jdkent Your "without the flag" one looks very similar to mine, so I'm glad we're in a similar place. How do you feel about that threshold, by the way? We can make it more likely to reject that first run. The more I look at these results, the more I think we might want to make the threshold a little more stringent... Thanks for testing this out for us, by the way. |
oh, I thought the bbregister result was rejected for all the registrations. Then yes, a more stringent threshold could catch that case and improve the registration. I just don't know how much optimization for my case would sacrifice the generalization of the threshold to other datasets. Also, thank you for making this great tool, the work you are doing is awesome. |
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 some cosmetic comments. Great work. Thanks @jdkent for following up.
fmriprep/workflows/util.py
Outdated
from niworkflows.interfaces.masks import SimpleShowMaskRPT | ||
|
||
from ..interfaces.images import extract_wm | ||
|
||
DEFAULT_MEMORY_MIN_GB = 0.01 | ||
|
||
|
||
def compare_xforms(lta_list, norm_threshold=15): | ||
import numpy as np |
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.
Please add a docstring with a link to the original issue to get quickly to the discussion and examples of how the threshold plays out (e.g. 10 = very small affine changes accepted and 20 = large changes accepted). Something along these lines.
"""
Computes a *distance* between two transforms as the maximum overall displacement of
the midpoints of the faces of a cube due to translation and rotation. See discussion
`here <https://github.com/poldracklab/fmriprep/issues/681>`_.
Parameters
lta_list : list or tuple of str
the two given affines in LTA format
norm_threshold : int
the upper bound limit to the distance of the second transform w.r.t. the first; the lower
the smaller distances are accepted; a default of 15 was found empirically appropriate
to decide whether BBR transforms should be rejected.
"""
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.
Added link to original issue.
BBRegisterRPT(dof=bold2t1w_dof, contrast_type='t2', init='coreg', | ||
registered_file=True, out_lta_file=True, generate_report=True), | ||
name='bbregister') | ||
mri_coreg = pe.Node( |
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.
Does MRICoreg generate a new T1w space with the appropriate xforms? I'll need to get this clear before I start with #745.
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.
No. mri_coreg
only generates a transform matrix. If you want to apply the transform, you can use mri_vol2vol
(see MRICoregRPT or mri_convert
).
@@ -174,7 +194,7 @@ def init_skullstrip_bold_wf(name='skullstrip_bold_wf'): | |||
return workflow | |||
|
|||
|
|||
def init_bbreg_wf(bold2t1w_dof, name='bbreg_wf'): | |||
def init_bbreg_wf(use_bbr, bold2t1w_dof, omp_nthreads, name='bbreg_wf'): |
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.
An additional paragraph describing the feature being added would be very valuable. Just a couple of sentences to describe what happens if use_bbr=None
.
]) | ||
|
||
return workflow | ||
|
||
|
||
def init_fsl_bbr_wf(bold2t1w_dof, name='fsl_bbr_wf'): | ||
def init_fsl_bbr_wf(use_bbr, bold2t1w_dof, name='fsl_bbr_wf'): |
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.
Refer to previous workflow regarding use_bbr
The easiest way to read this PR is to separately review #741, and read effigies/fmriprep@fsfix_common...bbr_fallback.
Closes #681.