Quart

New in version 3.2.

Overview

The Quart module provides routines for automatically analyzing DICOM images of the Quart DVT phantom typically used with the Halcyon linac system. It can load a folder or zip file of images, correcting for translational and rotational offsets.

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.

Typical Use

The Quart phantom analysis 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.

To use the Quart analysis, import the class:

from pylinac import QuartDVT
from pylinac.quart import QuartDVT  # equivalent import

And then load, analyze, and view the results:

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

    quart_folder = r"C:/CT/Quart/Sept 2021"
    quart = QuartDVT(quart_folder)
    

    or load from zip:

    quart_folder = r"C:/CT/Quart/Sept 2021.zip"  # this contains all the DICOM files of the scan
    quart = QuartDVT.from_zip(quart_folder)
    
  • Analyze – Analyze the dataset:

    quart.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(quart.results())
    # view analyzed image summary
    quart.plot_analyzed_image()
    # view images independently
    quart.plot_images()
    # save the images
    quart.save_images()
    # finally, save a PDF
    quart.publish_pdf('myquart.pdf')
    

Advanced Use

Using results_data

Using the Quart 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 QuartDVTResult instance.

Continuing from above:

data = quart.results_data()
data.hu_module.roi_radius_mm
# and more

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

API Documentation

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

Bases: pylinac.ct.CatPhanBase

A class for loading and analyzing CT DICOM files of a Quart phantom that comes with the Halcyon. Analyzes: HU Uniformity, Image Scaling & HU Linearity.

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
static run_demo(show: bool = True)[source]

Run the Quart algorithm with a head dataset.

analyze(hu_tolerance: Union[int, float] = 40, scaling_tolerance: Union[int, float] = 1, thickness_tolerance: Union[int, float] = 0.2, cnr_threshold: Union[int, float] = 5)[source]

Single-method full analysis of CBCT DICOM files.

Parameters:
  • hu_tolerance (int) – The HU tolerance value for both HU uniformity and linearity.
  • scaling_tolerance (float, int) – The scaling tolerance in mm of the geometric nodes on the HU linearity slice (CTP404 module).
  • thickness_tolerance (float, int) –

    The tolerance of the thickness calculation in mm, based on the wire ramps in the CTP404 module.

    Warning

    Thickness accuracy degrades with image noise; i.e. low mAs images are less accurate.

  • low_contrast_tolerance (int) – The number of low-contrast bubbles needed to be “seen” to pass.
  • cnr_threshold (float, int) –

    Deprecated since version 3.0: Use visibility parameter instead.

    The threshold for “detecting” low-contrast image. See RTD for calculation info.

  • zip_after (bool) – If the CT images were not compressed before analysis and this is set to true, pylinac will compress the analyzed images into a ZIP archive.
  • contrast_method – The contrast equation to use. See Low contrast.
  • visibility_threshold – The threshold for detecting low-contrast ROIs. Use instead of cnr_threshold. Follows the Rose equation. See Visibility.
plot_analyzed_image(show: bool = True, **plt_kwargs) → None[source]

Plot the images used in the calculation and summary data.

Parameters:
  • show (bool) – Whether to plot the image or not.
  • plt_kwargs (dict) – Keyword args passed to the plt.figure() method. Allows one to set things like figure size.
plot_analyzed_subimage(*args, **kwargs) → None[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.
results(as_str: bool = True) → Union[str, Tuple[str, ...]][source]

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

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

Return results in a data structure for more programmatic use.

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) → Union[List[pathlib.Path], Dict[str, _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) → 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 ————–
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_roll(func: Optional[Callable] = None) → float

Determine the “roll” of the phantom.

This algorithm uses the two air bubbles in the HU slice and the resulting angle between them.

Parameters:func – A callable to sort the air ROIs.
Returns:float
Return type:the angle of the phantom in degrees.
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.

save_analyzed_image(filename: Union[str, pathlib.Path], **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.
save_analyzed_subimage(filename: Union[str, BinaryIO], subimage: str = 'hu', **kwargs) → Optional[matplotlib.figure.Figure]

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.
class pylinac.quart.QuartHUModule(catphan, offset: int, hu_tolerance: float, thickness_tolerance: float, scaling_tolerance: float)[source]

Bases: pylinac.ct.CTP404CP504

Parameters:
  • catphan (~pylinac.cbct.CatPhanBase instance.) –
  • offset (int) –
  • hu_tolerance (float) –
  • thickness_tolerance (float) –
  • scaling_tolerance (float) –
meas_slice_thickness

The average slice thickness for the 4 wire measurements in mm.

signal_to_noise

SNR = (HU + 1000) / sigma, where HU is the mean HU of a chosen insert and sigma is the stdev of the HU insert. We choose to use the Polystyrene as the target HU insert

Type:Calculate the SNR based on the suggested procedure in the manual
contrast_to_noise

CNR = abs(HU_target - HU_background) / sigma, where HU_target is the mean HU of a chosen insert, HU_background is the mean HU of the background insert and sigma is the stdev of the HU background. We choose to use the Polystyrene as the target HU insert and Acrylic (base phantom material) as the background

Type:Calculate the CNR based on the suggested procedure in the manual
lcv

The low-contrast visibility

passed_geometry

Returns whether all the line lengths were within tolerance.

passed_hu

Boolean specifying whether all the ROIs passed within tolerance.

passed_thickness

Whether the slice thickness was within tolerance from nominal.

phan_center

Determine the location of the center of the phantom.

The image is analyzed to see if: 1) the CatPhan is even in the image (if there were any ROIs detected) 2) an ROI is within the size criteria of the catphan 3) the ROI area that is filled compared to the bounding box area is close to that of a circle

Raises:ValueError – If any of the above conditions are not met.
plot(axis: matplotlib.axes._axes.Axes)

Plot the image along with ROIs to an axis

plot_linearity(axis: Optional[matplotlib.axes._axes.Axes] = None, plot_delta: bool = True) → tuple

Plot the HU linearity values to an axis.

Parameters:
  • axis (None, matplotlib.Axes) – The axis to plot the values on. If None, will create a new figure.
  • plot_delta (bool) – Whether to plot the actual measured HU values (False), or the difference from nominal (True).
plot_rois(axis: matplotlib.axes._axes.Axes) → None

Plot the ROIs onto the image, as well as the background ROIs

preprocess(catphan) → None

A preprocessing step before analyzing the CTP module.

Parameters:catphan (~pylinac.cbct.CatPhanBase instance.) –
slice_num

The slice number of the spatial resolution module.

Returns:
Return type:float
class pylinac.quart.QuartUniformityModule(catphan, tolerance: Optional[float] = None, offset: int = 0, clear_borders: bool = True)[source]

Bases: pylinac.ct.CTP486

Class for analysis of the Uniformity slice of the CTP module. Measures 5 ROIs around the slice that should all be close to the same value.

integral_non_uniformity

The Integral Non-Uniformity

overall_passed

Boolean specifying whether all the ROIs passed within tolerance.

phan_center

Determine the location of the center of the phantom.

The image is analyzed to see if: 1) the CatPhan is even in the image (if there were any ROIs detected) 2) an ROI is within the size criteria of the catphan 3) the ROI area that is filled compared to the bounding box area is close to that of a circle

Raises:ValueError – If any of the above conditions are not met.
plot(axis: matplotlib.axes._axes.Axes)

Plot the image along with ROIs to an axis

plot_profiles(axis: Optional[matplotlib.axes._axes.Axes] = None) → None

Plot the horizontal and vertical profiles of the Uniformity slice.

Parameters:axis (None, matplotlib.Axes) – The axis to plot on; if None, will create a new figure.
plot_rois(axis: matplotlib.axes._axes.Axes) → None

Plot the ROIs to the axis.

preprocess(catphan)

A preprocessing step before analyzing the CTP module.

Parameters:catphan (~pylinac.cbct.CatPhanBase instance.) –
slice_num

The slice number of the spatial resolution module.

Returns:
Return type:float
uniformity_index

The Uniformity Index

class pylinac.quart.QuartGeometryModule(catphan, tolerance: Optional[float] = None, offset: int = 0, clear_borders: bool = True)[source]

Bases: pylinac.ct.CatPhanModule

Class for analysis of the Uniformity slice of the CTP module. Measures 5 ROIs around the slice that should all be close to the same value.

plot_rois(axis: matplotlib.axes._axes.Axes)[source]

Plot the ROIs to the axis.

distances() → Dict[str, float][source]

The measurements of the phantom size for the two lines in mm

phan_center

Determine the location of the center of the phantom.

The image is analyzed to see if: 1) the CatPhan is even in the image (if there were any ROIs detected) 2) an ROI is within the size criteria of the catphan 3) the ROI area that is filled compared to the bounding box area is close to that of a circle

Raises:ValueError – If any of the above conditions are not met.
plot(axis: matplotlib.axes._axes.Axes)

Plot the image along with ROIs to an axis

preprocess(catphan)

A preprocessing step before analyzing the CTP module.

Parameters:catphan (~pylinac.cbct.CatPhanBase instance.) –
roi_dist_mm

alias of builtins.float

roi_radius_mm

alias of builtins.float

slice_num

The slice number of the spatial resolution module.

Returns:
Return type:float
class pylinac.quart.QuartDVTResult(phantom_model: str, phantom_roll_deg: float, origin_slice: int, num_images: int, hu_module: pylinac.quart.QuartHUModuleOutput, uniformity_module: pylinac.quart.QuartUniformityModuleOutput, geometric_module: pylinac.quart.QuartGeometryModuleOutput)[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
hu_module = None
uniformity_module = None
geometric_module = None
class pylinac.quart.QuartHUModuleOutput(offset: int, roi_settings: dict, rois: dict, measured_slice_thickness_mm: float, signal_to_noise: float, contrast_to_noise: 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.quart.QuartUniformityModuleOutput(offset: int, roi_settings: dict, rois: dict, passed: bool)[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.quart.QuartGeometryModuleOutput(offset: int, roi_settings: dict, rois: 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.