Skip to content

Commit

Permalink
allow for ct-like algorithms to adjust ROIs
Browse files Browse the repository at this point in the history
  • Loading branch information
jrkerns committed Nov 4, 2024
1 parent 1ca7276 commit f3ea44c
Show file tree
Hide file tree
Showing 15 changed files with 399 additions and 20 deletions.
5 changes: 5 additions & 0 deletions docs/source/acr.rst
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,11 @@ Continuing from above:
data_dict["ct_module"]["roi_radius_mm"]
...
Adjusting ROI locations
^^^^^^^^^^^^^^^^^^^^^^^

To adjust ROI locations, see the sister section for CT analysis: :ref:`adjusting-roi-locations`.

CT Analysis Parameters
----------------------

Expand Down
37 changes: 37 additions & 0 deletions docs/source/cbct.rst
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,41 @@ The resulting plot will look like so:
Advanced Use
------------

.. _adjusting-roi-locations:

Adjusting ROI locations
^^^^^^^^^^^^^^^^^^^^^^^

.. versionadded:: 3.29

The ROIs of the Catphan modules can be individually adjusted (see :ref:`customizing_catphan_module_locations`). However,
sometimes the couch can get in the way or some other systematic shift of the ROIs occur. To **globally** adjust the ROIs
use the parameters ``x_adjustment``, ``y_adjustment``, ``angle_adjustment``, ``roi_size_factor``, and ``scaling_factor``
in ``analyze``.
These parameters will shift the ROIs for all modules.

.. note::

This only applies to the x and y directions. To adjust where a *specific* module is or adjust the overall z-axis offset
of the phantom you will need to perform the customization linked above.

.. code-block:: python
from pylinac import CatPhan504
ct = CatPhan504(...)
ct.analyze(
x_adjustment=5,
y_adjustment=-2,
angle_adjustment=3,
roi_size_factor=1.1,
scaling_factor=1,
)
ct.plot_analyzed_image()
.. image:: images/catphan_adjust.png
:align: center

Using ``results_data``
^^^^^^^^^^^^^^^^^^^^^^

Expand Down Expand Up @@ -347,6 +382,8 @@ value you shouldn't have to do this.
In RadMachine, the "HU localization variance" parameter can be used to fix this. See :ref:`cbct-analysis-parameters`.

.. _customizing_catphan_module_locations:

Customizing module locations
^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Expand Down
3 changes: 2 additions & 1 deletion docs/source/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ TRS-398
CT
^^

* There is a new parameter for CT-like constructor classes: ``is_zip``. This is mostly an internal
* bdg-success:`Feature` All CT-like algorithms (CatPhan, Quart, Cheese, ACR) now have global ROI adjustment parameters in ``analyze``. See :ref:`adjusting-roi-locations`.
* :bdg-primary:`Refactor` There is a new parameter for CT-like constructor classes: ``is_zip``. This is mostly an internal
flag and is used when calling the ``.from_zip`` method. The default is ``False``. This is backwards-compatible
and should not affect users. This was done for internal refactoring reasons.

Expand Down
5 changes: 5 additions & 0 deletions docs/source/cheese.rst
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,11 @@ This will plot a simple HU vs density graph.

Not all ROI densities have to be defined. Any ROI between 1 and 20 can be set.

Adjusting ROI locations
-----------------------

To adjust ROI locations, see the sister section for CT analysis: :ref:`adjusting-roi-locations`.

.. _extending_cheese_phantom:

Extending for other phantoms
Expand Down
Binary file added docs/source/images/catphan_adjust.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions docs/source/quart.rst
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,11 @@ The class can be used interchangeably with the normal class and throughout this
Advanced Use
------------

Adjusting ROI locations
^^^^^^^^^^^^^^^^^^^^^^^

To adjust ROI locations, see the sister section for CT analysis: :ref:`adjusting-roi-locations`.

Using ``results_data``
^^^^^^^^^^^^^^^^^^^^^^

Expand Down
71 changes: 67 additions & 4 deletions pylinac/acr.py
Original file line number Diff line number Diff line change
Expand Up @@ -302,8 +302,41 @@ def plot_analyzed_subimage(self, *args, **kwargs):
def save_analyzed_subimage(self, *args, **kwargs):
raise NotImplementedError("Use `save_images`")

def analyze(self) -> None:
"""Analyze the ACR CT phantom"""
def analyze(
self,
x_adjustment: float = 0,
y_adjustment: float = 0,
angle_adjustment: float = 0,
roi_size_factor: float = 1,
scaling_factor: float = 1,
) -> None:
"""Analyze the ACR CT phantom
Parameters
----------
x_adjustment: float
A fine-tuning adjustment to the detected x-coordinate of the phantom center. This will move the
detected phantom position by this amount in the x-direction in mm. Positive values move the phantom to the right.
y_adjustment: float
A fine-tuning adjustment to the detected y-coordinate of the phantom center. This will move the
detected phantom position by this amount in the y-direction in mm. Positive values move the phantom down.
angle_adjustment: float
A fine-tuning adjustment to the detected angle of the phantom. This will rotate the phantom by this amount in degrees.
Positive values rotate the phantom clockwise.
roi_size_factor: float
A fine-tuning adjustment to the ROI sizes of the phantom. This will scale the ROIs by this amount.
Positive values increase the ROI sizes. In contrast to the scaling adjustment, this
adjustment effectively makes the ROIs bigger or smaller, but does not adjust their position.
scaling_factor: float
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.
"""
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.ct_calibration_module = self.ct_calibration_module(
self, offset=0, clear_borders=self.clear_borders
Expand Down Expand Up @@ -1159,7 +1192,7 @@ def save_analyzed_subimage(self, *args, **kwargs):

def localize(self) -> None:
self._phantom_center_func = self.find_phantom_axis()
self.catphan_roll = self.find_phantom_roll()
self.catphan_roll = self.find_phantom_roll() + self.angle_adjustment
# 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 @@ -1213,14 +1246,44 @@ def find_phantom_roll(self) -> float:
"Could not determine the roll of the phantom. Ensure the 20mm top-left circle is visible on Slice 1"
)

def analyze(self, echo_number: int | None = None) -> None:
def analyze(
self,
echo_number: int | None = None,
x_adjustment: float = 0,
y_adjustment: float = 0,
angle_adjustment: float = 0,
roi_size_factor: float = 1,
scaling_factor: float = 1,
) -> None:
"""Analyze the ACR CT phantom
Parameters
----------
echo_number:
The echo to analyze. If not passed, uses the minimum echo number found.
x_adjustment: float
A fine-tuning adjustment to the detected x-coordinate of the phantom center. This will move the
detected phantom position by this amount in the x-direction in mm. Positive values move the phantom to the right.
y_adjustment: float
A fine-tuning adjustment to the detected y-coordinate of the phantom center. This will move the
detected phantom position by this amount in the y-direction in mm. Positive values move the phantom down.
angle_adjustment: float
A fine-tuning adjustment to the detected angle of the phantom. This will rotate the phantom by this amount in degrees.
Positive values rotate the phantom clockwise.
roi_size_factor: float
A fine-tuning adjustment to the ROI sizes of the phantom. This will scale the ROIs by this amount.
Positive values increase the ROI sizes. In contrast to the scaling adjustment, this
adjustment effectively makes the ROIs bigger or smaller, but does not adjust their position.
scaling_factor: float
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.
"""
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._select_echo_images(echo_number)
self.localize()
self.slice1 = self.slice1(self, offset=0)
Expand Down
32 changes: 31 additions & 1 deletion pylinac/cheese.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,14 +251,44 @@ class CheesePhantomBase(CatPhanBase, ResultsDataMixin[CheeseResult]):
module: CheeseModule
clip_in_localization = True

def analyze(self, roi_config: dict | None = None) -> None:
def analyze(
self,
roi_config: dict | None = None,
x_adjustment: float = 0,
y_adjustment: float = 0,
angle_adjustment: float = 0,
roi_size_factor: float = 1,
scaling_factor: float = 1,
) -> None:
"""Analyze the Tomo Cheese phantom.
Parameters
----------
roi_config : dict
The configuration of the ROIs, specifically the known densities.
x_adjustment: float
A fine-tuning adjustment to the detected x-coordinate of the phantom center. This will move the
detected phantom position by this amount in the x-direction in mm. Positive values move the phantom to the right.
y_adjustment: float
A fine-tuning adjustment to the detected y-coordinate of the phantom center. This will move the
detected phantom position by this amount in the y-direction in mm. Positive values move the phantom down.
angle_adjustment: float
A fine-tuning adjustment to the detected angle of the phantom. This will rotate the phantom by this amount in degrees.
Positive values rotate the phantom clockwise.
roi_size_factor: float
A fine-tuning adjustment to the ROI sizes of the phantom. This will scale the ROIs by this amount.
Positive values increase the ROI sizes. In contrast to the scaling adjustment, this
adjustment effectively makes the ROIs bigger or smaller, but does not adjust their position.
scaling_factor: float
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.
"""
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.module = self.module_class(self, clear_borders=self.clear_borders)
self.roi_config = roi_config
Expand Down
6 changes: 6 additions & 0 deletions pylinac/core/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -2011,6 +2011,12 @@ def roll(self, direction: str, amount: int):
for img in self.images:
img.roll(direction, amount)

def crop(
self, pixels: int, edges: tuple[str, ...] = ("top", "bottom", "left", "right")
):
for img in self.images:
img.crop(pixels, edges=edges)

def __getitem__(self, item) -> DicomImage:
return self.images[item]

Expand Down
61 changes: 51 additions & 10 deletions pylinac/ct.py
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,8 @@ def __init__(
self.slice_thickness = catphan.dicom_stack.metadata.SliceThickness
self.slice_spacing = catphan.dicom_stack.slice_spacing
self.catphan_roll = catphan.catphan_roll
self.roi_size_factor = catphan.roi_size_factor
self.scaling_factor = catphan.scaling_factor
self.mm_per_pixel = catphan.mm_per_pixel
self.rois: dict[str, HUDiskROI] = {}
self.background_rois: dict[str, HUDiskROI] = {}
Expand All @@ -477,21 +479,29 @@ def _convert_units_in_settings(self) -> None:
if isinstance(settings, dict):
if settings.get("distance") is not None:
settings["distance_pixels"] = (
settings["distance"] / self.mm_per_pixel
settings["distance"]
* self.scaling_factor
/ self.mm_per_pixel
)
if settings.get("angle") is not None:
settings["angle_corrected"] = (
settings["angle"] + self.catphan_roll
)
if settings.get("radius") is not None:
settings["radius_pixels"] = (
settings["radius"] / self.mm_per_pixel
settings["radius"]
* self.roi_size_factor
/ self.mm_per_pixel
)
if settings.get("width") is not None:
settings["width_pixels"] = settings["width"] / self.mm_per_pixel
settings["width_pixels"] = (
settings["width"] * self.roi_size_factor / self.mm_per_pixel
)
if settings.get("height") is not None:
settings["height_pixels"] = (
settings["height"] / self.mm_per_pixel
settings["height"]
* self.roi_size_factor
/ self.mm_per_pixel
)

def preprocess(self, catphan):
Expand Down Expand Up @@ -1376,7 +1386,7 @@ def mtf(self) -> MTF:
@property
def radius2linepairs(self) -> float:
"""Radius from the phantom center to the line-pair region, corrected for pixel spacing."""
return self.radius2linepairs_mm / self.mm_per_pixel
return self.radius2linepairs_mm * self.scaling_factor / self.mm_per_pixel

def plotly_rois(self, fig: go.Figure) -> None:
self.circle_profile.plotly(fig, color="blue", plot_peaks=False)
Expand All @@ -1401,7 +1411,7 @@ def circle_profile(self) -> CollapsedCircleProfile:
self.radius2linepairs,
image_array=self.image,
start_angle=self.start_angle + np.deg2rad(self.catphan_roll),
width_ratio=0.04,
width_ratio=0.04 * self.roi_size_factor,
sampling_ratio=2,
ccw=self.ccw,
)
Expand Down Expand Up @@ -1748,6 +1758,11 @@ class CatPhanBase(ResultsDataMixin[CatphanResult], QuaacMixin):
modules: dict[CatPhanModule, dict[str, int]]
dicom_stack: image.DicomImageStack | image.LazyDicomImageStack
clip_in_localization: bool = False
x_adjustment: float
y_adjustment: float
roi_size_factor: float
scaling_factor: float
angle_adjustment: float

def __init__(
self,
Expand Down Expand Up @@ -2057,7 +2072,7 @@ def localize(self) -> 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()
self.catphan_roll = self.find_phantom_roll()
self.catphan_roll = self.find_phantom_roll() + self.angle_adjustment
self.origin_slice = self.refine_origin_slice(
initial_slice_num=self.origin_slice
)
Expand Down Expand Up @@ -2119,8 +2134,8 @@ def find_phantom_axis(self) -> (Callable, Callable):
center_x.append(roi.centroid[1])
# clip to exclude any crazy values
zs = np.array(z)
center_xs = np.array(center_x)
center_ys = np.array(center_y)
center_xs = np.array(center_x) + self.x_adjustment
center_ys = np.array(center_y) + self.y_adjustment
# gives an absolute and relative range so tight ranges are all included
# but extreme values are excluded. Sometimes the range is very tight
# and thus percentiles are not a sure thing
Expand Down Expand Up @@ -2410,6 +2425,11 @@ def analyze(
visibility_threshold: float = 0.15,
thickness_slice_straddle: str | int = "auto",
expected_hu_values: dict[str, int | float] | None = None,
x_adjustment: float = 0,
y_adjustment: float = 0,
angle_adjustment: float = 0,
roi_size_factor: float = 1,
scaling_factor: float = 1,
):
"""Single-method full analysis of CBCT DICOM files.
Expand Down Expand Up @@ -2454,8 +2474,29 @@ def analyze(
expected_hu_values
An optional dictionary of the expected HU values for the HU linearity module. The keys are the ROI names and the values
are the expected HU values. If a key is not present or the parameter is None, the default values will be used.
x_adjustment: float
A fine-tuning adjustment to the detected x-coordinate of the phantom center. This will move the
detected phantom position by this amount in the x-direction in mm. Positive values move the phantom to the right.
y_adjustment: float
A fine-tuning adjustment to the detected y-coordinate of the phantom center. This will move the
detected phantom position by this amount in the y-direction in mm. Positive values move the phantom down.
angle_adjustment: float
A fine-tuning adjustment to the detected angle of the phantom. This will rotate the phantom by this amount in degrees.
Positive values rotate the phantom clockwise.
roi_size_factor: float
A fine-tuning adjustment to the ROI sizes of the phantom. This will scale the ROIs by this amount.
Positive values increase the ROI sizes. In contrast to the scaling adjustment, this
adjustment effectively makes the ROIs bigger or smaller, but does not adjust their position.
scaling_factor: float
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.
"""
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()
ctp404, offset = self._get_module(CTP404CP504, raise_empty=True)
self.ctp404 = ctp404(
Expand Down
Loading

0 comments on commit f3ea44c

Please sign in to comment.