diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index b418ce3a..4d2aceb0 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -10,6 +10,16 @@ Legend * :bdg-primary:`Refactor` denotes a code refactor; usually this means an efficiency boost or code cleanup. * :bdg-danger:`Change` denotes a change that may break existing code. +v 3.31.0 +-------- + +ACR MRI +^^^^^^^ + +* :bdg-warning:`Fixed` The positive and negative diagonal geometric distortion calculations (``results_data().geometric_distortion_module.profiles["negative diagonal"]['width (mm)']``) for the ACR Large MRI phantom were not scaled correctly. + Since the pixel distance is diagonal the physical spacing between pixels is actually :math:`\sqrt{2}` times the pixel spacing. + This is now fixed. Prior values can be scaled by :math:`\sqrt{2}` to get the correct values. + v 3.30.0 -------- diff --git a/pylinac/acr.py b/pylinac/acr.py index 8439a5a2..a3732c42 100644 --- a/pylinac/acr.py +++ b/pylinac/acr.py @@ -1,6 +1,7 @@ from __future__ import annotations import io +import math import textwrap import warnings import webbrowser @@ -1079,7 +1080,6 @@ def _setup_rois(self) -> None: ys = xs + b coords = ndimage.map_coordinates(bin_image, [ys, xs], order=1, mode="mirror") f_data = fill_middle_zeros(coords, cutoff_px=px_to_cut_off) - # pixels are now diagonal and thus spacing between pixels is now the hypotenuse prof = FWXMProfile(values=f_data) line = Line( Point( @@ -1091,8 +1091,11 @@ def _setup_rois(self) -> None: ys[int(round(prof.field_edge_idx(side="right")))], ), ) + # pixels are now diagonal and thus spacing between pixels is now the hypotenuse + # We don't have to fix the line above because that's in pixels. The issue is the + # geometric distance and thus we only have to correct here. self.profiles["negative diagonal"] = { - "width (mm)": prof.field_width_px * self.mm_per_pixel, + "width (mm)": prof.field_width_px * self.mm_per_pixel * math.sqrt(2), "line": line, } # calculate positive diagonal @@ -1114,7 +1117,7 @@ def _setup_rois(self) -> None: ), ) self.profiles["positive diagonal"] = { - "width (mm)": prof.field_width_px * self.mm_per_pixel, + "width (mm)": prof.field_width_px * self.mm_per_pixel * math.sqrt(2), "line": line, } diff --git a/tests_basic/test_acr.py b/tests_basic/test_acr.py index 0629f932..39c19496 100644 --- a/tests_basic/test_acr.py +++ b/tests_basic/test_acr.py @@ -300,6 +300,36 @@ def test_load_from_list_of_streams(self): paths = [io.BytesIO(open(p, "rb").read()) for p in paths] ACRMRILarge(paths) + def test_geometric_distortion_profile_lengths(self): + path = get_file_from_cloud_test_repo([*TEST_DIR_MR, "GE 3T.zip"]) + mri = ACRMRILarge.from_zip(path) + mri.analyze() + data = mri.results_data() + self.assertAlmostEqual( + data.geometric_distortion_module.profiles["negative diagonal"][ + "width (mm)" + ], + 190.63, + delta=0.05, + ) + self.assertAlmostEqual( + data.geometric_distortion_module.profiles["positive diagonal"][ + "width (mm)" + ], + 190.59, + delta=0.05, + ) + self.assertAlmostEqual( + data.geometric_distortion_module.profiles["vertical"]["width (mm)"], + 190.44, + delta=0.05, + ) + self.assertAlmostEqual( + data.geometric_distortion_module.profiles["horizontal"]["width (mm)"], + 190.44, + delta=0.05, + ) + class TestACRMRIResultData(TestCase, ResultsDataBase): def construct_analyzed_instance(self):