Picket Fence and dist2cax

Hello everyone,

I’m using the PicketFence module to analyze our EPID-based picket fence images. Our plan uses picket positions centered on the origin, spaced every 15mm. I then use pf.pickets.dist2cax-PicketPositions to evaluate how far off the measured picket positions are from their planned positions:

`

PicketPositions = [75, 60, 45, 30, 15, 0, -15, -30, -45, -60, -75]
PicketDiscrepancy = [0]*11

for x in range(11):
PicketDiscrepancy = pf.pickets.dist2cax-PicketPositions

`

We do this for images acquired at the four cardinal gantry angles. The problem is, we’re getting some pretty bad results on some of our units for some angles, as bad as 2.8mm.

Some questions:

  1. Have I coded this correctly to give me the information that I desire?
  2. Is the IsoCal calibration reflected in dist2cax, or is it not represented? Would that be different on Clinacs and TrueBeams?
  3. Is the correlation between planned and measured picket positions something that you would look at in your clinic? Why or why not? It doesn’t seem to be part of the DoseLab software that we previously used.

Here is the code for dist2cax:

`
@property def dist2cax(self) → float: “”“The distance from the CAX to the picket, in mm.”“” center_fit = np.poly1d(self.fit) if self.settings.orientation == UP_DOWN: length = self.image.shape[0] else: length = self.image.shape[1] x_data = np.arange(length) y_data = center_fit(x_data) idx = int(round(len(x_data) / 2)) if self.settings.orientation == UP_DOWN: axis = ‘x’ p1 = Point(y_data[idx], x_data[idx]) else: axis = ‘y’ p1 = Point(x_data[idx], y_data[idx]) return (getattr(self.image.center, axis) - getattr(p1, axis)) * self.settings.mmpd

`

Thanks in advance,

Considering it further, dist2cax is just finding the center of the image, right? So, it wouldn’t know, for example, about isocal on a Clinac, or if the EPID was shifted (intentionally or otherwise)?

Hi David,
Yes, dist2cax measures the distance to the geometric center of the image. I would need to brush up on exactly how IsoCal on Clinac works. It’s supposed to apply a virtual shift to the data, but it’s possible this is in a tag I haven’t seen/don’t use. Of course, this isn’t a problem on TrueBeam as the correction is to the imager itself.

Have you looked at the shift as a function of gantry angle? If the apparent shift correlated with what is expected (lateral shift at 90/270) then it would indeed be indicative of isocal not being taken into account.

Thanks for the response. What I ended up doing was to include:

`

dcm_img = pylinac.core.image.LinacDicomImage(PF_file)
metadata = dcm_img.metadata
ImagerTranslation=metadata.XRayImageReceptorTranslation[1]

PicketPositions = [75, 60, 45, 30, 15, 0, -15, -30, -45, -60, -75]
PicketDiscrepancy = [0]*11

for x in range(11):
PicketDiscrepancy = pf.pickets.dist2cax-PicketPositions

max_picket_discrepancy= max(map(abs, PicketDiscrepancy))-ImagerTranslation

`

I used XRayImageReceptorTranslation[1] because our collimator rotation is 90 degrees. I could have coded it better to check for collimator rotation first, but we have no plans to change our picket fence plans so I took the lazy way out.

I’d be very interested to see what those receptor shifts looked like as a function of gantry angle =)

Three clinacs, four gantry angles 0, 90, 270, 180 (mm):

  • RT1: 0.84, 1.58, 1.75, 2.46
  • RT3: 0.86, 1.44, 1.95, 2.8
  • RT8: 0.33, 0.66, 0.95, 1.18

All the values were positive? Interesting. Sweeping away the cobwebs I think I recall that g=0 is typically the best accuracy because that’s where it’s calibrated (minus isocal). The part I don’t get is why 180 is so high. Also, shouldn’t there be two offsets, one for x and one for y?

I was only reporting absolute values only, worry.

I’m ignoring the X direction offsets.

Note that the above posted code gave some wrong/misleading results. I’ve now changed it to

`
dcm_img = pylinac.core.image.LinacDicomImage(PF_file)

metadata = dcm_img.metadata
ImagerTranslation=metadata.XRayImageReceptorTranslation[1]

PicketPositions = [75, 60, 45, 30, 15, 0, -15, -30, -45, -60, -75]
PicketDiscrepancy = [0]*11

for x in range(11):

PicketDiscrepancy = pf.pickets.dist2cax-PicketPositions + ImagerTranslation

max_picket_discrepancy= max(map(abs, PicketDiscrepancy))
`