# Path: pylinac/core/roi.py
# class HighContrastDiskROI(DiskROI):
#     """A class for analyzing the high-contrast disks."""
#     contrast_threshold: Optional[float]
# 
#     def __init__(self, array: np.ndarray, angle: float, roi_radius: float, dist_from_center: float, phantom_center: Union[tuple, Point], contrast_threshold: float):
#         """
#         Parameters
#         ----------
#         contrast_threshold : float, int
#             The threshold for considering a bubble to be "seen".
#         """
#         super().__init__(array, angle, roi_radius, dist_from_center, phantom_center)
#         self.contrast_threshold = contrast_threshold
# 
#     @cached_property
#     def max(self) -> np.ndarray:
#         """The max pixel value of the ROI."""
#         masked_img = self.circle_mask()
#         return np.nanmax(masked_img)
# 
#     @cached_property
#     def min(self) -> np.ndarray:
#         """The min pixel value of the ROI."""
#         masked_img = self.circle_mask()
#         return np.nanmin(masked_img)


import warnings
import argue
import numpy as np
from typing import Sequence
from scipy.interpolate import interp1d
from pylinac.core.roi import HighContrastDiskROI
        self.norm_mtfs = {}
        for (spacing, max, min) in zip(lp_spacings, lp_maximums, lp_minimums):
            self.mtfs[spacing] = (max - min) / (max + min)
        # sort according to spacings
        self.mtfs = {k: v for k, v in sorted(self.mtfs.items(), key=lambda x: x[0])}
        for key, value in self.mtfs.items():
            self.norm_mtfs[key] = value / self.mtfs[lp_spacings[0]]  # normalize to first region

        # check that the MTF drops monotonically by measuring the deltas between MTFs
        # if the delta is increasing it means the MTF rose on a subsequent value
        max_delta = np.max(np.diff(list(self.norm_mtfs.values())))
        if max_delta > 0:
            warnings.warn("The MTF does not drop monotonically; be sure the ROIs are correctly aligned.")

    @argue.bounds(x=(0, 100))
    def relative_resolution(self, x=50) -> float:
        """Return the line pair value at the given rMTF resolution value.

        Parameters
        ----------
        x : float
            The percentage of the rMTF to determine the line pair value. Must be between 0 and 100.
        """
        f = interp1d(list(self.norm_mtfs.values()), list(self.norm_mtfs.keys()), fill_value='extrapolate')
        mtf = f(x / 100)
        if mtf > max(self.spacings):
            warnings.warn(f"MTF resolution wasn't calculated for {x}% that was asked for. The value returned is an extrapolation. Use a higher % MTF to get a non-interpolated value.")
        return float(mtf)

    @classmethod

# Path: pylinac/core/roi.py
# class HighContrastDiskROI(DiskROI):
#     """A class for analyzing the high-contrast disks."""
#     contrast_threshold: Optional[float]
# 
#     def __init__(self, array: np.ndarray, angle: float, roi_radius: float, dist_from_center: float, phantom_center: Union[tuple, Point], contrast_threshold: float):
#         """
#         Parameters
#         ----------
#         contrast_threshold : float, int
#             The threshold for considering a bubble to be "seen".
#         """
#         super().__init__(array, angle, roi_radius, dist_from_center, phantom_center)
#         self.contrast_threshold = contrast_threshold
# 
#     @cached_property
#     def max(self) -> np.ndarray:
#         """The max pixel value of the ROI."""
#         masked_img = self.circle_mask()
#         return np.nanmax(masked_img)
# 
#     @cached_property
#     def min(self) -> np.ndarray:
#         """The min pixel value of the ROI."""
#         masked_img = self.circle_mask()
#         return np.nanmin(masked_img)


# Path: pylinac/core/mtf.py
import warnings
import argue
import numpy as np
from typing import Sequence
from scipy.interpolate import interp1d
from pylinac.core.roi import HighContrastDiskROI

        self.norm_mtfs = {}
        for (spacing, max, min) in zip(lp_spacings, lp_maximums, lp_minimums):
            self.mtfs[spacing] = (max - min) / (max + min)
        # sort according to spacings
        self.mtfs = {k: v for k, v in sorted(self.mtfs.items(), key=lambda x: x[0])}
        for key, value in self.mtfs.items():
            self.norm_mtfs[key] = value / self.mtfs[lp_spacings[0]]  # normalize to first region

        # check that the MTF drops monotonically by measuring the deltas between MTFs
        # if the delta is increasing it means the MTF rose on a subsequent value
        max_delta = np.max(np.diff(list(self.norm_mtfs.values())))
        if max_delta > 0:
            warnings.warn("The MTF does not drop monotonically; be sure the ROIs are correctly aligned.")

    @argue.bounds(x=(0, 100))
    def relative_resolution(self, x=50) -> float:
        """Return the line pair value at the given rMTF resolution value.

        Parameters
        ----------
        x : float
            The percentage of the rMTF to determine the line pair value. Must be between 0 and 100.
        """
        f = interp1d(list(self.norm_mtfs.values()), list(self.norm_mtfs.keys()), fill_value='extrapolate')
        mtf = f(x / 100)
        if mtf > max(self.spacings):
            warnings.warn(f"MTF resolution wasn't calculated for {x}% that was asked for. The value returned is an extrapolation. Use a higher % MTF to get a non-interpolated value.")
        return float(mtf)

    @classmethod

    def from_high_contrast_diskset(cls, spacings: Sequence[float], diskset: Sequence[HighContrastDiskROI]):