Skip to content

Conversation

@larsoner
Copy link
Member

@larsoner larsoner commented Jun 24, 2021

@alexrockhill can you take over the first two steps below:

  • Fix example so that all electrodes get assigned locations (currently the np.where can be length zero, which is bad)
  • Make sure example is still structurally okay

Then I can do for the functions I added:

  • Reduce footprint from 71.64 sec 2537.8 MB
  • Add tests
  • Speed up tests
  • Add imposed translation, ensure translation is found in each pipeline
  • Update SourceMorph object attributes niter_* -> niter dict, add pipeline

@larsoner
Copy link
Member Author

Oh wow 71.64 sec 2537.8 MB that's too much memory. I'll have to figure out how to reduce that...

@larsoner
Copy link
Member Author

Just pushed a commit to make CircleCI complete without error (my hack workaround for having none at the right value was wrong), feel free to push right over it @alexrockhill if you have a correct fix.

@alexrockhill
Copy link
Contributor

Hmmm, yeah I just tried and it doesn't work either. I think that's because the rigid registration can't be zoomed... Do you want to split it up into two steps? mne.morph.compute_volume_translation and mne.morph.compute_volume_alignment... I don't think warp is that accurate either, you're not changing the scaling, just rotating and translating

@alexrockhill
Copy link
Contributor

The reason np.where is empty is because the elec_image that got morphed was off because the alignment was off.

@larsoner
Copy link
Member Author

I don't think warp is that accurate either, you're not changing the scaling, just rotating and translating

You must mean for the CT-MR warp step in the subject's own space. I'll I add a rigid=False param that skips the affine step that only does trans+rot.

I don't think warp is that accurate either, you're not changing the scaling, just rotating and translating

Is this still in reference to the CT-MR warp, or the MR-template warp?

@alexrockhill
Copy link
Contributor

I don't think warp is that accurate either, you're not changing the scaling, just rotating and translating

You must mean for the CT-MR warp step in the subject's own space. I'll I add a rigid=False param that skips the affine step that only does trans+rot.

I don't think warp is that accurate either, you're not changing the scaling, just rotating and translating

Is this still in reference to the CT-MR warp, or the MR-template warp?

In reference to the MR-CT registration which I don't think qualifies as a warp and so I think is misleading.

@alexrockhill
Copy link
Contributor

I think it should be split into two separate registration and warp steps, I can take a pass if you'd like

@larsoner
Copy link
Member Author

larsoner commented Jun 24, 2021

I think it should be split into two separate registration and warp steps, I can take a pass if you'd like

To me it's all registration/warping/coreg, just with different levels of granularity (translation is one, rotation is another, affine-but-still-linear / shear / scaling is another, SDR is another). This is why I'm inclined toward a single function to do it...

@larsoner
Copy link
Member Author

I made the first step rigid and it still has invalid electrodes

@alexrockhill
Copy link
Contributor

I made the first step rigid and it still has invalid electrodes

If it has the same zoom=5 then it won't align properly, the CT to MR rigid can't be zoomed.

@alexrockhill
Copy link
Contributor

alexrockhill commented Jun 24, 2021

I think it should be split into two separate registration and warp steps, I can take a pass if you'd like

To me it's all registration/warping/coreg, just with different levels of granularity (translation is one, rotation is another, affine-but-still-linear / shear / scaling is another, SDR is another). This is why I'm inclined toward a single function to do it...

I just think when you're passing a CT into a function to align with an MR, you don't want it to be "warped", that would be bad, just registered from my best guess at a naive user's perspective

@alexrockhill
Copy link
Contributor

alexrockhill commented Jun 24, 2021

Maybe a good solution is to call it mne.transforms.register with a sdr_warp=True keyword argument to do the SDR as well all in one call. I think even having under in the morph module is misleading since you don't want to "morph" the CT either.

That's just my perspective though, maybe I'm being unreasonable and others are perfectly fine having rigid registrations under morph and warp.

@larsoner
Copy link
Member Author

Maybe under mne.source_space is best actually, that's where a bunch of MRI wrangling things live

@larsoner
Copy link
Member Author

... I think you're right that mne.transforms is probably best, I'll push

@larsoner
Copy link
Member Author

Okay all electrodes get assigned now, feel free to look at the outputs to see if they make sense, and also the naming/structure etc.

@alexrockhill
Copy link
Contributor

Looks great!

I don't have write access but I would suggest having zooms be consistent with niter and either have zooms_trans, zooms_affine and zooms_sdr or have one niter dictionary. I would break up zooms_trans and zooms_affine as that will speed things up for the CT registration; it's nice to zoom the translation and not the rigid affine for how I would actually do it for a real analysis.

@larsoner
Copy link
Member Author

. I would break up zooms_trans and zooms_affine as that will speed things up for the CT registration;

This suggests we might actually want the entire set zooms_translation, zooms_rigid, zooms_affine, zooms_sdr. I'm inclined to make both niter and zooms dicts for this reason, we'll end up with a ton of parameters otherwise. And maybe the logic should be that if a given zooms level is not provided, the zooms of the previous level are used. If zooms['whatever'] = None it means "use native". So for example zooms=dict(translation=5, rigid=None) would mean "use 5 for translation, then native from rigid onward"

@alexrockhill
Copy link
Contributor

I think also a keyword warp for whether to compute the SDR would be really helpful, to me niters_sdr=() is confusing

@larsoner
Copy link
Member Author

I think also a keyword warp for whether to compute the SDR would be really helpful, to me niters_sdr=() is confusing

How about niter=dict(affine=None, sdr=()) would mean "only do rigid" (i.e., use None as an alias for () meaning, "do not do it")?

@alexrockhill
Copy link
Contributor

. I would break up zooms_trans and zooms_affine as that will speed things up for the CT registration;

This suggests we might actually want the entire set zooms_translation, zooms_rigid, zooms_affine, zooms_sdr. I'm inclined to make both niter and zooms dicts for this reason, we'll end up with a ton of parameters otherwise. And maybe the logic should be that if a given zooms level is not provided, the zooms of the previous level are used. If zooms['whatever'] = None it means "use native". So for example zooms=dict(translation=5, rigid=None) would mean "use 5 for translation, then native from rigid onward"

That sounds great except that I wouldn't want it to carry on at a lower resolution. If the previous level used some downsampling, that doesn't mean the next level has to, in fact in the case of the CT to MR, you want to zoom and then not. On None I would just use the full resolution.

@alexrockhill
Copy link
Contributor

alexrockhill commented Jun 24, 2021

I think also a keyword warp for whether to compute the SDR would be really helpful, to me niters_sdr=() is confusing

How about niter=dict(affine=None, sdr=()) would mean "only do rigid" (i.e., use None as an alias for () meaning, "do not do it")?

I think that's getting really confusing to me. Maybe a steps list would be good, with ['trans', 'rigid'] for CT to MR and ['trans', 'rigid', 'affine', 'sdr'] for the T1 to fsaverage.

In Dipy it's pipeline so I guess that would be appropriate too.

Should be a tuple if the default is ('trans', 'rigid', 'affine', 'sdr') so that it's immutable also.

@larsoner
Copy link
Member Author

I think that's getting really confusing to me. Maybe a steps list would be good, with ['trans', 'rigid'] for CT to MR and ['trans', 'rigid', 'affine', 'sdr'] for the T1 to fsaverage.

That seems like a nice way to do it!

That sounds great except that I wouldn't want it to carry on at a lower resolution. If the previous level used some downsampling, that doesn't mean the next level has to, in fact in the case of the CT to MR, you want to zoom and then not. On None I would just use the full resolution.

So the logic would be "native unless specified"? I can live with this, especially if we alias zooms=5 to mean "use 5 for all steps". Even in the example here we use 6 then 4 for subject-to-fsaverage, I'll probably switch it to zooms=5 in that case (and the results are still fine it seems).

@alexrockhill
Copy link
Contributor

I think that's getting really confusing to me. Maybe a steps list would be good, with ['trans', 'rigid'] for CT to MR and ['trans', 'rigid', 'affine', 'sdr'] for the T1 to fsaverage.

That seems like a nice way to do it!

That sounds great except that I wouldn't want it to carry on at a lower resolution. If the previous level used some downsampling, that doesn't mean the next level has to, in fact in the case of the CT to MR, you want to zoom and then not. On None I would just use the full resolution.

So the logic would be "native unless specified"? I can live with this, especially if we alias zooms=5 to mean "use 5 for all steps". Even in the example here we use 6 then 4 for subject-to-fsaverage, I'll probably switch it to zooms=5 in that case (and the results are still fine it seems).

Sounds perfect, totally on the same page

@larsoner
Copy link
Member Author

Okay I'll get to it tomorrow, or if you want to give it a shot you can

@alexrockhill
Copy link
Contributor

I didn't finish and I need to fix the commit history but I pushed these in case you want to finish it tomorrow morning before I'm up, but I can also fix and finish tomorrow just a bit later, I'm on PST.



@verbose
def compute_volume_registration(moving, static, steps='warp', zooms=None,
Copy link
Member

Choose a reason for hiding this comment

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

I would call moving_img and static_img to suggest it's a nibabel Image

Copy link
Member Author

Choose a reason for hiding this comment

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

I had it that way at first but thought moving and static alone were clearer because it matches what dipy calls it in affine_registration:

https://dipy.org/documentation/1.4.1./reference/dipy.align/#affine-registration

So here I think being consistent with their naming is worth it. (Plus the name is shorter.) But I don't feel strongly so if you aren't convinced by the consistency argument I'll change it

@larsoner larsoner changed the title WIP, ENH: Refactor MRI/CT code MRG, ENH: Refactor MRI/CT code Jun 30, 2021
@larsoner
Copy link
Member Author

Okay @alexrockhill feel free to have a look and see if you're happy with the code, here is the updated example:

https://30168-1301584-gh.circle-artifacts.com/0/dev/auto_tutorials/clinical/10_ieeg_localize.html

@larsoner
Copy link
Member Author

Windows is having a bad day because of the VTK 9.0.2 release, can be ignored here as I'm working on it in #9512

@alexrockhill
Copy link
Contributor

Okay @alexrockhill feel free to have a look and see if you're happy with the code, here is the updated example:

https://30168-1301584-gh.circle-artifacts.com/0/dev/auto_tutorials/clinical/10_ieeg_localize.html

Still looks off... I would rather have a maximally accurate example that has a lot of code that's not abstracted than one that doesn't work quite right. I think the existing morph functions may not be compatible with the what is on main now for the tutorial. I think first priority if you want to go this route of refactoring is getting a version that works and then maybe in a followup we can get the SDR morph from the volume source estimate working using the same code. I really would rather not spend a ton of time debugging the volume source estimate code so I think it's best to part ways. I'll take a pass now.

@larsoner
Copy link
Member Author

Still looks off...

Can you clarify how/where? I'm assuming you're talking about subject->fsaverage and not CT-MRI. For "to fsaverage", those temporal electrodes are now nicely on/around the actual lobe, which is what I was always worried about, so to me it looks okay. Is there an image above somewhere (or in some artifact) that looks better?

I'll take a pass now.

Can you see if just zooms=None in the SDR pass makes things look okay to you? If so I can go back to saving the warped MRIs.

I really would rather not spend a ton of time debugging the volume source estimate code so I think it's best to part ways.

So maybe as an intermediate, if zooms=None does not work, we can keep the MR-CT alignment that is here currently since it works, then put back your custom dipy code for the subject->fsaverage alignment and save those warped electrode and brain images?

@alexrockhill
Copy link
Contributor

Still looks off...

Can you clarify how/where? I'm assuming you're talking about subject->fsaverage and not CT-MRI. For "to fsaverage", those temporal electrodes are now nicely on/around the actual lobe, which is what I was always worried about, so to me it looks okay. Is there an image above somewhere (or in some artifact) that looks better?

I'll take a pass now.

Can you see if just zooms=None in the SDR pass makes things look okay to you? If so I can go back to saving the warped MRIs.

I really would rather not spend a ton of time debugging the volume source estimate code so I think it's best to part ways.

So maybe as an intermediate, if zooms=None does not work, we can keep the MR-CT alignment that is here currently since it works, then put back your custom dipy code for the subject->fsaverage alignment and save those warped electrode and brain images?

The CT-MR is way off still, that's the main thing. The SDR looks pretty good.

@larsoner
Copy link
Member Author

The CT-MR is way off still, that's the main thing.

So this is what we have now:

sphx_glr_10_ieeg_localize_002

And this is what we have in main:

sphx_glr_10_ieeg_localize_002_main

I think it only differs by 1.2mm and 1.9 degrees from what you had before. I have a hard time seeing meaningful differences here, and if I didn't know which is which I'm not sure which I'd say was better, but I am no expert here!

As much as I'm inclined to keep it, indeed we shouldn't keep something if you think it is "way off" even if I can't see it. I thought you had said before that the MR-CT was now okay, but since it's not I agree perhaps the easiest thing to do is just to revert to the old affine and commented out code for the MR-CT step, and revisit sometime later how to use our standard pipeline to get an affine of equivalent quality.

@alexrockhill
Copy link
Contributor

The CT-MR is way off still, that's the main thing.

So this is what we have now:

sphx_glr_10_ieeg_localize_002

And this is what we have in main:

sphx_glr_10_ieeg_localize_002_main

I think it only differs by 1.2mm and 1.9 degrees from what you had before. I have a hard time seeing meaningful differences here, and if I didn't know which is which I'm not sure which I'd say was better, but I am no expert here!

As much as I'm inclined to keep it, indeed we shouldn't keep something if you think it is "way off" even if I can't see it. I thought you had said before that the MR-CT was now okay, but since it's not I agree perhaps the easiest thing to do is just to revert to the old affine and commented out code for the MR-CT step, and revisit sometime later how to use our standard pipeline to get an affine of equivalent quality.

Way off is a relative term and I think you have to spend some time aligning these by hand to appreciate when the fit is right and when it's not. 1.2 mm and 1.9 degrees isn't a whole lot but those are clearly how much it's wrong and to me, that's too much. You can align by eye closer than that, the whole point of this is that it should be as precise as possible with the alignment, better than what can be done by human eye. I could fix the top image to be much closer by hand.

@alexrockhill
Copy link
Contributor

Keep in mind that you're only seeing a bit of the alignment with the three slices and in a viewer like freeview it would be much more obvious how much the alignment is actually off and I would consider the first alignment to be poor and need fixing if someone had done it by hand and I was reviewing it and the second alignment would be good.

alignment_affine = np.array([
[0.99235816, -0.03412124, 0.11857915, -133.22262329],
[0.04601133, 0.99402046, -0.09902669, -97.64542095],
[-0.11449119, 0.10372593, 0.98799428, -84.39915646],
Copy link
Member Author

Choose a reason for hiding this comment

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

@alexrockhill okay if I just revert the text removal here, then we're good to merge?

Copy link
Contributor

Choose a reason for hiding this comment

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

That's the correct alignment but the commented out code should return that matrix and it doesn't right?

Copy link
Member Author

Choose a reason for hiding this comment

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

By revert the text I mean both this affine and the old commented code, which I assume is what you used to generate it

@alexrockhill
Copy link
Contributor

Ok I just pushed a different pull request to split this up. First, I think a version that works with the tutorial should be merged which is the PR I just pushed. Then we can try and refactor the volume source estimate code to use the new transforms. I think combining the two steps is causing too much of an issue.

@larsoner
Copy link
Member Author

larsoner commented Jul 1, 2021

Closing for #9515

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.

3 participants