diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index c142dc31..5797243c 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -13,6 +13,13 @@ Legend v 3.26.0 -------- +Picket Fence +^^^^^^^^^^^^ + +* :bdg-danger:`Change` The ``mlc_positions_by_leaf`` attribute from the ``results_data`` call has been changed + to be **relative to the CAX**. Previously, the positions were relative to the left/top of the image. This attribute + was requested by French sites and this change was also requested by them. + Gamma ^^^^^ diff --git a/pylinac/picketfence.py b/pylinac/picketfence.py index c9af7fb5..9f0d823e 100644 --- a/pylinac/picketfence.py +++ b/pylinac/picketfence.py @@ -36,7 +36,7 @@ from . import Normalization from .core import image, pdf -from .core.geometry import Line, Point, Rectangle +from .core.geometry import Line, Point, PointSerialized, Rectangle from .core.io import get_url, retrieve_demo_file from .core.profile import FWXMProfilePhysical, MultiProfile from .core.utilities import ( @@ -187,11 +187,13 @@ class PFResult(ResultBase): description="The widths of the pickets in mm." ) mlc_positions_by_leaf: dict[str, list[float]] = Field( - description="A dictionary where the key is the leaf number and the value is a list of positions in mm **from the left or top of the image**." + description="A dictionary where the key is the leaf number and the value is a list of positions in mm **from the CAX**. The distance is from the x-value (or y-value for left-right orientation) of the CAX. " + "Rotation of the MLCs would affect these distances." ) mlc_errors_by_leaf: dict[str, list[float]] = Field( description="A dictionary where the key is the leaf number and the value is a list of errors in mm." ) + cax: PointSerialized = Field(description="The position of the CAX in pixels.") class PFDicomImage(image.LinacDicomImage): @@ -248,7 +250,7 @@ def center(self) -> Point: cax = super().center + cax_shift # invert the y-axis for plotting purposes/consistency cax.y = 2 * (self.shape[0] // 2) - cax.y - return cax + return Point(cax.x, cax.y) else: return super().center @@ -1139,11 +1141,19 @@ def _generate_results_data(self) -> PFResult: } errors_by_leaf = {} positions_by_leaf = {} + cax_position = ( + self.image.center.x + if self.orientation == Orientation.UP_DOWN + else self.image.center.y + ) + cax_physical_position = cax_position / self.image.dpmm for _, group_iter in groupby(self.mlc_meas, key=lambda m: m.leaf_num): leaf_items = list(group_iter) # group_iter is a generator leaf_names = leaf_items[0].full_leaf_nums for idx, leaf_name in enumerate(leaf_names): - pos_vals = [m.position_mm[idx] for m in leaf_items] + pos_vals = [ + m.position_mm[idx] - cax_physical_position for m in leaf_items + ] error_vals = [m.error[idx] for m in leaf_items] positions_by_leaf[str(leaf_name)] = pos_vals errors_by_leaf[str(leaf_name)] = error_vals @@ -1170,6 +1180,7 @@ def _generate_results_data(self) -> PFResult: picket_widths=picket_widths, mlc_positions_by_leaf=positions_by_leaf, mlc_errors_by_leaf=errors_by_leaf, + cax=self.image.center, ) def publish_pdf( diff --git a/tests_basic/test_picketfence.py b/tests_basic/test_picketfence.py index ce78651c..a77574bf 100644 --- a/tests_basic/test_picketfence.py +++ b/tests_basic/test_picketfence.py @@ -157,7 +157,7 @@ def test_results_data(self): ) # 36 leaf pairs in the image # constancy check self.assertAlmostEqual( - statistics.mean(data.mlc_positions_by_leaf["17"]), 204.63, delta=0.1 + statistics.mean(data.mlc_positions_by_leaf["17"]), 7.91, delta=0.1 ) # check max error matches a combination of the leaf values self.assertEqual( @@ -168,7 +168,7 @@ def test_results_data(self): data_dict = self.pf.results_data(as_dict=True) self.assertIsInstance(data_dict, dict) self.assertIn("pylinac_version", data_dict) - self.assertEqual(len(data_dict), 18) + self.assertEqual(len(data_dict), 19) data_str = self.pf.results_data(as_json=True) self.assertIsInstance(data_str, str) @@ -347,6 +347,9 @@ def test_bb_pf_combo(self): pf.analyze(separate_leaves=False) results = pf.results_data() self.assertAlmostEqual(results.max_error_mm, 0.0, delta=0.005) + self.assertAlmostEqual(results.cax.x, 636.5, delta=0.1) + # bb is 2mm off in bb setup image above + self.assertAlmostEqual(results.mlc_positions_by_leaf["17"][0], -102, delta=0.1) class LoadingFromMultiple(TestCase):