Source code for torch_uncertainty.metrics.segmentation.seg_binary_auroc

from typing import Any

import torch
from torch import Tensor
from torchmetrics import Metric
from torchmetrics.functional.classification import binary_auroc

from ._binary import binary_images, binary_target_has_classes


[docs] class SegmentationBinaryAUROC(Metric): is_differentiable = False higher_is_better = True full_state_update = False binary_auroc: Tensor total: Tensor def __init__( self, max_fpr: float | None = None, thresholds: int | list[float] | Tensor | None = None, ignore_index: int | None = None, validate_args: bool = True, **kwargs: Any, ) -> None: r"""Image-averaged binary AUROC for dense binary segmentation tasks. At each image, a per-pixel binary AUROC is computed from the pixel scores :math:`s_{ij}` and binary labels :math:`y_{ij} \in \{0, 1\}`: .. math:: \text{AUROC}_b = \int_0^1 \text{TPR}_b\!\left(\text{FPR}_b^{-1}(u)\right) \mathrm{d}u, where TPR and FPR are computed by sweeping a threshold over the pixel-level scores of image :math:`b`. The final metric is the average over all images: .. math:: \text{AUROC} = \frac{1}{B} \sum_{b=1}^{B} \text{AUROC}_b. This image-wise averaging is the convention used in the dense OOD-detection literature (e.g., MUAD) and behaves better than computing AUROC over the flattened set of all pixels when image sizes or OOD prevalences vary. Images without both positive and negative pixels are excluded because their AUROC is undefined. The metric returns ``nan`` if no valid image was observed. A one-dimensional input is treated as one image; otherwise, the first dimension is the image batch dimension. Args: max_fpr: If set, computes the partial AUROC up to this FPR value (passed to :class:`~torchmetrics.classification.BinaryAUROC`). thresholds: Optional explicit thresholds to use when computing the ROC. ignore_index: Optional label value to ignore. validate_args: Whether to validate input arguments. kwargs: Additional keyword arguments, see `Advanced metric settings <https://torchmetrics.readthedocs.io/en/stable/pages/overview.html#metric-kwargs>`_. """ super().__init__(**kwargs) self.max_fpr = max_fpr self.thresholds = thresholds self.ignore_index = ignore_index self.validate_args = validate_args self.add_state("binary_auroc", default=torch.tensor(0.0), dist_reduce_fx="sum") self.add_state("total", default=torch.tensor(0.0), dist_reduce_fx="sum") def update(self, preds: Tensor, target: Tensor) -> None: # pyrefly: ignore[bad-override] for image_preds, image_target in binary_images(preds, target): if not binary_target_has_classes(image_target, ignore_index=self.ignore_index): continue thresholds = self.thresholds if isinstance(thresholds, Tensor): thresholds = thresholds.to(image_preds.device) self.binary_auroc += binary_auroc( image_preds, image_target, max_fpr=self.max_fpr, thresholds=thresholds, ignore_index=self.ignore_index, validate_args=self.validate_args, ) self.total += 1 def compute(self) -> Tensor: if self.total == 0: return torch.tensor(torch.nan, device=self.binary_auroc.device) return self.binary_auroc / self.total