Core Modules

The following is the API documentation for the core modules of pylinac. These can be used directly, or as the base for mixin classes or methods.

Image Module

This module holds classes for image loading and manipulation.

pylinac.core.image.equate_images(image1: DicomImage | ArrayImage | FileImage | LinacDicomImage, image2: DicomImage | ArrayImage | FileImage | LinacDicomImage) tuple[DicomImage | ArrayImage | FileImage | LinacDicomImage, DicomImage | ArrayImage | FileImage | LinacDicomImage][source]
Crop and resize two images to make them:
  • The same pixel dimensions

  • The same DPI

The usefulness of the function comes when trying to compare images from different sources. The best example is calculating gamma on a machine log fluence and EPID image. The physical and pixel dimensions must be normalized, the SID normalized

Parameters

image1{ArrayImage, DicomImage, FileImage}

Must have DPI and SID.

image2{ArrayImage, DicomImage, FileImage}

Must have DPI and SID.

Returns

image1ArrayImage

The first image equated.

image2ArrayImage

The second image equated.

pylinac.core.image.is_image(path: str | BytesIO | DicomImage | ArrayImage | FileImage | LinacDicomImage | ndarray) bool[source]

Determine whether the path is a valid image file.

Returns

bool

pylinac.core.image.retrieve_image_files(path: str) list[str][source]

Retrieve the file names of all the valid image files in the path.

Returns

list

Contains strings pointing to valid image paths.

pylinac.core.image.load(path: str | Path | DicomImage | ArrayImage | FileImage | LinacDicomImage | ndarray | BinaryIO, **kwargs) DicomImage | ArrayImage | FileImage | LinacDicomImage[source]

Load a DICOM image, JPG/TIF/BMP image, or numpy 2D array.

Parameters

pathstr, file-object

The path to the image file or data stream or array.

kwargs

See FileImage, DicomImage, or ArrayImage for keyword arguments.

Returns

:FileImage, ArrayImage, or DicomImage

Return type depends on input image.

Examples

Load an image from a file and then apply a filter:

>>> from pylinac.core.image import load
>>> my_image = r"C:\QA\image.tif"
>>> img = load(my_image)  # returns a FileImage
>>> img.filter(5)

Loading from an array is just like loading from a file:

>>> arr = np.arange(36).reshape(6, 6)
>>> img = load(arr)  # returns an ArrayImage
pylinac.core.image.load_url(url: str, progress_bar: bool = True, **kwargs) DicomImage | ArrayImage | FileImage | LinacDicomImage[source]

Load an image from a URL.

Parameters

urlstr

A string pointing to a valid URL that points to a file.

Note

For some images (e.g. Github), the raw binary URL must be used, not simply the basic link.

progress_bar: bool

Whether to display a progress bar of download status.

pylinac.core.image.load_multiples(image_file_list: ~typing.Sequence, method: str = 'mean', stretch_each: bool = True, loader: callable = <function load>, **kwargs) DicomImage | ArrayImage | FileImage | LinacDicomImage[source]

Combine multiple image files into one superimposed image.

Parameters

image_file_listlist

A list of the files to be superimposed.

method{‘mean’, ‘max’, ‘sum’}

A string specifying how the image values should be combined.

stretch_eachbool

Whether to normalize the images being combined by stretching their high/low values to the same values across images.

loader: callable

The function to use to load the images. If a special image subclass is used, this is how it can be passed.

kwargs :

Further keyword arguments are passed to the load function and stretch function.

Examples

Load multiple images:

>>> from pylinac.core.image import load_multiples
>>> paths = ['starshot1.tif', 'starshot2.tif']
>>> superimposed_img = load_multiples(paths)
class pylinac.core.image.BaseImage(path: str | Path | BytesIO | DicomImage | ArrayImage | FileImage | LinacDicomImage | ndarray | BufferedReader)[source]

Bases: object

Base class for the Image classes.

Attributes

pathstr

The path to the image file.

arraynumpy.ndarray

The actual image pixel array.

Parameters

pathstr

The path to the image.

classmethod from_multiples(filelist: list[str], method: str = 'mean', stretch: bool = True, **kwargs) DicomImage | ArrayImage | FileImage | LinacDicomImage[source]

Load an instance from multiple image items. See load_multiples().

property center: Point

Return the center position of the image array as a Point. Even-length arrays will return the midpoint between central two indices. Odd will return the central index.

property physical_shape: float, float

The physical size of the image in mm.

date_created(format: str = '%A, %B %d, %Y') str[source]

The date the file was created. Tries DICOM data before falling back on OS timestamp. The method use one or more inputs of formatted code, where % means a placeholder and the letter the time unit of interest. For a full description of the several formatting codes see strftime() documentation.

Parameters

formatstr

%A means weekday full name, %B month full name, %d day of the month as a zero-padded decimal number and %Y year with century as a decimal number.

Returns

str

The date the file was created.

plot(ax: Axes = None, show: bool = True, clear_fig: bool = False, show_metrics: bool = True, metric_kwargs: dict | None = None, **kwargs) Axes[source]

Plot the image.

Parameters

axmatplotlib.Axes instance

The axis to plot the image to. If None, creates a new figure.

showbool

Whether to actually show the image. Set to false when plotting multiple items.

clear_figbool

Whether to clear the prior items on the figure before plotting.

show_metricsbool

Whether to show the metrics on the image.

metric_kwargsdict

kwargs passed to the metric plot method.

kwargs

kwargs passed to plt.imshow()

plot_metrics(show: bool = True) list[figure][source]

Plot any additional figures from the metrics.

Returns a list of figures of the metrics. These metrics are not drawn on the original image but rather are something complete separate. E.g. a profile plot or a histogram of the metric.

filter(size: float | int = 0.05, kind: str = 'median') None[source]

Filter the profile in place.

Parameters

sizeint, float

Size of the median filter to apply. If a float, the size is the ratio of the length. Must be in the range 0-1. E.g. if size=0.1 for a 1000-element array, the filter will be 100 elements. If an int, the filter is the size passed.

kind{‘median’, ‘gaussian’}

The kind of filter to apply. If gaussian, size is the sigma value.

crop(pixels: int = 15, edges: tuple[str, ...] = ('top', 'bottom', 'left', 'right')) None[source]

Removes pixels on all edges of the image in-place.

Parameters

pixelsint

Number of pixels to cut off all sides of the image.

edgestuple

Which edges to remove from. Can be any combination of the four edges.

flipud() None[source]

Flip the image array upside down in-place. Wrapper for np.flipud()

fliplr() None[source]

Flip the image array upside down in-place. Wrapper for np.fliplr()

invert() None[source]

Invert (imcomplement) the image.

bit_invert() None[source]

Invert the image bit-wise

roll(direction: str = 'x', amount: int = 1) None[source]

Roll the image array around in-place. Wrapper for np.roll().

Parameters

direction{‘x’, ‘y’}

The axis to roll over.

amountint

The amount of elements to roll over.

rot90(n: int = 1) None[source]

Wrapper for numpy.rot90; rotate the array by 90 degrees CCW n times.

rotate(angle: float, mode: str = 'edge', *args, **kwargs)[source]

Rotate the image counter-clockwise. Simple wrapper for scikit-image. See https://scikit-image.org/docs/stable/api/skimage.transform.html#skimage.transform.rotate. All parameters are passed to that function.

threshold(threshold: float, kind: str = 'high') None[source]

Apply a high- or low-pass threshold filter.

Parameters

thresholdint

The cutoff value.

kindstr

If high (default), will apply a high-pass threshold. All values above the cutoff are left as-is. Remaining points are set to 0. If low, will apply a low-pass threshold.

as_binary(threshold: int) DicomImage | ArrayImage | FileImage | LinacDicomImage[source]

Return a binary (black & white) image based on the given threshold.

Parameters

thresholdint, float

The threshold value. If the value is above or equal to the threshold it is set to 1, otherwise to 0.

Returns

ArrayImage

dist2edge_min(point: Point | tuple) float[source]

Calculates distance from given point to the closest edge.

Parameters

point : geometry.Point, tuple

Returns

float

ground() float[source]

Ground the profile in place such that the lowest value is 0.

Note

This will also “ground” profiles that are negative or partially-negative. For such profiles, be careful that this is the behavior you desire.

Returns

float

The amount subtracted from the image.

normalize(norm_val: str | float | None = None) None[source]

Normalize the profile to the given value.

Parameters

valuenumber or None

If a number, normalize the array to that number. If None, normalizes to the maximum value.

check_inversion(box_size: int = 20, position: float, float = (0.0, 0.0)) None[source]

Check the image for inversion by sampling the 4 image corners. If the average value of the four corners is above the average pixel value, then it is very likely inverted.

Parameters

box_sizeint

The size in pixels of the corner box to detect inversion.

position2-element sequence

The location of the sampling boxes.

check_inversion_by_histogram(percentiles: float, float, float = (5, 50, 95)) bool[source]

Check the inversion of the image using histogram analysis. The assumption is that the image is mostly background-like values and that there is a relatively small amount of dose getting to the image (e.g. a picket fence image). This function looks at the distance from one percentile to another to determine if the image should be inverted.

Parameters

percentiles3-element tuple

The 3 percentiles to compare. Default is (5, 50, 95). Recommend using (x, 50, y). To invert the other way (where pixel value is decreasing with dose, reverse the percentiles, e.g. (95, 50, 5).

Returns

bool: Whether an inversion was performed.

gamma(comparison_image: DicomImage | ArrayImage | FileImage | LinacDicomImage, doseTA: float = 1, distTA: float = 1, threshold: float = 0.1, ground: bool = True, normalize: bool = True) ndarray[source]

Calculate the gamma between the current image (reference) and a comparison image.

Added in version 1.2.

The gamma calculation is based on Bakai et al eq.6, which is a quicker alternative to the standard Low gamma equation.

Parameters

comparison_image{ArrayImage, DicomImage, or FileImage}

The comparison image. The image must have the same DPI/DPMM to be comparable. The size of the images must also be the same.

doseTAint, float

Dose-to-agreement in percent; e.g. 2 is 2%.

distTAint, float

Distance-to-agreement in mm.

thresholdfloat

The dose threshold percentage of the maximum dose, below which is not analyzed. Must be between 0 and 1.

groundbool

Whether to “ground” the image values. If true, this sets both datasets to have the minimum value at 0. This can fix offset errors in the data.

normalizebool

Whether to normalize the images. This sets the max value of each image to the same value.

Returns

gamma_mapnumpy.ndarray

The calculated gamma map.

See Also

equate_images()

compute(metrics: list[MetricBase] | MetricBase) Any | dict[str, Any][source]

Compute the given metrics on the image.

This can be called multiple times to compute different metrics. Metrics are appended on each call. This allows for modification of the image between metric calls as well as the ability to compute different metrics on the same image that might depend on earlier metrics.

Metrics are both returned and stored in the metrics attribute. The metrics attribute will store all metrics every calculated. The metrics returned are only those passed in the metrics argument.

Parameters

metricslist[MetricBase] | MetricBase

The metric(s) to compute.

class pylinac.core.image.XIM(file_path: str | Path, read_pixels: bool = True)[source]

Bases: BaseImage

A class to open, read, and/or export an .xim image, Varian’s custom image format which is 99.999% PNG

This had inspiration from a number of places: - https://gist.github.com/1328/7da697c71f9c4ef12e1e - https://medium.com/@duhroach/how-png-works-f1174e3cc7b7 - https://www.mathworks.com/matlabcentral/answers/419228-how-to-write-for-loop-and-execute-data - https://www.w3.org/TR/PNG-Filters.html - https://bitbucket.org/dmoderesearchtools/ximreader/src/master/

Parameters

file_path

The path to the file of interest.

read_pixels

Whether to read and parse the pixel information. Doing so is quite slow. Set this to false if, e.g., you are searching for images only via tags or doing a pre-filtering of image selection.

array: np.ndarray
properties: dict
property dpmm: float

The dots/mm value of the XIM images. The value appears to be in cm in the file.

as_dicom() Dataset[source]

Save the XIM image as a simplistic DICOM file. Only meant for basic image storage/analysis.

It appears that XIM images are in the Varian standard coordinate system. We convert to IEC61217 for more general compatibility.

save_as(file: str | Path, format: str | None = None) None[source]

Save the image to a NORMAL format. PNG is highly suggested. Accepts any format supported by Pillow. Ironically, an equivalent PNG image (w/ metadata) is ~50% smaller than an .xim image.

Warning

Any format other than PNG will not include the properties included in the .xim image!

Parameters

file

The file to save the image to. E.g. my_xim.png

format

The format to save the image as. Uses the Pillow logic, which will infer the format if the file name has one.

class pylinac.core.image.DicomImage(path: str | Path | BytesIO | BufferedReader, *, dtype: dtype | None = None, dpi: float = None, sid: float = None, sad: float = 1000, raw_pixels: bool = False)[source]

Bases: BaseImage

An image from a DICOM RTImage file.

Attributes

metadatapydicom Dataset

The dataset of the file as returned by pydicom without pixel data.

Parameters

pathstr, file-object

The path to the file or the data stream.

dtypedtype, None, optional

The data type to cast the image data as. If None, will use whatever raw image format is.

dpiint, float

The dots-per-inch of the image, defined at isocenter.

Note

If a DPI tag is found in the image, that value will override the parameter, otherwise this one will be used.

sidint, float

The Source-to-Image distance in mm.

sadfloat

The Source-to-Axis distance in mm.

raw_pixelsbool

Whether to apply pixel intensity correction to the DICOM data. Typically, Rescale Slope, Rescale Intercept, and other tags are included and meant to be applied to the raw pixel data, which is potentially compressed. If True, no correction will be applied. This is typically used for scenarios when you want to match behavior to older or different software.

classmethod from_dataset(dataset: Dataset)[source]

Create a DICOM image instance from a pydicom Dataset.

save(filename: str | Path) str | Path[source]

Save the image instance back out to a .dcm file.

Parameters

filenamestr, Path

The filename to save the DICOM file as.

Returns

A string pointing to the new filename.

property z_position: float

The z-position of the slice. Relevant for CT and MR images.

property slice_spacing: float

Determine the distance between slices. The spacing can be greater than the slice thickness (i.e. gaps). Uses the absolute version as it can apparently be negative: https://dicom.innolitics.com/ciods/nm-image/nm-reconstruction/00180088

This attempts to use the slice spacing attr and if it doesn’t exist, use the slice thickness attr

property sid: float

The Source-to-Image in mm.

property sad: float

The source to axis (iso) in mm

property dpi: float

The dots-per-inch of the image, defined at isocenter.

property dpmm: float

The Dots-per-mm of the image, defined at isocenter. E.g. if an EPID image is taken at 150cm SID, the dpmm will scale back to 100cm.

property cax: Point

The position of the beam central axis. If no DICOM translation tags are found then the center is returned. Uses this tag: https://dicom.innolitics.com/ciods/rt-beams-delivery-instruction/rt-beams-delivery-instruction/00741020/00741030/3002000d

class pylinac.core.image.LinacDicomImage(path: str | Path | BinaryIO, use_filenames: bool = False, axes_precision: int | None = None, **kwargs)[source]

Bases: DicomImage

DICOM image taken on a linac. Also allows passing of gantry/coll/couch values via the filename.

Parameters

pathstr, file-object

The path to the file or the data stream.

dtypedtype, None, optional

The data type to cast the image data as. If None, will use whatever raw image format is.

dpiint, float

The dots-per-inch of the image, defined at isocenter.

Note

If a DPI tag is found in the image, that value will override the parameter, otherwise this one will be used.

sidint, float

The Source-to-Image distance in mm.

sadfloat

The Source-to-Axis distance in mm.

raw_pixelsbool

Whether to apply pixel intensity correction to the DICOM data. Typically, Rescale Slope, Rescale Intercept, and other tags are included and meant to be applied to the raw pixel data, which is potentially compressed. If True, no correction will be applied. This is typically used for scenarios when you want to match behavior to older or different software.

property gantry_angle: float

Gantry angle of the irradiation.

property collimator_angle: float

Collimator angle of the irradiation.

property couch_angle: float

Couch angle of the irradiation.

class pylinac.core.image.FileImage(path: str | Path | BinaryIO, *, dpi: float | None = None, sid: float | None = None, dtype: dtype | None = None)[source]

Bases: BaseImage

An image from a “regular” file (.tif, .jpg, .bmp).

Attributes

infodict

The info dictionary as generated by Pillow.

sidfloat

The SID value as passed in upon construction.

Parameters

pathstr, file-object

The path to the file or a data stream.

dpiint, float

The dots-per-inch of the image, defined at isocenter.

Note

If a DPI tag is found in the image, that value will override the parameter, otherwise this one will be used.

sidint, float

The Source-to-Image distance in mm.

dtypenumpy.dtype

The data type to cast the array as. If None, will use the datatype stored in the file. If the file is multi-channel (e.g. RGB), it will be converted to int32

property dpi: float | None

The dots-per-inch of the image, defined at isocenter.

property dpmm: float | None

The Dots-per-mm of the image, defined at isocenter. E.g. if an EPID image is taken at 150cm SID, the dpmm will scale back to 100cm.

class pylinac.core.image.ArrayImage(array: ndarray, *, dpi: float = None, sid: float = None, dtype=None)[source]

Bases: BaseImage

An image constructed solely from a numpy array.

Parameters

arraynumpy.ndarray

The image array.

dpiint, float

The dots-per-inch of the image, defined at isocenter.

Note

If a DPI tag is found in the image, that value will override the parameter, otherwise this one will be used.

sidint, float

The Source-to-Image distance in mm.

dtypedtype, None, optional

The data type to cast the image data as. If None, will use whatever raw image format is.

property dpmm: float | None

The Dots-per-mm of the image, defined at isocenter. E.g. if an EPID image is taken at 150cm SID, the dpmm will scale back to 100cm.

property dpi: float | None

The dots-per-inch of the image, defined at isocenter.

class pylinac.core.image.DicomImageStack(folder: str | Path, dtype: dtype | None = None, min_number: int = 39, check_uid: bool = True, raw_pixels: bool = False)[source]

Bases: LazyDicomImageStack

A class that loads and holds a stack of DICOM images (e.g. a CT dataset). The class can take a folder or zip file and will read CT images. The images must all be the same size. Supports indexing to individual images.

Attributes

imageslist

Holds instances of DicomImage. Can be accessed via index; i.e. self[0] == self.images[0].

Examples

Load a folder of Dicom images >>> from pylinac import image >>> img_folder = r”folder/qa/cbct/june” >>> dcm_stack = image.DicomImageStack(img_folder) # loads and sorts the images >>> dcm_stack.plot(3) # plot the 3rd image

Load a zip archive >>> img_folder_zip = r”archive/qa/cbct/june.zip” # save space and zip your CBCTs >>> dcm_stack = image.DicomImageStack.from_zip(img_folder_zip)

Load as a certain data type >>> dcm_stack_uint32 = image.DicomImageStack(img_folder, dtype=np.uint32)

Load a folder with DICOM CT images.

Parameters

folderstr

Path to the folder.

dtypedtype, None, optional

The data type to cast the image data as. If None, will use whatever raw image format is.

classmethod from_zip(zip_path: str | Path, dtype: dtype | None = None, **kwargs)[source]

Load a DICOM ZIP archive.

Parameters

zip_pathstr

Path to the ZIP archive.

dtypedtype, None, optional

The data type to cast the image data as. If None, will use whatever raw image format is.

plot_3view()[source]

Plot the stack in 3 views: axial, coronal, and sagittal.

class pylinac.core.image.NMImageStack(path: str | Path)[source]

Bases: object

A class of frames of a nuclear medicine image. A single image can have N frames. For our purposes, we can treat this as a stack of images.

Load a single NM image with N frames.

as_3d_array() ndarray[source]

Return the frames as a 3D array.

pylinac.core.image.tiff_to_dicom(tiff_file: str | Path | BytesIO, sid: float, gantry: float, coll: float, couch: float, dpi: float | None = None) Dataset[source]

Converts a TIFF file into a simplistic DICOM file. Not meant to be a full-fledged tool. Used for conversion so that tools that are traditionally oriented towards DICOM have a path to accept TIFF. Currently used to convert files for WL.

Note

This will convert the image into an uint16 datatype to match the native EPID datatype.

Parameters

tiff_file

The TIFF file to be converted.

sid

The Source-to-Image distance in mm.

dpi

The dots-per-inch value of the TIFF image.

gantry

The gantry value that the image was taken at.

coll

The collimator value that the image was taken at.

couch

The couch value that the image was taken at.

pylinac.core.image.gamma_2d(reference: ndarray, evaluation: ndarray, dose_to_agreement: float = 1, distance_to_agreement: int = 1, gamma_cap_value: float = 2, global_dose: bool = True, dose_threshold: float = 5, fill_value: float = nan) ndarray[source]

Compute a 2D gamma of two 2D numpy arrays. This does NOT do size or spatial resolution checking. It performs an element-by-element evaluation. It is the responsibility of the caller to ensure the reference and evaluation have comparable spatial resolution.

The algorithm follows Table I of D. Low’s 2004 paper: Evaluation of the gamma dose distribution comparison method: https://aapm.onlinelibrary.wiley.com/doi/epdf/10.1118/1.1598711

This is similar to the gamma_1d function for profiles, except we must search a 2D grid around the reference point.

Parameters

reference

The reference 2D array.

evaluation

The evaluation 2D array.

dose_to_agreement

The dose to agreement in %. E.g. 1 is 1% of global reference max dose.

distance_to_agreement

The distance to agreement in elements. E.g. if the value is 4 this means 4 elements from the reference point under calculation. Must be >0

gamma_cap_value

The value to cap the gamma at. E.g. a gamma of 5.3 will get capped to 2. Useful for displaying data with a consistent range.

global_dose

Whether to evaluate the dose to agreement threshold based on the global max or the dose point under evaluation.

dose_threshold

The dose threshold as a number between 0 and 100 of the % of max dose under which a gamma is not calculated. This is not affected by the global/local dose normalization and the threshold value is evaluated against the global max dose, period.

fill_value

The value to give pixels that were not calculated because they were under the dose threshold. Default is NaN, but another option would be 0. If NaN, allows the user to calculate mean/median gamma over just the evaluated portion and not be skewed by 0’s that should not be considered.

pylinac.core.image.z_position(metadata: Dataset) float[source]

The ‘z-position’ of the image. Relevant for CT and MR images.

Geometry Module

Module for classes that represent common geometric objects or patterns.

pylinac.core.geometry.tan(degrees: float) float[source]

Calculate the tangent of the given degrees.

pylinac.core.geometry.atan(x: float, y: float) float[source]

Calculate the degrees of a given x/y from the origin

pylinac.core.geometry.cos(degrees: float) float[source]

Calculate the cosine of the given degrees.

pylinac.core.geometry.sin(degrees: float) float[source]

Calculate the sine of the given degrees.

pylinac.core.geometry.direction_to_coords(start_x: float, start_y: float, distance: float, angle_degrees: float)[source]

Calculate destination coordinates given a start position, distance, and angle.

The 0-angle position is pointing to the right (i.e. unit circle)

Parameters

start_xfloat

Starting x position

start_yfloat

Starting y position

distancefloat

Distance to travel

angle_degreesfloat

Angle to travel in degrees.

class pylinac.core.geometry.Point(x: float | tuple | Point = 0, y: float = 0, z: float = 0, idx: int | None = None, value: float | None = None, as_int: bool = False)[source]

Bases: object

A geometric point with x, y, and z coordinates/attributes.

Parameters

xnumber-like, Point, iterable

x-coordinate or iterable type containing all coordinates. If iterable, values are assumed to be in order: (x,y,z).

ynumber-like, optional

y-coordinate

idxint, optional

Index of point. Useful for sequential coordinates; e.g. a point on a circle profile is sometimes easier to describe in terms of its index rather than x,y coords.

valuenumber-like, optional

value at point location (e.g. pixel value of an image)

as_intboolean

If True, coordinates are converted to integers.

distance_to(thing: Point | Circle) float[source]

Calculate the distance to the given point.

Parameters

thingCircle, Point, 2 element iterable

The other thing to calculate distance to.

as_array(only_coords: bool = True) ndarray[source]

Return the point as a numpy array.

dict() dict[source]

Convert to dict. Shim until convert to dataclass

pylinac.core.geometry.to_json(data: Point | Vector)[source]

Simple serialization call

class pylinac.core.geometry.Circle(center_point: Point | Iterable = (0, 0), radius: float = 0)[source]

Bases: object

A geometric circle with center Point, radius, and diameter.

Parameters

center_pointPoint, optional

Center point of the wobble circle.

radiusfloat, optional

Radius of the wobble circle.

property area: float

The area of the circle.

property diameter: float

Get the diameter of the circle.

plot2axes(axes: Axes, edgecolor: str = 'black', fill: bool = False, text: str = '', fontsize: str = 'medium', **kwargs) None[source]

Plot the Circle on the axes.

Parameters

axesmatplotlib.axes.Axes

An MPL axes to plot to.

edgecolorstr

The color of the circle.

fillbool

Whether to fill the circle with color or leave hollow.

text: str

If provided, plots the given text at the center. Useful for identifying ROIs on a plotted image apart.

fontsize: str

The size of the text, if provided. See https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.text.html for options.

as_dict() dict[source]

Convert to dict. Useful for dataclasses/Result

class pylinac.core.geometry.Vector(x: float = 0, y: float = 0, z: float = 0)[source]

Bases: object

A vector with x, y, and z coordinates.

as_scalar() float[source]

Return the scalar equivalent of the vector.

dict() dict[source]

Convert to a dict. Shim until converting to dataclass

distance_to(thing: Circle | Point) float[source]

Calculate the distance to the given point.

Parameters

thingCircle, Point, 2 element iterable

The other point to calculate distance to.

pylinac.core.geometry.vector_is_close(vector1: Vector, vector2: Vector, delta: float = 0.1) bool[source]

Determine if two vectors are with delta of each other; this is a simple coordinate comparison check.

class pylinac.core.geometry.Line(point1: Point | tuple[float, float], point2: Point | tuple[float, float])[source]

Bases: object

A line that is represented by two points or by m*x+b.

Notes

Calculations of slope, etc are from here: http://en.wikipedia.org/wiki/Linear_equation and here: http://www.mathsisfun.com/algebra/line-equation-2points.html

Parameters

point1Point

One point of the line

point2Point

Second point along the line.

property m: float

Return the slope of the line.

m = (y1 - y2)/(x1 - x2)

From: http://www.purplemath.com/modules/slope.htm

property b: float

Return the y-intercept of the line.

b = y - m*x

y(x) float[source]

Return y-value along line at position x.

x(y) float[source]

Return x-value along line at position y.

property center: Point

Return the center of the line as a Point.

property length: float

Return length of the line, if finite.

distance_to(point: Point) float[source]

Calculate the minimum distance from the line to a point.

Equations are from here: http://mathworld.wolfram.com/Point-LineDistance2-Dimensional.html #14

Parameters

pointPoint, iterable

The point to calculate distance to.

plot2axes(axes: Axes, width: float = 1, color: str = 'w', **kwargs) Line3D[source]

Plot the line to an axes.

Parameters

axesmatplotlib.axes.Axes

An MPL axes to plot to.

colorstr

The color of the line.

class pylinac.core.geometry.Rectangle(width: float, height: float, center: Point | tuple, as_int: bool = False)[source]

Bases: object

A rectangle with width, height, center Point, top-left corner Point, and bottom-left corner Point.

Parameters

widthnumber

Width of the rectangle. Must be positive

heightnumber

Height of the rectangle. Must be positive.

centerPoint, iterable, optional

Center point of rectangle.

as_intbool

If False (default), inputs are left as-is. If True, all inputs are converted to integers.

property area: float

The area of the rectangle.

property br_corner: Point

The location of the bottom right corner.

property bl_corner: Point

The location of the bottom left corner.

property tl_corner: Point

The location of the top left corner.

property tr_corner: Point

The location of the top right corner.

plot2axes(axes: Axes, edgecolor: str = 'black', angle: float = 0.0, fill: bool = False, alpha: float = 1, facecolor: str = 'g', label=None, text: str = '', fontsize: str = 'medium', text_rotation: float = 0, **kwargs)[source]

Plot the Rectangle to the axes.

Parameters

axesmatplotlib.axes.Axes

An MPL axes to plot to.

edgecolorstr

The color of the circle.

anglefloat

Angle of the rectangle.

fillbool

Whether to fill the rectangle with color or leave hollow.

text: str

If provided, plots the given text at the center. Useful for identifying ROIs on a plotted image apart.

fontsize: str

The size of the text, if provided. See https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.text.html for options.

text_rotation: float

The rotation of the text in degrees.

Profile Module

See Profiles & 1D Metrics.

I/O Module

I/O helper functions for pylinac.

pylinac.core.io.is_dicom(file: str | Path) bool[source]

Boolean specifying if file is a proper DICOM file.

This function is a pared down version of read_preamble meant for a fast return. The file is read for a proper preamble (‘DICM’), returning True if so, and False otherwise. This is a conservative approach.

Parameters

filestr

The path to the file.

See Also

pydicom.filereader.read_preamble pydicom.filereader.read_partial

pylinac.core.io.is_dicom_image(file: str | Path | BinaryIO) bool[source]

Boolean specifying if file is a proper DICOM file with a image

Parameters

filestr

The path to the file.

See Also

pydicom.filereader.read_preamble pydicom.filereader.read_partial

pylinac.core.io.retrieve_dicom_file(file: str | Path | BinaryIO) FileDataset[source]

Read and return the DICOM dataset.

Parameters

filestr

The path to the file.

class pylinac.core.io.TemporaryZipDirectory(zfile: str | Path | BinaryIO, delete: bool = True)[source]

Bases: TemporaryDirectory

Creates a temporary directory that unpacks a ZIP archive. Shockingly useful

Parameters

zfilestr

String that points to a ZIP archive.

deletebool

Whether to delete the temporary directory when the context manager exits.

pylinac.core.io.retrieve_filenames(directory: str | Path, func: Callable | None = None, recursive: bool = True, **kwargs) list[str][source]

Retrieve file names in a directory.

Parameters

directorystr

The directory to walk over recursively.

funcfunction, None

The function that validates if the file name should be kept. If None, no validation will be performed and all file names will be returned.

recursivebool

Whether to search only the root directory.

kwargs

Additional arguments passed to the func parameter.

pylinac.core.io.retrieve_demo_file(name: str, force: bool = False) Path[source]

Retrieve the demo file either by getting it from file or from a URL.

If the file is already on disk it returns the file name. If the file isn’t on disk, get the file from the URL and put it at the expected demo file location on disk for lazy loading next time.

Parameters

namestr

The suffix to the url (location within the S3 bucket) pointing to the demo file.

pylinac.core.io.is_url(url: str) bool[source]

Determine whether a given string is a valid URL.

Parameters

url : str

Returns

bool

pylinac.core.io.get_url(url: str, destination: str | Path | None = None, progress_bar: bool = True) str[source]

Download a URL to a local file.

Parameters

urlstr

The URL to download.

destinationstr, None

The destination of the file. If None is given the file is saved to a temporary directory.

progress_barbool

Whether to show a command-line progress bar while downloading.

Returns

filenamestr

The location of the downloaded file.

Notes

Progress bar use/example adapted from tqdm documentation: https://github.com/tqdm/tqdm

class pylinac.core.io.SNCProfiler(path: str, gain_row: int = 20, detector_row: int = 106, bias_row: int = 107, calibration_row: int = 108, data_row: int = -1, data_columns: slice = slice(5, 259, None))[source]

Bases: object

Load a file from a Sun Nuclear Profiler device. This accepts .prs files.

Parameters

pathstr

Path to the .prs file.

detector_row

The row that contains the detector data.

bias_row

The row that contains the bias data.

calibration_row

The row that contains the calibration data.

data_row

The row that contains the data.

data_columns

The range of columns that the data is in. Usually, there are some columns before and after the real data.

to_profiles(n_detectors_row: int = 63, **kwargs) tuple[SingleProfile, SingleProfile, SingleProfile, SingleProfile][source]

Convert the SNC data to SingleProfiles. These can be analyzed directly or passed to other modules like flat/sym. For the horizontal/cross-plane profile, the detectors on either side of the central detector are missing. We adjust the x-values to reflect this.

Parameters

n_detectors_rowint

The number of detectors in a given row. Note that they Y profile includes 2 extra detectors from the other 3.

ROI Module

pylinac.core.roi.bbox_center(region: RegionProperties) Point[source]

Return the center of the bounding box of an scikit-image region.

Parameters

region

A scikit-image region as calculated by skimage.measure.regionprops().

Returns

point : Point

class pylinac.core.roi.DiskROI(array: ndarray, angle: float, roi_radius: float, dist_from_center: float, phantom_center: tuple | Point)[source]

Bases: Circle

An class representing a disk-shaped Region of Interest.

Parameters

arrayndarray

The 2D array representing the image the disk is on.

angleint, float

The angle of the ROI in degrees from the phantom center.

roi_radiusint, float

The radius of the ROI from the center of the phantom.

dist_from_centerint, float

The distance of the ROI from the phantom center.

phantom_centertuple

The location of the phantom center.

property pixel_value: float

The median pixel value of the ROI.

property std: float

The standard deviation of the pixel values.

circle_mask() ndarray[source]

Return a mask of the image, only showing the circular ROI.

plot2axes(axes: Axes | None = None, edgecolor: str = 'black', fill: bool = False, text: str = '', fontsize: str = 'medium', **kwargs) None[source]

Plot the Circle on the axes.

Parameters

axesmatplotlib.axes.Axes

An MPL axes to plot to.

edgecolorstr

The color of the circle.

fillbool

Whether to fill the circle with color or leave hollow.

text: str

If provided, plots the given text at the center. Useful for differentiating ROIs on a plotted image.

fontsize: str

The size of the text, if provided. See https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.text.html for options.

as_dict() dict[source]

Convert to dict. Useful for dataclasses/Result

class pylinac.core.roi.LowContrastDiskROI(array: ndarray | ArrayImage, angle: float, roi_radius: float, dist_from_center: float, phantom_center: tuple | Point, contrast_threshold: float | None = None, contrast_reference: float | None = None, cnr_threshold: float | None = None, contrast_method: str = 'Michelson', visibility_threshold: float | None = 0.1)[source]

Bases: DiskROI

A class for analyzing the low-contrast disks.

Parameters

contrast_thresholdfloat, int

The threshold for considering a bubble to be “seen”.

property signal_to_noise: float

The signal-to-noise ratio. Cast to numpy first to use numpy overflow handling.

property contrast_to_noise: float

The contrast to noise ratio of the ROI. Cast to numpy first to use numpy overflow handling.

property michelson: float

The Michelson contrast

property weber: float

The Weber contrast

property rms: float

The root-mean-square contrast

property ratio: float

The ratio contrast

property contrast: float

The contrast of the bubble. Uses the contrast method passed in the constructor. See https://en.wikipedia.org/wiki/Contrast_(vision).

property cnr_constant: float

The contrast-to-noise value times the bubble diameter.

property visibility: float

The visual perception of CNR. Uses the model from A Rose: https://www.osapublishing.org/josa/abstract.cfm?uri=josa-38-2-196. See also here: https://howradiologyworks.com/x-ray-cnr/. Finally, a review paper here: http://xrm.phys.northwestern.edu/research/pdf_papers/1999/burgess_josaa_1999.pdf Importantly, the Rose model is not applicable for high-contrast use cases.

property contrast_constant: float

The contrast value times the bubble diameter.

property passed: bool

Whether the disk ROI contrast passed.

property passed_visibility: bool

Whether the disk ROI’s visibility passed.

property passed_contrast_constant: bool

Boolean specifying if ROI pixel value was within tolerance of the nominal value.

property passed_cnr_constant: bool

Boolean specifying if ROI pixel value was within tolerance of the nominal value.

property plot_color: str

Return one of two colors depending on if ROI passed.

property plot_color_constant: str

Return one of two colors depending on if ROI passed.

property plot_color_cnr: str

Return one of two colors depending on if ROI passed.

as_dict() dict[source]

Dump important data as a dictionary. Useful when exporting a results_data output

percentile(percentile: float) float[source]

Return the pixel value at the given percentile.

property std: float

The std within the ROI.

property max: float

The max pixel value of the ROI.

property min: float

The min pixel value of the ROI.

class pylinac.core.roi.HighContrastDiskROI(array: ndarray, angle: float, roi_radius: float, dist_from_center: float, phantom_center: tuple | Point, contrast_threshold: float)[source]

Bases: DiskROI

A class for analyzing the high-contrast disks.

Parameters

contrast_thresholdfloat, int

The threshold for considering a bubble to be “seen”.

property max: float

The max pixel value of the ROI.

property min: float

The min pixel value of the ROI.

property mean: float

The mean pixel value of the ROI.

class pylinac.core.roi.RectangleROI(array, width, height, angle, dist_from_center, phantom_center)[source]

Bases: Rectangle

Class that represents a rectangular ROI.

Parameters

widthnumber

Width of the rectangle. Must be positive

heightnumber

Height of the rectangle. Must be positive.

centerPoint, iterable, optional

Center point of rectangle.

as_intbool

If False (default), inputs are left as-is. If True, all inputs are converted to integers.

property pixel_array: ndarray

The pixel array within the ROI.

property pixel_value: float

The pixel array within the ROI.

property mean: float

The mean value within the ROI.

property std: float

The std within the ROI.

property min: float

The min value within the ROI.

property max: float

The max value within the ROI.

Mask Module

Module for processing “masked” arrays, i.e. binary images.

pylinac.core.mask.bounding_box(array: np.ndarray)[source]

Get the bounding box values of an ROI in a 2D array.

Utilities Module

Utility functions for pylinac.

pylinac.core.utilities.convert_to_enum(value: str | Enum | None, enum: type[Enum]) Enum[source]

Convert a value to an enum representation from an enum value if needed

class pylinac.core.utilities.OptionListMixin[source]

Bases: object

A mixin class that will create a list of the class attributes. Used for enum-like classes

class pylinac.core.utilities.ResultBase(*, pylinac_version: str = '3.22.0', date_of_analysis: datetime = None)[source]

Bases: BaseModel

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

model_config: ClassVar[ConfigDict] = {'arbitrary_types_allowed': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

pylinac_version: str
date_of_analysis: datetime
model_computed_fields: ClassVar[dict[str, ComputedFieldInfo]] = {}

A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

model_fields: ClassVar[dict[str, FieldInfo]] = {'date_of_analysis': FieldInfo(annotation=datetime, required=False, default_factory=builtin_function_or_method), 'pylinac_version': FieldInfo(annotation=str, required=False, default='3.22.0')}

Metadata about the fields defined on the model, mapping of field names to [FieldInfo][pydantic.fields.FieldInfo].

This replaces Model.__fields__ from Pydantic V1.

class pylinac.core.utilities.ResultsDataMixin[source]

Bases: Generic[T]

A mixin for classes that generate results data. This mixin is used to generate the results data and present it in different formats. The generic types allow correct type hinting of the results data.

results_data(as_dict: bool = False, as_json: bool = False) T | dict | str[source]

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

Parameters

as_dictbool

If True, return the results as a dictionary.

as_jsonbool

If True, return the results as a JSON string. Cannot be True if as_dict is True.

pylinac.core.utilities.clear_data_files()[source]

Delete all demo files, image classifiers, etc from the demo folder

pylinac.core.utilities.assign2machine(source_file: str, machine_file: str)[source]

Assign a DICOM RT Plan file to a specific machine. The source file is overwritten to contain the machine of the machine file.

Parameters

source_filestr

Path to the DICOM RTPlan file that contains the fields/plan desired (e.g. a Winston Lutz set of fields or Varian’s default PF files).

machine_filestr

Path to a DICOM RTPlan file that has the desired machine. This is easily obtained from pushing a plan from the TPS for that specific machine. The file must contain at least one valid field.

pylinac.core.utilities.is_close(val: float, target: float | Sequence, delta: float = 1)[source]

Return whether the value is near the target value(s).

Parameters

valnumber

The value being compared against.

targetnumber, iterable

If a number, the values are simply evaluated. If a sequence, each target is compared to val. If any values of target are close, the comparison is considered True.

Returns

bool

pylinac.core.utilities.simple_round(number: float | int, decimals: int | None = 0) float | int[source]

Round a number to the given number of decimals. Fixes small floating number errors. If decimals is None, no rounding is performed

pylinac.core.utilities.is_iterable(object) bool[source]

Determine if an object is iterable.

class pylinac.core.utilities.Structure(**kwargs)[source]

Bases: object

A simple structure that assigns the arguments to the object.

pylinac.core.utilities.decode_binary(file: BinaryIO, dtype: type[int] | type[float] | type[str] | str | dtype, num_values: int = 1, cursor_shift: int = 0, strip_empty: bool = True) int | float | str | ndarray | list[source]

Read in a raw binary file and convert it to given data types.

Parameters

file

The open file object.

dtype

The expected data type to return. If int or float and num_values > 1, will return numpy array.

num_values

The expected number of dtype to return

Note

This is not the same as the number of bytes.

cursor_shiftint

The number of bytes to move the cursor forward after decoding. This is used if there is a reserved section after the read-in segment.

strip_emptybool

Whether to strip trailing empty/null values for strings.

Contrast Module

class pylinac.core.contrast.Contrast[source]

Bases: OptionListMixin

Contrast calculation technique. See Visibility

MICHELSON = 'Michelson'
WEBER = 'Weber'
RATIO = 'Ratio'
RMS = 'Root Mean Square'
DIFFERENCE = 'Difference'
pylinac.core.contrast.visibility(array: ndarray, radius: float, std: float, algorithm: str) float[source]

The visual perception of CNR. Uses the model from A Rose: https://www.osapublishing.org/josa/abstract.cfm?uri=josa-38-2-196. See also here: https://howradiologyworks.com/x-ray-cnr/. Finally, a review paper here: http://xrm.phys.northwestern.edu/research/pdf_papers/1999/burgess_josaa_1999.pdf Importantly, the Rose model is not applicable for high-contrast use cases.

This uses the contrast function under the hood. Consult before using.

Parameters

array

The numpy array of the contrast ROI or a 2-element array containing the individual inputs. See contrast for more.

radius

The radius of the contrast ROI

std

Standard deviation of the array. This can sometimes be obtained from another ROI, so it is a separate parameter.

algorithm

The contrast method. See Contrast for options.

pylinac.core.contrast.contrast(array: ndarray, algorithm: str) float[source]

Generic contrast function. Different algorithms have different inputs, so caution is advised. When possible, the exact contrast function is preferred.

For Michelson and RMS algorithms, the input array can be any ordinary numpy array. For Weber and Ratio algorithms, the array is assumed to be a 2-element array.

Parameters

array

The numpy array of the ROI or 2-element input array. This is used in combination with the method.

algorithm

The contrast method. See Contrast for options.

pylinac.core.contrast.rms(array: ndarray) float[source]

The root-mean-square contrast. Requires values be within 0 and 1.

pylinac.core.contrast.difference(feature: float, background: float) float[source]

The simple absolute difference between the feature ROI and background ROI. This can be useful if the default CNR formula is desired (since pylinac CNR is based on the contrast algorithm chosen.

pylinac.core.contrast.michelson(array: ndarray) float[source]

The Michelson contrast. Used for sinusoidal patterns. Ranges from 0 to 1.

See also

https://en.wikipedia.org/wiki/Contrast_(vision)#Michelson_contrast

pylinac.core.contrast.weber(feature: float, background: float) float[source]

The Weber contrast. Used for patterns with a small feature within a large background. Ranges from 0 to infinity.

For backwards compatibility with previous versions, the absolute difference is used, making the range 0 to infinity vs -1 to infinity.

Danger

The default definition does not use the absolute value. We only use it here for backwards compatibility.

pylinac.core.contrast.ratio(feature: float, reference: float) float[source]

The ratio of luminescence