-
Notifications
You must be signed in to change notification settings - Fork 184
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
(WIP) Lucas-Kanade registration #100
base: master
Are you sure you want to change the base?
Conversation
|
||
|
||
def v2v(v, dims=None): |
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.
Maybe it would be cleaner / clearer to break this into two functions, one that goes one way and the other going the other way?
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.
For one thing, the dims are only required going one direction.
@poolio Have gone through this carefully, looking really cool! My only main concern is the same one you raised about I think it would be an improvement to create the We could then pass
Where Finally, inside
Basically, broadcast the whole model instead of just the transformations, so we can expose the auxiliary parameters (if not What do you think about that? |
@poolio This pull request is off to a fantastic start, and I'm glad you elected to implement Lucas-Kanade since it really helps us focus on some of the underlying infrastructure before you start adding in the complexities of low-rank decompositions and RASL. Most of my comments so far revolve around the transformations classes. These do indeed seem a little bit overly complex, and I suspect there are a few things we can do to make it more elegant. Here are my preliminary thoughts on this: • The use is transform names is good throughout. I think we should stick to this, and over time we will likely flesh out more of the linear transform family. To better make room for future transform types, I would suggest you rename the For the record, here is the hierarchy of linear projective transformations (and their names) that I think we should follow, more or less. For more about this, see Section 2.4 of Hartley's "Multiple View Geometry", and especially p.44 which has a nice table on this. Isometry - translation, rotation. (Euclidean transformations are a special kind of isometry that preserve the orientation of the rotation (i.e. no mirror image flipping) Similarity - translation, rotation, and isotropic scaling Affine - translation, rotation, non-isotropic scaling, shearing Projective - all of the above, plus perspective transforms of the plane/volume (also called a homography in computer vision) • The GridTransformer class is indeed something of of a sore thumb. It does provide necessary functionality (essentially, applying an arbitrary linear transform using • Also, I think that the • In • Will we always • Somewhere we ought to have a • • The transformMatrix() helper function should probably be called euclideanTransformMatrix, and possibly made a . Also, the docstring for All else is looking pretty good from an architecture standpoint to me. The |
Okay, @poolio and I just chatted on the phone a bit about the
We also both feel like this pull request should probably be restricted to the Lucas-Kanade implementation. If it's possible to wrap it up after some of these comments are addressed, then the next pull request could encompass RASL itself. @freeman-lab , does that sound good to you? |
Thanks so much @broxtronix and @freeman-lab for the feedback! I'll try to address all your comments over the next few days, but wanted to first clarify some of the confusion/difficulty around To transform volumes by an arbitrary displacement, we need to compute a grid of points used by As @broxtronix pointed out, it probably doesn't make sense to generate But that means the OK, so here's a possible solution! Create and store
OR we could store a class AffineTransformation(...):
grid = None
def apply(self, vol, regParams):
if grid is None or AffineTransformation.grid.dims != vol.shape:
AffineTransformation.grid = GridTransformer(vol.shape)
... We would still need to use a What do you guys think? |
@poolio, I kinda like the class variable approach (my intuition is that this is something that ought to be pre-computed and stored privately by the class that actually calls The regMethods approach is ok, but seems a little complicated and leaky to me. Hmm... before we get too much farther down this road of trying to figure out how to pre-compute this grid, it might be worth doing a really quick performance test to see how much time it really takes to compute such a grid, relative to the time it takes to call |
@broxtronix What scenario could you see where the class would transform different sized volumes? With PySpark, I see us calling Good point about relative cost...I ran some benchmarks on my desktop and got the following:
So it's between 1.5 and 2x slower if we don't cache the grid for most 3d datasets, and 7x slower on 2d data (!). I'm going to go ahead and implement the class variable approach, as in the worst case that will reduce to initializing a new grid for each |
@poolio @broxtronix awesome work and discussion guys, I think all the issues are making sense to me. Now that I understand this Viewing it as a cached lazy attribute is neat, but I'm confused how that will work if it's a class variable (or maybe property?) on the
But then we have the problem that it's attached to the registration method rather than transformation. I'm somewhat inclined to, for the purpose of this PR, do it the much simpler purely-in-place way, as @broxtronix seems to be leaning. That will definitely simplify the code, and there's already a lot being added here. We could then consider a separate PR targeted at optimizing the performance through a pre-computation (and btw, completely agree about keeping this PR to LK only, in advance of one about RASL). And that might let us do some more extensive testing of performance once other pieces are in place. |
@poolio, I think Jeremy hit the nail on the head... a class variable would be shared by all AffineTransformation instances on each worker. I think one case where we could run into trouble with that would be if we process one dataset of a particular volume size, and then without shutting down the spark context we push another dataset through alignment that has a slightly different volume size. Probably the easy way to address that issue would be to run a quick check each time before you use the grid: if it doesn't exist, create it. If it does exist, but is not the right size, re-create it. If it exists and is the right size, use it! That said, as @freeman-lab suggests, it might be best to implement it first without this performance enhancement, and then once that structure is in place it will be more apparent how to implement this grid caching. I agree it's worthwhile, though, based on those performance numbers. An 8x speed increase (in 2D) is a lot. It's too bad Python is so bad at performing this type of "general" dense image transformation. But if we get something in place that works well enough, it'll be useful for all of the image transform types that I foresee us using. BTW, pull request #100! Pretty awesome!! :) |
Sorry for the insane delay. I rebased to the latest master, and removed One major problem with the |
I added tests and moved the |
@poolio, this is looking great! If I understand correctly, the Putting Aside from that, this is looking great! @freeman-lab and @logang, does this look good to you guys? |
@broxtronix I changed the comment about _grid to your clearer descriptiion, and switched _grid to a dict. |
Awesome! Looks totally great to me, then! 👍 |
@freeman-lab ping |
is it ready for merge? |
you could try to fix the merge conflict by rebasing |
Sorry for the trouble, but given the major restructuring described in #243, which is now complete, it would be ideal to refactor this and open as a new PR adding the algorithm to https://github.com/thunder-project/thunder-registration |
This PR follows up on the initial work in #74 to incorporate more complex registration methods into thunder. As a precursor to a full RASL implementation, I've put together an implementation of the Lucas-Kande (LK) registration method for iterative registration. This method takes a volume and a reference, and iteratively updates the transformation parameters to minimize the difference between the transformed volume and the reference. It's a very old algorithm that is still popular and works pretty well. Checkout the original paper here:
https://cseweb.ucsd.edu/classes/sp02/cse252/lucaskanade81.pdf
With LK we can perform sub-voxel alignment and compute both translations and rotations. Adding support for other transformations like planar displacements or non-rigid deformations is easy, as you just need to expose a
jacobian
method that computes the change in pixels for each parameter (but regularization can get trickier).Some of the additions to the existing transformations that were needed to get this working:
DifferentiableTransformation
class that has anupdateParams
andjacobian
methodTranslationTransformation
andEuclideanTransformation
class for supporting affine transformations parametrized by shifts and shifts + Euler angle rotationsGridTransformer
class that can transform a volume by an arbitrary affine transformationHandy extensions:
robust
option that minimizes least absolute deviation instead of least squaresIt would be great to get some initial feedback on the structure of these additions and the interaction of transformations with the
GridTransformer
objects. These are needed to callmap_coordinates
so that we can perform arbitrary affine transformations of volumes in 3d, but require us to keep track of a set of points that is the same size as the volume. For this reason, I try to create this object once ingetTransform
and reuse it. But it ends up creating a pretty messy chain of a transformation's apply calling a gridstransform_vol
. Any better ideas are appreciated :)Things that are still missing include: