Skip to content

Flying Focal Spot for FanBeam and ConeBeam geometries#1509

Merged
kohr-h merged 11 commits into
odlgroup:masterfrom
JevgenijaAksjonova:master
Sep 5, 2020
Merged

Flying Focal Spot for FanBeam and ConeBeam geometries#1509
kohr-h merged 11 commits into
odlgroup:masterfrom
JevgenijaAksjonova:master

Conversation

@JevgenijaAksjonova

Copy link
Copy Markdown
Contributor

Added possibility to arbitrary shift source positions. Since ASTRA allows to provide arbitrary source positions to projection and back-projection functions, it is possible now to do reconstruction with flying focal spot.

Implementation:
The sequence of unique shifts should be defined relative to the default source position and provided as an input to the geometry. Then this sequence is extended for all angles and returned when calling geometry.flying_focal_spot. However, one has to manually provide flying_focal_spot sequence to geometry.src_position() as an argument. This choice was made because FFS shifts correspond to discretization of angular partition and defining a FFS shift for any angle would introduce significant overhead in terms of work (moreover, in practice this is not needed).

@pep8speaks

pep8speaks commented Jul 5, 2019

Copy link
Copy Markdown

Checking updated PR...

Line 823:5: E306 expected 1 blank line before a nested definition, found 0

Line 681:55: E226 missing whitespace around arithmetic operator
Line 681:44: E226 missing whitespace around arithmetic operator
Line 664:35: E226 missing whitespace around arithmetic operator
Line 626:55: E226 missing whitespace around arithmetic operator
Line 626:44: E226 missing whitespace around arithmetic operator
Line 547:55: E226 missing whitespace around arithmetic operator
Line 547:44: E226 missing whitespace around arithmetic operator
Line 533:1: E302 expected 2 blank lines, found 1
Line 504:58: E251 unexpected spaces around keyword / parameter equals
Line 492:55: E226 missing whitespace around arithmetic operator
Line 492:44: E226 missing whitespace around arithmetic operator

Comment last updated at 2020-09-05 19:33:41 UTC

@kohr-h kohr-h left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I highly appreciate the effort to support FFS, it's a long-standing open issue we had while working with the medical data. As usual, code and documentation are of good quality, as far as I have seen.

Regarding the implementation, I think we need to take a step back and see if we can't come up with something that is more generic than this approach. I feel that the principle is generic ("apply some shifts at each angle") but the implementation is specifically tailored to FFS, so that the next similar thing needs another extension.

What I could imagine is to accept a function like src_shift_func(angle, geometry) that produces the shifts for a given angle. We could supply the flying_focal_spot function as an example (in this module) and point to it. The good thing about this is that it allows other functions that a user can come up with.

Opinions?

Comment thread odl/tomo/backends/astra_setup.py Outdated
Comment thread odl/tomo/geometry/conebeam.py Outdated
Comment thread odl/tomo/geometry/conebeam.py Outdated
Comment thread odl/tomo/geometry/conebeam.py Outdated
Comment thread odl/tomo/geometry/conebeam.py Outdated
Comment thread odl/tomo/geometry/conebeam.py Outdated
Comment thread odl/tomo/geometry/conebeam.py
Comment thread odl/tomo/geometry/conebeam.py Outdated
center_to_src_init = center_to_src_init + ffs_shift
# broadcasting to perform matrix multiplication "manually",
# since existing numpy functions do cross product along the outer
# dimensions, which we don't need here

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think you can achieve this with numpy.matmul (which I didn't know either when I wrote similar code).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think einsum is usually the most beautiful way to do it.

@JevgenijaAksjonova

Copy link
Copy Markdown
Contributor Author

Hi! Thank you for the review @kohr-h! The main issue with a function of the type src_shift_func(angle, geometry) is that it is not clear how to define a shift for an arbitrary angle. Basically, FFS is defined for a sequence of angles that correspond to angular partition. One could use interpolation to define it for any angle, but it seems a bit strange to me to do it just for flexibility of the interface. Or maybe it is not strange? What do you think?

@kohr-h

kohr-h commented Jul 18, 2019

Copy link
Copy Markdown
Member

That's exactly the main concern, yes. However, you needed to define FFS for arbitrary angle, too, so it's a concern no matter what.
Perhaps we could make the job easier by doing the interpolation behind the scenes. For instance, if the given shifts are a sequence of vectors, one for each angle, then we interpolate in between, and if it's a function then it is responsible for handling arbitrary angle.

@JevgenijaAksjonova

JevgenijaAksjonova commented Jul 18, 2019

Copy link
Copy Markdown
Contributor Author

Why is it necessary to define FFS for arbitrary an angle?

@kohr-h

kohr-h commented Jul 19, 2019

Copy link
Copy Markdown
Member

Why is it necessary to define FFS for arbitrary an angle?

In practice it isn't, but you still had to do it because the methods require it:

flying_focal_spot_shift = rot_matrix(phi) *
(ffs1 * (-src_to_det_init) +
ffs2 * tangent)

Other kinds of shifts would look similar.

@JevgenijaAksjonova

Copy link
Copy Markdown
Contributor Author

Hi! I have been thinking about this and I am not convinced that FFS should be a function of the rotation angle. Because I imagine that in practical CT scanners both FFS and rotation angle depend on time. Since we don't have a notion of time in the geometry, we could use the rotation angle as a proxy for time, but I don't see how this would make life easier for people who are trying to define arbitrary shifts.
On the other hand, providing FFS as a sequence implicitly defines it as a function of the time step (the moment when projection is made). Just to be clear - the sequence can be arbitrary long, so if one actually has a function FFS(angle), he can just pass a sequence FFS(geometry.angles).

Secondly, even if we define FFS(angle), I don't think that we should allow an arbitrary angle. Firstly, I was concerned only about introducing unnecessary complexity in the code. Now I am thinking that we actually don't know how the source moves between the shifts, e.g. if the speed is uniform, if it is not, doing linear interpolation is wrong and can lead to "silent errors". To circumvent this issue, we could define FFS only for angles that belong to discretization and rise "Not implemented" or "Invalid argument error", if the provided angle does not belong to angular partition.

@kohr-h

kohr-h commented Aug 13, 2019

Copy link
Copy Markdown
Member

Hi! I have been thinking about this and I am not convinced that FFS should be a function of the rotation angle. Because I imagine that in practical CT scanners both FFS and rotation angle depend on time. Since we don't have a notion of time in the geometry

That's a valid concern. However, note that geometries have a notion of "motion parameter", which usually is, but doesn't have to be, a rotation angle. It can be anything that parametrizes the part of the geometry that is not the detector coordinate. So it could easily be time. The angles property is there purely for convenience, since it's usually the same as motion_params.coord_vectors[0].

On the other hand, providing FFS as a sequence implicitly defines it as a function of the time step (the moment when projection is made). Just to be clear - the sequence can be arbitrary long, so if one actually has a function FFS(angle), he can just pass a sequence FFS(geometry.angles).

The problem with this definition of FFS is that it "sticks" to the time steps, i.e., if you decide to double the frequency of the projections being taken, the frequency of the FFS will also double. It's not a function of continuous time, but of discrete time points. If that's the correct behavior, then okay.

Now I am thinking that we actually don't know how the source moves between the shifts, e.g. if the speed is uniform, if it is not, doing linear interpolation is wrong and can lead to "silent errors".

That's correct, and a very valid concern.

To circumvent this issue, we could define FFS only for angles that belong to discretization and rise "Not implemented" or "Invalid argument error", if the provided angle does not belong to angular partition.

That suggestion is impractical, though, because you need to compare floating point values to do the check, and floating point numbers are practically never equal. Checking for "almost equal" is also tricky, in particular around 0. So raising errors for invalid input will inevitably generate annoyance.

The best thing I can think of here is to do nearest neighbor interpolation, i.e., to always "snap" to the known values. That gives correct results for the given angles, and still allows arbitrary input.

@JevgenijaAksjonova

Copy link
Copy Markdown
Contributor Author

Sorry, it took such a long time, I have added a function flying_focal_spot in a separate file, which just snaps to the known values. It is possible to provide this function as an argument to geometry.

@jonasteuwen

Copy link
Copy Markdown

What is the status of this issue?

@ozanoktem

Copy link
Copy Markdown
Contributor

What is the status of this issue?

@JevgenijaAksjonova, could you provide an update please?

@kohr-h

kohr-h commented Apr 30, 2020

Copy link
Copy Markdown
Member

Oh dear. If I remember correctly this is just a final review short of being merged 😬

I'll give it a fresh look asap.

@jonasteuwen

Copy link
Copy Markdown

It would be very convenient, it appears that this new Mayo clinic data has it for some of the scans!

@adler-j

adler-j commented May 1, 2020

Copy link
Copy Markdown
Member

Oh did Mayo finally release their data, do you have a link? I was buggering them for years.

@ozanoktem

Copy link
Copy Markdown
Contributor

Mayo Clinic announced the public availability of 299 patient CT cases containing head, chest and abdomen exams from Siemens and GE scanners. All cases have routine dose and simulated low dose projection data, full dose images, and clinical data identifying all pathology.

They appreciate receiving a copy of any publications that make use of these data.

Detailed information and a link to the data library are available here: https://ctcicblog.mayo.edu/hubcap/patient-ct-projection-data-library/.

@jonasteuwen

Copy link
Copy Markdown

I did manage to read their data in python, but they provide this FFS in some of them. Would be good if this gets implemented, then I can publish some code giving a baseline.

@kohr-h kohr-h left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Some suggestions and comments, first round. Don't have more time right now, will come back to it.

Comment thread odl/tomo/backends/astra_setup.py Outdated
Comment thread odl/tomo/geometry/conebeam.py Outdated
Comment thread odl/tomo/geometry/conebeam.py Outdated
Comment thread odl/tomo/util/source_detector_shifts.py Outdated
Comment thread odl/tomo/util/source_detector_shifts.py Outdated
Comment thread odl/tomo/util/source_detector_shifts.py Outdated
Comment thread odl/tomo/util/source_detector_shifts.py Outdated
Comment thread odl/tomo/util/source_detector_shifts.py Outdated
Comment thread odl/tomo/util/source_detector_shifts.py Outdated
Comment thread odl/tomo/geometry/conebeam.py Outdated
Jevgenija Rudzusika added 3 commits May 12, 2020 14:27
@JevgenijaAksjonova

Copy link
Copy Markdown
Contributor Author

Hi! Are there more changes required?

Comment thread odl/test/tomo/geometry/geometry_test.py Outdated
Comment thread odl/test/tomo/geometry/geometry_test.py Outdated
Comment thread odl/test/tomo/geometry/geometry_test.py Outdated
Comment thread odl/test/tomo/geometry/geometry_test.py Outdated
Comment thread odl/test/tomo/geometry/geometry_test.py
Comment thread odl/tomo/geometry/conebeam.py Outdated
Comment thread odl/tomo/util/source_detector_shifts.py Outdated
Comment thread odl/tomo/util/source_detector_shifts.py Outdated
__all__ = ('flying_focal_spot')


def flying_focal_spot(angle, apart, shifts):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Could you add a test for this in particular?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

What do you mean? Tests in geometry_tests use this function, since it is the only implemented option

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I agree, but they test the whole thing. It might be good to have a test for this in isolation to help track down errors.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The other option, if this is not intended to be used externally, is to make this a private function in the cone beam geometry file.

Comment thread odl/tomo/util/source_detector_shifts.py
@JevgenijaAksjonova

Copy link
Copy Markdown
Contributor Author

Hi! Thank you for the feedback!
I have addressed all issues even though some of them are not marked outdated

@adler-j

adler-j commented Jun 18, 2020

Copy link
Copy Markdown
Member

Some of the seemingly outdated comments are still unresolved, could you check them and mark as resolved?

@JevgenijaAksjonova

Copy link
Copy Markdown
Contributor Author

Hi! Please check the latest updates.

@adler-j adler-j left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Very minor comments, fix and go :)

(the detector must be large enough, not to be influenced by shifts)
"""

# If no implementation is available, skip

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Redundant comment


# If no implementation is available, skip
if not odl.tomo.ASTRA_AVAILABLE:
pytest.skip(msg='ASTRA not available, skipping 2d test')

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The fact that the test is skipped is documented automatically, I'd prefer information on why it was skipped. E.g. astra is needed to run it.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Bump on this

assert np.max(proj[3, 15:]) > 5


def test_source_detector_shifts_2d():

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Very nice test!

phantom = odl.phantom.shepp_logan(space)

full_angle = np.pi
n_angles = 2 * 7

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why not just 14?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

it must be even

@JevgenijaAksjonova

Copy link
Copy Markdown
Contributor Author

Hi, Jonas! The commit you checked was not the last one. I fixed some issues in ray_transform tests for 2d, now it works fine.
However, in the 3d test there is a problem, the back-projected images are visually indistinguishable, but numerically, there is a gap between pixel values - 0.39.
I have checked the projection data and it looks that it differs only by a shift (as it is supposed to). I have also added an assertion on the total sum of the projected data for each angle. It it is equal.
However, the total sum of back-projected images is different, for the ffs geometry it is smaller. In fact the following rescaling

im_combined =im_combined/ np.sum(im_combined) * np.sum(im)

reduces the gap to 0.00079.

Do you have any ideas, what is wrong? Could this be because the detector is not perpendicular to src_det_axis?

@adler-j

adler-j commented Jun 24, 2020

Copy link
Copy Markdown
Member

How large is the relative error? 0.39 sounds like a lot (if the values should be ~1) so it might be worth investigating. Could you perhaps try to trigger it without the FFS somehow?

@JevgenijaAksjonova

Copy link
Copy Markdown
Contributor Author

Hi!

I have added detector shifts and changed the ray_transform tests so that source and detector positions coincide. Even after this a 2% error remained in cone beam, while using astra 1.8.3. Simply rescaling one of the images to match the scale of the other solves the issue. (This is not needed for the latest ASTRA, there it works fine).

The detector shifts themself dont work as expected in 3D, this is related to issue #1567.

@JevgenijaAksjonova

Copy link
Copy Markdown
Contributor Author

Now I have added tests for the radon transform with detector shifts. It is ready for review

@ozanoktem

Copy link
Copy Markdown
Contributor

@kohr-h, @adler-j and/or @aringh , care to give this a final review? Perhaps we can push this to acceptance.

@ThomasBudd

Copy link
Copy Markdown

Hi! Can I help with this somewhere? Also I have a question regarding the tam-danielsen window for ffs. To me it is not obvious how to mask out overlapping rays when the focal spot jumps between two different positions. Did you take care of this @JevgenijaAksjonova ? What way can this be solved? Maybe this was the source for the small error in 3d!

@adler-j adler-j left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Some minor comments that should be doable without further review.

Sorry for the late review again.


# If no implementation is available, skip
if not odl.tomo.ASTRA_AVAILABLE:
pytest.skip(msg='ASTRA not available, skipping 2d test')

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Bump on this

det_shift_func=lambda angle: shift)

assert all_almost_equal(geom.angles, geom_shift.angles)
angles = geom.angles

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Move this to the line above

geometries which mimic ffs by using initial angular offsets and
detector shifts
"""

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Extra newline

@kohr-h

kohr-h commented Sep 5, 2020

Copy link
Copy Markdown
Member

I was absent from GH for a while, sorry for the delay. Go ahead after the minor things @adler-j mentioned. There's nothing left to fix that can't be fixed afterwards :-)

Comment thread odl/test/tomo/operators/ray_trafo_test.py Outdated
Comment thread odl/test/tomo/operators/ray_trafo_test.py Outdated
Comment thread odl/test/tomo/operators/ray_trafo_test.py Outdated
@kohr-h

kohr-h commented Sep 5, 2020

Copy link
Copy Markdown
Member

Okay, now I just did the nit edits myself. Merging after CI.

@kohr-h kohr-h merged commit 6ea4cda into odlgroup:master Sep 5, 2020
@ThomasBudd

Copy link
Copy Markdown

Is the issue solved why @JevgenijaAksjonova got some errors in 3d? I think this has to do with the implementation of the tam-danielsen window. When we have projections at different source positions it is much more complicated to find and mask out the overlapping rays and this leads to errors in the backprojection.

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.

7 participants