ACR Phantoms

Overview

New in version 3.2.

Warning

These algorithms have only a limited amount of testing data and results should be scrutinized. Further, the algorithm is more likely to change in the future when a more robust test suite is built up. If you’d like to submit data, enter it here.

The ACR module provides routines for automatically analyzing DICOM images of the ACR CT 464 phantom and Large MR phantom. It can load a folder or zip file of images, correcting for translational and rotational offsets.

Phantom reference information is drawn from the ACR CT solution article and the analysis is drawn from the ACR CT testing article. MR analysis is drawn from the ACR Guidance document.

Warning

Due to the rectangular ROIs on the MRI phantom analysis, rotational errors should be <= 1 degree. Translational errors are still accounted for however for any reasonable amount.

Typical Use

The ACR CT and MR analyses follows a similar pattern of load/analyze/output as the rest of the library. Unlike the CatPhan analysis, customization is not a goal, as the phantoms and analyses are much more well-defined. I.e. there’s less of a use case for custom phantoms in this scenario. CT is mostly used here but is interchangeable with the MRI class.

To use the ACR analysis, import the class:

from pylinac import ACRCT, ACRMRILarge

And then load, analyze, and view the results:

  • Load images – Loading can be done with a directory or zip file:

    acr_ct_folder = r"C:/CT/ACR/Sept 2021"
    ct = ACRCT(acr_ct_folder)
    acr_mri_folder = r"C:/MRI/ACR/Sept 2021"
    mri = ACRMRILarge(acr_mri_folder)
    

    or load from zip:

    acr_ct_zip = r"C:/CT/ACR/Sept 2021.zip"
    ct = ACRCT.from_zip(acr_ct_zip)
    
  • Analyze – Analyze the dataset:

    ct.analyze()
    
  • View the results – Reviewing the results can be done in text or dict format as well as images:

    # print text to the console
    print(ct.results())
    # view analyzed image summary
    ct.plot_analyzed_image()
    # view images independently
    ct.plot_images()
    # save the images
    ct.save_analyzed_image()
    # or
    ct.save_images()
    # finally, save a PDF
    ct.publish_pdf()
    

Advanced Use

Using results_data

Using the ACR module in your own scripts? While the analysis results can be printed out, if you intend on using them elsewhere (e.g. in an API), they can be accessed the easiest by using the results_data() method which returns a ACRCTResult instance. For MRI this is results_data() method and ACRMRILargeResult respectively.

Continuing from above:

data = ct.results_data()
data.ct_module.roi_radius_mm
# and more

# return as a dict
data_dict = ct.results_data(as_dict=True)
data_dict['ct_module']['roi_radius_mm']
...

API Documentation

class pylinac.acr.ACRCT(folderpath: Union[str, Sequence[str], pathlib.Path, Sequence[pathlib.Path], Sequence[_io.BytesIO]], check_uid: bool = True)[source]

Bases: pylinac.ct.CatPhanBase

Parameters:
  • folderpath (str, list of strings, or Path to folder) – String that points to the CBCT image folder location.
  • check_uid (bool) – Whether to enforce raising an error if more than one UID is found in the dataset.
Raises:
  • NotADirectoryError – If folder str passed is not a valid directory.
  • FileNotFoundError – If no CT images are found in the folder
ct_calibration_module

alias of CTModule

low_contrast_module

alias of LowContrastModule

spatial_resolution_module

alias of SpatialResolutionModule

uniformity_module

alias of UniformityModule

plot_analyzed_subimage(*args, **kwargs)[source]

Plot a specific component of the CBCT analysis.

Parameters:
  • subimage ({'hu', 'un', 'sp', 'lc', 'mtf', 'lin', 'prof'}) –

    The subcomponent to plot. Values must contain one of the following letter combinations. E.g. linearity, linear, and lin will all draw the HU linearity values.

    • hu draws the HU linearity image.
    • un draws the HU uniformity image.
    • sp draws the Spatial Resolution image.
    • lc draws the Low Contrast image (if applicable).
    • mtf draws the RMTF plot.
    • lin draws the HU linearity values. Used with delta.
    • prof draws the HU uniformity profiles.
  • delta (bool) – Only for use with lin. Whether to plot the HU delta or actual values.
  • show (bool) – Whether to actually show the plot.
save_analyzed_subimage(*args, **kwargs)[source]

Save a component image to file.

Parameters:
  • filename (str, file object) – The file to write the image to.
  • subimage (str) – See plot_analyzed_subimage() for parameter info.
  • delta (bool) – Only for use with lin. Whether to plot the HU delta or actual values.
analyze() → None[source]

Analyze the ACR CT phantom

plot_analyzed_image(show: bool = True, **plt_kwargs) → matplotlib.figure.Figure[source]

Plot the analyzed image

Parameters:
  • show – Whether to show the image.
  • plt_kwargs – Keywords to pass to matplotlib for figure customization.
save_analyzed_image(filename: Union[str, pathlib.Path, _io.BytesIO], **plt_kwargs) → None[source]

Save the analyzed image to disk or stream

Parameters:
  • filename – Where to save the image to
  • plt_kwargs – Keywords to pass to matplotlib for figure customization.
plot_images(show: bool = True, **plt_kwargs) → Dict[str, matplotlib.figure.Figure][source]

Plot all the individual images separately

Parameters:
  • show – Whether to show the images.
  • plt_kwargs – Keywords to pass to matplotlib for figure customization.
save_images(directory: Union[pathlib.Path, str, None] = None, to_stream: bool = False, **plt_kwargs) → List[Union[pathlib.Path, _io.BytesIO]][source]

Save separate images to disk or stream.

Parameters:
  • directory – The directory to write the images to. If None, will use current working directory
  • to_stream – Whether to write to stream or disk. If True, will return streams. Directory is ignored in that scenario.
  • plt_kwargs – Keywords to pass to matplotlib for figure customization.
find_phantom_roll(func=<function ACRCT.<lambda>>) → float[source]

Determine the “roll” of the phantom.

Only difference of base method is that we sort the ROIs by size, not by being in the center since the two we’re looking for are both right-sided.

results() → str[source]

Return the results of the analysis as a string. Use with print().

results_data(as_dict=False) → Union[pylinac.acr.ACRCTResult, dict][source]

Present the results data and metadata as a dataclass or dict. The default return type is a dataclass.

publish_pdf(filename: Union[str, pathlib.Path], notes: Optional[str] = None, open_file: bool = False, metadata: Optional[dict] = None, logo: Union[pathlib.Path, str, None] = None) → None[source]

Publish (print) a PDF containing the analysis and quantitative results.

Parameters:
  • filename ((str, file-like object}) – The file to write the results to.
  • notes (str, list of strings) – Text; if str, prints single line. If list of strings, each list item is printed on its own line.
  • open_file (bool) – Whether to open the file using the default program after creation.
  • metadata (dict) – Extra data to be passed and shown in the PDF. The key and value will be shown with a colon. E.g. passing {‘Author’: ‘James’, ‘Unit’: ‘TrueBeam’} would result in text in the PDF like: ————– Author: James Unit: TrueBeam ————–
  • logo (Path, str) – A custom logo to use in the PDF report. If nothing is passed, the default pylinac logo is used.
catphan_size

The expected size of the phantom in pixels, based on a 20cm wide phantom.

find_origin_slice() → int

Using a brute force search of the images, find the median HU linearity slice.

This method walks through all the images and takes a collapsed circle profile where the HU linearity ROIs are. If the profile contains both low (<800) and high (>800) HU values and most values are the same (i.e. it’s not an artifact), then it can be assumed it is an HU linearity slice. The median of all applicable slices is the center of the HU slice.

Returns:The middle slice of the HU linearity module.
Return type:int
find_phantom_axis() -> (typing.Callable, typing.Callable)

We fit all the center locations of the phantom across all slices to a 1D poly function instead of finding them individually for robustness.

Normally, each slice would be evaluated individually, but the RadMachine jig gets in the way of detecting the HU module (🤦‍♂️). To work around that in a backwards-compatible way we instead look at all the slices and if the phantom was detected, capture the phantom center. ALL the centers are then fitted to a 1D poly function and passed to the individual slices. This way, even if one slice is messed up (such as because of the phantom jig), the poly function is robust to give the real center based on all the other properly-located positions on the other slices.

classmethod from_demo_images()

Construct a CBCT object from the demo images.

classmethod from_url(url: str, check_uid: bool = True)

Instantiate a CBCT object from a URL pointing to a .zip object.

Parameters:
  • url (str) – URL pointing to a zip archive of CBCT images.
  • check_uid (bool) – Whether to enforce raising an error if more than one UID is found in the dataset.
classmethod from_zip(zip_file: Union[str, zipfile.ZipFile, BinaryIO], check_uid: bool = True)

Construct a CBCT object and pass the zip file.

Parameters:
  • zip_file (str, ZipFile) – Path to the zip file or a ZipFile object.
  • check_uid (bool) – Whether to enforce raising an error if more than one UID is found in the dataset.
Raises:
  • FileExistsError : If zip_file passed was not a legitimate zip file.
  • FileNotFoundError : If no CT images are found in the folder
localize() → None

Find the slice number of the catphan’s HU linearity module and roll angle

mm_per_pixel

The millimeters per pixel of the DICOM images.

num_images

The number of images loaded.

class pylinac.acr.ACRCTResult(phantom_model: str, phantom_roll_deg: float, origin_slice: int, num_images: int, ct_module: pylinac.acr.CTModuleOutput, uniformity_module: pylinac.acr.UniformityModuleOutput, low_contrast_module: pylinac.acr.LowContrastModuleOutput, spatial_resolution_module: pylinac.acr.SpatialResolutionModuleOutput)[source]

Bases: pylinac.core.utilities.ResultBase

This class should not be called directly. It is returned by the results_data() method. It is a dataclass under the hood and thus comes with all the dunder magic.

Use the following attributes as normal class attributes.

phantom_model = None
phantom_roll_deg = None
origin_slice = None
num_images = None
ct_module = None
uniformity_module = None
low_contrast_module = None
spatial_resolution_module = None
class pylinac.acr.CTModuleOutput(offset: int, roi_distance_from_center_mm: int, roi_radius_mm: int, roi_settings: dict, rois: dict)[source]

Bases: object

This class should not be called directly. It is returned by the results_data() method. It is a dataclass under the hood and thus comes with all the dunder magic.

Use the following attributes as normal class attributes.

class pylinac.acr.UniformityModuleOutput(offset: int, roi_distance_from_center_mm: int, roi_radius_mm: int, roi_settings: dict, rois: dict, center_roi_stdev: float)[source]

Bases: pylinac.acr.CTModuleOutput

This class should not be called directly. It is returned by the results_data() method. It is a dataclass under the hood and thus comes with all the dunder magic.

Use the following attributes as normal class attributes.

class pylinac.acr.SpatialResolutionModuleOutput(offset: int, roi_distance_from_center_mm: int, roi_radius_mm: int, roi_settings: dict, rois: dict, lpmm_to_rmtf: dict)[source]

Bases: pylinac.acr.CTModuleOutput

This class should not be called directly. It is returned by the results_data() method. It is a dataclass under the hood and thus comes with all the dunder magic.

Use the following attributes as normal class attributes.

class pylinac.acr.LowContrastModuleOutput(offset: int, roi_distance_from_center_mm: int, roi_radius_mm: int, roi_settings: dict, rois: dict, cnr: float)[source]

Bases: pylinac.acr.CTModuleOutput

This class should not be called directly. It is returned by the results_data() method. It is a dataclass under the hood and thus comes with all the dunder magic.

Use the following attributes as normal class attributes.

class pylinac.acr.ACRMRILarge(folderpath: Union[str, Sequence[str], pathlib.Path, Sequence[pathlib.Path], Sequence[_io.BytesIO]], check_uid: bool = True)[source]

Bases: pylinac.ct.CatPhanBase

Parameters:
  • folderpath (str, list of strings, or Path to folder) – String that points to the CBCT image folder location.
  • check_uid (bool) – Whether to enforce raising an error if more than one UID is found in the dataset.
Raises:
  • NotADirectoryError – If folder str passed is not a valid directory.
  • FileNotFoundError – If no CT images are found in the folder
plot_analyzed_subimage(*args, **kwargs)[source]

Plot a specific component of the CBCT analysis.

Parameters:
  • subimage ({'hu', 'un', 'sp', 'lc', 'mtf', 'lin', 'prof'}) –

    The subcomponent to plot. Values must contain one of the following letter combinations. E.g. linearity, linear, and lin will all draw the HU linearity values.

    • hu draws the HU linearity image.
    • un draws the HU uniformity image.
    • sp draws the Spatial Resolution image.
    • lc draws the Low Contrast image (if applicable).
    • mtf draws the RMTF plot.
    • lin draws the HU linearity values. Used with delta.
    • prof draws the HU uniformity profiles.
  • delta (bool) – Only for use with lin. Whether to plot the HU delta or actual values.
  • show (bool) – Whether to actually show the plot.
save_analyzed_subimage(*args, **kwargs)[source]

Save a component image to file.

Parameters:
  • filename (str, file object) – The file to write the image to.
  • subimage (str) – See plot_analyzed_subimage() for parameter info.
  • delta (bool) – Only for use with lin. Whether to plot the HU delta or actual values.
localize() → None[source]

Find the slice number of the catphan’s HU linearity module and roll angle

find_phantom_roll() → float[source]

Determine the “roll” of the phantom. This algorithm uses the circular left-upper hole on slice 1 as the reference

Returns:float
Return type:the angle of the phantom in degrees.
analyze() → None[source]

Analyze the ACR CT phantom

plot_analyzed_image(show: bool = True, **plt_kwargs) → matplotlib.figure.Figure[source]

Plot the analyzed image

Parameters:
  • show – Whether to show the image.
  • plt_kwargs – Keywords to pass to matplotlib for figure customization.
plot_images(show: bool = True, **plt_kwargs) → Dict[str, matplotlib.figure.Figure][source]

Plot all the individual images separately

Parameters:
  • show – Whether to show the images.
  • plt_kwargs – Keywords to pass to matplotlib for figure customization.
save_images(directory: Union[pathlib.Path, str, None] = None, to_stream: bool = False, **plt_kwargs) → List[Union[pathlib.Path, _io.BytesIO]][source]

Save separate images to disk or stream.

Parameters:
  • directory – The directory to write the images to. If None, will use current working directory
  • to_stream – Whether to write to stream or disk. If True, will return streams. Directory is ignored in that scenario.
  • plt_kwargs – Keywords to pass to matplotlib for figure customization.
publish_pdf(filename: Union[str, pathlib.Path], notes: Optional[str] = None, open_file: bool = False, metadata: Optional[dict] = None, logo: Union[pathlib.Path, str, None] = None) → None[source]

Publish (print) a PDF containing the analysis and quantitative results.

Parameters:
  • filename ((str, file-like object}) – The file to write the results to.
  • notes (str, list of strings) – Text; if str, prints single line. If list of strings, each list item is printed on its own line.
  • open_file (bool) – Whether to open the file using the default program after creation.
  • metadata (dict) – Extra data to be passed and shown in the PDF. The key and value will be shown with a colon. E.g. passing {‘Author’: ‘James’, ‘Unit’: ‘TrueBeam’} would result in text in the PDF like: ————– Author: James Unit: TrueBeam ————–
  • logo (Path, str) – A custom logo to use in the PDF report. If nothing is passed, the default pylinac logo is used.
results(as_str: bool = True) → Union[str, Tuple][source]

Return the results of the analysis as a string. Use with print().

results_data(as_dict: bool = False) → Union[pylinac.acr.ACRMRIResult, dict][source]

Present the results data and metadata as a dataclass or dict. The default return type is a dataclass.

catphan_size

The expected size of the phantom in pixels, based on a 20cm wide phantom.

find_origin_slice() → int

Using a brute force search of the images, find the median HU linearity slice.

This method walks through all the images and takes a collapsed circle profile where the HU linearity ROIs are. If the profile contains both low (<800) and high (>800) HU values and most values are the same (i.e. it’s not an artifact), then it can be assumed it is an HU linearity slice. The median of all applicable slices is the center of the HU slice.

Returns:The middle slice of the HU linearity module.
Return type:int
find_phantom_axis() -> (typing.Callable, typing.Callable)

We fit all the center locations of the phantom across all slices to a 1D poly function instead of finding them individually for robustness.

Normally, each slice would be evaluated individually, but the RadMachine jig gets in the way of detecting the HU module (🤦‍♂️). To work around that in a backwards-compatible way we instead look at all the slices and if the phantom was detected, capture the phantom center. ALL the centers are then fitted to a 1D poly function and passed to the individual slices. This way, even if one slice is messed up (such as because of the phantom jig), the poly function is robust to give the real center based on all the other properly-located positions on the other slices.

classmethod from_demo_images()

Construct a CBCT object from the demo images.

classmethod from_url(url: str, check_uid: bool = True)

Instantiate a CBCT object from a URL pointing to a .zip object.

Parameters:
  • url (str) – URL pointing to a zip archive of CBCT images.
  • check_uid (bool) – Whether to enforce raising an error if more than one UID is found in the dataset.
classmethod from_zip(zip_file: Union[str, zipfile.ZipFile, BinaryIO], check_uid: bool = True)

Construct a CBCT object and pass the zip file.

Parameters:
  • zip_file (str, ZipFile) – Path to the zip file or a ZipFile object.
  • check_uid (bool) – Whether to enforce raising an error if more than one UID is found in the dataset.
Raises:
  • FileExistsError : If zip_file passed was not a legitimate zip file.
  • FileNotFoundError : If no CT images are found in the folder
mm_per_pixel

The millimeters per pixel of the DICOM images.

num_images

The number of images loaded.

save_analyzed_image(filename: Union[str, pathlib.Path, BinaryIO], **kwargs) → None

Save the analyzed summary plot.

Parameters:
  • filename (str, file object) – The name of the file to save the image to.
  • kwargs – Any valid matplotlib kwargs.
class pylinac.acr.ACRMRIResult(phantom_model: str, phantom_roll_deg: float, origin_slice: int, num_images: int, slice1: pylinac.acr.MRSlice1ModuleOutput, slice11: pylinac.acr.MRSlice11ModuleOutput, uniformity_module: pylinac.acr.MRUniformityModuleOutput, geometric_distortion_module: pylinac.acr.MRGeometricDistortionModuleOutput)[source]

Bases: pylinac.core.utilities.ResultBase

This class should not be called directly. It is returned by the results_data() method. It is a dataclass under the hood and thus comes with all the dunder magic.

Use the following attributes as normal class attributes.

phantom_model = None
phantom_roll_deg = None
origin_slice = None
num_images = None
slice1 = None
slice11 = None
uniformity_module = None
geometric_distortion_module = None
class pylinac.acr.MRSlice11ModuleOutput(offset: int, roi_settings: dict, rois: dict, bar_difference_mm: float, slice_shift_mm: float)[source]

Bases: object

This class should not be called directly. It is returned by the results_data() method. It is a dataclass under the hood and thus comes with all the dunder magic.

Use the following attributes as normal class attributes.

class pylinac.acr.MRSlice1ModuleOutput(offset: int, roi_settings: dict, rois: dict, bar_difference_mm: float, slice_shift_mm: float, measured_slice_thickness_mm: float, row_mtf_50: float, col_mtf_50: float)[source]

Bases: object

This class should not be called directly. It is returned by the results_data() method. It is a dataclass under the hood and thus comes with all the dunder magic.

Use the following attributes as normal class attributes.

class pylinac.acr.MRUniformityModuleOutput(offset: int, roi_settings: dict, rois: dict, ghost_roi_settings: dict, ghose_rois: dict, psg: float, ghosting_ratio: float, piu_passed: bool, piu: float)[source]

Bases: object

This class should not be called directly. It is returned by the results_data() method. It is a dataclass under the hood and thus comes with all the dunder magic.

Use the following attributes as normal class attributes.

class pylinac.acr.MRGeometricDistortionModuleOutput(offset: int, profiles: dict, distances: dict)[source]

Bases: object

This class should not be called directly. It is returned by the results_data() method. It is a dataclass under the hood and thus comes with all the dunder magic.

Use the following attributes as normal class attributes.