Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Convert adaptive_threshold to Enum in configs #637

Merged
merged 23 commits into from
Oct 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
80f47b4
Rename image/pixel_metrics_names to image/pixel_metrics
samet-akcay Sep 27, 2022
8039413
Rename image/pixel_metrics_names to image/pixel_metrics
samet-akcay Sep 27, 2022
2e6b831
Modified config files.
samet-akcay Oct 3, 2022
8dce79e
Modify get_callbacks function for the old cli
samet-akcay Oct 3, 2022
508d469
Create pre-processing-configuration callback
samet-akcay Oct 3, 2022
e1a0671
Add new CLI configuration for the post-processing configuration
samet-akcay Oct 3, 2022
b9ae696
Add options to normalization_method
samet-akcay Oct 3, 2022
00202c8
Address mypy issues
samet-akcay Oct 3, 2022
54c9a39
Fix docstring
samet-akcay Oct 3, 2022
ba450f4
Fix merge conflicts
samet-akcay Oct 3, 2022
838bd0b
Address codacy issues
samet-akcay Oct 4, 2022
567bb9b
Merge branch 'main' of github.com:openvinotoolkit/anomalib into cli/s…
samet-akcay Oct 17, 2022
61352ae
renamed adaptive: true to threshold_method: adaptive in config.yaml f…
samet-akcay Oct 17, 2022
6ce5f71
Reorder threshold params in config.yaml
samet-akcay Oct 17, 2022
c234967
Add backward compatibility to threshold configs
samet-akcay Oct 17, 2022
86156d1
renamed the new cli config files
samet-akcay Oct 17, 2022
b2426e8
Rename threshold_method to method in config.yaml
samet-akcay Oct 18, 2022
2756b8d
Convert names to Enum
samet-akcay Oct 18, 2022
429bad7
Fix tests
samet-akcay Oct 18, 2022
c0c0a49
Rename AdaptiveThreshold to AnomalyScoreThreshold
samet-akcay Oct 18, 2022
26b1577
Fixed import
samet-akcay Oct 18, 2022
332e737
Rename fixed to manual
samet-akcay Oct 18, 2022
d0be111
Rename some left-over variables.
samet-akcay Oct 18, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions anomalib/config/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,19 @@ def get_configurable_parameters(

# thresholding
if "metrics" in config.keys():
if "pixel_default" not in config.metrics.threshold.keys():
config.metrics.threshold.pixel_default = config.metrics.threshold.image_default
# NOTE: Deprecate this after v0.4.0.
if "adaptive" in config.metrics.threshold.keys():
warn("adaptive will be deprecated in favor of method in config.metrics.threshold in v0.4.0.")
config.metrics.threshold.method = "adaptive" if config.metrics.threshold.adaptive else "manual"
if "image_default" in config.metrics.threshold.keys():
warn("image_default will be deprecated in favor of manual_image in config.metrics.threshold in v0.4.0.")
config.metrics.threshold.manual_image = (
None if config.metrics.threshold.adaptive else config.metrics.threshold.image_default
)
if "pixel_default" in config.metrics.threshold.keys():
warn("pixel_default will be deprecated in favor of manual_pixel in config.metrics.threshold in v0.4.0.")
config.metrics.threshold.manual_pixel = (
None if config.metrics.threshold.adaptive else config.metrics.threshold.pixel_default
)

return config
6 changes: 3 additions & 3 deletions anomalib/models/cflow/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ metrics:
- F1Score
- AUROC
threshold:
image_default: 0
pixel_default: 0
adaptive: true
method: adaptive #options: [adaptive, manual]
manual_image: null
manual_pixel: null

visualization:
show_images: False # show images on the screen
Expand Down
12 changes: 6 additions & 6 deletions anomalib/models/components/base/anomaly_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@
from torch import Tensor, nn
from torchmetrics import Metric

from anomalib.post_processing import ThresholdMethod
from anomalib.utils.metrics import (
AdaptiveThreshold,
AnomalibMetricCollection,
AnomalyScoreDistribution,
AnomalyScoreThreshold,
MinMax,
)

Expand All @@ -38,10 +39,9 @@ def __init__(self):
self.loss: Tensor
self.callbacks: List[Callback]

self.adaptive_threshold: bool

self.image_threshold = AdaptiveThreshold().cpu()
self.pixel_threshold = AdaptiveThreshold().cpu()
self.threshold_method: ThresholdMethod
self.image_threshold = AnomalyScoreThreshold().cpu()
self.pixel_threshold = AnomalyScoreThreshold().cpu()

self.normalization_metrics: Metric

Expand Down Expand Up @@ -115,7 +115,7 @@ def validation_epoch_end(self, outputs):
Args:
outputs: Batch of outputs from the validation step
"""
if self.adaptive_threshold:
if self.threshold_method == ThresholdMethod.ADAPTIVE:
self._compute_adaptive_threshold(outputs)
self._collect_outputs(self.image_metrics, self.pixel_metrics, outputs)
self._log_metrics()
Expand Down
4 changes: 2 additions & 2 deletions anomalib/models/dfkde/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ metrics:
- F1Score
- AUROC
threshold:
image_default: 0
adaptive: true
method: adaptive #options: [adaptive, manual]
manual_image: null

visualization:
show_images: False # show images on the screen
Expand Down
4 changes: 2 additions & 2 deletions anomalib/models/dfm/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ metrics:
- F1Score
- AUROC
threshold:
image_default: 0
adaptive: true
method: adaptive #options: [adaptive, manual]
manual_image: null

visualization:
show_images: False # show images on the screen
Expand Down
6 changes: 3 additions & 3 deletions anomalib/models/draem/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ metrics:
- F1Score
- AUROC
threshold:
image_default: 3
pixel_default: 3
adaptive: true
method: adaptive #options: [adaptive, manual]
manual_image: null
manual_pixel: null

visualization:
show_images: False # show images on the screen
Expand Down
6 changes: 3 additions & 3 deletions anomalib/models/fastflow/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ metrics:
- F1Score
- AUROC
threshold:
image_default: 0
pixel_default: 0
adaptive: true
method: adaptive #options: [adaptive, manual]
manual_image: null
manual_pixel: null

visualization:
show_images: False # show images on the screen
Expand Down
4 changes: 2 additions & 2 deletions anomalib/models/ganomaly/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ metrics:
- F1Score
- AUROC
threshold:
image_default: 0
adaptive: true
method: adaptive #options: [adaptive, manual]
manual_image: null

visualization:
show_images: False # show images on the screen
Expand Down
6 changes: 3 additions & 3 deletions anomalib/models/padim/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ metrics:
- F1Score
- AUROC
threshold:
image_default: 3
pixel_default: 3
adaptive: true
method: adaptive #options: [adaptive, manual]
manual_image: null
manual_pixel: null

visualization:
show_images: False # show images on the screen
Expand Down
6 changes: 3 additions & 3 deletions anomalib/models/patchcore/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ metrics:
- F1Score
- AUROC
threshold:
image_default: 0
pixel_default: 0
adaptive: true
method: adaptive #options: [adaptive, manual]
manual_image: null
manual_pixel: null

visualization:
show_images: False # show images on the screen
Expand Down
6 changes: 3 additions & 3 deletions anomalib/models/reverse_distillation/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,9 @@ metrics:
- F1Score
- AUROC
threshold:
image_default: 0
pixel_default: 0
adaptive: true
method: adaptive #options: [adaptive, manual]
manual_image: null
manual_pixel: null

visualization:
show_images: False # show images on the screen
Expand Down
6 changes: 3 additions & 3 deletions anomalib/models/stfpm/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,9 @@ metrics:
- F1Score
- AUROC
threshold:
image_default: 0
pixel_default: 0
adaptive: true
method: adaptive #options: [adaptive, manual]
manual_image: null
manual_pixel: null

visualization:
show_images: False # show images on the screen
Expand Down
4 changes: 4 additions & 0 deletions anomalib/post_processing/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
# Copyright (C) 2022 Intel Corporation
# SPDX-License-Identifier: Apache-2.0

from .normalization import NormalizationMethod
from .post_process import (
ThresholdMethod,
add_anomalous_label,
add_normal_label,
anomaly_map_to_color_map,
Expand All @@ -19,5 +21,7 @@
"superimpose_anomaly_map",
"compute_mask",
"ImageResult",
"NormalizationMethod",
"Visualizer",
"ThresholdMethod",
]
10 changes: 10 additions & 0 deletions anomalib/post_processing/normalization/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,13 @@

# Copyright (C) 2022 Intel Corporation
# SPDX-License-Identifier: Apache-2.0

from enum import Enum


class NormalizationMethod(str, Enum):
"""Normalization method for normalization."""

CDF = "cdf"
MIN_MAX = "min_max"
NONE = "none"
8 changes: 8 additions & 0 deletions anomalib/post_processing/post_process.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,21 @@


import math
from enum import Enum
from typing import Optional, Tuple

import cv2
import numpy as np
from skimage import morphology


class ThresholdMethod(str, Enum):
"""Threshold method to apply post-processing to the output predictions."""

ADAPTIVE = "adaptive"
MANUAL = "manual"


def add_label(
image: np.ndarray,
label_name: str,
Expand Down
10 changes: 5 additions & 5 deletions anomalib/utils/callbacks/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,15 +69,15 @@ def get_callbacks(config: Union[ListConfig, DictConfig]) -> List[Callback]:

# Add post-processing configurations to AnomalyModule.
image_threshold = (
config.metrics.threshold.image_default if "image_default" in config.metrics.threshold.keys() else None
config.metrics.threshold.manual_image if "manual_image" in config.metrics.threshold.keys() else None
)
pixel_threshold = (
config.metrics.threshold.pixel_default if "pixel_default" in config.metrics.threshold.keys() else None
config.metrics.threshold.manual_pixel if "manual_pixel" in config.metrics.threshold.keys() else None
)
post_processing_callback = PostProcessingConfigurationCallback(
adaptive_threshold=config.metrics.threshold.adaptive,
default_image_threshold=image_threshold,
default_pixel_threshold=pixel_threshold,
threshold_method=config.metrics.threshold.method,
manual_image_threshold=image_threshold,
manual_pixel_threshold=pixel_threshold,
)
callbacks.append(post_processing_callback)

Expand Down
49 changes: 31 additions & 18 deletions anomalib/utils/callbacks/post_processing_configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from pytorch_lightning.utilities.cli import CALLBACK_REGISTRY

from anomalib.models.components.base.anomaly_module import AnomalyModule
from anomalib.post_processing import NormalizationMethod, ThresholdMethod

logger = logging.getLogger(__name__)

Expand All @@ -23,29 +24,41 @@ class PostProcessingConfigurationCallback(Callback):
"""Post-Processing Configuration Callback.

Args:
normalization_method(Optional[str]): Normalization method. <None, min_max, cdf>
adaptive_threshold (bool): Flag indicating whether threshold should be adaptive.
default_image_threshold (Optional[float]): Default image threshold value.
default_pixel_threshold (Optional[float]): Default pixel threshold value.
normalization_method(NormalizationMethod): Normalization method. <none, min_max, cdf>
threshold_method (ThresholdMethod): Flag indicating whether threshold should be manual or adaptive.
manual_image_threshold (Optional[float]): Default manual image threshold value.
manual_pixel_threshold (Optional[float]): Default manual pixel threshold value.
"""

def __init__(
self,
normalization_method: str = "min_max",
adaptive_threshold: bool = True,
default_image_threshold: Optional[float] = None,
default_pixel_threshold: Optional[float] = None,
normalization_method: NormalizationMethod = NormalizationMethod.MIN_MAX,
threshold_method: ThresholdMethod = ThresholdMethod.ADAPTIVE,
manual_image_threshold: Optional[float] = None,
manual_pixel_threshold: Optional[float] = None,
) -> None:
super().__init__()
self.normalization_method = normalization_method

assert (
adaptive_threshold or default_image_threshold is not None and default_pixel_threshold is not None
), "Default thresholds must be specified when adaptive threshold is disabled."
if threshold_method == ThresholdMethod.ADAPTIVE and all(
i is not None for i in [manual_image_threshold, manual_pixel_threshold]
):
raise ValueError(
"When `threshold_method` is set to `adaptive`, `manual_image_threshold` and `manual_pixel_threshold` "
"must not be set."
)

self.adaptive_threshold = adaptive_threshold
self.default_image_threshold = default_image_threshold
self.default_pixel_threshold = default_pixel_threshold
if threshold_method == ThresholdMethod.MANUAL and all(
i is None for i in [manual_image_threshold, manual_pixel_threshold]
):
raise ValueError(
"When `threshold_method` is set to `manual`, `manual_image_threshold` and `manual_pixel_threshold` "
"must be set."
)

self.threshold_method = threshold_method
self.manual_image_threshold = manual_image_threshold
self.manual_pixel_threshold = manual_pixel_threshold

# pylint: disable=unused-argument
def setup(self, trainer: Trainer, pl_module: LightningModule, stage: Optional[str] = None) -> None:
Expand All @@ -57,7 +70,7 @@ def setup(self, trainer: Trainer, pl_module: LightningModule, stage: Optional[st
stage (Optional[str], optional): fit, validate, test or predict. Defaults to None.
"""
if isinstance(pl_module, AnomalyModule):
pl_module.adaptive_threshold = self.adaptive_threshold
if pl_module.adaptive_threshold is False:
pl_module.image_threshold.value = torch.tensor(self.default_image_threshold).cpu()
pl_module.pixel_threshold.value = torch.tensor(self.default_pixel_threshold).cpu()
pl_module.threshold_method = self.threshold_method
if pl_module.threshold_method == ThresholdMethod.MANUAL:
pl_module.image_threshold.value = torch.tensor(self.manual_image_threshold).cpu()
pl_module.pixel_threshold.value = torch.tensor(self.manual_pixel_threshold).cpu()
6 changes: 3 additions & 3 deletions anomalib/utils/cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,9 @@ def add_arguments_to_parser(self, parser: LightningArgumentParser) -> None:
parser.set_defaults(
{
"post_processing.normalization_method": "min_max",
"post_processing.adaptive_threshold": True,
"post_processing.default_image_threshold": None,
"post_processing.default_pixel_threshold": None,
"post_processing.threshold_method": "adaptive",
"post_processing.manual_image_threshold": None,
"post_processing.manual_pixel_threshold": None,
}
)

Expand Down
4 changes: 2 additions & 2 deletions anomalib/utils/metrics/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
import torchmetrics
from omegaconf import DictConfig, ListConfig

from .adaptive_threshold import AdaptiveThreshold
from .anomaly_score_distribution import AnomalyScoreDistribution
from .anomaly_score_threshold import AnomalyScoreThreshold
from .aupr import AUPR
from .aupro import AUPRO
from .auroc import AUROC
Expand All @@ -20,7 +20,7 @@
from .optimal_f1 import OptimalF1
from .pro import PRO

__all__ = ["AUROC", "AUPR", "AUPRO", "OptimalF1", "AdaptiveThreshold", "AnomalyScoreDistribution", "MinMax", "PRO"]
__all__ = ["AUROC", "AUPR", "AUPRO", "OptimalF1", "AnomalyScoreThreshold", "AnomalyScoreDistribution", "MinMax", "PRO"]


def get_metrics(config: Union[ListConfig, DictConfig]) -> Tuple[AnomalibMetricCollection, AnomalibMetricCollection]:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Implementation of Optimal F1 score based on TorchMetrics."""
"""Implementation of AnomalyScoreThreshold based on TorchMetrics."""

# Copyright (C) 2022 Intel Corporation
# SPDX-License-Identifier: Apache-2.0
Expand All @@ -7,11 +7,16 @@
from torchmetrics import PrecisionRecallCurve


class AdaptiveThreshold(PrecisionRecallCurve):
"""Optimal F1 Metric.
class AnomalyScoreThreshold(PrecisionRecallCurve):
"""Anomaly Score Threshold.

Compute the optimal F1 score at the adaptive threshold, based on the F1 metric of the true labels and the
predicted anomaly scores.
This class computes/stores the threshold that determines the anomalous label
given anomaly scores. If the threshold method is ``manual``, the class only
stores the manual threshold values.

If the threshold method is ``adaptive``, the class initially computes the
adaptive threshold to find the optimal f1_score and stores the computed
adaptive threshold value.
"""

def __init__(self, default_value: float = 0.5, **kwargs):
Expand Down
Loading