ARRAY = 'Array'
DICOM = 'DICOM'
IMAGE = 'Image'
FILE_TYPE = 'file'
STREAM_TYPE = 'stream'
MM_PER_INCH = 25.4
def equate_images(image1: ImageLike, image2: ImageLike) -> Tuple[ImageLike, ImageLike]:
def is_image(path: Union[str, io.BytesIO, ImageLike, np.ndarray]) -> bool:
def retrieve_image_files(path: str) -> List[str]:
def load(path: Union[str, ImageLike, np.ndarray, BinaryIO], **kwargs) -> ImageLike:
def load_url(url: str, progress_bar: bool = True, **kwargs) -> ImageLike:
def load_multiples(image_file_list: Sequence, method: str = 'mean', stretch_each: bool = True, **kwargs) -> ImageLike:
def _is_dicom(path: Union[str, io.BytesIO, ImageLike, np.ndarray]) -> bool:
def _is_image_file(path: str) -> bool:
def _is_array(obj: Any) -> bool:
    def __init__(self, path: Union[str, BytesIO, ImageLike, np.ndarray, BufferedReader]):
    def truncated_path(self) -> str:
    def from_multiples(cls, filelist: List[str], method: str='mean', stretch: bool=True, **kwargs) -> ImageLike:
    def center(self) -> Point:
    def physical_shape(self) -> Tuple[float, float]:
    def date_created(self, format: str="%A, %B %d, %Y") -> str:
    def plot(self, ax: plt.Axes=None, show: bool=True, clear_fig: bool=False, **kwargs) -> plt.Axes:
    def filter(self, size: Union[float, int]=0.05, kind: str='median') -> None:
    def crop(self, pixels: int=15, edges: Tuple[str, ...]=('top', 'bottom', 'left', 'right')) -> None:
    def remove_edges(self, pixels: int=15, edges: Tuple[str, ...]=('top', 'bottom', 'left', 'right')) -> None:
    def flipud(self) -> None:
    def fliplr(self) -> None:
    def invert(self) -> None:
    def roll(self, direction: str='x', amount: int=1) -> None:
    def rot90(self, n: int=1) -> None:
    def threshold(self, threshold: int, kind: str='high') -> None:
    def as_binary(self, threshold: int) -> ImageLike:
    def dist2edge_min(self, point: Union[Point, Tuple]) -> float:
    def ground(self) -> float:
    def normalize(self, norm_val: Union[str, NumberLike]='max') -> None:
    def check_inversion(self, box_size: int=20, position: Sequence=(0.0, 0.0)) -> None:
    def check_inversion_by_histogram(self, percentiles=(5, 50, 95)) -> bool:
    def gamma(self, comparison_image: ImageLike, doseTA: NumberLike=1, distTA: NumberLike=1,
              threshold: NumberLike=0.1, ground: bool=True, normalize: bool=True) -> np.ndarray:
    def as_type(self, dtype) -> np.ndarray:
    def shape(self) -> Tuple[int, int]:
    def size(self) -> int:
    def ndim(self) -> int:
    def dtype(self) -> np.dtype:
    def sum(self) -> float:
    def ravel(self) -> np.ndarray:
    def flat(self) -> np.ndarray:
    def __len__(self):
    def __getitem__(self, item):
    def __init__(self, path: Union[str, BytesIO, BufferedReader], *, dtype=None, dpi: NumberLike=None, sid: NumberLike=None):
    def save(self, filename: str) -> str:
    def sid(self) -> NumberLike:
    def dpi(self) -> NumberLike:
    def dpmm(self) -> NumberLike:
    def cax(self) -> Point:
    def __init__(self, path: str, use_filenames: bool=False):
    def gantry_angle(self) -> float:
    def collimator_angle(self) -> float:
    def couch_angle(self) -> float:
    def _get_axis_value(self, axis_str: str, axis_dcm_attr: str) -> float:
    def __init__(self, path: str, *, dpi: NumberLike=None, sid: NumberLike=None, dtype=None):
    def dpi(self) -> float:
    def dpmm(self) -> Optional[float]:
    def __init__(self, array: np.array, *, dpi: NumberLike=None, sid: NumberLike=None, dtype=None):
    def dpmm(self) -> Optional[float]:
    def dpi(self) -> Optional[float]:
    def __sub__(self, other):
    def __init__(self, folder: str, dtype=None, min_number: int=39, check_uid: bool=True):
    def from_zip(cls, zip_path: str, dtype=None):
    def is_CT_slice(file: str) -> bool:
    def _check_number_and_get_common_uid_imgs(self, min_number: int) -> List:
    def plot(self, slice: int=0) -> None:
    def metadata(self) -> pydicom.FileDataset:
    def __getitem__(self, item) -> DicomImage:
    def __setitem__(self, key, value: DicomImage):
    def __len__(self):
class BaseImage:
class DicomImage(BaseImage):
class LinacDicomImage(DicomImage):
class FileImage(BaseImage):
class ArrayImage(BaseImage):
class DicomImageStack: def is_dicom(file: str) -> bool:
def is_dicom_image(file: str) -> bool:
def retrieve_dicom_file(file: str) -> pydicom.FileDataset:
def is_zipfile(file: str) -> bool:
    def __init__(self, zfile):
def retrieve_filenames(directory: str, func: Callable=None, recursive: bool=True, **kwargs) -> List[str]:
def retrieve_demo_file(url: str, force: bool = False) -> str:
def is_url(url: str) -> bool:
def get_url(url: str, destination: str=None, progress_bar: bool=True) -> str:
    def my_hook(t):
        def inner(b=1, bsize=1, tsize=None):
    def __init__(self, path: str, detector_row: int = 106, bias_row: int = 107, calibration_row: int = 108,
                 data_row: int = -1, data_columns: slice = slice(5, 259)):
    def to_profiles(self, n_detectors_row: int = 63, **kwargs) -> Tuple[SingleProfile, SingleProfile, SingleProfile, SingleProfile]:
class TemporaryZipDirectory(TemporaryDirectory):
class SNCProfiler: class PylinacCanvas:
    def __init__(self, filename: str, page_title: str, font: str="Helvetica", metadata: dict=None, metadata_location: tuple=(2, 25.5)):
    def add_new_page(self) -> None:
    def _add_metadata(self) -> None:
    def _generate_pylinac_template_theme(self) -> None:
    def add_text(self, text: Union[str, List[str]], location: Tuple[NumberLike, NumberLike], font_size: int=10) -> None:
    def add_image(self, image_data: io.BytesIO, location: Sequence, dimensions: Sequence, preserve_aspect_ratio: bool=True) -> None:
    def finish(self) -> None: def lru_cache(*lru_args, **lru_kwargs):
    """https://stackoverflow.com/a/33672499"""
    def decorator(func):
        @functools.wraps(func)
        def wrapped_func(self, *args, **kwargs):
            # We're storing the wrapped method inside the instance. If we had
            # a strong reference to self the instance would never die.
            self_weak = weakref.ref(self)
            @functools.wraps(func)
            @functools.lru_cache(*lru_args, **lru_kwargs)
            def cached_method(*args, **kwargs):
                return func(self_weak(), *args, **kwargs)
            setattr(self, func.__name__, cached_method)
            return cached_method(*args, **kwargs)
        return wrapped_func
    return decorator def is_iterable(object) -> bool:
    """Determine if an object is iterable."""
    return isinstance(object, Iterable) def decode_binary(file: BinaryIO, dtype: Union[Type[int], Type[float], Type[str]], num_values: int = 1, cursor_shift: int = 0) -> \
                    Union[int, float, str, np.ndarray]:
    """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_shift : int
        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.
    """
    f = file

    if dtype == str:  # if string
        output = f.read(num_values)
        if type(f) is not str:  # in py3 fc will be bytes
            output = output.decode()
        # strip the padding ("\x00")
        output = output.strip('\x00')
    elif dtype == int:
        ssize = struct.calcsize('i') * num_values
        output = np.asarray(struct.unpack('i' * num_values, f.read(ssize)))
        if len(output) == 1:
            output = int(output)
    elif dtype == float:
        ssize = struct.calcsize('f') * num_values
        output = np.asarray(struct.unpack('f' * num_values, f.read(ssize)))
        if len(output) == 1:
            output = float(output)
    else:
        raise TypeError(f"datatype '{dtype}' was not valid")

    # shift cursor if need be (e.g. if a reserved section follows)
    if cursor_shift:
        f.seek(cursor_shift, 1)
    return output class Structure:
    """A simple structure that assigns the arguments to the object."""
    def __init__(self, **kwargs):
        self.__dict__.update(**kwargs)

    def update(self, **kwargs):
        self.__dict__.update(**kwargs) def open_path(path: str) -> None:
    """Open the specified path in the system default viewer."""

    if os.name == 'darwin':
        launcher = "open"
    elif os.name == 'posix':
        launcher = "xdg-open"
    elif os.name == 'nt':
        launcher = "explorer"
    subprocess.call([launcher, path]) def convert_to_enum(value: Union[str, Enum, None], enum: Type[Enum]) -> Enum:
    """Convert a value to an enum representation from an enum value if needed"""
    if isinstance(value, enum):
        return value
    else:
        return enum(value) def get_array_cmap():
    """Return the array colormap. Passed to cmap parameter in matplotlib calls."""
    return ARRAY_COLORMAP
import collections
import concurrent.futures
import copy
import csv
import enum
import gc
import itertools
import multiprocessing
import os
import os.path as osp
import shutil
import argue
import matplotlib.pyplot as plt
import numpy as np
from io import BytesIO, BufferedReader
from typing import Union, List, Optional, Tuple, Iterable, Sequence, BinaryIO
from cached_property import cached_property
from .core import image, io, pdf
from .core.decorators import lru_cache
from .core.utilities import is_iterable, decode_binary, Structure, open_path, convert_to_enum
from .settings import get_array_cmap

    @argue.options(dtype=('actual', 'expected'))
    def get_snapshot_values(self, bank_or_leaf: Union[MLCBank, Iterable] = MLCBank.BOTH,
                            dtype: str = 'actual') -> np.ndarray:
        """Retrieve the snapshot data of the given MLC bank or leaf/leaves

        Parameters
        ----------
        bank_or_leaf : str, array, list
            If a str, specifies what bank ('A', 'B', 'both').
            If an array/list, specifies what leaves (e.g. [1,2,3])
        dtype : {'actual', 'expected'}
            The type of MLC snapshot data to return.

        Returns
        -------
        ndarray
            An array of shape (number of leaves - x - number of snapshots). E.g. for an MLC bank
            and 500 snapshots, the array would be (60, 500).
        """
        if isinstance(bank_or_leaf, (str, MLCBank)):
            leaves = self.get_leaves(bank=bank_or_leaf)
            leaves -= 1
        else:
            leaves = bank_or_leaf

        arr = self._snapshot_array(dtype)
        return arr[leaves, :]

    def plot_mlc_error_hist(self, show: bool = True) -> None:
        """Plot an MLC error histogram."""
        plt.hist(self._abs_error_all_leaves.flatten())
        if show:
            plt.show()

    def save_mlc_error_hist(self, filename: str, **kwargs) -> None:
        """Save the MLC error histogram to file."""
        self.plot_mlc_error_hist(show=False)
        plt.savefig(filename, **kwargs)

    def plot_rms_by_leaf(self, show: bool = True) -> None:
        """Plot RMSs by leaf."""
        plt.clf()
        plt.bar(np.arange(len(self.get_RMS(MLCBank.BOTH)))[::-1], self.get_RMS(MLCBank.BOTH), align='center')
        if show:
            plt.show()

    def save_rms_by_leaf(self, filename: str, **kwargs) -> None:
        """Save the RMS-leaf to file."""
        self.plot_rms_by_leaf(show=False)
        plt.savefig(filename, **kwargs)


class JawStruct:
    """Jaw Axes data structure.

    Attributes
    ----------
    x1 : :class:`~pylinac.log_analyzer.Axis`
    y1 : :class:`~pylinac.log_analyzer.Axis`
    x2 : :class:`~pylinac.log_analyzer.Axis`
    y2 : :class:`~pylinac.log_analyzer.Axis`
    """
    def __init__(self, x1: HeadAxis, y1: HeadAxis, x2: HeadAxis, y2: HeadAxis):
        if not all((
                isinstance(x1, HeadAxis),
                isinstance(y1, HeadAxis),
                isinstance(x2, HeadAxis),
                isinstance(y2, HeadAxis))):
            raise TypeError("HeadAxis not passed into Jaw structure")
        self.x1 = x1
        self.y1 = y1
        self.x2 = x2
        self.y2 = y2


class CouchStruct:
    """Couch Axes data structure.

    Attributes
    ----------
    vert : :class:`~pylinac.log_analyzer.Axis`
    long : :class:`~pylinac.log_analyzer.Axis`
    latl : :class:`~pylinac.log_analyzer.Axis`
    rotn : :class:`~pylinac.log_analyzer.Axis`
    """
    def __init__(self, vertical: CouchAxis, longitudinal: CouchAxis, lateral: CouchAxis, rotational: CouchAxis,
                 pitch: Optional[CouchAxis] = None, roll: Optional[CouchAxis] = None):
        if not all((isinstance(vertical, CouchAxis),
                    isinstance(longitudinal, CouchAxis),
                    isinstance(lateral, CouchAxis),
                    isinstance(rotational, CouchAxis))):
            raise TypeError("Couch structure must be passed Couch Axes.")
        self.vert = vertical
        self.long = longitudinal
        self.latl = lateral
        self.rotn = rotational
        if pitch is not None:
            self.pitch = pitch
            self.roll = roll


class Subbeam:
    """Data structure for trajectory log "subbeams". Only applicable for auto-sequenced beams.

    Attributes
    ----------
    control_point : int
        Internally-defined marker that defines where the plan is currently executing.
    mu_delivered : float
        Dose delivered in units of MU.
    rad_time : float
        Radiation time in seconds.
    sequence_num : int
        Sequence number of the subbeam.
    beam_name : str
        Name of the subbeam.
    """
    def __init__(self, file, log_version: float):
        f = file

        self.control_point = decode_binary(f, int)