Skip to content

Commit

Permalink
Merged in feature/RAM-4185-origin-slice-parameter-ct (pull request #485)
Browse files Browse the repository at this point in the history
RAM-4185 add origin slice parameter to CT-like analysis

Approved-by: Randy Taylor
  • Loading branch information
jrkerns committed Dec 5, 2024
2 parents 7741174 + 0bed567 commit 1724af7
Show file tree
Hide file tree
Showing 12 changed files with 71 additions and 9 deletions.
2 changes: 2 additions & 0 deletions docs/source/acr.rst
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,8 @@ CT Analysis Parameters
* **Scaling factor**: A fine-tuning adjustment to the detected magnification of the phantom. This will zoom the ROIs and phantom outline (if applicable) by this amount.
In contrast to the roi size adjustment, the scaling adjustment effectively moves the phantom and ROIs
closer or further from the phantom center. I.e. this zooms the outline and ROI positions, but not ROI size.
* **Origin slice**: The slice number that corresponds to the HU linearity slice.
This is a fallback mechanism in case the automatic detection fails.

Interpreting CT Results
-----------------------
Expand Down
2 changes: 2 additions & 0 deletions docs/source/cbct.rst
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,8 @@ This applies to the 503, 504, 600, and 604. Model-specific parameters are called
* **Scaling factor**: A fine-tuning adjustment to the detected magnification of the phantom. This will zoom the ROIs and phantom outline (if applicable) by this amount.
In contrast to the roi size adjustment, the scaling adjustment effectively moves the phantom and ROIs
closer or further from the phantom center. I.e. this zooms the outline and ROI positions, but not ROI size.
* **Origin slice**: The slice number that corresponds to the HU linearity slice.
This is a fallback mechanism in case the automatic detection fails.

.. _cbct-algorithm:

Expand Down
7 changes: 7 additions & 0 deletions docs/source/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ Starshot

* :bdg-success:`Feature` Angles of the spokes are now reported via the ``angles`` attribute of ``results_data``. See :ref:`interpreting-starshot-results`.

CT-like
^^^^^^^

* :bdg-success:`Feature` CT-like algorithms have a new parameter for the ``analyze`` method: ``origin_slice``.
This parameter lets the user set the z-position of the phantom. This is a fallback method to let the user set the
slice of (usually) the HU linearity module. This is useful if the automatic detection of the origin slice fails.

v 3.29.0
--------

Expand Down
2 changes: 2 additions & 0 deletions docs/source/cheese.rst
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,8 @@ Analysis Parameters
* **Scaling factor**: A fine-tuning adjustment to the detected magnification of the phantom. This will zoom the ROIs and phantom outline (if applicable) by this amount.
In contrast to the roi size adjustment, the scaling adjustment effectively moves the phantom and ROIs
closer or further from the phantom center. I.e. this zooms the outline and ROI positions, but not ROI size.
* **Origin slice**: The slice number that corresponds to the slice to analyze.
This is a fallback mechanism in case the automatic detection fails.

Algorithm
---------
Expand Down
2 changes: 2 additions & 0 deletions docs/source/quart.rst
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,8 @@ Analysis Parameters
* **Scaling factor**: A fine-tuning adjustment to the detected magnification of the phantom. This will zoom the ROIs and phantom outline (if applicable) by this amount.
In contrast to the roi size adjustment, the scaling adjustment effectively moves the phantom and ROIs
closer or further from the phantom center. I.e. this zooms the outline and ROI positions, but not ROI size.
* **Origin slice**: The slice number that corresponds to the HU linearity slice.
This is a fallback mechanism in case the automatic detection fails.

Algorithm
---------
Expand Down
6 changes: 5 additions & 1 deletion pylinac/acr.py
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,7 @@ def analyze(
angle_adjustment: float = 0,
roi_size_factor: float = 1,
scaling_factor: float = 1,
origin_slice: int | None = None,
) -> None:
"""Analyze the ACR CT phantom
Expand All @@ -331,13 +332,16 @@ def analyze(
A fine-tuning adjustment to the detected magnification of the phantom. This will zoom the ROIs and phantom outline (if applicable) by this amount.
In contrast to the roi size adjustment, the scaling adjustment effectively moves the phantom and ROIs
closer or further from the phantom center. I.e. this zooms the outline and ROI positions, but not ROI size.
origin_slice: int, None
The slice number of the HU linearity module. If None, will be automatically determined. This is a fallback
method in case the automatic determination fails.
"""
self.x_adjustment = x_adjustment
self.y_adjustment = y_adjustment
self.angle_adjustment = angle_adjustment
self.roi_size_factor = roi_size_factor
self.scaling_factor = scaling_factor
self.localize()
self.localize(origin_slice=origin_slice)
self.ct_calibration_module = self.ct_calibration_module(
self, offset=0, clear_borders=self.clear_borders
)
Expand Down
6 changes: 5 additions & 1 deletion pylinac/cheese.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,7 @@ def analyze(
angle_adjustment: float = 0,
roi_size_factor: float = 1,
scaling_factor: float = 1,
origin_slice: int | None = None,
) -> None:
"""Analyze the Tomo Cheese phantom.
Expand All @@ -283,13 +284,16 @@ def analyze(
A fine-tuning adjustment to the detected magnification of the phantom. This will zoom the ROIs and phantom outline (if applicable) by this amount.
In contrast to the roi size adjustment, the scaling adjustment effectively moves the phantom and ROIs
closer or further from the phantom center. I.e. this zooms the outline and ROI positions, but not ROI size.
origin_slice : int, None
The slice number to analyze. If None, the slice will be automatically determined. This is a fallback
method in case the automatic slice detection fails.
"""
self.x_adjustment = x_adjustment
self.y_adjustment = y_adjustment
self.angle_adjustment = angle_adjustment
self.roi_size_factor = roi_size_factor
self.scaling_factor = scaling_factor
self.localize()
self.localize(origin_slice=origin_slice)
self.module = self.module_class(self, clear_borders=self.clear_borders)
self.roi_config = roi_config

Expand Down
24 changes: 18 additions & 6 deletions pylinac/ct.py
Original file line number Diff line number Diff line change
Expand Up @@ -2068,14 +2068,18 @@ def _results(self) -> None:
mtfs[mtf] = mtfval
print(f"MTFs: {mtfs}")

def localize(self) -> None:
def localize(self, origin_slice: int | None) -> None:
"""Find the slice number of the catphan's HU linearity module and roll angle"""
self._phantom_center_func = self.find_phantom_axis()
self.origin_slice = self.find_origin_slice()
if origin_slice is not None:
self.origin_slice = origin_slice
else:
self.origin_slice = self.find_origin_slice()
self.catphan_roll = self.find_phantom_roll() + self.angle_adjustment
self.origin_slice = self.refine_origin_slice(
initial_slice_num=self.origin_slice
)
if origin_slice is None:
self.origin_slice = self.refine_origin_slice(
initial_slice_num=self.origin_slice
)
# now that we have the origin slice, ensure we have scanned all linked modules
if not self._ensure_physical_scan_extent():
raise ValueError(
Expand Down Expand Up @@ -2259,6 +2263,10 @@ def find_phantom_roll(self, func: Callable | None = None) -> float:
sorted_bubbles = sorted(
central_bubbles, key=lambda x: x.centroid[0]
) # top, bottom
if not sorted_bubbles:
raise ValueError(
"No air bubbles were found in the HU slice. The origin slice algorithm likely failed or the origin slice was passed and is incorrect."
)
y_dist = sorted_bubbles[1].centroid[0] - sorted_bubbles[0].centroid[0]
x_dist = sorted_bubbles[1].centroid[1] - sorted_bubbles[0].centroid[1]
phan_roll = np.arctan2(y_dist, x_dist)
Expand Down Expand Up @@ -2430,6 +2438,7 @@ def analyze(
angle_adjustment: float = 0,
roi_size_factor: float = 1,
scaling_factor: float = 1,
origin_slice: int | None = None,
):
"""Single-method full analysis of CBCT DICOM files.
Expand Down Expand Up @@ -2491,13 +2500,16 @@ def analyze(
A fine-tuning adjustment to the detected magnification of the phantom. This will zoom the ROIs and phantom outline (if applicable) by this amount.
In contrast to the roi size adjustment, the scaling adjustment effectively moves the phantom and ROIs
closer or further from the phantom center. I.e. this zooms the outline and ROI positions, but not ROI size.
origin_slice : int, None
The slice number of the HU linearity module. If None, the slice will be determined automatically. This is
a fallback method if the automatic localization algorithm fails.
"""
self.x_adjustment = x_adjustment
self.y_adjustment = y_adjustment
self.angle_adjustment = angle_adjustment
self.roi_size_factor = roi_size_factor
self.scaling_factor = scaling_factor
self.localize()
self.localize(origin_slice)
ctp404, offset = self._get_module(CTP404CP504, raise_empty=True)
self.ctp404 = ctp404(
self,
Expand Down
6 changes: 5 additions & 1 deletion pylinac/quart.py
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,7 @@ def analyze(
angle_adjustment: float = 0,
roi_size_factor: float = 1,
scaling_factor: float = 1,
origin_slice: int | None = None,
):
"""Single-method full analysis of Quart DICOM files.
Expand Down Expand Up @@ -470,13 +471,16 @@ def analyze(
A fine-tuning adjustment to the detected magnification of the phantom. This will zoom the ROIs and phantom outline (if applicable) by this amount.
In contrast to the roi size adjustment, the scaling adjustment effectively moves the phantom and ROIs
closer or further from the phantom center. I.e. this zooms the outline and ROI positions, but not ROI size.
origin_slice : int, None
The slice number of the HU linearity slice. If None, will be automatically determined. This is a
fallback method in case the automatic method fails.
"""
self.x_adjustment = x_adjustment
self.y_adjustment = y_adjustment
self.angle_adjustment = angle_adjustment
self.roi_size_factor = roi_size_factor
self.scaling_factor = scaling_factor
self.localize()
self.localize(origin_slice=origin_slice)
self.hu_module = self.hu_module_class(
self,
offset=0,
Expand Down
5 changes: 5 additions & 0 deletions tests_basic/test_acr.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ def test_lazy_is_same_as_default(self):
lazy_ct.analyze()
self.assertEqual(self.ct.results(), lazy_ct.results())

def test_passing_origin_slice(self):
ct = ACRCT.from_zip(self.path)
ct.analyze(origin_slice=3) # automatic is 2
self.assertEqual(ct.origin_slice, 3)


class TestPlottingSaving(TestCase):
@classmethod
Expand Down
13 changes: 13 additions & 0 deletions tests_basic/test_cbct.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,19 @@ def test_same_results_for_lazy_load(self):
lazy_ct.analyze()
self.assertEqual(ct.results(), lazy_ct.results())

def test_passing_origin_slice_works(self):
ct = CatPhan504.from_demo_images()
ct.analyze(origin_slice=33) # automatic slice is 32
self.assertEqual(ct.origin_slice, 33)

def test_passing_origin_slice_doesnt_perform_refinement(self):
# the catphan604 has a refinement algorithm to "fine-tune"
# the origin slice; ensure that it doesn't run when the origin slice is passed
# You get what you set!
ct = CatPhan604.from_demo_images()
ct.analyze(origin_slice=46) # automatic slice is 45
self.assertEqual(ct.origin_slice, 46)


class Test504Quaac(QuaacTestBase, TestCase):
def quaac_instance(self):
Expand Down
5 changes: 5 additions & 0 deletions tests_basic/test_cheese.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ class TestGeneral(TestCase):
def test_demo(self):
TomoCheese.run_demo()

def test_passing_origin_slice(self):
cheese = TomoCheese.from_demo_images()
cheese.analyze(origin_slice=26) # automatic is 24
assert cheese.origin_slice == 26


class TestAnalysis(TestCase):
def test_cropping_before_analysis(self):
Expand Down

0 comments on commit 1724af7

Please sign in to comment.