-
Notifications
You must be signed in to change notification settings - Fork 106
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
Apply global rotation to (axis) geometries #784
Comments
Frankly I'm not sure if this should be in core or not. I feel that there is a quite limited applicability while complicating the interface with questions like how does this interact with other parameters. Why not simply change the angle in the projections? Are you using a tilted geometry or something? |
True. But there's more of that to come.
Interface doesn't change, everything goes via init parameters. You just make a new geometry from the old one, rotating the input vectors in the correct way. |
Well my second question still holds. How is this different from simply giving an |
@kohr-h , is the need for having a utility to generate a parallel beam geometry along an arbitrary rotation axis related to the tilt-axis orientation in single-axis tilting in ET? |
@adler-j It's totally different. If you offset the angle partition, you get the same geometry starting and ending at different angles. If you have an axis-oriented geometry, that's an intrinsic rotation around the axis. What I'm talking about is rotating the axis, the source curve and the detector curve all simultaneously, thus applying an extrinsic rotation. Basically it's a transformation of the "world" system, but we keep sitting in it so for us the geometry rotates. |
I still don't see that you are aiming at here, could you make a figure or something? And regarding the design, I guess your idea is to make a helper function for this? What would you call it and what would the parameters be? perhaps that would help. |
Sure, check out the code in this example. The workflow is as follows:
Result: FBP reconstruction in the rotated and shifted slice only. Some images: Standard orientation (x-y plane) Now for something skew - normal vector |
Note that the last 3 points contain quite some boilerplate code which I feel should be put into some helper function. There are rotations all over the place, and it's easy to get it wrong (matrix or transpose? what to rotate? rotate then shift or the other way around?) so I would like to make it easier. This is related to #783 and #152. Most of the time here is spent swapping the array axes and copying the data to GPU. The actual BP only takes 70 ms on my machine, that would pretty much give you 15 FPS "live reco". |
How is what you did different from this code? I feel like I am missing something still. The only difference is that you give the shift in the new coordinate system, while I give it in the old system, and you seem to be applying a different rotation than I do, but that is to be expected since determining a rotation given a "from" vector and a "to" vector is under-determined anyway. Example of the code running your second to last example: |
That's the point. The initializer of the geometry chooses the detector coordinate system arbitrarily and not consistent with the rotation of the axis, i.e. the detector orientation is not the rotated version of the old one, except for special cases. The same applies to
which is also under-determined. So in summary, I apply the rotation consistently, you rotate the axis and rely on defaults otherwise. |
And I'm thinking of adding an intrinsic rotation that aligns the slice axes with the "image axes". Then it's a unique rotation. |
I may be missing some definitions here, but in general picking a rotation given a "from" and a "to" is under determined up to a (one dimensional) rotation parameter, so the only difference between our codes would be how that rotation is picked. You seem to have another choice than what ODL does, but in general ANY choice will be a "bad choice" due to the hairy ball theorem, i.e. you always get a singularity somewhere, where a minimal change in the "to" axis drastically changes the rotation. For you, that happens when "from" and "to" are close to parallel/anti-parallel, while in ODL this happens when either axis is close to +- [0, 0, 1]. Anyway, what I'm getting at is that if the difference between your approach and the one I outlined is down to the specific choice of that free rotation parameter, wouldn't it be better to focus on that instead of adding a whole machinery for this? I guess we could simply add a text parameter that specifies how the other axes should be selected given an axis. Then you could add your idea in addition to the current one (or perhaps simply change the current implementation) |
I still think that's not the case, rather that your version rotates the axis and then picks the default "derived vectors" while I take the derived vectors in the old system and rotate them all simultaneously.
See above, that's not the problem I try to solve. I want to rotate everything the same way, no more, no less.
So what would be an option is to internally define defaults for all vectors based on the |
I fully agree that there is a difference in how you think about it, but looking at your code it all boils down to the "rotation_matrix" function, and in there you make a particular choice. The "ODL" choice would satisfy the same conditions i.e. transfer "from_vec" to "to_vec", but would give a different matrix. So sure, the casuality arrows point in different directions, but the correlation is still the same.
My code also rotates everything the same way, so there has to be some other constraint that you add that I am missing, and I think this is where the confusion is. Perhaps it is related to your comment "And I'm thinking of adding an intrinsic rotation that aligns the slice axes with the "image axes". Then it's a unique rotation.", but I don't think the current code does that.
Morally, that is what the current code does. Now, the implementation is not the best and actually writing this using a rotation matrix would likely be much preferable to the current way of writing it, I agree with that. And in doing that, we would get this one free parameter that I've been talking about here, and we could give users an option in how to select that. |
I think what it all boils down to is that the |
I actually think that it does, since the free variable is "orthogonal" to "being a rotation from [0, 0, 1] to axis" in some sense. Anyway an obvious way to do this would be to stop using the We could even let users provide the full rotation matrix themselves as an option to providing Finally, the current "det_init_axes" should then ONLY be used for when users want detectors with non-rectangular pixels, or when the detector should not point straight towards the source. |
>>> unit_vecs = np.eye(3)
>>> perp_vec_matrix = np.zeros((3, 3))
>>> for i in range(3):
... for j in range(3):
... perp_vec_matrix[i, j] = perpendicular_vector(unit_vecs[j]).dot(
... unit_vecs[i])
...
>>> print(perp_vec_matrix)
[[ 0. -1. 1.]
[ 1. 0. 0.]
[ 0. 0. 0.]] Doesn't look like a rotation matrix to me.
Yes, I think that's a decent suggestion. Perhaps good to let it settle a bit over Christmas :-) |
Sure several calls to this will not give a rotation matrix, in fact it is mathematically impossible to write a function that takes a vector and returns another vector that is perpendicular to the initial vector in a continuous way, and if I'm not wrong that rules out a matrix since a matrix would be linear and thus continuous. I could get the same problem if I called your code with from_vec == to_vec, for example. However, the function is only called once for each geometry, and thus that does not become a problem.
Yupp. I think this is best settled with an overall look at geometries, they are not top notch right now. |
That's true, and it's not really the point of the discussion. However, the resulting geometrical layout of the acquisition system is different for the different approaches. Here is some example code that simulates both approaches. The output is:
And it's not really surprising that the results differ. How should the So that issue could simply be solved by using a rotation matrix internally instead of the coordinate system(s) set in the initializer. |
I agree with that, but my point is that sadly any approach we pick will be arbitrary so if we do something about this it would feel better if they were treated equally (i.e. the current is not magically more important than the one you suggested)
Well they differ by design, but they are both still "guaranteed" to give "rotation matrices" (noticed now that the current implementation does not preserve handedness though), in your case
Agree wholeheartedly. |
This commit changes the convention of the default initial detector position from (1, 0) to (-1, 0) in 2D and from (1, 0, 0) to (-1, 0, 0) in 3D. Furthermore, it uses a consistent set of rotations to change from default to custom configuration. The new code works such that every geometry has a "rotation handle" -- the initial detector position in 2D and the symmetry axis in 3D axis geometries -- that is rotated from the default vector to a given custom vector. The same rotation is then applied to the remaining default vectors of the geometry (e.g. detector axes) unless those are explicitly given. One consequence of this change is that, e.g., 2D parallel beam behaves exactly like a "horizontal slice" of a 3D single-axis parallel beam geometry in terms of initial axes and rotations. Closes odlgroup#784
This commit changes the convention of the default initial detector position from (1, 0) to (-1, 0) in 2D and from (1, 0, 0) to (-1, 0, 0) in 3D. Furthermore, it uses a consistent set of rotations to change from default to custom configuration. The new code works such that every geometry has a "rotation handle" -- the initial detector position in 2D and the symmetry axis in 3D axis geometries -- that is rotated from the default vector to a given custom vector. The same rotation is then applied to the remaining default vectors of the geometry (e.g. detector axes) unless those are explicitly given. One consequence of this change is that, e.g., 2D parallel beam behaves exactly like a "horizontal slice" of a 3D single-axis parallel beam geometry in terms of initial axes and rotations. Closes odlgroup#784
This commit changes the convention of the default initial detector position from (1, 0) to (-1, 0) in 2D and from (1, 0, 0) to (-1, 0, 0) in 3D. Furthermore, it uses a consistent set of rotations to change from default to custom configuration. The new code works such that every geometry has a "rotation handle" -- the initial detector position in 2D and the symmetry axis in 3D axis geometries -- that is rotated from the default vector to a given custom vector. The same rotation is then applied to the remaining default vectors of the geometry (e.g. detector axes) unless those are explicitly given. One consequence of this change is that, e.g., 2D parallel beam behaves exactly like a "horizontal slice" of a 3D single-axis parallel beam geometry in terms of initial axes and rotations. Closes odlgroup#784
Fixed by #968 |
When reconstructing arbitrary slices, one can trick ODL into doing that by reconstructing in a space with shape
(n0, n1, 1)
and rotating the whole geometry with the matrix rotating the normal vector of the slice to the original geometry axis. Of course, the same can be done in 2D, too. To do it right, one must rotate the following geometry vectors:axis
(if present)src_to_detector_init
ordet_init_pos
det_init_axes
ordet_init_axis
I think a helper function or method would be good to get the rotations right -- what I'm not so sure about is whether this is functionality we need in the core. Opinions?
The text was updated successfully, but these errors were encountered: