diff --git a/docs/source/acr.rst b/docs/source/acr.rst index 58791501..d56999e5 100644 --- a/docs/source/acr.rst +++ b/docs/source/acr.rst @@ -461,6 +461,8 @@ Interpreting MRI Results * ``measured_slice_thickness_mm``: The measured slice thickness in mm. * ``row_mtf_50``: The MTF at 50% for the row-based ROIs. * ``col_mtf_50``: The MTF at 50% for the column-based ROIs. + * ``row_mtf_lp_mm``: The MTF:lp/mm for the row-based ROIs from 10-90% in 10% increments. + * ``col_mtf_lp_mm``: The MTF:lp/mm for the column-based ROIs from 10-90% in 10% increments. * ``rois``: A dictionary of the analyzed MTF ROIs. The key is the name of the ROI; e.g. ``Row 1.1`` and the key is a dictionary of the following items: diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index 4d2aceb0..e94cd29f 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -19,6 +19,8 @@ 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. +* :bdg-success:`Feature` MTF is now provided in 10% increments from 10-90% in the ``results_data`` return object + for both row and column MTFs as ``row_mtf_lp_mm`` and ``col_mtf_lp_mm``. This is to match CT-like MTF outputs. v 3.30.0 -------- diff --git a/pylinac/acr.py b/pylinac/acr.py index a3732c42..b7b21df7 100644 --- a/pylinac/acr.py +++ b/pylinac/acr.py @@ -912,6 +912,14 @@ class MRSlice1ModuleOutput(BaseModel): description="The MTF at 50% for the column-based ROIs.", title="Column-wise 50% MTF (lp/mm)", ) + row_mtf_lp_mm: dict[int, float] = Field( + description="A key-value pair of the MTF. The key is the relative resolution in % and the value is the lp/mm at that resolution", + title="MTF (lp/mm)", + ) + col_mtf_lp_mm: dict[int, float] = Field( + description="A key-value pair of the MTF. The key is the relative resolution in % and the value is the lp/mm at that resolution", + title="MTF (lp/mm)", + ) class MRUniformityModule(CatPhanModule): @@ -1636,6 +1644,15 @@ def results(self, as_str: bool = True) -> str | tuple: return string def _generate_results_data(self) -> ACRMRIResult: + resolutions = range(10, 91, 10) # 10-90% in 10% increments + row_mtfs = { + resolution: self.slice1.row_mtf.relative_resolution(resolution) + for resolution in resolutions + } + col_mtfs = { + resolution: self.slice1.col_mtf.relative_resolution(resolution) + for resolution in resolutions + } return ACRMRIResult( phantom_model=self._model, phantom_roll_deg=self.catphan_roll, @@ -1650,6 +1667,8 @@ def _generate_results_data(self) -> ACRMRIResult: measured_slice_thickness_mm=self.slice1.measured_slice_thickness_mm, row_mtf_50=self.slice1.row_mtf.relative_resolution(50), col_mtf_50=self.slice1.col_mtf.relative_resolution(50), + row_mtf_lp_mm=row_mtfs, + col_mtf_lp_mm=col_mtfs, ), slice11=MRSlice11ModuleOutput( offset=MR_SLICE11_MODULE_OFFSET_MM, diff --git a/tests_basic/test_acr.py b/tests_basic/test_acr.py index 39c19496..a1c21233 100644 --- a/tests_basic/test_acr.py +++ b/tests_basic/test_acr.py @@ -338,6 +338,18 @@ def construct_analyzed_instance(self): mri.analyze() return mri + def test_row_mtf_keys(self): + phantom = self.construct_analyzed_instance() + data = phantom.results_data() + for key, value in {10: 1.13, 80: 0.564}.items(): + self.assertAlmostEqual(data.slice1.row_mtf_lp_mm[key], value, delta=0.01) + + def test_col_mtf_keys(self): + phantom = self.construct_analyzed_instance() + data = phantom.results_data() + for key, value in {10: 1.11, 80: 0.826}.items(): + self.assertAlmostEqual(data.slice1.col_mtf_lp_mm[key], value, delta=0.01) + class TestMRGeneral(TestCase): def setUp(self):