Source code for torch_uncertainty.metrics.segmentation.seg_binary_average_precision
from typing import Any
import torch
from torch import Tensor
from torchmetrics import Metric
from torchmetrics.functional.classification import binary_average_precision
from ._binary import binary_images, binary_target_has_classes
[docs]
class SegmentationBinaryAveragePrecision(Metric):
is_differentiable = False
higher_is_better = True
full_state_update = False
binary_aupr: Tensor
total: Tensor
def __init__(
self,
thresholds: int | list[float] | Tensor | None = None,
ignore_index: int | None = None,
validate_args: bool = True,
**kwargs: Any,
) -> None:
r"""Image-averaged binary Average Precision for dense segmentation tasks.
Per-image Average Precision summarises the precision-recall curve obtained by
sweeping a threshold over the pixel scores of image :math:`b`:
.. math::
\text{AP}_b = \sum_{k} \left( R_b(k) - R_b(k-1) \right) P_b(k),
where :math:`P_b(k)` and :math:`R_b(k)` are the precision and recall at the
:math:`k`-th threshold. The final metric is averaged over all :math:`B` images:
.. math::
\text{AP} = \frac{1}{B} \sum_{b=1}^{B} \text{AP}_b.
As for :class:`SegmentationBinaryAUROC`, image-wise averaging is the convention
used in the dense OOD-detection literature.
Images without positive pixels are excluded because their Average Precision 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:
thresholds: Optional explicit thresholds for the PR curve, see
:class:`~torchmetrics.classification.BinaryAveragePrecision`.
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.thresholds = thresholds
self.ignore_index = ignore_index
self.validate_args = validate_args
self.add_state("binary_aupr", 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,
require_negative=False,
):
continue
thresholds = self.thresholds
if isinstance(thresholds, Tensor):
thresholds = thresholds.to(image_preds.device)
self.binary_aupr += binary_average_precision(
image_preds,
image_target,
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_aupr.device)
return self.binary_aupr / self.total