def is_per_image_rate_curves(
    rate_curves: Tensor,
    nan_allowed: bool,
    decreasing: bool | None,
) -> None:
    """Validate per-image rate curves tensor.

    Args:
        rate_curves: Tensor of shape (N, T) containing rate curves for N images.
        nan_allowed: Whether NaN values are allowed.
        decreasing: Whether curves should be monotonically decreasing.

    Raises:
        ValueError: If curves have invalid values or wrong monotonicity.
        TypeError: If tensor has wrong dtype.

    Example:
        >>> curves = torch.zeros(10, 5)  # 10 images, 5 thresholds
        >>> is_per_image_rate_curves(curves, nan_allowed=False, decreasing=None)
        >>> # No error
    """
    if rate_curves.ndim != 2:
        msg = f"Expected per-image rate curves to be 2D, but got {rate_curves.ndim}D."
        raise ValueError(msg)

    if not rate_curves.dtype.is_floating_point:
        msg = f"Expected per-image rate curves to have dtype of float type, but got {rate_curves.dtype}."
        raise TypeError(msg)

    isnan_mask = torch.isnan(rate_curves)
    if nan_allowed:
        # if they are all nan, then there is nothing to validate
        if isnan_mask.all():
            return
        valid_values = rate_curves[~isnan_mask]
    elif isnan_mask.any():
        msg = "Expected per-image rate curves to not contain NaN values, but got NaN values."
        raise ValueError(msg)
    else:
        valid_values = rate_curves

    if (valid_values < 0).any():
        msg = "Expected per-image rate curves to have values in the interval [0, 1], but got values < 0."
        raise ValueError(msg)

    if (valid_values > 1).any():
        msg = "Expected per-image rate curves to have values in the interval [0, 1], but got values > 1."
        raise ValueError(msg)

    if decreasing is None:
        return

    diffs = torch.diff(rate_curves, dim=1)
    diffs_valid = diffs[~torch.isnan(diffs)] if nan_allowed else diffs

    if decreasing and (diffs_valid > 0).any():
        msg = (
            "Expected per-image rate curves to be monotonically decreasing, "
            "but got non-monotonically decreasing values."
        )
        raise ValueError(msg)

    if not decreasing and (diffs_valid < 0).any():
        msg = (
            "Expected per-image rate curves to be monotonically increasing, "
            "but got non-monotonically increasing values."
        )
        raise ValueError(msg)
Validate per-image rate curves tensor.

    Args:
        rate_curves: Tensor of shape (N, T) containing rate curves for N images.
        nan_allowed: Whether NaN values are allowed.
        decreasing: Whether curves should be monotonically decreasing.

    Raises:
        ValueError: If curves have invalid values or wrong monotonicity.
        TypeError: If tensor has wrong dtype.

    Example:
        >>> curves = torch.zeros(10, 5)  # 10 images, 5 thresholds
        >>> is_per_image_rate_curves(curves, nan_allowed=False, decreasing=None)
        >>> # No error
    
src/anomalib/metrics/pimo/_validate.py