Winston Lutz Math

Hey everyone,

is someone willing to share the math behind the Winston Lutz test applied in this module? I can’t retrieve the Information from the two mentioned studies. It would be nice to help me out here. Thanks in Advance!!

Could someone please explain the math of this lines of Code:

  1. def bb_x_offset(self):

  2. “”“The offset or distance between the field CAX and BB in the x-direction (LR).”“”

  3. return cos(self.gantry_angle) * self.cax2bb_vector.x

  4. def cax_line_projection(self):

  5. “”"The projection of the field CAX through space around the area of the BB.

  6. Used for determining gantry isocenter size.

  7. Returns

  8. Line

  9. The virtual line in space made by the beam CAX.

  10. “”"

  11. p1 = Point()

  12. p2 = Point()

  13. point 1 - ray origin

  14. p1.x = self.bb_x_offset + 20 * sin(self.gantry_angle)

  15. p1.y = self.bb_y_offset + 20 * cos(self.gantry_angle)

  16. p1.z = self.bb_z_offset

  17. point 2 - ray destination

  18. p2.x = self.bb_x_offset - 20 * sin(self.gantry_angle)

  19. p2.y = self.bb_y_offset - 20 * cos(self.gantry_angle)

  20. p2.z = self.bb_z_offset

  21. l = Line(p1, p2)

  22. return l

Thank you very much!!!

Hi Niklas,
The WL module gets complicated quickly due to quite a bit of trigonometry. I probably overcomplicated it in my implementation. Finally, after a reread I realize I didn’t make the in-plane and global frame of reference clear.

Let’s start with the cax2bb_vector. This vector is an XY vector in the plane of the image (i.e. perpendicular to the gantry).
The bb_x_offset is the x offset in the global frame of reference.
Applying a cosine of the gantry angle is not a technically correct solution. It’s more like a weighting function.
If you look at the image below this becomes clearer. This shows the visuals of the CAX at gantry0,45,90 with a BB perfectly at iso and with an offset of x+2 and y+1. When the BB is offset then as we rotate the gantry, the knowledge of the x-offset becomes more uncertain. At gantry 45 from the frame of reference of the linac the x distance between iso and the BB is 2.12 but we don’t know anything about the position of the BB with respect to (w/r/t) the CAX. If there was no global y-shift then the x distance w/r/t the linac would be less, but we don’t know if that’s from a decrease of the global y-shift or an increase of the global x-shift. As we go to gantry=90 the knowledge of the global x distance becomes 0 (technically the shift appears to be infinity). Thus, the bb_x_offset (i.e. global) is unknown at the g=90 image. The cosine function thus weights the result to contribute less for angles that are oblique to the global x shift.


As for the cax line projection, it is there to do exactly what it says: create projections of the the CAX for each image. It creates lines +/-2cm from the approximate iso and then run an optimization minimization function to find the smallest sphere that touches all the lines. Note that these lines are in 3D.

I’m sure if I spent some time thinking on it again I would have done it a bit differently that wasn’t so complicated.

Hi Niklas,
I’ve been mulling this over for a long time and finally realized I overcomplicated it (as usual). The weighting that was being applied would underestimate the shift needed to move the bb to iso. I have reimplemented the 3D shift algorithm according to Low’s paper ( which is more mathematically stable. After accounting for the weighting correction, the CAX line projection method was accurate for images without couch kicks. However, trying to account for couch kicks in the line projection model became a nightmare, thus the reimplementation using Low. The CAX line projection is still useful for determining gantry iso size. A new version of pylinac is on the way.

Hey James,

thanks for your outstanding work and the fast response! Keep up the good work!!!