diff --git a/CHANGELOG.md b/CHANGELOG.md index 599a658557..a3ea0c9846 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). +## [v.0.7.0.dev151] + +### Fixed + +- Use combined torch.nextafter and fixed epsilon to select optimal threshold when only one target is present. + ## [v.0.7.0.dev150] ### Updated diff --git a/pyproject.toml b/pyproject.toml index 6a75f30502..7859bbcaa9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "anomalib-orobix" -version = "0.7.0.dev150" +version = "0.7.0.dev151" description = "Orobix anomalib fork" authors = [ "Intel OpenVINO ", diff --git a/src/anomalib/__init__.py b/src/anomalib/__init__.py index fcb74a8812..afdc82a385 100644 --- a/src/anomalib/__init__.py +++ b/src/anomalib/__init__.py @@ -4,6 +4,6 @@ # SPDX-License-Identifier: Apache-2.0 anomalib_version = "0.7.0" -custom_orobix_version = "1.5.0" +custom_orobix_version = "1.5.1" __version__ = f"{anomalib_version}.dev{custom_orobix_version.replace('.', '')}" diff --git a/src/anomalib/utils/metrics/optimal_f1.py b/src/anomalib/utils/metrics/optimal_f1.py index 9d0dbd0259..a4185cda6c 100644 --- a/src/anomalib/utils/metrics/optimal_f1.py +++ b/src/anomalib/utils/metrics/optimal_f1.py @@ -2,8 +2,9 @@ # SPDX-License-Identifier: Apache-2.0 """Implementation of Optimal F1 score based on TorchMetrics.""" -from typing import Optional + import warnings +from typing import Optional import torch from torch import Tensor @@ -54,12 +55,21 @@ def compute(self) -> Tensor: epsilon = 1e-3 if len(current_targets.unique()) == 1: + # Use torch nextafter to ensure that the threshold is higher (or smaller) + # than the maximum (or minimum) score. This ensures correctness for lower precisions. + # Combined method is to avoid very small shifts around zero. + _inf = torch.tensor(torch.inf, dtype=current_preds.dtype, device=current_preds.device) optimal_f1_score = torch.tensor(1.0) if current_targets.max() == 0: - self.threshold = current_preds.max() + epsilon + max_score = current_preds.max() + self.threshold = torch.max(torch.nextafter(max_score, _inf), max_score + epsilon) else: - self.threshold = current_preds.min() - epsilon + min_score = current_preds.min() + self.threshold = torch.min(torch.nextafter(min_score, -_inf), min_score - epsilon) + + if torch.isinf(self.threshold) or torch.isnan(self.threshold): + raise RuntimeError(f"Invalid value computed for the threshold: {self.threshold}.") return optimal_f1_score else: