From 1defd7b85c8b3d5083a5128bf23ea8f7f7023ee8 Mon Sep 17 00:00:00 2001 From: Lorenzo Mammana Date: Mon, 8 Apr 2024 18:12:01 +0200 Subject: [PATCH 1/7] refactor: Refactor anomaly score computation for training --- docs/tutorials/examples/anomaly_detection.md | 12 +- quadra/callbacks/anomalib.py | 2 +- .../configs/callbacks/default_anomalib.yaml | 6 +- .../experiment/generic/mnist/anomaly/cfa.yaml | 2 +- .../generic/mnist/anomaly/cflow.yaml | 2 +- .../generic/mnist/anomaly/csflow.yaml | 2 +- .../generic/mnist/anomaly/draem.yaml | 2 +- .../generic/mnist/anomaly/fastflow.yaml | 2 +- .../generic/mnist/anomaly/padim.yaml | 2 +- .../generic/mnist/anomaly/patchcore.yaml | 2 +- .../experiment/generic/mvtec/anomaly/cfa.yaml | 2 +- .../generic/mvtec/anomaly/cflow.yaml | 2 +- .../generic/mvtec/anomaly/csflow.yaml | 2 +- .../generic/mvtec/anomaly/draem.yaml | 2 +- .../generic/mvtec/anomaly/efficient_ad.yaml | 2 +- .../generic/mvtec/anomaly/fastflow.yaml | 2 +- .../generic/mvtec/anomaly/padim.yaml | 2 +- .../generic/mvtec/anomaly/patchcore.yaml | 2 +- quadra/tasks/anomaly.py | 26 +++-- quadra/utils/anomaly.py | 104 ++++++++++++++++++ 20 files changed, 143 insertions(+), 37 deletions(-) create mode 100644 quadra/utils/anomaly.py diff --git a/docs/tutorials/examples/anomaly_detection.md b/docs/tutorials/examples/anomaly_detection.md index 79cde2c9..e0039c53 100644 --- a/docs/tutorials/examples/anomaly_detection.md +++ b/docs/tutorials/examples/anomaly_detection.md @@ -105,8 +105,8 @@ What can be useful to customize are the default callbacks: ```yaml callbacks: # Anomalib specific callbacks - min_max_normalization: - _target_: anomalib.utils.callbacks.min_max_normalization.MinMaxNormalizationCallback + threshold_normalization: + _target_: quadra.utils.anomaly.ThresholdNormalizationCallback threshold_type: image post_processing_configuration: _target_: anomalib.utils.callbacks.post_processing_configuration.PostProcessingConfigurationCallback @@ -122,7 +122,7 @@ callbacks: _target_: quadra.callbacks.anomalib.VisualizerCallback inputs_are_normalized: true output_path: anomaly_output - threshold_type: ${callbacks.min_max_normalization.threshold_type} + threshold_type: ${callbacks.threshold_normalization.threshold_type} disable: true plot_only_wrong: false plot_raw_outputs: false @@ -140,13 +140,13 @@ callbacks: By default lightning batch_size_finder callback is disabled. This callback will automatically try to infer the maximum batch size that can be used for training without running out of memory. We've experimented runtime errors with this callback on some machines due to a Pytorch/CUDNN incompatibility so be careful when using it. -The min_max_normalization callback is used to normalize the anomaly maps to the range [0, 1] such that the threshold will become 0.5. +The threshold_normalization callback is used to normalize the anomaly maps to the range [0, 1000] such that the threshold will become 100. The threshold_type can be either "image" or "pixel" and it indicates which threshold to use to normalize the pixel level threshold, if no masks are available for segmentation this should always be "image", otherwise the normalization will use the threshold computed without masks which would result in wrong segmentations. The post processing configuration allow to specify the method used to compute the threshold, methods and manual metrics are generally specified in the model configuration and should not be changed here. -The visualizer callback is used to produce a visualization of the results on the test data, when the min_max_normalization callback is used the input_are_normalized flag must be set to true and the threshold_type should match the one used for normalization. By default it is disabled as it may take a while to compute, to enable just set `disable: false`. +The visualizer callback is used to produce a visualization of the results on the test data, when the threshold_normalization callback is used the input_are_normalized flag must be set to true and the threshold_type should match the one used for normalization. By default it is disabled as it may take a while to compute, to enable just set `disable: false`. In the context where many images are supplied to our model, we may be more interested in restricting the output images that are generated to only the cases where the result is not correct. By default it is disabled, to enable just set `plot_only_wrong: true`. @@ -224,7 +224,7 @@ datamodule: good_number: 9 callbacks: - min_max_normalization: + threshold_normalization: threshold_type: "image" print_config: false diff --git a/quadra/callbacks/anomalib.py b/quadra/callbacks/anomalib.py index ce9197a5..70f3abe5 100644 --- a/quadra/callbacks/anomalib.py +++ b/quadra/callbacks/anomalib.py @@ -92,7 +92,7 @@ class VisualizerCallback(Callback): Args: task: either 'segmentation' or 'classification' output_path: location where the images will be saved. - inputs_are_normalized: whether the input images are normalized (i.e using MinMax callback). + inputs_are_normalized: whether the input images are normalized (like when using MinMax or Treshold callback). threshold_type: Either 'pixel' or 'image'. If 'pixel', the threshold is computed on the pixel-level. disable: whether to disable the callback. plot_only_wrong: whether to plot only the images that are not correctly predicted. diff --git a/quadra/configs/callbacks/default_anomalib.yaml b/quadra/configs/callbacks/default_anomalib.yaml index cce97e65..441ba193 100644 --- a/quadra/configs/callbacks/default_anomalib.yaml +++ b/quadra/configs/callbacks/default_anomalib.yaml @@ -1,6 +1,6 @@ # Anomalib specific callbacks -min_max_normalization: - _target_: anomalib.utils.callbacks.min_max_normalization.MinMaxNormalizationCallback +threshold_normalization: + _target_: quadra.utils.anomaly.ThresholdNormalizationCallback threshold_type: image post_processing_configuration: _target_: anomalib.utils.callbacks.post_processing_configuration.PostProcessingConfigurationCallback @@ -16,7 +16,7 @@ visualizer: _target_: quadra.callbacks.anomalib.VisualizerCallback inputs_are_normalized: true output_path: anomaly_output - threshold_type: ${callbacks.min_max_normalization.threshold_type} + threshold_type: ${callbacks.threshold_normalization.threshold_type} disable: true plot_only_wrong: false plot_raw_outputs: false diff --git a/quadra/configs/experiment/generic/mnist/anomaly/cfa.yaml b/quadra/configs/experiment/generic/mnist/anomaly/cfa.yaml index 382c7a65..e142e84b 100644 --- a/quadra/configs/experiment/generic/mnist/anomaly/cfa.yaml +++ b/quadra/configs/experiment/generic/mnist/anomaly/cfa.yaml @@ -11,7 +11,7 @@ datamodule: good_number: 9 callbacks: - min_max_normalization: + threshold_normalization: threshold_type: "image" early_stopping: patience: 2 diff --git a/quadra/configs/experiment/generic/mnist/anomaly/cflow.yaml b/quadra/configs/experiment/generic/mnist/anomaly/cflow.yaml index e515c6e2..34ea8d3e 100644 --- a/quadra/configs/experiment/generic/mnist/anomaly/cflow.yaml +++ b/quadra/configs/experiment/generic/mnist/anomaly/cflow.yaml @@ -10,7 +10,7 @@ datamodule: good_number: 9 callbacks: - min_max_normalization: + threshold_normalization: threshold_type: "image" early_stopping: patience: 2 diff --git a/quadra/configs/experiment/generic/mnist/anomaly/csflow.yaml b/quadra/configs/experiment/generic/mnist/anomaly/csflow.yaml index bed4769b..fabd23f8 100644 --- a/quadra/configs/experiment/generic/mnist/anomaly/csflow.yaml +++ b/quadra/configs/experiment/generic/mnist/anomaly/csflow.yaml @@ -10,7 +10,7 @@ datamodule: good_number: 9 callbacks: - min_max_normalization: + threshold_normalization: threshold_type: "image" early_stopping: patience: 3 diff --git a/quadra/configs/experiment/generic/mnist/anomaly/draem.yaml b/quadra/configs/experiment/generic/mnist/anomaly/draem.yaml index 73cdb487..1abb9aa6 100644 --- a/quadra/configs/experiment/generic/mnist/anomaly/draem.yaml +++ b/quadra/configs/experiment/generic/mnist/anomaly/draem.yaml @@ -10,7 +10,7 @@ datamodule: good_number: 9 callbacks: - min_max_normalization: + threshold_normalization: threshold_type: "image" early_stopping: patience: 20 diff --git a/quadra/configs/experiment/generic/mnist/anomaly/fastflow.yaml b/quadra/configs/experiment/generic/mnist/anomaly/fastflow.yaml index f5ad1cca..c8e61f0e 100644 --- a/quadra/configs/experiment/generic/mnist/anomaly/fastflow.yaml +++ b/quadra/configs/experiment/generic/mnist/anomaly/fastflow.yaml @@ -10,7 +10,7 @@ datamodule: good_number: 9 callbacks: - min_max_normalization: + threshold_normalization: threshold_type: "image" print_config: false diff --git a/quadra/configs/experiment/generic/mnist/anomaly/padim.yaml b/quadra/configs/experiment/generic/mnist/anomaly/padim.yaml index 95b57614..10a78288 100644 --- a/quadra/configs/experiment/generic/mnist/anomaly/padim.yaml +++ b/quadra/configs/experiment/generic/mnist/anomaly/padim.yaml @@ -15,7 +15,7 @@ datamodule: good_number: 9 callbacks: - min_max_normalization: + threshold_normalization: threshold_type: "image" print_config: false diff --git a/quadra/configs/experiment/generic/mnist/anomaly/patchcore.yaml b/quadra/configs/experiment/generic/mnist/anomaly/patchcore.yaml index c50593f7..7fe4ea0e 100644 --- a/quadra/configs/experiment/generic/mnist/anomaly/patchcore.yaml +++ b/quadra/configs/experiment/generic/mnist/anomaly/patchcore.yaml @@ -15,7 +15,7 @@ datamodule: good_number: 9 callbacks: - min_max_normalization: + threshold_normalization: threshold_type: "image" print_config: false diff --git a/quadra/configs/experiment/generic/mvtec/anomaly/cfa.yaml b/quadra/configs/experiment/generic/mvtec/anomaly/cfa.yaml index 2618c9fb..3dbf7b02 100644 --- a/quadra/configs/experiment/generic/mvtec/anomaly/cfa.yaml +++ b/quadra/configs/experiment/generic/mvtec/anomaly/cfa.yaml @@ -11,7 +11,7 @@ datamodule: category: bottle callbacks: - min_max_normalization: + threshold_normalization: threshold_type: "pixel" early_stopping: patience: 2 diff --git a/quadra/configs/experiment/generic/mvtec/anomaly/cflow.yaml b/quadra/configs/experiment/generic/mvtec/anomaly/cflow.yaml index f5b55f9a..5fd49980 100644 --- a/quadra/configs/experiment/generic/mvtec/anomaly/cflow.yaml +++ b/quadra/configs/experiment/generic/mvtec/anomaly/cflow.yaml @@ -10,7 +10,7 @@ datamodule: category: bottle callbacks: - min_max_normalization: + threshold_normalization: threshold_type: "pixel" early_stopping: patience: 2 diff --git a/quadra/configs/experiment/generic/mvtec/anomaly/csflow.yaml b/quadra/configs/experiment/generic/mvtec/anomaly/csflow.yaml index a76b471a..06a34825 100644 --- a/quadra/configs/experiment/generic/mvtec/anomaly/csflow.yaml +++ b/quadra/configs/experiment/generic/mvtec/anomaly/csflow.yaml @@ -10,7 +10,7 @@ datamodule: category: bottle callbacks: - min_max_normalization: + threshold_normalization: threshold_type: "pixel" early_stopping: patience: 3 diff --git a/quadra/configs/experiment/generic/mvtec/anomaly/draem.yaml b/quadra/configs/experiment/generic/mvtec/anomaly/draem.yaml index 539a1628..8eaac562 100644 --- a/quadra/configs/experiment/generic/mvtec/anomaly/draem.yaml +++ b/quadra/configs/experiment/generic/mvtec/anomaly/draem.yaml @@ -10,7 +10,7 @@ datamodule: category: bottle callbacks: - min_max_normalization: + threshold_normalization: threshold_type: "pixel" early_stopping: patience: 20 diff --git a/quadra/configs/experiment/generic/mvtec/anomaly/efficient_ad.yaml b/quadra/configs/experiment/generic/mvtec/anomaly/efficient_ad.yaml index 0abf6601..54e7fc30 100644 --- a/quadra/configs/experiment/generic/mvtec/anomaly/efficient_ad.yaml +++ b/quadra/configs/experiment/generic/mvtec/anomaly/efficient_ad.yaml @@ -15,7 +15,7 @@ datamodule: category: hazelnut callbacks: - min_max_normalization: + threshold_normalization: threshold_type: "pixel" print_config: false diff --git a/quadra/configs/experiment/generic/mvtec/anomaly/fastflow.yaml b/quadra/configs/experiment/generic/mvtec/anomaly/fastflow.yaml index aab68f55..cc1402ad 100644 --- a/quadra/configs/experiment/generic/mvtec/anomaly/fastflow.yaml +++ b/quadra/configs/experiment/generic/mvtec/anomaly/fastflow.yaml @@ -10,7 +10,7 @@ datamodule: category: bottle callbacks: - min_max_normalization: + threshold_normalization: threshold_type: "pixel" print_config: false diff --git a/quadra/configs/experiment/generic/mvtec/anomaly/padim.yaml b/quadra/configs/experiment/generic/mvtec/anomaly/padim.yaml index 93e8f082..4dd01495 100644 --- a/quadra/configs/experiment/generic/mvtec/anomaly/padim.yaml +++ b/quadra/configs/experiment/generic/mvtec/anomaly/padim.yaml @@ -15,7 +15,7 @@ datamodule: category: bottle callbacks: - min_max_normalization: + threshold_normalization: threshold_type: "pixel" print_config: false diff --git a/quadra/configs/experiment/generic/mvtec/anomaly/patchcore.yaml b/quadra/configs/experiment/generic/mvtec/anomaly/patchcore.yaml index 8b0607ac..abd9ef09 100644 --- a/quadra/configs/experiment/generic/mvtec/anomaly/patchcore.yaml +++ b/quadra/configs/experiment/generic/mvtec/anomaly/patchcore.yaml @@ -15,7 +15,7 @@ datamodule: category: bottle callbacks: - min_max_normalization: + threshold_normalization: threshold_type: "pixel" print_config: false diff --git a/quadra/tasks/anomaly.py b/quadra/tasks/anomaly.py index 9260318e..f7bbd618 100644 --- a/quadra/tasks/anomaly.py +++ b/quadra/tasks/anomaly.py @@ -24,6 +24,7 @@ from quadra.modules.base import ModelSignatureWrapper from quadra.tasks.base import Evaluation, LightningTask from quadra.utils import utils +from quadra.utils.anomaly import ThresholdNormalizationCallback from quadra.utils.classification import get_results from quadra.utils.evaluation import automatic_datamodule_batch_size from quadra.utils.export import export_model @@ -202,6 +203,8 @@ def _generate_report(self) -> None: class_to_idx.pop("false_defect") anomaly_scores = all_output_flatten["pred_scores"] + if isinstance(anomaly_scores, torch.Tensor): + anomaly_scores = anomaly_scores.cpu().tolist() # Zip the lists together to create rows for the CSV file rows = zip(image_paths, pred_labels, gt_labels, anomaly_scores) # Specify the CSV file name @@ -223,17 +226,16 @@ def _generate_report(self) -> None: defect_scores = anomaly_scores[np.where(all_output_flatten["label"] == 1)] # Lightning has a callback attribute but is not inside the __init__ so mypy complains - threshold = ( - torch.tensor(0.5) - if any( - isinstance(x, MinMaxNormalizationCallback) for x in self.trainer.callbacks # type: ignore[attr-defined] - ) - else self.module.image_metrics.F1Score.threshold # type: ignore[union-attr] - ) + if any(isinstance(x, MinMaxNormalizationCallback) for x in self.trainer.callbacks): + threshold = torch.tensor(0.5) + elif any(isinstance(x, ThresholdNormalizationCallback) for x in self.trainer.callbacks): + threshold = torch.tensor(100.0) + else: + threshold = self.module.image_metrics.F1Score.threshold - plot_cumulative_histogram( - good_scores, defect_scores, threshold.item(), self.report_path # type: ignore[arg-type, operator] - ) + # The output of the prediction is a normalized score so the cumulative histogram is displayed with the + # normalized scores + plot_cumulative_histogram(good_scores, defect_scores, threshold.item(), self.report_path) _, pd_cm, _ = get_results(np.array(gt_labels), np.array(pred_labels), idx_to_class) np_cm = np.array(pd_cm) @@ -462,7 +464,7 @@ def generate_report(self) -> None: if hasattr(self.datamodule, "valid_area_mask") and self.datamodule.valid_area_mask is not None: mask_area = cv2.imread(self.datamodule.valid_area_mask, 0) - mask_area = (mask_area > 0).astype(np.uint8) + mask_area = (mask_area > 0).astype(np.uint8) # type: ignore[operator] if hasattr(self.datamodule, "crop_area") and self.datamodule.crop_area is not None: crop_area = self.datamodule.crop_area @@ -478,7 +480,7 @@ def generate_report(self) -> None: ): img = cv2.imread(img_path, 0) if mask_area is not None: - img = img * mask_area + img = img * mask_area # type: ignore[operator] if crop_area is not None: img = img[crop_area[1] : crop_area[3], crop_area[0] : crop_area[2]] diff --git a/quadra/utils/anomaly.py b/quadra/utils/anomaly.py new file mode 100644 index 00000000..48dc1624 --- /dev/null +++ b/quadra/utils/anomaly.py @@ -0,0 +1,104 @@ +"""Anomaly Score Normalization Callback that uses min-max normalization.""" + +# Copyright (C) 2022 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from typing import Any, TypeAlias + +# MyPy wants TypeAlias, but pylint has problems dealing with it +import numpy as np # pylint: disable=unused-import +import pytorch_lightning as pl +import torch # pylint: disable=unused-import +from anomalib.models.components import AnomalyModule +from pytorch_lightning import Callback +from pytorch_lightning.utilities.types import STEP_OUTPUT + +# https://github.com/python/cpython/issues/90015#issuecomment-1172996118 +MapOrValue: TypeAlias = "float | torch.Tensor | np.ndarray" + + +def normalize_anomaly_score(raw_score: MapOrValue, threshold: float) -> MapOrValue: + """Normalize anomaly score value or map based on threshold. + + Args: + raw_score: Raw anomaly score valure or map + threshold: Threshold for anomaly detection + + Returns: + Normalized anomaly score value or map clipped between 0 and 1000 + """ + if threshold > 0: + normalized_score = (raw_score / threshold) * 100.0 + elif threshold == 0: + # TODO: Is this the best way to handle this case? + normalized_score = (raw_score + 1) * 100.0 + else: + normalized_score = 200.0 - ((raw_score / threshold) * 100.0) + + if isinstance(normalized_score, torch.Tensor): + return torch.clamp(normalized_score, 0.0, 1000.0) + + return np.clip(normalized_score, 0.0, 1000.0) + + +class ThresholdNormalizationCallback(Callback): + """Callback that normalizes the image-level and pixel-level anomaly scores dividing by the threshold value. + + Args: + threshold_type: Threshold used to normalize pixel level anomaly scores, either image or pixel (default) + """ + + def __init__(self, threshold_type: str = "pixel"): + super().__init__() + self.threshold_type = threshold_type + + def on_test_start(self, trainer: pl.Trainer, pl_module: AnomalyModule) -> None: + """Called when the test begins.""" + del trainer # `trainer` variable is not used. + + for metric in (pl_module.image_metrics, pl_module.pixel_metrics): + if metric is not None: + metric.set_threshold(100.0) + + def on_test_batch_end( + self, + trainer: pl.Trainer, + pl_module: AnomalyModule, + outputs: STEP_OUTPUT | None, + batch: Any, + batch_idx: int, + dataloader_idx: int = 0, + ) -> None: + """Called when the test batch ends, normalizes the predicted scores and anomaly maps.""" + del trainer, batch, batch_idx, dataloader_idx # These variables are not used. + + self._normalize_batch(outputs, pl_module) + + def on_predict_batch_end( + self, + trainer: pl.Trainer, + pl_module: AnomalyModule, + outputs: Any, + batch: Any, + batch_idx: int, + dataloader_idx: int = 0, + ) -> None: + """Called when the predict batch ends, normalizes the predicted scores and anomaly maps.""" + del trainer, batch, batch_idx, dataloader_idx # These variables are not used. + + self._normalize_batch(outputs, pl_module) + + def _normalize_batch(self, outputs, pl_module): + """Normalize a batch of predictions.""" + image_threshold = pl_module.image_threshold.value.cpu() + pixel_threshold = pl_module.pixel_threshold.value.cpu() + outputs["pred_scores"] = normalize_anomaly_score(outputs["pred_scores"], image_threshold) + + threshold = pixel_threshold if self.threshold_type == "pixel" else image_threshold + if "anomaly_maps" in outputs.keys(): + outputs["anomaly_maps"] = normalize_anomaly_score(outputs["anomaly_maps"], threshold) + + if "box_scores" in outputs: + outputs["box_scores"] = [normalize_anomaly_score(scores, threshold) for scores in outputs["box_scores"]] From 1dfdaed643f0e5b74e1513f33fd1593a8bbd51fb Mon Sep 17 00:00:00 2001 From: Lorenzo Mammana Date: Wed, 10 Apr 2024 11:13:57 +0200 Subject: [PATCH 2/7] refactor: Refactor inference code to use the new normalization, fix incorrect output save when images have the same name --- quadra/tasks/anomaly.py | 69 +++++++++++++++++++++++++---------------- 1 file changed, 43 insertions(+), 26 deletions(-) diff --git a/quadra/tasks/anomaly.py b/quadra/tasks/anomaly.py index f7bbd618..3f8a0889 100644 --- a/quadra/tasks/anomaly.py +++ b/quadra/tasks/anomaly.py @@ -24,7 +24,7 @@ from quadra.modules.base import ModelSignatureWrapper from quadra.tasks.base import Evaluation, LightningTask from quadra.utils import utils -from quadra.utils.anomaly import ThresholdNormalizationCallback +from quadra.utils.anomaly import MapOrValue, ThresholdNormalizationCallback, normalize_anomaly_score from quadra.utils.classification import get_results from quadra.utils.evaluation import automatic_datamodule_batch_size from quadra.utils.export import export_model @@ -204,9 +204,12 @@ def _generate_report(self) -> None: anomaly_scores = all_output_flatten["pred_scores"] if isinstance(anomaly_scores, torch.Tensor): - anomaly_scores = anomaly_scores.cpu().tolist() + exportable_anomaly_scores = anomaly_scores.cpu().numpy() + else: + exportable_anomaly_scores = anomaly_scores + # Zip the lists together to create rows for the CSV file - rows = zip(image_paths, pred_labels, gt_labels, anomaly_scores) + rows = zip(image_paths, pred_labels, gt_labels, exportable_anomaly_scores) # Specify the CSV file name csv_file = "test_predictions.csv" # Write the data to the CSV file @@ -220,22 +223,28 @@ def _generate_report(self) -> None: log.info("CSV file %s has been created.", csv_file) if not isinstance(anomaly_scores, torch.Tensor): - raise ValueError("Anoaly scores must be a tensor") + raise ValueError("Anomaly scores must be a tensor") good_scores = anomaly_scores[np.where(all_output_flatten["label"] == 0)] defect_scores = anomaly_scores[np.where(all_output_flatten["label"] == 1)] # Lightning has a callback attribute but is not inside the __init__ so mypy complains - if any(isinstance(x, MinMaxNormalizationCallback) for x in self.trainer.callbacks): + if any( + isinstance(x, MinMaxNormalizationCallback) for x in self.trainer.callbacks # type: ignore[attr-defined] + ): threshold = torch.tensor(0.5) - elif any(isinstance(x, ThresholdNormalizationCallback) for x in self.trainer.callbacks): + elif any( + isinstance(x, ThresholdNormalizationCallback) for x in self.trainer.callbacks # type: ignore[attr-defined] + ): threshold = torch.tensor(100.0) else: threshold = self.module.image_metrics.F1Score.threshold # The output of the prediction is a normalized score so the cumulative histogram is displayed with the # normalized scores - plot_cumulative_histogram(good_scores, defect_scores, threshold.item(), self.report_path) + plot_cumulative_histogram( + good_scores.cpu().numpy(), defect_scores.cpu().numpy(), threshold.item(), self.report_path + ) _, pd_cm, _ = get_results(np.array(gt_labels), np.array(pred_labels), idx_to_class) np_cm = np.array(pd_cm) @@ -486,13 +495,10 @@ def generate_report(self) -> None: img = img[crop_area[1] : crop_area[3], crop_area[0] : crop_area[2]] output_mask = (anomaly_map >= self.metadata["threshold"]).cpu().numpy().squeeze().astype(np.uint8) + output_mask_label = os.path.basename(os.path.dirname(img_path)) output_mask_name = os.path.splitext(os.path.basename(img_path))[0] + ".png" pred_label = int(anomaly_score >= self.metadata["threshold"]) - anomaly_probability = np.clip( - ((anomaly_score.item() - self.metadata["threshold"]) / (max_anomaly_score - min_anomaly_score)) + 0.5, - 0, - 1, - ) + anomaly_confidence = normalize_anomaly_score(anomaly_score.item(), threshold=self.metadata["threshold"]) json_output["observations"].append( { @@ -500,11 +506,11 @@ def generate_report(self) -> None: "file_name": os.path.basename(img_path), "expectation": gt_label if gt_label != -1 else "", "prediction": pred_label, - "prediction_mask": output_mask_name, - "prediction_heatmap": output_mask_name, + "prediction_mask": os.path.join("predictions", output_mask_label, output_mask_name), + "prediction_heatmap": os.path.join("heatmaps", output_mask_label, output_mask_name), "is_correct": pred_label == gt_label if gt_label != -1 else True, "anomaly_score": f"{anomaly_score.item():.3f}", - "anomaly_probability": f"{anomaly_probability:.3f}", + "anomaly_confidence": f"{anomaly_confidence:.3f}", } ) @@ -519,19 +525,30 @@ def generate_report(self) -> None: output_mask = output_mask * 255 output_mask = cv2.resize(output_mask, (img.shape[1], img.shape[0])) - cv2.imwrite(os.path.join(self.report_path, "predictions", output_mask_name), output_mask) - - # Normalize the heatmaps based on the current min and max anomaly score, otherwise even on good images the - # anomaly map looks like there are defects while it's not true - output_heatmap = ( - (anomaly_map.cpu().numpy().squeeze() - self.metadata["threshold"]) - / (max_anomaly_score - min_anomaly_score) - ) + 0.5 - output_heatmap = np.clip(output_heatmap, 0, 1) - output_heatmap = anomaly_map_to_color_map(output_heatmap, normalize=False) + output_prediction_folder = os.path.join(self.report_path, "predictions", output_mask_label) + os.makedirs(output_prediction_folder, exist_ok=True) + cv2.imwrite(os.path.join(output_prediction_folder, output_mask_name), output_mask) + + # Normalize the map and rescale it to 0-1 range + # In this case we are saying that the anomaly map is in the range [50, 150] + # This allow to have a stronger color for the anomalies and a lighter one for really normal regions + # It's also independent from the max or min anomaly score! + normalized_map: MapOrValue = (normalize_anomaly_score(anomaly_map, self.metadata["threshold"]) - 50.0) / ( + 150.0 - 50.0 + ) + + if isinstance(normalized_map, torch.Tensor): + normalized_map = normalized_map.cpu().numpy().squeeze() + + normalized_map = np.clip(normalized_map, 0, 1) + output_heatmap = anomaly_map_to_color_map(normalized_map, normalize=False) output_heatmap = cv2.resize(output_heatmap, (img.shape[1], img.shape[0])) + + output_heatmap_folder = os.path.join(self.report_path, "heatmaps", output_mask_label) + os.makedirs(output_heatmap_folder, exist_ok=True) + cv2.imwrite( - os.path.join(self.report_path, "heatmaps", output_mask_name), + os.path.join(output_heatmap_folder, output_mask_name), cv2.cvtColor(output_heatmap, cv2.COLOR_RGB2BGR), ) From ba954cbbd43cf06d245115486612d4c33f908e06 Mon Sep 17 00:00:00 2001 From: Lorenzo Mammana Date: Wed, 10 Apr 2024 11:26:21 +0200 Subject: [PATCH 3/7] build: Upgrade version, update documentation --- BREAKING_CHANGES.md | 7 +++++++ CHANGELOG.md | 11 +++++++++++ pyproject.toml | 2 +- quadra/__init__.py | 2 +- 4 files changed, 20 insertions(+), 2 deletions(-) diff --git a/BREAKING_CHANGES.md b/BREAKING_CHANGES.md index c31cbd8e..58d63283 100644 --- a/BREAKING_CHANGES.md +++ b/BREAKING_CHANGES.md @@ -1,6 +1,13 @@ # Breaking Changes All the breaking changes will be documented in this file. +### [2.1.0] + +#### Changed + +- Change the way anomaly scores are normalized by default, instead of using a [0-1] range with a 0.5 threshold, the scores are now normalized to a [0-1000] range with a threshold of 100, the new score represents the distance from the selected threshold, for example, a score of 200 means that the anomaly score is 100% of the threshold above the threshold itself, a score of 50 means that the anomaly score is 50% of the threshold below. This change is intended to make the scores more interpretable and easier to understand, also it makes the score independent from the min and max +scores in the dataset. + ### [2.0.0] #### Changed diff --git a/CHANGELOG.md b/CHANGELOG.md index b227507f..d0ecc820 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,17 @@ # Changelog All notable changes to this project will be documented in this file. +### [2.1.0] + +#### Updated + +- Change the way anomaly scores are normalized by default, instead of using a [0-1] range with a 0.5 threshold, the scores are now normalized to a [0-1000] range with a threshold of 100, the new score represents the distance from the selected threshold, for example, a score of 200 means that the anomaly score is 100% of the threshold above the threshold itself, a score of 50 means that the anomaly score is 50% of the threshold below. + +#### Fixed + +- Fix the output heatmaps and preditions of anomaly inference tasks not being saved properly when images belonged to +different classes but had the same name. + ### [2.0.4] #### Fixed diff --git a/pyproject.toml b/pyproject.toml index be02f35d..dc29a5da 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "quadra" -version = "2.0.4" +version = "2.1.0" description = "Deep Learning experiment orchestration library" authors = [ "Federico Belotti ", diff --git a/quadra/__init__.py b/quadra/__init__.py index 6ef6310e..46fda9e5 100644 --- a/quadra/__init__.py +++ b/quadra/__init__.py @@ -1,4 +1,4 @@ -__version__ = "2.0.4" +__version__ = "2.1.0" def get_version(): From 4edaf3e000aaa0cc6377b42148eb2a9735fdbf8a Mon Sep 17 00:00:00 2001 From: Lorenzo Mammana Date: Wed, 10 Apr 2024 12:27:18 +0200 Subject: [PATCH 4/7] build: Add support for newer typings for python 3.9 --- poetry.lock | 21 ++++++++++----------- pyproject.toml | 1 + 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/poetry.lock b/poetry.lock index 602d4ac1..8ffdb368 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2952,7 +2952,7 @@ files = [ cssselect = ["cssselect (>=0.7)"] html5 = ["html5lib"] htmlsoup = ["BeautifulSoup4"] -source = ["Cython (>=3.0.7)"] +source = ["Cython (>=3.0.8)"] [[package]] name = "mako" @@ -5412,7 +5412,7 @@ files = [ [[package]] name = "pyyaml-env-tag" version = "0.1" -description = "A custom YAML tag for referencing environment variables in YAML files. " +description = "A custom YAML tag for referencing environment variables in YAML files." optional = false python-versions = ">=3.6" files = [ @@ -6048,7 +6048,6 @@ description = "Scikit-multilearn is a BSD-licensed library for multi-label class optional = false python-versions = "*" files = [ - {file = "scikit-multilearn-0.2.0.linux-x86_64.tar.gz", hash = "sha256:3179fed29b1492f6a69600696c23045b9f494d2b89d1796a8bdc43ccbb33712b"}, {file = "scikit_multilearn-0.2.0-py2-none-any.whl", hash = "sha256:0a389600a6797db6567f2f6ca1d0dca30bebfaaa73f75de62d7ae40f8f03d4fb"}, {file = "scikit_multilearn-0.2.0-py3-none-any.whl", hash = "sha256:068c652f22704a084ca252d05d21a655e7c9b248d0a4543847b74de5fca2b3f0"}, ] @@ -6192,7 +6191,7 @@ huey = ["huey (>=2)"] loguru = ["loguru (>=0.5)"] opentelemetry = ["opentelemetry-distro (>=0.35b0)"] opentelemetry-experimental = ["opentelemetry-distro (>=0.40b0,<1.0)", "opentelemetry-instrumentation-aiohttp-client (>=0.40b0,<1.0)", "opentelemetry-instrumentation-django (>=0.40b0,<1.0)", "opentelemetry-instrumentation-fastapi (>=0.40b0,<1.0)", "opentelemetry-instrumentation-flask (>=0.40b0,<1.0)", "opentelemetry-instrumentation-requests (>=0.40b0,<1.0)", "opentelemetry-instrumentation-sqlite3 (>=0.40b0,<1.0)", "opentelemetry-instrumentation-urllib (>=0.40b0,<1.0)"] -pure-eval = ["asttokens", "executing", "pure_eval"] +pure-eval = ["asttokens", "executing", "pure-eval"] pymongo = ["pymongo (>=3.1)"] pyspark = ["pyspark (>=2.4.4)"] quart = ["blinker (>=1.1)", "quart (>=0.16.1)"] @@ -6496,7 +6495,7 @@ typing-extensions = ">=4.6.0" [package.extras] aiomysql = ["aiomysql (>=0.2.0)", "greenlet (!=0.4.17)"] aioodbc = ["aioodbc", "greenlet (!=0.4.17)"] -aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing_extensions (!=3.10.0.1)"] +aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing-extensions (!=3.10.0.1)"] asyncio = ["greenlet (!=0.4.17)"] asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"] mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5)"] @@ -6506,7 +6505,7 @@ mssql-pyodbc = ["pyodbc"] mypy = ["mypy (>=0.910)"] mysql = ["mysqlclient (>=1.4.0)"] mysql-connector = ["mysql-connector-python"] -oracle = ["cx_oracle (>=8)"] +oracle = ["cx-oracle (>=8)"] oracle-oracledb = ["oracledb (>=1.0.1)"] postgresql = ["psycopg2 (>=2.7)"] postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] @@ -6516,7 +6515,7 @@ postgresql-psycopg2binary = ["psycopg2-binary"] postgresql-psycopg2cffi = ["psycopg2cffi"] postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"] pymysql = ["pymysql"] -sqlcipher = ["sqlcipher3_binary"] +sqlcipher = ["sqlcipher3-binary"] [[package]] name = "sqlparse" @@ -7086,13 +7085,13 @@ files = [ [[package]] name = "typing-extensions" -version = "4.9.0" +version = "4.11.0" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"}, - {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"}, + {file = "typing_extensions-4.11.0-py3-none-any.whl", hash = "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a"}, + {file = "typing_extensions-4.11.0.tar.gz", hash = "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0"}, ] [[package]] @@ -7830,4 +7829,4 @@ onnx = ["onnx", "onnxruntime_gpu", "onnxsim"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<3.11" -content-hash = "71b257f752974e3c002c0a4027a9faf5c5a99ae2eac9704e668393b6c2c53299" +content-hash = "fb91a2b045cb6f2b02e6d9f9814ad2d3a287ec60af595ec8f1467ba0e3503359" diff --git a/pyproject.toml b/pyproject.toml index dc29a5da..bfdb31b3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -82,6 +82,7 @@ segmentation_models_pytorch = { git = "https://github.com/qubvel/segmentation_mo anomalib = { git = "https://github.com/orobix/anomalib.git", tag = "v0.7.0+obx.1.3.0" } xxhash = "~3.2" torchinfo = "~1.8" +typing_extensions = { version = "4.11.0", optional = true, python = "<3.10" } # ONNX dependencies onnx = { version = "1.15.0", optional = true } From b7f9f4b2b7fa2580d5e59784327d7ed3cb9d56da Mon Sep 17 00:00:00 2001 From: Lorenzo Mammana Date: Wed, 10 Apr 2024 12:29:00 +0200 Subject: [PATCH 5/7] build: Add support for newer typings for python 3.9 --- poetry.lock | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/poetry.lock b/poetry.lock index 8ffdb368..8f1fb06d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -7829,4 +7829,4 @@ onnx = ["onnx", "onnxruntime_gpu", "onnxsim"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<3.11" -content-hash = "fb91a2b045cb6f2b02e6d9f9814ad2d3a287ec60af595ec8f1467ba0e3503359" +content-hash = "573f1a7c2d46ae89ff81f9658d45a306a63995006b867151ccff5c360c958775" diff --git a/pyproject.toml b/pyproject.toml index bfdb31b3..aca6385f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -82,7 +82,7 @@ segmentation_models_pytorch = { git = "https://github.com/qubvel/segmentation_mo anomalib = { git = "https://github.com/orobix/anomalib.git", tag = "v0.7.0+obx.1.3.0" } xxhash = "~3.2" torchinfo = "~1.8" -typing_extensions = { version = "4.11.0", optional = true, python = "<3.10" } +typing_extensions = { version = "4.11.0", python = "<3.10" } # ONNX dependencies onnx = { version = "1.15.0", optional = true } From 20ac66866eef7b5892fd51c289772cdfe948cf8c Mon Sep 17 00:00:00 2001 From: Lorenzo Mammana Date: Wed, 10 Apr 2024 13:00:30 +0200 Subject: [PATCH 6/7] fix: Fix type alias not being imported correctly with python 3.9 --- quadra/utils/anomaly.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/quadra/utils/anomaly.py b/quadra/utils/anomaly.py index 48dc1624..779b3924 100644 --- a/quadra/utils/anomaly.py +++ b/quadra/utils/anomaly.py @@ -5,7 +5,13 @@ from __future__ import annotations -from typing import Any, TypeAlias +try: + from typing import Any, TypeAlias +except ImportError: + from typing import Any + + from typing_extensions import TypeAlias + # MyPy wants TypeAlias, but pylint has problems dealing with it import numpy as np # pylint: disable=unused-import From f51a8c300901cdcfb8fefdfe789240219b8d8328 Mon Sep 17 00:00:00 2001 From: Lorenzo Mammana Date: Wed, 10 Apr 2024 15:48:12 +0200 Subject: [PATCH 7/7] refactor: Refactor default callback name --- CHANGELOG.md | 1 + docs/tutorials/examples/anomaly_detection.md | 10 +++++----- quadra/configs/callbacks/default_anomalib.yaml | 4 ++-- .../configs/experiment/generic/mnist/anomaly/cfa.yaml | 2 +- .../experiment/generic/mnist/anomaly/cflow.yaml | 2 +- .../experiment/generic/mnist/anomaly/csflow.yaml | 2 +- .../experiment/generic/mnist/anomaly/draem.yaml | 2 +- .../experiment/generic/mnist/anomaly/fastflow.yaml | 2 +- .../experiment/generic/mnist/anomaly/padim.yaml | 2 +- .../experiment/generic/mnist/anomaly/patchcore.yaml | 2 +- .../configs/experiment/generic/mvtec/anomaly/cfa.yaml | 2 +- .../experiment/generic/mvtec/anomaly/cflow.yaml | 2 +- .../experiment/generic/mvtec/anomaly/csflow.yaml | 2 +- .../experiment/generic/mvtec/anomaly/draem.yaml | 2 +- .../experiment/generic/mvtec/anomaly/efficient_ad.yaml | 2 +- .../experiment/generic/mvtec/anomaly/fastflow.yaml | 2 +- .../experiment/generic/mvtec/anomaly/padim.yaml | 2 +- .../experiment/generic/mvtec/anomaly/patchcore.yaml | 2 +- 18 files changed, 23 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d0ecc820..4fe02f62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ All notable changes to this project will be documented in this file. #### Updated - Change the way anomaly scores are normalized by default, instead of using a [0-1] range with a 0.5 threshold, the scores are now normalized to a [0-1000] range with a threshold of 100, the new score represents the distance from the selected threshold, for example, a score of 200 means that the anomaly score is 100% of the threshold above the threshold itself, a score of 50 means that the anomaly score is 50% of the threshold below. +- Change the default normalization config name for anomaly from `min_max_normalization` to `score_normalization`. #### Fixed diff --git a/docs/tutorials/examples/anomaly_detection.md b/docs/tutorials/examples/anomaly_detection.md index e0039c53..aa4f705d 100644 --- a/docs/tutorials/examples/anomaly_detection.md +++ b/docs/tutorials/examples/anomaly_detection.md @@ -105,7 +105,7 @@ What can be useful to customize are the default callbacks: ```yaml callbacks: # Anomalib specific callbacks - threshold_normalization: + score_normalization: _target_: quadra.utils.anomaly.ThresholdNormalizationCallback threshold_type: image post_processing_configuration: @@ -122,7 +122,7 @@ callbacks: _target_: quadra.callbacks.anomalib.VisualizerCallback inputs_are_normalized: true output_path: anomaly_output - threshold_type: ${callbacks.threshold_normalization.threshold_type} + threshold_type: ${callbacks.score_normalization.threshold_type} disable: true plot_only_wrong: false plot_raw_outputs: false @@ -140,13 +140,13 @@ callbacks: By default lightning batch_size_finder callback is disabled. This callback will automatically try to infer the maximum batch size that can be used for training without running out of memory. We've experimented runtime errors with this callback on some machines due to a Pytorch/CUDNN incompatibility so be careful when using it. -The threshold_normalization callback is used to normalize the anomaly maps to the range [0, 1000] such that the threshold will become 100. +The score_normalization callback is used to normalize the anomaly maps to the range [0, 1000] such that the threshold will become 100. The threshold_type can be either "image" or "pixel" and it indicates which threshold to use to normalize the pixel level threshold, if no masks are available for segmentation this should always be "image", otherwise the normalization will use the threshold computed without masks which would result in wrong segmentations. The post processing configuration allow to specify the method used to compute the threshold, methods and manual metrics are generally specified in the model configuration and should not be changed here. -The visualizer callback is used to produce a visualization of the results on the test data, when the threshold_normalization callback is used the input_are_normalized flag must be set to true and the threshold_type should match the one used for normalization. By default it is disabled as it may take a while to compute, to enable just set `disable: false`. +The visualizer callback is used to produce a visualization of the results on the test data, when the score_normalization callback is used the input_are_normalized flag must be set to true and the threshold_type should match the one used for normalization. By default it is disabled as it may take a while to compute, to enable just set `disable: false`. In the context where many images are supplied to our model, we may be more interested in restricting the output images that are generated to only the cases where the result is not correct. By default it is disabled, to enable just set `plot_only_wrong: true`. @@ -224,7 +224,7 @@ datamodule: good_number: 9 callbacks: - threshold_normalization: + score_normalization: threshold_type: "image" print_config: false diff --git a/quadra/configs/callbacks/default_anomalib.yaml b/quadra/configs/callbacks/default_anomalib.yaml index 441ba193..44915244 100644 --- a/quadra/configs/callbacks/default_anomalib.yaml +++ b/quadra/configs/callbacks/default_anomalib.yaml @@ -1,5 +1,5 @@ # Anomalib specific callbacks -threshold_normalization: +score_normalization: _target_: quadra.utils.anomaly.ThresholdNormalizationCallback threshold_type: image post_processing_configuration: @@ -16,7 +16,7 @@ visualizer: _target_: quadra.callbacks.anomalib.VisualizerCallback inputs_are_normalized: true output_path: anomaly_output - threshold_type: ${callbacks.threshold_normalization.threshold_type} + threshold_type: ${callbacks.score_normalization.threshold_type} disable: true plot_only_wrong: false plot_raw_outputs: false diff --git a/quadra/configs/experiment/generic/mnist/anomaly/cfa.yaml b/quadra/configs/experiment/generic/mnist/anomaly/cfa.yaml index e142e84b..8b35a858 100644 --- a/quadra/configs/experiment/generic/mnist/anomaly/cfa.yaml +++ b/quadra/configs/experiment/generic/mnist/anomaly/cfa.yaml @@ -11,7 +11,7 @@ datamodule: good_number: 9 callbacks: - threshold_normalization: + score_normalization: threshold_type: "image" early_stopping: patience: 2 diff --git a/quadra/configs/experiment/generic/mnist/anomaly/cflow.yaml b/quadra/configs/experiment/generic/mnist/anomaly/cflow.yaml index 34ea8d3e..5c7d2d25 100644 --- a/quadra/configs/experiment/generic/mnist/anomaly/cflow.yaml +++ b/quadra/configs/experiment/generic/mnist/anomaly/cflow.yaml @@ -10,7 +10,7 @@ datamodule: good_number: 9 callbacks: - threshold_normalization: + score_normalization: threshold_type: "image" early_stopping: patience: 2 diff --git a/quadra/configs/experiment/generic/mnist/anomaly/csflow.yaml b/quadra/configs/experiment/generic/mnist/anomaly/csflow.yaml index fabd23f8..56dac73a 100644 --- a/quadra/configs/experiment/generic/mnist/anomaly/csflow.yaml +++ b/quadra/configs/experiment/generic/mnist/anomaly/csflow.yaml @@ -10,7 +10,7 @@ datamodule: good_number: 9 callbacks: - threshold_normalization: + score_normalization: threshold_type: "image" early_stopping: patience: 3 diff --git a/quadra/configs/experiment/generic/mnist/anomaly/draem.yaml b/quadra/configs/experiment/generic/mnist/anomaly/draem.yaml index 1abb9aa6..346f646f 100644 --- a/quadra/configs/experiment/generic/mnist/anomaly/draem.yaml +++ b/quadra/configs/experiment/generic/mnist/anomaly/draem.yaml @@ -10,7 +10,7 @@ datamodule: good_number: 9 callbacks: - threshold_normalization: + score_normalization: threshold_type: "image" early_stopping: patience: 20 diff --git a/quadra/configs/experiment/generic/mnist/anomaly/fastflow.yaml b/quadra/configs/experiment/generic/mnist/anomaly/fastflow.yaml index c8e61f0e..260f188d 100644 --- a/quadra/configs/experiment/generic/mnist/anomaly/fastflow.yaml +++ b/quadra/configs/experiment/generic/mnist/anomaly/fastflow.yaml @@ -10,7 +10,7 @@ datamodule: good_number: 9 callbacks: - threshold_normalization: + score_normalization: threshold_type: "image" print_config: false diff --git a/quadra/configs/experiment/generic/mnist/anomaly/padim.yaml b/quadra/configs/experiment/generic/mnist/anomaly/padim.yaml index 10a78288..d794e24d 100644 --- a/quadra/configs/experiment/generic/mnist/anomaly/padim.yaml +++ b/quadra/configs/experiment/generic/mnist/anomaly/padim.yaml @@ -15,7 +15,7 @@ datamodule: good_number: 9 callbacks: - threshold_normalization: + score_normalization: threshold_type: "image" print_config: false diff --git a/quadra/configs/experiment/generic/mnist/anomaly/patchcore.yaml b/quadra/configs/experiment/generic/mnist/anomaly/patchcore.yaml index 7fe4ea0e..1485000d 100644 --- a/quadra/configs/experiment/generic/mnist/anomaly/patchcore.yaml +++ b/quadra/configs/experiment/generic/mnist/anomaly/patchcore.yaml @@ -15,7 +15,7 @@ datamodule: good_number: 9 callbacks: - threshold_normalization: + score_normalization: threshold_type: "image" print_config: false diff --git a/quadra/configs/experiment/generic/mvtec/anomaly/cfa.yaml b/quadra/configs/experiment/generic/mvtec/anomaly/cfa.yaml index 3dbf7b02..a7827925 100644 --- a/quadra/configs/experiment/generic/mvtec/anomaly/cfa.yaml +++ b/quadra/configs/experiment/generic/mvtec/anomaly/cfa.yaml @@ -11,7 +11,7 @@ datamodule: category: bottle callbacks: - threshold_normalization: + score_normalization: threshold_type: "pixel" early_stopping: patience: 2 diff --git a/quadra/configs/experiment/generic/mvtec/anomaly/cflow.yaml b/quadra/configs/experiment/generic/mvtec/anomaly/cflow.yaml index 5fd49980..0872634b 100644 --- a/quadra/configs/experiment/generic/mvtec/anomaly/cflow.yaml +++ b/quadra/configs/experiment/generic/mvtec/anomaly/cflow.yaml @@ -10,7 +10,7 @@ datamodule: category: bottle callbacks: - threshold_normalization: + score_normalization: threshold_type: "pixel" early_stopping: patience: 2 diff --git a/quadra/configs/experiment/generic/mvtec/anomaly/csflow.yaml b/quadra/configs/experiment/generic/mvtec/anomaly/csflow.yaml index 06a34825..b5b4effd 100644 --- a/quadra/configs/experiment/generic/mvtec/anomaly/csflow.yaml +++ b/quadra/configs/experiment/generic/mvtec/anomaly/csflow.yaml @@ -10,7 +10,7 @@ datamodule: category: bottle callbacks: - threshold_normalization: + score_normalization: threshold_type: "pixel" early_stopping: patience: 3 diff --git a/quadra/configs/experiment/generic/mvtec/anomaly/draem.yaml b/quadra/configs/experiment/generic/mvtec/anomaly/draem.yaml index 8eaac562..929b9cc1 100644 --- a/quadra/configs/experiment/generic/mvtec/anomaly/draem.yaml +++ b/quadra/configs/experiment/generic/mvtec/anomaly/draem.yaml @@ -10,7 +10,7 @@ datamodule: category: bottle callbacks: - threshold_normalization: + score_normalization: threshold_type: "pixel" early_stopping: patience: 20 diff --git a/quadra/configs/experiment/generic/mvtec/anomaly/efficient_ad.yaml b/quadra/configs/experiment/generic/mvtec/anomaly/efficient_ad.yaml index 54e7fc30..448ad382 100644 --- a/quadra/configs/experiment/generic/mvtec/anomaly/efficient_ad.yaml +++ b/quadra/configs/experiment/generic/mvtec/anomaly/efficient_ad.yaml @@ -15,7 +15,7 @@ datamodule: category: hazelnut callbacks: - threshold_normalization: + score_normalization: threshold_type: "pixel" print_config: false diff --git a/quadra/configs/experiment/generic/mvtec/anomaly/fastflow.yaml b/quadra/configs/experiment/generic/mvtec/anomaly/fastflow.yaml index cc1402ad..2edf7999 100644 --- a/quadra/configs/experiment/generic/mvtec/anomaly/fastflow.yaml +++ b/quadra/configs/experiment/generic/mvtec/anomaly/fastflow.yaml @@ -10,7 +10,7 @@ datamodule: category: bottle callbacks: - threshold_normalization: + score_normalization: threshold_type: "pixel" print_config: false diff --git a/quadra/configs/experiment/generic/mvtec/anomaly/padim.yaml b/quadra/configs/experiment/generic/mvtec/anomaly/padim.yaml index 4dd01495..575b4ce3 100644 --- a/quadra/configs/experiment/generic/mvtec/anomaly/padim.yaml +++ b/quadra/configs/experiment/generic/mvtec/anomaly/padim.yaml @@ -15,7 +15,7 @@ datamodule: category: bottle callbacks: - threshold_normalization: + score_normalization: threshold_type: "pixel" print_config: false diff --git a/quadra/configs/experiment/generic/mvtec/anomaly/patchcore.yaml b/quadra/configs/experiment/generic/mvtec/anomaly/patchcore.yaml index abd9ef09..1acb9e77 100644 --- a/quadra/configs/experiment/generic/mvtec/anomaly/patchcore.yaml +++ b/quadra/configs/experiment/generic/mvtec/anomaly/patchcore.yaml @@ -15,7 +15,7 @@ datamodule: category: bottle callbacks: - threshold_normalization: + score_normalization: threshold_type: "pixel" print_config: false