Source code for torch_uncertainty.metrics.segmentation.seg_fpr95
import torch
from torch import Tensor
from torchmetrics import Metric
from torch_uncertainty.metrics.classification.fpr import _fprx_compute
from ._binary import binary_images, binary_target_has_classes
[docs]
class SegmentationFPR95(Metric):
is_differentiable = False
higher_is_better = False
full_state_update = False
fpr95: Tensor
total: Tensor
def __init__(self, pos_label: int, ignore_index: int | None = None, **kwargs) -> None:
r"""Image-averaged FPR@95 TPR for dense binary segmentation tasks.
For each image, a per-pixel False Positive Rate at 95% True Positive Rate is
computed (see :class:`~torch_uncertainty.metrics.classification.FPR95`) from
the pixel scores and binary OOD labels. The metric is then averaged over the
:math:`B` images of the test set:
.. math::
\text{FPR95} = \frac{1}{B} \sum_{b=1}^{B} \text{FPR95}_b.
Image-wise averaging is the convention used in the dense OOD-detection
literature.
Images without both positive and negative pixels are excluded because FPR@95
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:
pos_label: The positive label in the segmentation OOD detection task
(typically ``1`` for OOD pixels).
ignore_index: Optional label value to ignore.
kwargs: Additional keyword arguments for the underlying
:class:`~torch_uncertainty.metrics.classification.FPR95` metric.
"""
super().__init__(**kwargs)
self.pos_label = pos_label
self.ignore_index = ignore_index
self.add_state("fpr95", 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 self.ignore_index is not None:
keep = image_target != self.ignore_index
image_preds = image_preds[keep]
image_target = image_target[keep]
if not binary_target_has_classes(image_target, pos_label=self.pos_label):
continue
self.fpr95 += _fprx_compute(
image_preds,
image_target,
recall_level=0.95,
pos_label=self.pos_label,
)
self.total += 1
def compute(self) -> Tensor:
if self.total == 0:
return torch.tensor(torch.nan, device=self.fpr95.device)
return self.fpr95 / self.total