From 4a34d40d8f8eb4d24b028f00ecf8af797b7cfb4c Mon Sep 17 00:00:00 2001 From: Michele Milesi Date: Fri, 20 Feb 2026 12:33:39 +0100 Subject: [PATCH 1/4] fix: optimal threhsold computation --- CHANGELOG.md | 6 ++++++ pyproject.toml | 2 +- src/anomalib/utils/metrics/optimal_f1.py | 17 ++++++++++++++--- 3 files changed, 21 insertions(+), 4 deletions(-) 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/utils/metrics/optimal_f1.py b/src/anomalib/utils/metrics/optimal_f1.py index 9d0dbd0259..a905597dbb 100644 --- a/src/anomalib/utils/metrics/optimal_f1.py +++ b/src/anomalib/utils/metrics/optimal_f1.py @@ -2,12 +2,14 @@ # 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 from torchmetrics import Metric, PrecisionRecallCurve +from zmq import device class OptimalF1(Metric): @@ -54,12 +56,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 ensure 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): + raise RuntimeError(f"Invalid value computed for the threshold: {self.threshold}.") return optimal_f1_score else: From 6be777bce576fc30961598ac1a53d5ef58376f21 Mon Sep 17 00:00:00 2001 From: Michele Milesi Date: Fri, 20 Feb 2026 13:15:34 +0100 Subject: [PATCH 2/4] fix: remove unused import --- src/anomalib/utils/metrics/optimal_f1.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/anomalib/utils/metrics/optimal_f1.py b/src/anomalib/utils/metrics/optimal_f1.py index a905597dbb..d9f6a99cfb 100644 --- a/src/anomalib/utils/metrics/optimal_f1.py +++ b/src/anomalib/utils/metrics/optimal_f1.py @@ -9,7 +9,6 @@ import torch from torch import Tensor from torchmetrics import Metric, PrecisionRecallCurve -from zmq import device class OptimalF1(Metric): @@ -57,7 +56,7 @@ 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 ensure correctness for lower precisions. + # 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) From 1072992e09ec22d8bdd1cc0001149b38256159ea Mon Sep 17 00:00:00 2001 From: Michele Milesi Date: Fri, 20 Feb 2026 13:18:22 +0100 Subject: [PATCH 3/4] fix: added nan check after threshold computation --- src/anomalib/utils/metrics/optimal_f1.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/anomalib/utils/metrics/optimal_f1.py b/src/anomalib/utils/metrics/optimal_f1.py index d9f6a99cfb..a4185cda6c 100644 --- a/src/anomalib/utils/metrics/optimal_f1.py +++ b/src/anomalib/utils/metrics/optimal_f1.py @@ -68,7 +68,7 @@ def compute(self) -> Tensor: min_score = current_preds.min() self.threshold = torch.min(torch.nextafter(min_score, -_inf), min_score - epsilon) - if torch.isinf(self.threshold): + 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 From 7ab88714377e0769c49da20014375b5acdec8219 Mon Sep 17 00:00:00 2001 From: Michele Milesi Date: Fri, 20 Feb 2026 14:07:34 +0100 Subject: [PATCH 4/4] chore: bump version --- src/anomalib/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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('.', '')}"