Closed psadil closed 2 years ago
SynthStrip inputs require some massaging, which are done with the freesurfer python bundle I didn't want to have in MRIQC.
I thought my implementation was similar to the original, but there must be some problem. It would be nice to look at the images that MRIQC and the SynthStrip container generate and pass along into the model.
Would you like to do that debugging?
cc/ @ahoopes
If it helps, I've updated the synthstrip code to no longer rely on the internal 'freesurfer' python package. Now it uses the 'surfa' library, which is available on pip and should be relatively lightweight. Might be worth updating the MRIQC implementation as well to keep things consistent.
If it helps, I've updated the synthstrip code to no longer rely on the internal 'freesurfer' python package. Now it uses the 'surfa' library, which is available on pip and should be relatively lightweight. Might be worth updating the MRIQC implementation as well to keep things consistent.
I'll let @oesteban , comment on whether depending on surfa
is an option.
I have a bit of time now to try debugging. But when trying to run the model through either the MRIQC or current FS implementations, memory usage jumps way higher than what when using the SynthStrip container (i.e., to above 50GB). Is that expected? Are there settings in the SynthStrip container that keep memory usage lower?
PyTorch tends to use a lot more memory than it necessarily needs to (if space is available). Docker has a default setting that might limit container memory on your machine - maybe it's related to that? I have not set anything specific in the SynthStrip configuration.
PyTorch tends to use a lot more memory than it necessarily needs to (if space is available). Docker has a default setting that might limit container memory on your machine - maybe it's related to that? I have not set anything specific in the SynthStrip configuration.
Something to do with Docker seems plausible, though I'm not sure. I suppose that's not critical. FWIW, bringing in the cpuonly package results in memory usage comparable to when the container is run.
@oesteban, I think there might be an issue in how mriqc
prepares the input for SynthStrip. A clue was that, when testing on an image with shape (256, 256, 176)
, the variable input_tensor
as given to the model ends up with shape (1,1,256,256,192)
. At the analogous step in mri_synthstrip
, the shape is (1,1,256,192,256)
. I think this difference comes from how the target shape is calculated. For example, mriqc
uses the coordinates of the corner voxels e.g.,, but I think that result is in the input space when it should be in the output--accounting for rotations to get there.
The image that I'm working with has affine
nb.load('test.nii.gz').affine
array([[ 1. , 0. , 0. , -122. ],
[ 0. , 1. , 0. , -122. ],
[ 0. , 0. , 1. , -102.5],
[ 0. , 0. , 0. , 1. ]])
Conformation swaps the second and third axes. The original z-axis was rather narrow, and so the brain in the resulting image gets cropped. I guess that's the source of the sharp edge.
Perhaps it would work to use the target_affine
when calculating the corners in mm?
# ...calculate target_affine above here...
# Get corner voxel centers in mm
corners_xyz = np.abs(
affine
@ target_affine
@ np.hstack((corner_centers_ijk, np.ones((len(corner_centers_ijk), 1)))).T
)
Using that gives a better mask, see below. As above, the mask from synthstrip-docker
is in white and the one from the (updated) script is in blue.
Hi Maintainers,
Following up on this. Did that patch sound feasible? Or, how about adding a dependence on surfa
, following the official SynthStrip
implementation? If either of those sound okay, I'd be happy to try submitting a pull request.
Hi @psadil - sorry you caught me on vacation. Yes, your patch sounds reasonable. We'd love to see your PR adding it. Let's fix this bug first and then consider the dependency on surfa
, if we believe that will be the best option.
Actually, I'm not completely sure of your fix, the target_affine
has an offset which happens to work with you image but may not work anywhere else. Could you share this particular image with me?
I can't share that image, but I may be able to share another that shows the same issue. If this is the problem, then it might also be that the sharp edge can be elicited by manually clipping the z-axis. Let me look in to this, and I'll get back to you in the next few days.
Perhaps it would be better if you could just report the original shape and affine of that failing image. I would work from there.
Sure, they are
dcmmeta_shape: [256, 256, 176]
dcmmeta_affine: [
[1.0, 0.0, 0.0, -122.0],
[0.0, 1.0, 0.0, -117.0],
[0.0, 0.0, 1.0, -102.5],
[0.0, 0.0, 0.0, 1.0]
]
Yes, it seems our reorientation is not producing the right ordering at the output.
Just to confirm: was it still going to be helpful to see another example? It's my impression that the reorientation you mention is the cause of the sharp masks (when combined with a relatively small axis)
Thanks for your patience, really hard for me to focus on MRIQC these days.
Yes, I'm pretty sure this is a result of the wrong reorientation happening. Let me first try to figure it out without more data. Thanks!
Thanks for your patience, really hard for me to focus on MRIQC these days.
Thanks for the fix, and in general for maintaining this package!
With the current version of MRIQC (22.0.1), some proportion of participants end up with brain masks that have a sharp boundary that excludes part of the occipital pole.
I haven't noticed a pattern in the scans that end up with masks that look like this. However, when I run
synthstrip
outside of MRIQC on the output of the pre_n4 node (which pulls docker imagefreesurfer/synthstrip:1.0
)I get a mask that looks better. Here is a comparison, with the MRIQC mask in blue and the
synthstrip-docker
version in white.This is how MRIQC is being called:
Let me know if I should ask about this elsewhere, or if there are further suggestions for digging into this.