def threshold_and_binary_classification_curve(
    anomaly_maps: torch.Tensor,
    masks: torch.Tensor,
    threshold_choice: ThresholdMethod | str = ThresholdMethod.MINMAX_LINSPACE,
    thresholds: torch.Tensor | None = None,
    num_thresholds: int | None = None,
) -> tuple[torch.Tensor, torch.Tensor]:
    """Get thresholds and binary classification matrices for a batch of images.

    Args:
        anomaly_maps: Anomaly score maps of shape ``(N, H, W)``
        masks: Binary ground truth masks of shape ``(N, H, W)``
        threshold_choice: Method for selecting thresholds. Defaults to
            ``MINMAX_LINSPACE``
        thresholds: Sequence of thresholds to use. Only used when
            ``threshold_choice`` is ``GIVEN``
        num_thresholds: Number of thresholds between min and max scores. Only
            used when ``threshold_choice`` is ``MINMAX_LINSPACE``

    Returns:
        Tuple containing:

        - Thresholds of shape ``(K,)`` with same dtype as ``anomaly_maps``
        - Binary classification matrices of shape ``(N, K, 2, 2)`` where:

          - ``[..., 1, 1]``: True Positives (TP)
          - ``[..., 0, 1]``: False Positives (FP)
          - ``[..., 1, 0]``: False Negatives (FN)
          - ``[..., 0, 0]``: True Negatives (TN)

        The counts are per-instance pixel counts. Thresholds are shared across
        instances and sorted in ascending order.

    Example:
        >>> maps = torch.rand(10, 32, 32)  # 10 images
        >>> masks = torch.randint(0, 2, (10, 32, 32)).bool()
        >>> thresh, curves = threshold_and_binary_classification_curve(
        ...     maps,
        ...     masks,
        ...     num_thresholds=10,
        ... )
        >>> thresh.shape, curves.shape
        (torch.Size([10]), torch.Size([10, 10, 2, 2]))
    """
    threshold_choice = ThresholdMethod(threshold_choice)
    _validate.is_anomaly_maps(anomaly_maps)
    _validate.is_masks(masks)
    _validate.is_same_shape(anomaly_maps, masks)

    if threshold_choice == ThresholdMethod.GIVEN:
        assert thresholds is not None
        _validate.is_valid_threshold(thresholds)
        if num_thresholds is not None:
            logger.warning(
                "Argument `num_thresholds` was given, "
                f"but it is ignored because `thresholds_choice` is '{threshold_choice.value}'.",
            )
        thresholds = thresholds.to(anomaly_maps.dtype)

    elif threshold_choice == ThresholdMethod.MINMAX_LINSPACE:
        assert num_thresholds is not None
        if thresholds is not None:
            logger.warning(
                "Argument `thresholds_given` was given, "
                f"but it is ignored because `thresholds_choice` is '{threshold_choice.value}'.",
            )
        # `num_thresholds` is validated in the function below
        thresholds = _get_linspaced_thresholds(anomaly_maps, num_thresholds)

    elif threshold_choice == ThresholdMethod.MEAN_FPR_OPTIMIZED:
        raise NotImplementedError(f"TODO implement {threshold_choice.value}")  # noqa: EM102

    else:
        msg = (
            f"Expected `threshs_choice` to be from {list(ThresholdMethod.__members__)},"
            f" but got '{threshold_choice.value}'"
        )
        raise NotImplementedError(msg)

    # keep the batch dimension and flatten the rest
    scores_batch = anomaly_maps.reshape(anomaly_maps.shape[0], -1)
    gts_batch = masks.reshape(masks.shape[0], -1).to(dtype=torch.bool)

    binclf_curves = binary_classification_curve(scores_batch, gts_batch, thresholds)

    num_images = anomaly_maps.shape[0]

    try:
        _validate.is_binclf_curves(binclf_curves, valid_thresholds=thresholds)

        # these two validations cannot be done in `_validate.binclf_curves`
        # because it does not have access to the original shapes of
        # `anomaly_maps`
        if binclf_curves.shape[0] != num_images:
            msg = (
                "Expected `binclf_curves` to have the same number of images as `anomaly_maps`, "
                f"but got {binclf_curves.shape[0]} and {anomaly_maps.shape[0]}"
            )
            raise RuntimeError(msg)

    except (TypeError, ValueError) as ex:
        msg = f"Invalid `binclf_curves` was computed. Cause: {ex}"
        raise RuntimeError(msg) from ex

    return thresholds, binclf_curves
Get thresholds and binary classification matrices for a batch of images.

    Args:
        anomaly_maps: Anomaly score maps of shape ``(N, H, W)``
        masks: Binary ground truth masks of shape ``(N, H, W)``
        threshold_choice: Method for selecting thresholds. Defaults to
            ``MINMAX_LINSPACE``
        thresholds: Sequence of thresholds to use. Only used when
            ``threshold_choice`` is ``GIVEN``
        num_thresholds: Number of thresholds between min and max scores. Only
            used when ``threshold_choice`` is ``MINMAX_LINSPACE``

    Returns:
        Tuple containing:

        - Thresholds of shape ``(K,)`` with same dtype as ``anomaly_maps``
        - Binary classification matrices of shape ``(N, K, 2, 2)`` where:

          - ``[..., 1, 1]``: True Positives (TP)
          - ``[..., 0, 1]``: False Positives (FP)
          - ``[..., 1, 0]``: False Negatives (FN)
          - ``[..., 0, 0]``: True Negatives (TN)

        The counts are per-instance pixel counts. Thresholds are shared across
        instances and sorted in ascending order.

    Example:
        >>> maps = torch.rand(10, 32, 32)  # 10 images
        >>> masks = torch.randint(0, 2, (10, 32, 32)).bool()
        >>> thresh, curves = threshold_and_binary_classification_curve(
        ...     maps,
        ...     masks,
        ...     num_thresholds=10,
        ... )
        >>> thresh.shape, curves.shape
        (torch.Size([10]), torch.Size([10, 10, 2, 2]))
    
src/anomalib/metrics/pimo/binary_classification_curve.py