From d10f1667456b3a6d49d382a8dd2c7a12e0e3d160 Mon Sep 17 00:00:00 2001 From: Samet Date: Mon, 16 Jan 2023 06:21:43 -0700 Subject: [PATCH 01/85] Add missing type hints to export --- anomalib/deploy/export.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/anomalib/deploy/export.py b/anomalib/deploy/export.py index 25641bc826..d7c4fe83c5 100644 --- a/anomalib/deploy/export.py +++ b/anomalib/deploy/export.py @@ -54,7 +54,7 @@ def export( input_size: Union[List[int], Tuple[int, int]], export_mode: ExportMode, export_root: Union[str, Path], -): +) -> None: """Export the model to onnx format and (optionally) convert to OpenVINO IR if export mode is set to OpenVINO. Metadata.json is generated regardless of export mode. @@ -105,7 +105,7 @@ def _export_to_onnx(model: AnomalyModule, input_size: Union[List[int], Tuple[int return onnx_path -def _export_to_openvino(export_path: Union[str, Path], onnx_path: Path): +def _export_to_openvino(export_path: Union[str, Path], onnx_path: Path) -> None: """Convert onnx model to OpenVINO IR. Args: From 201a335579cf6b0c97ae5304f811bf4ad0ca1f93 Mon Sep 17 00:00:00 2001 From: Samet Date: Mon, 16 Jan 2023 06:22:20 -0700 Subject: [PATCH 02/85] Add missing type hints to inferencer interfaces --- anomalib/deploy/inferencers/base_inferencer.py | 4 ++-- anomalib/deploy/inferencers/openvino_inferencer.py | 4 ++-- anomalib/deploy/inferencers/torch_inferencer.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/anomalib/deploy/inferencers/base_inferencer.py b/anomalib/deploy/inferencers/base_inferencer.py index c4d9219750..c6005525bb 100644 --- a/anomalib/deploy/inferencers/base_inferencer.py +++ b/anomalib/deploy/inferencers/base_inferencer.py @@ -30,7 +30,7 @@ class Inferencer(ABC): """ @abstractmethod - def load_model(self, path: Union[str, Path]): + def load_model(self, path: Union[str, Path]) -> Any: """Load Model.""" raise NotImplementedError @@ -95,7 +95,7 @@ def predict( ) @staticmethod - def _superimpose_segmentation_mask(meta_data: dict, anomaly_map: np.ndarray, image: np.ndarray): + def _superimpose_segmentation_mask(meta_data: dict, anomaly_map: np.ndarray, image: np.ndarray) -> np.ndarray: """Superimpose segmentation mask on top of image. Args: diff --git a/anomalib/deploy/inferencers/openvino_inferencer.py b/anomalib/deploy/inferencers/openvino_inferencer.py index 2296ec5908..c151a2da3b 100644 --- a/anomalib/deploy/inferencers/openvino_inferencer.py +++ b/anomalib/deploy/inferencers/openvino_inferencer.py @@ -37,9 +37,9 @@ def __init__( self, config: Union[str, Path, DictConfig, ListConfig], path: Union[str, Path, Tuple[bytes, bytes]], - meta_data_path: Union[str, Path] = None, + meta_data_path: Optional[Union[str, Path]] = None, device: Optional[str] = "CPU", - ): + ) -> None: # Check and load the configuration if isinstance(config, (str, Path)): self.config = get_configurable_parameters(config_path=config) diff --git a/anomalib/deploy/inferencers/torch_inferencer.py b/anomalib/deploy/inferencers/torch_inferencer.py index 66be787e1a..f5102089aa 100644 --- a/anomalib/deploy/inferencers/torch_inferencer.py +++ b/anomalib/deploy/inferencers/torch_inferencer.py @@ -41,7 +41,7 @@ def __init__( model_source: Union[str, Path, AnomalyModule], meta_data_path: Optional[Union[str, Path]] = None, device: str = "auto", - ): + ) -> None: self.device = self._get_device(device) From 1b4f2a0aa56aa0aba29e26222c044c2af730709b Mon Sep 17 00:00:00 2001 From: Samet Date: Mon, 16 Jan 2023 06:23:06 -0700 Subject: [PATCH 03/85] Add missing type hints to post-processing modules --- anomalib/post_processing/post_process.py | 6 +++--- anomalib/post_processing/visualizer.py | 2 +- anomalib/pre_processing/pre_process.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/anomalib/post_processing/post_process.py b/anomalib/post_processing/post_process.py index 226cb21f23..c6e4577d8d 100644 --- a/anomalib/post_processing/post_process.py +++ b/anomalib/post_processing/post_process.py @@ -27,7 +27,7 @@ def add_label( confidence: Optional[float] = None, font_scale: float = 5e-3, thickness_scale=1e-3, -): +) -> np.ndarray: """Adds a label to an image. Args: @@ -71,12 +71,12 @@ def add_label( return image -def add_normal_label(image: np.ndarray, confidence: Optional[float] = None): +def add_normal_label(image: np.ndarray, confidence: Optional[float] = None) -> np.ndarray: """Adds the normal label to the image.""" return add_label(image, "normal", (225, 252, 134), confidence) -def add_anomalous_label(image: np.ndarray, confidence: Optional[float] = None): +def add_anomalous_label(image: np.ndarray, confidence: Optional[float] = None) -> np.ndarray: """Adds the anomalous label to the image.""" return add_label(image, "anomalous", (255, 100, 100), confidence) diff --git a/anomalib/post_processing/visualizer.py b/anomalib/post_processing/visualizer.py index d14e51ef58..ddc1e875cd 100644 --- a/anomalib/post_processing/visualizer.py +++ b/anomalib/post_processing/visualizer.py @@ -233,7 +233,7 @@ class ImageGrid: must be called to compile the image grid and obtain the final visualization. """ - def __init__(self): + def __init__(self) -> None: self.images: List[Dict] = [] self.figure: matplotlib.figure.Figure self.axis: np.ndarray diff --git a/anomalib/pre_processing/pre_process.py b/anomalib/pre_processing/pre_process.py index 4b023199ba..ff82bb5827 100644 --- a/anomalib/pre_processing/pre_process.py +++ b/anomalib/pre_processing/pre_process.py @@ -9,7 +9,7 @@ import logging import warnings -from typing import Optional, Tuple, Union +from typing import Any, Dict, Optional, Tuple, Union import albumentations as A from albumentations.pytorch import ToTensorV2 @@ -229,6 +229,6 @@ def __init__( self.transforms = get_transforms(config, image_size, to_tensor) - def __call__(self, *args, **kwargs): + def __call__(self, *args, **kwargs) -> Dict[str, Any]: """Return transformed arguments.""" return self.transforms(*args, **kwargs) From 039da47b612a879072d0b7c0cb30166c7bfd9a85 Mon Sep 17 00:00:00 2001 From: Samet Date: Mon, 16 Jan 2023 06:23:41 -0700 Subject: [PATCH 04/85] Add missing type hints to pre-processing modules --- anomalib/pre_processing/transforms/custom.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/anomalib/pre_processing/transforms/custom.py b/anomalib/pre_processing/transforms/custom.py index 6ca4a67d34..da0f5b2558 100644 --- a/anomalib/pre_processing/transforms/custom.py +++ b/anomalib/pre_processing/transforms/custom.py @@ -13,7 +13,7 @@ class Denormalize: """Denormalize Torch Tensor into np image format.""" - def __init__(self, mean: Optional[List[float]] = None, std: Optional[List[float]] = None): + def __init__(self, mean: Optional[List[float]] = None, std: Optional[List[float]] = None) -> None: """Denormalize Torch Tensor into np image format. Args: @@ -52,7 +52,7 @@ def __call__(self, tensor: Tensor) -> np.ndarray: array = (tensor * 255).permute(1, 2, 0).cpu().numpy().astype(np.uint8) return array - def __repr__(self): + def __repr__(self) -> str: """Representational string.""" return self.__class__.__name__ + "()" From f7c8db5ead86faa183cdb32d405bd2155db5276e Mon Sep 17 00:00:00 2001 From: Samet Date: Mon, 16 Jan 2023 06:26:52 -0700 Subject: [PATCH 05/85] Add missing type hints to nncf callback --- anomalib/utils/callbacks/nncf/callback.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/anomalib/utils/callbacks/nncf/callback.py b/anomalib/utils/callbacks/nncf/callback.py index 5c1485eddf..b4a6f2ff62 100644 --- a/anomalib/utils/callbacks/nncf/callback.py +++ b/anomalib/utils/callbacks/nncf/callback.py @@ -29,18 +29,19 @@ class NNCFCallback(Callback): If None model will not be exported. """ - def __init__(self, config: Dict, export_dir: str = None): + def __init__(self, config: Dict, export_dir: Optional[str] = None) -> None: self.export_dir = export_dir self.config = NNCFConfig(config) self.nncf_ctrl: Optional[CompressionAlgorithmController] = None - # pylint: disable=unused-argument def setup(self, trainer: pl.Trainer, pl_module: pl.LightningModule, stage: Optional[str] = None) -> None: """Call when fit or test begins. Takes the pytorch model and wraps it using the compression controller so that it is ready for nncf fine-tuning. """ + del stage # `stage` variable is not used. + if self.nncf_ctrl is not None: return @@ -54,33 +55,34 @@ def setup(self, trainer: pl.Trainer, pl_module: pl.LightningModule, stage: Optio ) def on_train_batch_start( - self, - trainer: pl.Trainer, - _pl_module: pl.LightningModule, - _batch: Any, - _batch_idx: int, - _unused: Optional[int] = 0, + self, trainer: pl.Trainer, pl_module: pl.LightningModule, batch: Any, batch_idx: int, unused: int = 0 ) -> None: """Call when the train batch begins. Prepare compression method to continue training the model in the next step. """ + del trainer, pl_module, batch, batch_idx, unused # These variables are not used. + if self.nncf_ctrl: self.nncf_ctrl.scheduler.step() - def on_train_epoch_start(self, _trainer: pl.Trainer, _pl_module: pl.LightningModule) -> None: + def on_train_epoch_start(self, trainer: pl.Trainer, pl_module: pl.LightningModule) -> None: """Call when the train epoch starts. Prepare compression method to continue training the model in the next epoch. """ + del trainer, pl_module # `trainer` and `pl_module` variables are not used. + if self.nncf_ctrl: self.nncf_ctrl.scheduler.epoch_step() - def on_train_end(self, _trainer: pl.Trainer, _pl_module: pl.LightningModule) -> None: + def on_train_end(self, trainer: pl.Trainer, pl_module: pl.LightningModule) -> None: """Call when the train ends. Exports onnx model and if compression controller is not None, uses the onnx model to generate the OpenVINO IR. """ + del trainer, pl_module # `trainer` and `pl_module` variables are not used. + if self.export_dir is None or self.nncf_ctrl is None: return From 66bda3cba7f3febf4810808670762637e03ea3a2 Mon Sep 17 00:00:00 2001 From: Samet Date: Mon, 16 Jan 2023 06:53:22 -0700 Subject: [PATCH 06/85] Add missing type hints to visualizer callbacks --- .../callbacks/visualizer/visualizer_base.py | 6 ++- .../callbacks/visualizer/visualizer_image.py | 44 ++++++++++--------- 2 files changed, 28 insertions(+), 22 deletions(-) diff --git a/anomalib/utils/callbacks/visualizer/visualizer_base.py b/anomalib/utils/callbacks/visualizer/visualizer_base.py index da8df5de50..2233c62908 100644 --- a/anomalib/utils/callbacks/visualizer/visualizer_base.py +++ b/anomalib/utils/callbacks/visualizer/visualizer_base.py @@ -33,7 +33,7 @@ def __init__( show_images: bool = False, log_images: bool = True, save_images: bool = True, - ): + ) -> None: """Visualizer callback.""" if mode not in ["full", "simple"]: raise ValueError(f"Unknown visualization mode: {mode}. Please choose one of ['full', 'simple']") @@ -57,7 +57,7 @@ def _add_to_logger( module: AnomalyModule, trainer: pl.Trainer, filename: Union[Path, str], - ): + ) -> None: """Log image from a visualizer to each of the available loggers in the project. Args: @@ -96,6 +96,8 @@ def on_test_end(self, trainer: pl.Trainer, pl_module: AnomalyModule) -> None: trainer (pl.Trainer): Pytorch Lightning trainer pl_module (AnomalyModule): Anomaly module (unused) """ + del pl_module # `pl_module` is not used. + for logger in trainer.loggers: if isinstance(logger, AnomalibWandbLogger): logger.save() diff --git a/anomalib/utils/callbacks/visualizer/visualizer_image.py b/anomalib/utils/callbacks/visualizer/visualizer_image.py index d1d52b31ca..d0a2d9bc29 100644 --- a/anomalib/utils/callbacks/visualizer/visualizer_image.py +++ b/anomalib/utils/callbacks/visualizer/visualizer_image.py @@ -29,25 +29,27 @@ class ImageVisualizerCallback(BaseVisualizerCallback): def on_predict_batch_end( self, - _trainer: pl.Trainer, - _pl_module: AnomalyModule, + trainer: pl.Trainer, + pl_module: AnomalyModule, outputs: Optional[STEP_OUTPUT], - _batch: Any, - _batch_idx: int, - _dataloader_idx: int, + batch: Any, + batch_idx: int, + dataloader_idx: int, ) -> None: """Show images at the end of every batch. Args: - _trainer (Trainer): Pytorch lightning trainer object (unused). - _pl_module (LightningModule): Lightning modules derived from BaseAnomalyLightning object as + trainer (Trainer): Pytorch lightning trainer object (unused). + pl_module (AnomalyModule): Lightning modules derived from BaseAnomalyLightning object as currently only they support logging images. - outputs (Dict[str, Any]): Outputs of the current test step. - _batch (Any): Input batch of the current test step (unused). - _batch_idx (int): Index of the current test batch (unused). - _dataloader_idx (int): Index of the dataloader that yielded the current batch (unused). + outputs (Optional[STEP_OUTPUT]): Outputs of the current test step. + batch (Any): Input batch of the current test step (unused). + batch_idx (int): Index of the current test batch (unused). + dataloader_idx (int): Index of the dataloader that yielded the current batch (unused). """ + del trainer, pl_module, batch, batch_idx, dataloader_idx # These variables are not used. assert outputs is not None + for i, image in enumerate(self.visualizer.visualize_batch(outputs)): filename = Path(outputs["image_path"][i]) if self.save_images: @@ -61,22 +63,24 @@ def on_test_batch_end( trainer: pl.Trainer, pl_module: AnomalyModule, outputs: Optional[STEP_OUTPUT], - _batch: Any, - _batch_idx: int, - _dataloader_idx: int, + batch: Any, + batch_idx: int, + dataloader_idx: int, ) -> None: """Log images at the end of every batch. Args: trainer (Trainer): Pytorch lightning trainer object (unused). - pl_module (LightningModule): Lightning modules derived from BaseAnomalyLightning object as - currently only they support logging images. - outputs (Dict[str, Any]): Outputs of the current test step. - _batch (Any): Input batch of the current test step (unused). - _batch_idx (int): Index of the current test batch (unused). - _dataloader_idx (int): Index of the dataloader that yielded the current batch (unused). + pl_module (AnomalyModule): Lightning modules derived from BaseAnomalyLightning object as + currently only they support logging images. + outputs (Optional[STEP_OUTPUT]): Outputs of the current test step. + batch (Any): Input batch of the current test step (unused). + batch_idx (int): Index of the current test batch (unused). + dataloader_idx (int): Index of the dataloader that yielded the current batch (unused). """ + del batch, batch_idx, dataloader_idx # These variables are not used. assert outputs is not None + for i, image in enumerate(self.visualizer.visualize_batch(outputs)): if "image_path" in outputs.keys(): filename = Path(outputs["image_path"][i]) From 536078be810a22555832b86f493b963407c72cac Mon Sep 17 00:00:00 2001 From: Samet Date: Mon, 16 Jan 2023 06:58:37 -0700 Subject: [PATCH 07/85] Add missing type hints to cdf normalization callbacks --- anomalib/utils/callbacks/cdf_normalization.py | 43 +++++++++++-------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/anomalib/utils/callbacks/cdf_normalization.py b/anomalib/utils/callbacks/cdf_normalization.py index 5b426fc8a5..b8e459fff4 100644 --- a/anomalib/utils/callbacks/cdf_normalization.py +++ b/anomalib/utils/callbacks/cdf_normalization.py @@ -24,13 +24,14 @@ class CdfNormalizationCallback(Callback): """Callback that standardizes the image-level and pixel-level anomaly scores.""" - def __init__(self): + def __init__(self) -> None: self.image_dist: Optional[LogNormal] = None self.pixel_dist: Optional[LogNormal] = None - # pylint: disable=unused-argument def setup(self, trainer: pl.Trainer, pl_module: AnomalyModule, stage: Optional[str] = None) -> None: """Adds training_distribution metrics to normalization metrics.""" + del trainer, stage # These variabels are not used. + if not hasattr(pl_module, "normalization_metrics"): pl_module.normalization_metrics = AnomalyScoreDistribution().cpu() elif not isinstance(pl_module.normalization_metrics, AnomalyScoreDistribution): @@ -39,15 +40,16 @@ def setup(self, trainer: pl.Trainer, pl_module: AnomalyModule, stage: Optional[s f" got {type(pl_module.normalization_metrics)}" ) - # pylint: disable=unused-argument def on_test_start(self, trainer: pl.Trainer, pl_module: AnomalyModule) -> None: """Called when the test begins.""" + del trainer # `trainer` variable is not used. + if pl_module.image_metrics is not None: pl_module.image_metrics.set_threshold(0.5) if pl_module.pixel_metrics is not None: pl_module.pixel_metrics.set_threshold(0.5) - def on_validation_epoch_start(self, trainer: "pl.Trainer", pl_module: AnomalyModule) -> None: + def on_validation_epoch_start(self, trainer: pl.Trainer, pl_module: AnomalyModule) -> None: """Called when the validation starts after training. Use the current model to compute the anomaly score distributions @@ -59,44 +61,50 @@ def on_validation_epoch_start(self, trainer: "pl.Trainer", pl_module: AnomalyMod def on_validation_batch_end( self, - _trainer: pl.Trainer, + trainer: pl.Trainer, pl_module: AnomalyModule, outputs: Optional[STEP_OUTPUT], - _batch: Any, - _batch_idx: int, - _dataloader_idx: int, + batch: Any, + batch_idx: int, + dataloader_idx: int, ) -> None: """Called when the validation batch ends, standardizes the predicted scores and anomaly maps.""" + del trainer, batch, batch_idx, dataloader_idx # These variables are not used. + self._standardize_batch(outputs, pl_module) def on_test_batch_end( self, - _trainer: pl.Trainer, + trainer: pl.Trainer, pl_module: AnomalyModule, outputs: Optional[STEP_OUTPUT], - _batch: Any, - _batch_idx: int, - _dataloader_idx: int, + batch: Any, + batch_idx: int, + dataloader_idx: int, ) -> 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._standardize_batch(outputs, pl_module) self._normalize_batch(outputs, pl_module) def on_predict_batch_end( self, - _trainer: pl.Trainer, + trainer: pl.Trainer, pl_module: AnomalyModule, outputs: Dict, - _batch: Any, - _batch_idx: int, - _dataloader_idx: int, + batch: Any, + batch_idx: int, + dataloader_idx: int, ) -> 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._standardize_batch(outputs, pl_module) self._normalize_batch(outputs, pl_module) outputs["pred_labels"] = outputs["pred_scores"] >= 0.5 - def _collect_stats(self, trainer, pl_module): + def _collect_stats(self, trainer: pl.Trainer, pl_module: AnomalyModule) -> None: """Collect the statistics of the normal training data. Create a trainer and use it to predict the anomaly maps and scores of the normal training data. Then @@ -136,3 +144,4 @@ def _normalize_batch(outputs: STEP_OUTPUT, pl_module: AnomalyModule) -> None: outputs["pred_scores"] = normalize(outputs["pred_scores"], pl_module.image_threshold.value) if "anomaly_maps" in outputs.keys(): outputs["anomaly_maps"] = normalize(outputs["anomaly_maps"], pl_module.pixel_threshold.value) + outputs["anomaly_maps"] = normalize(outputs["anomaly_maps"], pl_module.pixel_threshold.value) From cfd86fa2f4a4bcf2a2c0bc11054a37a064ce3ba8 Mon Sep 17 00:00:00 2001 From: Samet Date: Mon, 16 Jan 2023 07:01:48 -0700 Subject: [PATCH 08/85] Add missing type hints to export callback --- anomalib/utils/callbacks/export.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/anomalib/utils/callbacks/export.py b/anomalib/utils/callbacks/export.py index c81eccbb72..4121c24af9 100644 --- a/anomalib/utils/callbacks/export.py +++ b/anomalib/utils/callbacks/export.py @@ -7,6 +7,7 @@ import os from typing import Tuple +import pytorch_lightning as pl from pytorch_lightning import Callback from pytorch_lightning.utilities.cli import CALLBACK_REGISTRY @@ -28,18 +29,20 @@ class ExportCallback(Callback): filename (str): Name of output model """ - def __init__(self, input_size: Tuple[int, int], dirpath: str, filename: str, export_mode: ExportMode): + def __init__(self, input_size: Tuple[int, int], dirpath: str, filename: str, export_mode: ExportMode) -> None: self.input_size = input_size self.dirpath = dirpath self.filename = filename self.export_mode = export_mode - def on_train_end(self, trainer, pl_module: AnomalyModule) -> None: # pylint: disable=W0613 + def on_train_end(self, trainer: pl.Trainer, pl_module: AnomalyModule) -> None: """Call when the train ends. Converts the model to ``onnx`` format and then calls OpenVINO's model optimizer to get the ``.xml`` and ``.bin`` IR files. """ + del trainer # `trainer` variable is not used. + logger.info("Exporting the model") os.makedirs(self.dirpath, exist_ok=True) export( From 495a9bfa3e7fb4f27b227b1c3371a1b21ed79108 Mon Sep 17 00:00:00 2001 From: Samet Date: Mon, 16 Jan 2023 07:03:54 -0700 Subject: [PATCH 09/85] Add missing type hints to metrics configuration callback --- anomalib/utils/callbacks/metrics_configuration.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/anomalib/utils/callbacks/metrics_configuration.py b/anomalib/utils/callbacks/metrics_configuration.py index d35bc6a295..bbab06436c 100644 --- a/anomalib/utils/callbacks/metrics_configuration.py +++ b/anomalib/utils/callbacks/metrics_configuration.py @@ -29,7 +29,7 @@ def __init__( task: TaskType = TaskType.SEGMENTATION, image_metrics: Optional[List[str]] = None, pixel_metrics: Optional[List[str]] = None, - ): + ) -> None: """Create image and pixel-level AnomalibMetricsCollection. This callback creates AnomalibMetricsCollection based on the @@ -48,17 +48,19 @@ def __init__( def setup( self, - _trainer: pl.Trainer, - pl_module: pl.LightningModule, - stage: Optional[str] = None, # pylint: disable=unused-argument + trainer: pl.Trainer, + pl_module: AnomalyModule, + stage: Optional[str] = None, ) -> None: """Setup image and pixel-level AnomalibMetricsCollection within Anomalib Model. Args: - _trainer (pl.Trainer): PyTorch Lightning Trainer - pl_module (pl.LightningModule): Anomalib Model that inherits pl LightningModule. + trainer (pl.Trainer): PyTorch Lightning Trainer + pl_module (AnomalyModule): Anomalib Model that inherits pl LightningModule. stage (Optional[str], optional): fit, validate, test or predict. Defaults to None. """ + del trainer, stage # These variables are not used. + image_metric_names = [] if self.image_metric_names is None else self.image_metric_names pixel_metric_names: List[str] From 6c394d06d5e08f9447c90853c0fdcbc4ef4e990f Mon Sep 17 00:00:00 2001 From: Samet Date: Mon, 16 Jan 2023 07:16:03 -0700 Subject: [PATCH 10/85] Add missing type hints to min-max normalization configuration callback --- .../utils/callbacks/min_max_normalization.py | 44 +++++++++++-------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/anomalib/utils/callbacks/min_max_normalization.py b/anomalib/utils/callbacks/min_max_normalization.py index 6d24aeb57a..4b7c5c72ad 100644 --- a/anomalib/utils/callbacks/min_max_normalization.py +++ b/anomalib/utils/callbacks/min_max_normalization.py @@ -3,7 +3,7 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -from typing import Any, Dict, Optional +from typing import Any, Optional import pytorch_lightning as pl import torch @@ -20,9 +20,10 @@ class MinMaxNormalizationCallback(Callback): """Callback that normalizes the image-level and pixel-level anomaly scores using min-max normalization.""" - # pylint: disable=unused-argument def setup(self, trainer: pl.Trainer, pl_module: AnomalyModule, stage: Optional[str] = None) -> None: """Adds min_max metrics to normalization metrics.""" + del trainer, stage # These variables are not used. + if not hasattr(pl_module, "normalization_metrics"): pl_module.normalization_metrics = MinMax().cpu() elif not isinstance(pl_module.normalization_metrics, MinMax): @@ -30,23 +31,26 @@ def setup(self, trainer: pl.Trainer, pl_module: AnomalyModule, stage: Optional[s f"Expected normalization_metrics to be of type MinMax, got {type(pl_module.normalization_metrics)}" ) - # pylint: disable=unused-argument 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(0.5) def on_validation_batch_end( self, - _trainer: pl.Trainer, + trainer: pl.Trainer, pl_module: AnomalyModule, outputs: STEP_OUTPUT, - _batch: Any, - _batch_idx: int, - _dataloader_idx: int, + batch: Any, + batch_idx: int, + dataloader_idx: int, ) -> None: """Called when the validation batch ends, update the min and max observed values.""" + del trainer, batch, batch_idx, dataloader_idx # These variables are not used. + if "anomaly_maps" in outputs: pl_module.normalization_metrics(outputs["anomaly_maps"]) elif "box_scores" in outputs: @@ -58,30 +62,34 @@ def on_validation_batch_end( def on_test_batch_end( self, - _trainer: pl.Trainer, + trainer: "pl.Trainer", pl_module: AnomalyModule, - outputs: STEP_OUTPUT, - _batch: Any, - _batch_idx: int, - _dataloader_idx: int, + outputs: Optional[STEP_OUTPUT], + batch: Any, + batch_idx: int, + dataloader_idx: int, ) -> 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, + trainer: pl.Trainer, pl_module: AnomalyModule, - outputs: Dict, - _batch: Any, - _batch_idx: int, - _dataloader_idx: int, + outputs: Any, + batch: Any, + batch_idx: int, + dataloader_idx: int, ) -> 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) @staticmethod - def _normalize_batch(outputs, pl_module): + def _normalize_batch(outputs, pl_module) -> None: """Normalize a batch of predictions.""" image_threshold = pl_module.image_threshold.value.cpu() pixel_threshold = pl_module.pixel_threshold.value.cpu() From 075b2fb51dc2a9dd8dc99130f1aac1591159930b Mon Sep 17 00:00:00 2001 From: Samet Date: Mon, 16 Jan 2023 07:18:05 -0700 Subject: [PATCH 11/85] Add missing type hints to init callbacks --- anomalib/utils/callbacks/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/anomalib/utils/callbacks/__init__.py b/anomalib/utils/callbacks/__init__.py index e5d5549b2e..ef1edc3e13 100644 --- a/anomalib/utils/callbacks/__init__.py +++ b/anomalib/utils/callbacks/__init__.py @@ -147,7 +147,7 @@ def get_callbacks(config: Union[ListConfig, DictConfig]) -> List[Callback]: return callbacks -def add_visualizer_callback(callbacks: List[Callback], config: Union[DictConfig, ListConfig]): +def add_visualizer_callback(callbacks: List[Callback], config: Union[DictConfig, ListConfig]) -> None: """Configure the visualizer callback based on the config and add it to the list of callbacks. Args: From 61beeae3ba77cba315c9477bd29eb872b24db694 Mon Sep 17 00:00:00 2001 From: Samet Date: Mon, 16 Jan 2023 07:19:06 -0700 Subject: [PATCH 12/85] Add missing type hints to model loader callback --- anomalib/utils/callbacks/model_loader.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/anomalib/utils/callbacks/model_loader.py b/anomalib/utils/callbacks/model_loader.py index 7621d87a1d..c00a4b0d02 100644 --- a/anomalib/utils/callbacks/model_loader.py +++ b/anomalib/utils/callbacks/model_loader.py @@ -19,14 +19,15 @@ class LoadModelCallback(Callback): """Callback that loads the model weights from the state dict.""" - def __init__(self, weights_path): + def __init__(self, weights_path) -> None: self.weights_path = weights_path - # pylint: disable=unused-argument def setup(self, trainer: Trainer, pl_module: AnomalyModule, stage: Optional[str] = None) -> None: """Call when inference begins. Loads the model weights from ``weights_path`` into the PyTorch module. """ + del trainer, stage # These variables are not used. + logger.info("Loading the model from %s", self.weights_path) pl_module.load_state_dict(torch.load(self.weights_path, map_location=pl_module.device)["state_dict"]) From fc448ddac249927a36faa481e991c5ce39fc0c6f Mon Sep 17 00:00:00 2001 From: Samet Date: Mon, 16 Jan 2023 07:20:14 -0700 Subject: [PATCH 13/85] Add missing type hints to post processing callback --- anomalib/utils/callbacks/post_processing_configuration.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/anomalib/utils/callbacks/post_processing_configuration.py b/anomalib/utils/callbacks/post_processing_configuration.py index 9437d3ba2e..42a044f4e9 100644 --- a/anomalib/utils/callbacks/post_processing_configuration.py +++ b/anomalib/utils/callbacks/post_processing_configuration.py @@ -60,7 +60,6 @@ def __init__( 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: """Setup post-processing configuration within Anomalib Model. @@ -69,6 +68,8 @@ def setup(self, trainer: Trainer, pl_module: LightningModule, stage: Optional[st pl_module (LightningModule): Anomalib Model that inherits pl LightningModule. stage (Optional[str], optional): fit, validate, test or predict. Defaults to None. """ + del trainer, stage # These variables are not used. + if isinstance(pl_module, AnomalyModule): pl_module.threshold_method = self.threshold_method if pl_module.threshold_method == ThresholdMethod.MANUAL: From 96b2092c464e167e39b0818621823bdce8d725a5 Mon Sep 17 00:00:00 2001 From: Samet Date: Mon, 16 Jan 2023 07:22:20 -0700 Subject: [PATCH 14/85] Add missing type hints to tiler configuration callback --- anomalib/utils/callbacks/tiler_configuration.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/anomalib/utils/callbacks/tiler_configuration.py b/anomalib/utils/callbacks/tiler_configuration.py index 227be27539..11137267be 100644 --- a/anomalib/utils/callbacks/tiler_configuration.py +++ b/anomalib/utils/callbacks/tiler_configuration.py @@ -28,7 +28,7 @@ def __init__( remove_border_count: int = 0, mode: str = "padding", tile_count: int = 4, - ): + ) -> None: """Sets tiling configuration from the command line. Args: @@ -51,11 +51,11 @@ def __init__( self.mode = mode self.tile_count = tile_count - def setup(self, _trainer: pl.Trainer, pl_module: pl.LightningModule, stage: Optional[str] = None) -> None: + def setup(self, trainer: pl.Trainer, pl_module: pl.LightningModule, stage: Optional[str] = None) -> None: """Setup Tiler object within Anomalib Model. Args: - _trainer (pl.Trainer): PyTorch Lightning Trainer + trainer (pl.Trainer): PyTorch Lightning Trainer pl_module (pl.LightningModule): Anomalib Model that inherits pl LightningModule. stage (Optional[str], optional): fit, validate, test or predict. Defaults to None. @@ -63,6 +63,8 @@ def setup(self, _trainer: pl.Trainer, pl_module: pl.LightningModule, stage: Opti ValueError: When Anomalib Model doesn't contain ``Tiler`` object, it means the model doesn not support tiling operation. """ + del trainer, stage # These variables are not used. + if self.enable: if isinstance(pl_module, AnomalyModule) and hasattr(pl_module.model, "tiler"): pl_module.model.tiler = Tiler( From 8ee2613dd2495360d7178bf0c542e125ac0c1d6b Mon Sep 17 00:00:00 2001 From: Samet Date: Mon, 16 Jan 2023 07:22:36 -0700 Subject: [PATCH 15/85] Add missing type hints to timer callback --- anomalib/utils/callbacks/timer.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/anomalib/utils/callbacks/timer.py b/anomalib/utils/callbacks/timer.py index 99360ad981..17f132a43f 100644 --- a/anomalib/utils/callbacks/timer.py +++ b/anomalib/utils/callbacks/timer.py @@ -16,12 +16,11 @@ class TimerCallback(Callback): """Callback that measures the training and testing time of a PyTorch Lightning module.""" - # pylint: disable=unused-argument - def __init__(self): + def __init__(self) -> None: self.start: float self.num_images: int = 0 - def on_fit_start(self, trainer: Trainer, pl_module: LightningModule) -> None: # pylint: disable=W0613 + def on_fit_start(self, trainer: Trainer, pl_module: LightningModule) -> None: """Call when fit begins. Sets the start time to the time training started. @@ -33,6 +32,8 @@ def on_fit_start(self, trainer: Trainer, pl_module: LightningModule) -> None: # Returns: None """ + del trainer, pl_module # These variables are not used. + self.start = time.time() def on_fit_end(self, trainer: Trainer, pl_module: LightningModule) -> None: # pylint: disable=W0613 From 3ab7cdcb25092bf484a4c1612f7ec28f45d613c6 Mon Sep 17 00:00:00 2001 From: Samet Date: Mon, 16 Jan 2023 07:36:18 -0700 Subject: [PATCH 16/85] Add missing type hints to utils --- anomalib/utils/hpo/config.py | 2 +- anomalib/utils/hpo/runners.py | 6 +++--- anomalib/utils/loggers/__init__.py | 2 +- anomalib/utils/loggers/comet.py | 2 +- .../utils/metrics/anomaly_score_distribution.py | 8 ++++---- anomalib/utils/metrics/anomaly_score_threshold.py | 11 ++++++----- anomalib/utils/metrics/collection.py | 4 ++-- anomalib/utils/metrics/min_max.py | 2 +- anomalib/utils/metrics/optimal_f1.py | 15 ++++++++------- anomalib/utils/sweep/config.py | 4 ++-- 10 files changed, 29 insertions(+), 27 deletions(-) diff --git a/anomalib/utils/hpo/config.py b/anomalib/utils/hpo/config.py index b01a831e4f..cda7b8699a 100644 --- a/anomalib/utils/hpo/config.py +++ b/anomalib/utils/hpo/config.py @@ -18,7 +18,7 @@ def flatten_hpo_params(params_dict: DictConfig) -> DictConfig: flattened version of the parameter dictionary. """ - def process_params(nested_params: DictConfig, keys: List[str], flattened_params: DictConfig): + def process_params(nested_params: DictConfig, keys: List[str], flattened_params: DictConfig) -> None: """Flatten nested dictionary till the time it reaches the hpo params. Recursive helper function that traverses the nested config object and stores the leaf nodes in a flattened diff --git a/anomalib/utils/hpo/runners.py b/anomalib/utils/hpo/runners.py index d726756224..c19a555b87 100644 --- a/anomalib/utils/hpo/runners.py +++ b/anomalib/utils/hpo/runners.py @@ -47,7 +47,7 @@ def __init__( if isinstance(self.sweep_config, DictConfig): self.sweep_config.pop("observation_budget") - def run(self): + def run(self) -> None: """Run the sweep.""" flattened_hpo_params = flatten_hpo_params(self.sweep_config.parameters) self.sweep_config.parameters = flattened_hpo_params @@ -58,7 +58,7 @@ def run(self): ) wandb.agent(sweep_id, function=self.sweep, count=self.observation_budget) - def sweep(self): + def sweep(self) -> None: """Method to load the model, update config and call fit. The metrics are logged to ```wandb``` dashboard.""" wandb_logger = WandbLogger(config=flatten_sweep_params(self.sweep_config), log_model=False) sweep_config = wandb_logger.experiment.config @@ -97,7 +97,7 @@ def __init__( self.sweep_config = sweep_config self.entity = entity - def run(self): + def run(self) -> None: """Run the sweep.""" flattened_hpo_params = flatten_hpo_params(self.sweep_config.parameters) self.sweep_config.parameters = flattened_hpo_params diff --git a/anomalib/utils/loggers/__init__.py b/anomalib/utils/loggers/__init__.py index e43218088c..3d53534548 100644 --- a/anomalib/utils/loggers/__init__.py +++ b/anomalib/utils/loggers/__init__.py @@ -35,7 +35,7 @@ class UnknownLogger(Exception): """This is raised when the logger option in `config.yaml` file is set incorrectly.""" -def configure_logger(level: Union[int, str] = logging.INFO): +def configure_logger(level: Union[int, str] = logging.INFO) -> None: """Get console logger by name. Args: diff --git a/anomalib/utils/loggers/comet.py b/anomalib/utils/loggers/comet.py index 6ca449309f..3fa406dd0f 100644 --- a/anomalib/utils/loggers/comet.py +++ b/anomalib/utils/loggers/comet.py @@ -93,7 +93,7 @@ def __init__( self.experiment.log_other("Created from", "Anomalib") @rank_zero_only - def add_image(self, image: Union[np.ndarray, Figure], name: Optional[str] = None, **kwargs: Any): + def add_image(self, image: Union[np.ndarray, Figure], name: Optional[str] = None, **kwargs: Any) -> None: """Interface to add image to comet logger. Args: diff --git a/anomalib/utils/metrics/anomaly_score_distribution.py b/anomalib/utils/metrics/anomaly_score_distribution.py index eb1af5a905..80188ab4f8 100644 --- a/anomalib/utils/metrics/anomaly_score_distribution.py +++ b/anomalib/utils/metrics/anomaly_score_distribution.py @@ -3,7 +3,7 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -from typing import Optional, Tuple +from typing import List, Optional, Tuple import torch from torch import Tensor @@ -13,10 +13,10 @@ class AnomalyScoreDistribution(Metric): """Mean and standard deviation of the anomaly scores of normal training data.""" - def __init__(self, **kwargs): + def __init__(self, **kwargs) -> None: super().__init__(**kwargs) - self.anomaly_maps = [] - self.anomaly_scores = [] + self.anomaly_maps: List[Tensor] = [] + self.anomaly_scores: List[Tensor] = [] self.add_state("image_mean", torch.empty(0), persistent=True) self.add_state("image_std", torch.empty(0), persistent=True) diff --git a/anomalib/utils/metrics/anomaly_score_threshold.py b/anomalib/utils/metrics/anomaly_score_threshold.py index 0a16c35f00..4d4a26fb4f 100644 --- a/anomalib/utils/metrics/anomaly_score_threshold.py +++ b/anomalib/utils/metrics/anomaly_score_threshold.py @@ -6,6 +6,7 @@ import warnings import torch +from torch import Tensor from torchmetrics import PrecisionRecallCurve @@ -21,13 +22,13 @@ class AnomalyScoreThreshold(PrecisionRecallCurve): adaptive threshold value. """ - def __init__(self, default_value: float = 0.5, **kwargs): + def __init__(self, default_value: float = 0.5, **kwargs) -> None: super().__init__(num_classes=1, **kwargs) self.add_state("value", default=torch.tensor(default_value), persistent=True) # pylint: disable=not-callable self.value = torch.tensor(default_value) # pylint: disable=not-callable - def compute(self) -> torch.Tensor: + def compute(self) -> Tensor: """Compute the threshold that yields the optimal F1 score. Compute the F1 scores while varying the threshold. Store the optimal @@ -36,9 +37,9 @@ def compute(self) -> torch.Tensor: Returns: Value of the F1 score at the optimal threshold. """ - precision: torch.Tensor - recall: torch.Tensor - thresholds: torch.Tensor + precision: Tensor + recall: Tensor + thresholds: Tensor if not any(1 in batch for batch in self.target): warnings.warn( diff --git a/anomalib/utils/metrics/collection.py b/anomalib/utils/metrics/collection.py index ecf2aa779f..7b86bd6fbd 100644 --- a/anomalib/utils/metrics/collection.py +++ b/anomalib/utils/metrics/collection.py @@ -9,12 +9,12 @@ class AnomalibMetricCollection(MetricCollection): """Extends the MetricCollection class for use in the Anomalib pipeline.""" - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) self._update_called = False self._threshold = 0.5 - def set_threshold(self, threshold_value): + def set_threshold(self, threshold_value) -> None: """Update the threshold value for all metrics that have the threshold attribute.""" self._threshold = threshold_value for metric in self.values(): diff --git a/anomalib/utils/metrics/min_max.py b/anomalib/utils/metrics/min_max.py index 2cf0fd09b8..809b371934 100644 --- a/anomalib/utils/metrics/min_max.py +++ b/anomalib/utils/metrics/min_max.py @@ -15,7 +15,7 @@ class MinMax(Metric): full_state_update: bool = True - def __init__(self, **kwargs): + def __init__(self, **kwargs) -> None: super().__init__(**kwargs) self.add_state("min", torch.tensor(float("inf")), persistent=True) # pylint: disable=not-callable self.add_state("max", torch.tensor(float("-inf")), persistent=True) # pylint: disable=not-callable diff --git a/anomalib/utils/metrics/optimal_f1.py b/anomalib/utils/metrics/optimal_f1.py index c091fc1894..f5598f0e31 100644 --- a/anomalib/utils/metrics/optimal_f1.py +++ b/anomalib/utils/metrics/optimal_f1.py @@ -6,6 +6,7 @@ import warnings import torch +from torch import Tensor from torchmetrics import Metric, PrecisionRecallCurve @@ -18,7 +19,7 @@ class OptimalF1(Metric): full_state_update: bool = False - def __init__(self, num_classes: int, **kwargs): + def __init__(self, num_classes: int, **kwargs) -> None: warnings.warn( DeprecationWarning( "OptimalF1 metric is deprecated and will be removed in a future release. The optimal F1 score for " @@ -30,14 +31,14 @@ def __init__(self, num_classes: int, **kwargs): self.precision_recall_curve = PrecisionRecallCurve(num_classes=num_classes) - self.threshold: torch.Tensor + self.threshold: Tensor # pylint: disable=arguments-differ - def update(self, preds: torch.Tensor, target: torch.Tensor) -> None: # type: ignore + def update(self, preds: Tensor, target: Tensor) -> None: # type: ignore """Update the precision-recall curve metric.""" self.precision_recall_curve.update(preds, target) - def compute(self) -> torch.Tensor: + def compute(self) -> Tensor: """Compute the value of the optimal F1 score. Compute the F1 scores while varying the threshold. Store the optimal @@ -46,9 +47,9 @@ def compute(self) -> torch.Tensor: Returns: Value of the F1 score at the optimal threshold. """ - precision: torch.Tensor - recall: torch.Tensor - thresholds: torch.Tensor + precision: Tensor + recall: Tensor + thresholds: Tensor precision, recall, thresholds = self.precision_recall_curve.compute() f1_score = (2 * precision * recall) / (precision + recall + 1e-10) diff --git a/anomalib/utils/sweep/config.py b/anomalib/utils/sweep/config.py index b3a1a369a3..ed97d99d41 100644 --- a/anomalib/utils/sweep/config.py +++ b/anomalib/utils/sweep/config.py @@ -63,7 +63,7 @@ def flatten_sweep_params(params_dict: DictConfig) -> DictConfig: flattened version of the parameter dictionary. """ - def flatten_nested_dict(nested_params: DictConfig, keys: List[str], flattened_params: DictConfig): + def flatten_nested_dict(nested_params: DictConfig, keys: List[str], flattened_params: DictConfig) -> None: """Flatten nested dictionary. Recursive helper function that traverses the nested config object and stores the leaf nodes in a flattened @@ -133,7 +133,7 @@ def get_from_nested_config(config: DictConfig, keymap: List) -> Any: return reduce(operator.getitem, keymap, config) -def set_in_nested_config(config: DictConfig, keymap: List, value: Any): +def set_in_nested_config(config: DictConfig, keymap: List, value: Any) -> None: """Set an item in a nested config object using a list of keys. Args: From e4bdb46a9ac111d5f1a7de3b86fb3ce97c2e31d7 Mon Sep 17 00:00:00 2001 From: Samet Date: Mon, 16 Jan 2023 10:45:14 -0700 Subject: [PATCH 17/85] Refactored datamodule --- anomalib/data/base/datamodule.py | 39 +++++++++++++++++--------------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/anomalib/data/base/datamodule.py b/anomalib/data/base/datamodule.py index 9365ae901b..41dc66d7e3 100644 --- a/anomalib/data/base/datamodule.py +++ b/anomalib/data/base/datamodule.py @@ -75,7 +75,7 @@ def __init__( test_split_mode: Optional[TestSplitMode] = None, test_split_ratio: Optional[float] = None, seed: Optional[int] = None, - ): + ) -> None: super().__init__() self.train_batch_size = train_batch_size self.eval_batch_size = eval_batch_size @@ -86,13 +86,13 @@ def __init__( self.val_split_ratio = val_split_ratio self.seed = seed - self.train_data: Optional[AnomalibDataset] = None - self.val_data: Optional[AnomalibDataset] = None - self.test_data: Optional[AnomalibDataset] = None + self.train_data: AnomalibDataset + self.val_data: AnomalibDataset + self.test_data: AnomalibDataset self._samples: Optional[DataFrame] = None - def setup(self, stage: Optional[str] = None): + def setup(self, stage: Optional[str] = None) -> None: """Setup train, validation and test data. Args: @@ -121,7 +121,7 @@ def _setup(self, _stage: Optional[str] = None) -> None: self._create_test_split() self._create_val_split() - def _create_test_split(self): + def _create_test_split(self) -> None: """Obtain the test set based on the settings in the config.""" if self.test_data.has_normal: # split the test data into normal and anomalous so these can be processed separately @@ -133,7 +133,8 @@ def _create_test_split(self): "No normal test images found. Sampling from training set using a split ratio of %d", self.test_split_ratio, ) - self.train_data, normal_test_data = random_split(self.train_data, self.test_split_ratio) + if self.test_split_ratio is not None: + self.train_data, normal_test_data = random_split(self.train_data, self.test_split_ratio) if self.test_split_mode == TestSplitMode.FROM_DIR: self.test_data += normal_test_data @@ -142,7 +143,7 @@ def _create_test_split(self): elif self.test_split_mode != TestSplitMode.NONE: raise ValueError(f"Unsupported Test Split Mode: {self.test_split_mode}") - def _create_val_split(self): + def _create_val_split(self) -> None: """Obtain the validation set based on the settings in the config.""" if self.val_split_mode == ValSplitMode.FROM_TEST: # randomly sampled from test set @@ -160,16 +161,18 @@ def _create_val_split(self): raise ValueError(f"Unknown validation split mode: {self.val_split_mode}") @property - def is_setup(self): - """Checks if setup() has been called.""" - # at least one of [train_data, val_data, test_data] should be setup - if self.train_data is not None and self.train_data.is_setup: - return True - if self.val_data is not None and self.val_data.is_setup: - return True - if self.test_data is not None and self.test_data.is_setup: - return True - return False + def is_setup(self) -> bool: + """Checks if setup() has been called. + + At least one of [train_data, val_data, test_data] should be setup. + """ + _is_setup: bool = False + for data in ("train_data", "val_data", "test_data"): + if hasattr(self, data): + if getattr(self, data).is_setup: + _is_setup = True + + return _is_setup def train_dataloader(self) -> TRAIN_DATALOADERS: """Get train dataloader.""" From 415e76ba253e46405d32b1b7c8047c506d8e11a3 Mon Sep 17 00:00:00 2001 From: Samet Date: Mon, 16 Jan 2023 10:56:43 -0700 Subject: [PATCH 18/85] Add missing type hints to dataset --- anomalib/data/base/dataset.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/anomalib/data/base/dataset.py b/anomalib/data/base/dataset.py index 8d239a5e7d..e4a36604ff 100644 --- a/anomalib/data/base/dataset.py +++ b/anomalib/data/base/dataset.py @@ -41,7 +41,7 @@ class AnomalibDataset(Dataset, ABC): transform (A.Compose): Albumentations Compose object describing the transforms that are applied to the inputs. """ - def __init__(self, task: TaskType, transform: A.Compose): + def __init__(self, task: TaskType, transform: A.Compose) -> None: super().__init__() self.task = task self.transform = transform @@ -76,7 +76,7 @@ def samples(self) -> DataFrame: return self._samples @samples.setter - def samples(self, samples: DataFrame): + def samples(self, samples: DataFrame) -> None: """Overwrite the samples with a new dataframe. Args: From e7a620e322fe14213184034177bcf5b6c67300e2 Mon Sep 17 00:00:00 2001 From: Samet Date: Mon, 16 Jan 2023 10:59:44 -0700 Subject: [PATCH 19/85] Add missing type hints to download --- anomalib/data/utils/download.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/anomalib/data/utils/download.py b/anomalib/data/utils/download.py index 24ed1df4c6..bfe36ed5b8 100644 --- a/anomalib/data/utils/download.py +++ b/anomalib/data/utils/download.py @@ -183,7 +183,7 @@ def __init__( ) self.total: Optional[Union[int, float]] - def update_to(self, chunk_number: int = 1, max_chunk_size: int = 1, total_size=None): + def update_to(self, chunk_number: int = 1, max_chunk_size: int = 1, total_size=None) -> None: """Progress bar hook for tqdm. Based on https://stackoverflow.com/a/53877507 @@ -200,7 +200,7 @@ def update_to(self, chunk_number: int = 1, max_chunk_size: int = 1, total_size=N self.update(chunk_number * max_chunk_size - self.n) -def hash_check(file_path: Path, expected_hash: str): +def hash_check(file_path: Path, expected_hash: str) -> None: """Raise assert error if hash does not match the calculated hash of the file. Args: @@ -213,7 +213,7 @@ def hash_check(file_path: Path, expected_hash: str): ), f"Downloaded file {file_path} does not match the required hash." -def download_and_extract(root: Path, info: DownloadInfo): +def download_and_extract(root: Path, info: DownloadInfo) -> None: """Download and extract a dataset. Args: From 7e521da0ff4d1ef43db6a2a8f51b5c24b03572a6 Mon Sep 17 00:00:00 2001 From: Samet Date: Mon, 16 Jan 2023 11:06:01 -0700 Subject: [PATCH 20/85] Add missing type hints to folder dataset --- anomalib/data/avenue.py | 12 ++++++------ anomalib/data/btech.py | 2 +- anomalib/data/folder.py | 6 +++--- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/anomalib/data/avenue.py b/anomalib/data/avenue.py index 8197e374ef..b95a973c5e 100644 --- a/anomalib/data/avenue.py +++ b/anomalib/data/avenue.py @@ -150,15 +150,15 @@ def __init__( split: Split, clip_length_in_frames: int = 1, frames_between_clips: int = 1, - ): + ) -> None: super().__init__(task, transform, clip_length_in_frames, frames_between_clips) - self.root = root - self.gt_dir = gt_dir + self.root = root if isinstance(root, Path) else Path(root) + self.gt_dir = Path(gt_dir) self.split = split self.indexer_cls: Callable = AvenueClipsIndexer - def _setup(self): + def _setup(self) -> None: """Create and assign samples.""" self.samples = make_avenue_dataset(self.root, self.gt_dir, self.split) @@ -209,7 +209,7 @@ def __init__( val_split_mode: ValSplitMode = ValSplitMode.FROM_TEST, val_split_ratio: float = 0.5, seed: Optional[int] = None, - ): + ) -> None: super().__init__( train_batch_size=train_batch_size, eval_batch_size=eval_batch_size, @@ -275,7 +275,7 @@ def prepare_data(self) -> None: self._convert_masks(self.gt_dir) @staticmethod - def _convert_masks(gt_dir: Path): + def _convert_masks(gt_dir: Path) -> None: """Convert mask files to .png. The masks in the Avenue datasets are provided as matlab (.mat) files. To speed up data loading, we convert the diff --git a/anomalib/data/btech.py b/anomalib/data/btech.py index 80a1ffe733..7e6ae08d07 100644 --- a/anomalib/data/btech.py +++ b/anomalib/data/btech.py @@ -170,7 +170,7 @@ def __init__( self.root_category = Path(root) / category self.split = split - def _setup(self): + def _setup(self) -> None: self.samples = make_btech_dataset(path=self.root_category, split=self.split) diff --git a/anomalib/data/folder.py b/anomalib/data/folder.py index 5e29b0bbb2..60e97be839 100644 --- a/anomalib/data/folder.py +++ b/anomalib/data/folder.py @@ -100,7 +100,7 @@ def make_folder_dataset( mask_dir: Optional[Union[str, Path]] = None, split: Optional[Union[Split, str]] = None, extensions: Optional[Tuple[str, ...]] = None, -): +) -> DataFrame: """Make Folder Dataset. Args: @@ -227,7 +227,7 @@ def __init__( self.mask_dir = mask_dir self.extensions = extensions - def _setup(self): + def _setup(self) -> None: """Assign samples.""" self.samples = make_folder_dataset( root=self.root, @@ -304,7 +304,7 @@ def __init__( val_split_mode: ValSplitMode = ValSplitMode.FROM_TEST, val_split_ratio: float = 0.5, seed: Optional[int] = None, - ): + ) -> None: super().__init__( train_batch_size=train_batch_size, eval_batch_size=eval_batch_size, From 2b22dabf702d96455f343d564e261806c54488ec Mon Sep 17 00:00:00 2001 From: Samet Date: Mon, 16 Jan 2023 11:06:52 -0700 Subject: [PATCH 21/85] Add missing type hints to mvtec dataset --- anomalib/data/mvtec.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/anomalib/data/mvtec.py b/anomalib/data/mvtec.py index e5048ac66c..f319d7babd 100644 --- a/anomalib/data/mvtec.py +++ b/anomalib/data/mvtec.py @@ -168,7 +168,7 @@ def __init__( self.root_category = Path(root) / Path(category) self.split = split - def _setup(self): + def _setup(self) -> None: self.samples = make_mvtec_dataset(self.root_category, split=self.split, extensions=IMG_EXTENSIONS) @@ -218,7 +218,7 @@ def __init__( val_split_mode: ValSplitMode = ValSplitMode.SAME_AS_TEST, val_split_ratio: float = 0.5, seed: Optional[int] = None, - ): + ) -> None: super().__init__( train_batch_size=train_batch_size, eval_batch_size=eval_batch_size, From a8a167d9a3f5466a7abb430af52bddb2b09d552a Mon Sep 17 00:00:00 2001 From: Samet Date: Mon, 16 Jan 2023 11:08:18 -0700 Subject: [PATCH 22/85] Add missing type hints to mvtec dataset --- anomalib/data/synthetic.py | 4 ++-- anomalib/data/ucsd_ped.py | 6 +++--- anomalib/data/visa.py | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/anomalib/data/synthetic.py b/anomalib/data/synthetic.py index 11ae7d2a5a..2ddf38180b 100644 --- a/anomalib/data/synthetic.py +++ b/anomalib/data/synthetic.py @@ -109,7 +109,7 @@ class SyntheticAnomalyDataset(AnomalibDataset): source_samples (DataFrame): Normal samples to which the anomalous augmentations will be applied. """ - def __init__(self, task: TaskType, transform: A.Compose, source_samples: DataFrame): + def __init__(self, task: TaskType, transform: A.Compose, source_samples: DataFrame) -> None: super().__init__(task, transform) self.source_samples = source_samples @@ -161,7 +161,7 @@ def _setup(self) -> None: logger.info("Generating synthetic anomalous images for validation set") self.samples = make_synthetic_dataset(self.source_samples, self.im_dir, self.mask_dir, 0.5) - def __del__(self): + def __del__(self) -> None: """Make sure the temporary directory is cleaned up when the dataset object is deleted.""" if self._cleanup: shutil.rmtree(self.root) diff --git a/anomalib/data/ucsd_ped.py b/anomalib/data/ucsd_ped.py index 88cb8f6b7a..9774956c36 100644 --- a/anomalib/data/ucsd_ped.py +++ b/anomalib/data/ucsd_ped.py @@ -164,14 +164,14 @@ def __init__( split: Split, clip_length_in_frames: int = 1, frames_between_clips: int = 1, - ): + ) -> None: super().__init__(task, transform, clip_length_in_frames, frames_between_clips) self.root_category = Path(root) / category self.split = split self.indexer_cls: Callable = UCSDpedClipsIndexer - def _setup(self): + def _setup(self) -> None: """Create and assign samples.""" self.samples = make_ucsd_dataset(self.root_category, self.split) @@ -225,7 +225,7 @@ def __init__( val_split_mode: ValSplitMode = ValSplitMode.FROM_TEST, val_split_ratio: float = 0.5, seed: Optional[int] = None, - ): + ) -> None: super().__init__( train_batch_size=train_batch_size, eval_batch_size=eval_batch_size, diff --git a/anomalib/data/visa.py b/anomalib/data/visa.py index 02ef4ce6b0..f378bee031 100644 --- a/anomalib/data/visa.py +++ b/anomalib/data/visa.py @@ -79,7 +79,7 @@ def __init__( self.root_category = Path(root) / category self.split = split - def _setup(self): + def _setup(self) -> None: self.samples = make_mvtec_dataset(self.root_category, split=self.split, extensions=EXTENSIONS) @@ -129,7 +129,7 @@ def __init__( val_split_mode: ValSplitMode = ValSplitMode.SAME_AS_TEST, val_split_ratio: float = 0.5, seed: Optional[int] = None, - ): + ) -> None: super().__init__( train_batch_size=train_batch_size, eval_batch_size=eval_batch_size, @@ -180,7 +180,7 @@ def prepare_data(self) -> None: logger.info("Downloaded the dataset. Applying train/test split.") self.apply_cls1_split() - def apply_cls1_split(self): + def apply_cls1_split(self) -> None: """Apply the 1-class subset splitting using the fixed split in the csv file. adapted from https://github.com/amazon-science/spot-diff From c87f582824ff98b7b770eeac4688e4c2ec7ceb12 Mon Sep 17 00:00:00 2001 From: Samet Date: Mon, 16 Jan 2023 13:21:13 -0700 Subject: [PATCH 23/85] Changed method signature of forward in AnomalyModule --- anomalib/models/components/base/anomaly_module.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/anomalib/models/components/base/anomaly_module.py b/anomalib/models/components/base/anomaly_module.py index af527f7105..1373ad4291 100644 --- a/anomalib/models/components/base/anomaly_module.py +++ b/anomalib/models/components/base/anomaly_module.py @@ -5,7 +5,7 @@ import logging from abc import ABC -from typing import Any, Dict, List, Optional, OrderedDict +from typing import Any, Dict, List, Optional, OrderedDict, Union from warnings import warn import pytorch_lightning as pl @@ -50,11 +50,11 @@ def __init__(self): self.image_metrics: AnomalibMetricCollection self.pixel_metrics: AnomalibMetricCollection - def forward(self, batch): # pylint: disable=arguments-differ + def forward(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> Any: """Forward-pass input tensor to the module. Args: - batch (Tensor): Input Tensor + batch (Dict[str, Union[str, Tensor]]): Input batch. Returns: Tensor: Output tensor from the model. From b05d20fe9df6fe4f36b6fe6a7d797f9243edb173 Mon Sep 17 00:00:00 2001 From: Samet Date: Mon, 16 Jan 2023 14:38:04 -0700 Subject: [PATCH 24/85] Changed method signature of validation step --- anomalib/models/cfa/lightning_model.py | 6 +-- .../models/components/base/anomaly_module.py | 48 ++++++++++--------- anomalib/models/csflow/lightning_model.py | 11 +++-- 3 files changed, 35 insertions(+), 30 deletions(-) diff --git a/anomalib/models/cfa/lightning_model.py b/anomalib/models/cfa/lightning_model.py index d9b777b75d..6de9691275 100644 --- a/anomalib/models/cfa/lightning_model.py +++ b/anomalib/models/cfa/lightning_model.py @@ -9,7 +9,7 @@ # SPDX-License-Identifier: Apache-2.0 import logging -from typing import List, Optional, Tuple, Union +from typing import Dict, List, Optional, Tuple, Union import torch from omegaconf import DictConfig, ListConfig @@ -86,7 +86,7 @@ def training_step(self, batch) -> STEP_OUTPUT: loss = self.loss_func(distance) return {"loss": loss} - def validation_step(self, batch, batch_idx) -> dict: + def validation_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> Optional[STEP_OUTPUT]: """Validation step for the CFA model. Args: @@ -99,7 +99,6 @@ def validation_step(self, batch, batch_idx) -> dict: batch["anomaly_maps"] = self.model(batch["image"]) return batch - # pylint: disable=unused-argument def backward( self, loss: Tensor, optimizer: Optional[Optimizer], optimizer_idx: Optional[int], *args, **kwargs ) -> None: @@ -110,6 +109,7 @@ def backward( optimizer (Optional[Optimizer]): Optimizer. optimizer_idx (Optional[int]): Optimizer index. """ + del optimizer, optimizer_idx # These variables are not used. # TODO: Investigate why retain_graph is needed. loss.backward(retain_graph=True) diff --git a/anomalib/models/components/base/anomaly_module.py b/anomalib/models/components/base/anomaly_module.py index 1373ad4291..46853795d1 100644 --- a/anomalib/models/components/base/anomaly_module.py +++ b/anomalib/models/components/base/anomaly_module.py @@ -11,6 +11,7 @@ import pytorch_lightning as pl import torch from pytorch_lightning.callbacks.base import Callback +from pytorch_lightning.utilities.types import STEP_OUTPUT from torch import Tensor, nn from torchmetrics import Metric @@ -32,7 +33,7 @@ class AnomalyModule(pl.LightningModule, ABC): Acts as a base class for all the Anomaly Modules in the library. """ - def __init__(self): + def __init__(self) -> None: super().__init__() logger.info("Initializing %s model.", self.__class__.__name__) @@ -61,53 +62,56 @@ def forward(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> Any: """ return self.model(batch) - def validation_step(self, batch, batch_idx) -> dict: # type: ignore # pylint: disable=arguments-differ + def validation_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> Optional[STEP_OUTPUT]: """To be implemented in the subclasses.""" raise NotImplementedError - def predict_step(self, batch: Any, batch_idx: int, _dataloader_idx: Optional[int] = None) -> Any: + def predict_step(self, batch: Any, batch_idx: int, dataloader_idx: int = 0) -> Any: """Step function called during :meth:`~pytorch_lightning.trainer.trainer.Trainer.predict`. By default, it calls :meth:`~pytorch_lightning.core.lightning.LightningModule.forward`. Override to add any processing logic. Args: - batch (Tensor): Current batch + batch (Any): Current batch batch_idx (int): Index of current batch - _dataloader_idx (int): Index of the current dataloader + dataloader_idx (int): Index of the current dataloader Return: Predicted output """ - outputs = self.validation_step(batch, batch_idx) + del batch_idx, dataloader_idx # These variables are not used. + + outputs = self.validation_step(batch) self._post_process(outputs) - outputs["pred_labels"] = outputs["pred_scores"] >= self.image_threshold.value - if "anomaly_maps" in outputs.keys(): - outputs["pred_masks"] = outputs["anomaly_maps"] >= self.pixel_threshold.value - if "pred_boxes" not in outputs.keys(): - outputs["pred_boxes"], outputs["box_scores"] = masks_to_boxes( - outputs["pred_masks"], outputs["anomaly_maps"] - ) - outputs["box_labels"] = [torch.ones(boxes.shape[0]) for boxes in outputs["pred_boxes"]] - # apply thresholding to boxes - if "box_scores" in outputs and "box_labels" not in outputs: - # apply threshold to assign normal/anomalous label to boxes - is_anomalous = [scores > self.pixel_threshold.value for scores in outputs["box_scores"]] - outputs["box_labels"] = [labels.int() for labels in is_anomalous] + if outputs is not None: + outputs["pred_labels"] = outputs["pred_scores"] >= self.image_threshold.value + if "anomaly_maps" in outputs.keys(): + outputs["pred_masks"] = outputs["anomaly_maps"] >= self.pixel_threshold.value + if "pred_boxes" not in outputs.keys(): + outputs["pred_boxes"], outputs["box_scores"] = masks_to_boxes( + outputs["pred_masks"], outputs["anomaly_maps"] + ) + outputs["box_labels"] = [torch.ones(boxes.shape[0]) for boxes in outputs["pred_boxes"]] + # apply thresholding to boxes + if "box_scores" in outputs and "box_labels" not in outputs: + # apply threshold to assign normal/anomalous label to boxes + is_anomalous = [scores > self.pixel_threshold.value for scores in outputs["box_scores"]] + outputs["box_labels"] = [labels.int() for labels in is_anomalous] return outputs - def test_step(self, batch, _): # pylint: disable=arguments-differ + def test_step(self, batch: Dict[str, Union[str, Tensor]], batch_idx: int, *args, **kwargs) -> Optional[STEP_OUTPUT]: """Calls validation_step for anomaly map/score calculation. Args: batch (Tensor): Input batch - _: Index of the batch. + batch_idx (int): Batch index Returns: Dictionary containing images, features, true labels and masks. These are required in `validation_epoch_end` for feature concatenation. """ - return self.predict_step(batch, _) + return self.predict_step(batch, batch_idx) def validation_step_end(self, val_step_outputs): # pylint: disable=arguments-differ """Called at the end of each validation step.""" diff --git a/anomalib/models/csflow/lightning_model.py b/anomalib/models/csflow/lightning_model.py index f4df795780..eac4747ba2 100644 --- a/anomalib/models/csflow/lightning_model.py +++ b/anomalib/models/csflow/lightning_model.py @@ -7,12 +7,13 @@ # SPDX-License-Identifier: Apache-2.0 import logging -from typing import Dict, Tuple, Union +from typing import Dict, List, Optional, Tuple, Union import torch from omegaconf import DictConfig, ListConfig from pytorch_lightning.callbacks import EarlyStopping from pytorch_lightning.utilities.cli import MODEL_REGISTRY +from pytorch_lightning.utilities.types import STEP_OUTPUT from torch import Tensor from anomalib.models.components import AnomalyModule @@ -44,7 +45,7 @@ def __init__( n_coupling_blocks: int, clamp: int, num_channels: int, - ): + ) -> None: super().__init__() self.model: CsFlowModel = CsFlowModel( input_size=input_size, @@ -71,7 +72,7 @@ def training_step(self, batch, _) -> Dict[str, Tensor]: self.log("train_loss", loss.item(), on_epoch=True, prog_bar=True, logger=True) return {"loss": loss} - def validation_step(self, batch, _) -> Dict[str, Tensor]: + def validation_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> Optional[STEP_OUTPUT]: """Validation step for CS Flow. Args: @@ -93,7 +94,7 @@ class CsflowLightning(Csflow): hprams (Union[DictConfig, ListConfig]): Model params """ - def __init__(self, hparams: Union[DictConfig, ListConfig]): + def __init__(self, hparams: Union[DictConfig, ListConfig]) -> None: super().__init__( input_size=hparams.model.input_size, n_coupling_blocks=hparams.model.n_coupling_blocks, @@ -104,7 +105,7 @@ def __init__(self, hparams: Union[DictConfig, ListConfig]): self.hparams: Union[DictConfig, ListConfig] # type: ignore self.save_hyperparameters(hparams) - def configure_callbacks(self): + def configure_callbacks(self) -> List[EarlyStopping]: """Configure model-specific callbacks. Note: From 1226ecc72c88894f8efa3d39ada6461025809582 Mon Sep 17 00:00:00 2001 From: Samet Date: Mon, 16 Jan 2023 14:55:15 -0700 Subject: [PATCH 25/85] Add type hints to cflow --- anomalib/models/cflow/anomaly_map.py | 2 +- anomalib/models/cflow/lightning_model.py | 28 ++++++++++++------------ anomalib/models/cflow/torch_model.py | 6 ++--- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/anomalib/models/cflow/anomaly_map.py b/anomalib/models/cflow/anomaly_map.py index 744cf0b3c6..b56f2cdc27 100644 --- a/anomalib/models/cflow/anomaly_map.py +++ b/anomalib/models/cflow/anomaly_map.py @@ -18,7 +18,7 @@ def __init__( self, image_size: Union[ListConfig, Tuple], pool_layers: List[str], - ): + ) -> None: super().__init__() self.distance = torch.nn.PairwiseDistance(p=2, keepdim=True) self.image_size = image_size if isinstance(image_size, tuple) else tuple(image_size) diff --git a/anomalib/models/cflow/lightning_model.py b/anomalib/models/cflow/lightning_model.py index dbc4f9c62d..7e80a951bd 100644 --- a/anomalib/models/cflow/lightning_model.py +++ b/anomalib/models/cflow/lightning_model.py @@ -6,7 +6,7 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -from typing import List, Tuple, Union +from typing import Dict, List, Optional, Tuple, Union import einops import torch @@ -14,7 +14,9 @@ from omegaconf import DictConfig, ListConfig from pytorch_lightning.callbacks import EarlyStopping from pytorch_lightning.utilities.cli import MODEL_REGISTRY -from torch import optim +from pytorch_lightning.utilities.types import STEP_OUTPUT +from torch import Tensor, optim +from torch.optim import Optimizer from anomalib.models.cflow.torch_model import CflowModel from anomalib.models.cflow.utils import get_logp, positional_encoding_2d @@ -40,7 +42,7 @@ def __init__( clamp_alpha: float = 1.9, permute_soft: bool = False, lr: float = 0.0001, - ): + ) -> None: super().__init__() self.model: CflowModel = CflowModel( @@ -60,7 +62,7 @@ def __init__( # optimizer this is to be addressed later. self.learning_rate = lr - def configure_optimizers(self) -> torch.optim.Optimizer: + def configure_optimizers(self) -> Optimizer: """Configures optimizers for each decoder. Note: @@ -82,7 +84,7 @@ def configure_optimizers(self) -> torch.optim.Optimizer: ) return optimizer - def training_step(self, batch, _): # pylint: disable=arguments-differ + def training_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> STEP_OUTPUT: """Training Step of CFLOW. For each batch, decoder layers are trained with a dynamic fiber batch size. @@ -90,8 +92,7 @@ def training_step(self, batch, _): # pylint: disable=arguments-differ per batch of input images Args: - batch: Input batch - _: Index of the batch. + batch (Dict[str, Union[str, Tensor]]): Input batch Returns: Loss value for the batch @@ -100,7 +101,7 @@ def training_step(self, batch, _): # pylint: disable=arguments-differ opt = self.optimizers() self.model.encoder.eval() - images = batch["image"] + images: Tensor = batch["image"] activation = self.model.encoder(images) avg_loss = torch.zeros([1], dtype=torch.float64).to(images.device) @@ -153,7 +154,7 @@ def training_step(self, batch, _): # pylint: disable=arguments-differ self.log("train_loss", avg_loss.item(), on_epoch=True, prog_bar=True, logger=True) return {"loss": avg_loss} - def validation_step(self, batch, _): # pylint: disable=arguments-differ + def validation_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> Optional[STEP_OUTPUT]: """Validation Step of CFLOW. Similar to the training step, encoder features @@ -161,12 +162,11 @@ def validation_step(self, batch, _): # pylint: disable=arguments-differ map is computed. Args: - batch: Input batch - _: Index of the batch. + batch (Dict[str, Union[str, Tensor]]): Input batch Returns: - Dictionary containing images, anomaly maps, true labels and masks. - These are required in `validation_epoch_end` for feature concatenation. + Dictionary containing images, anomaly maps, true labels and masks. + These are required in `validation_epoch_end` for feature concatenation. """ batch["anomaly_maps"] = self.model(batch["image"]) @@ -197,7 +197,7 @@ def __init__(self, hparams: Union[DictConfig, ListConfig]) -> None: self.hparams: Union[DictConfig, ListConfig] # type: ignore self.save_hyperparameters(hparams) - def configure_callbacks(self): + def configure_callbacks(self) -> List[EarlyStopping]: """Configure model-specific callbacks. Note: diff --git a/anomalib/models/cflow/torch_model.py b/anomalib/models/cflow/torch_model.py index fa86da0932..a45b2df776 100644 --- a/anomalib/models/cflow/torch_model.py +++ b/anomalib/models/cflow/torch_model.py @@ -3,7 +3,7 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -from typing import List, Tuple +from typing import Any, List, Tuple import einops import torch @@ -29,7 +29,7 @@ def __init__( coupling_blocks: int = 8, clamp_alpha: float = 1.9, permute_soft: bool = False, - ): + ) -> None: super().__init__() self.backbone = backbone @@ -59,7 +59,7 @@ def __init__( self.anomaly_map_generator = AnomalyMapGenerator(image_size=tuple(input_size), pool_layers=self.pool_layers) - def forward(self, images): + def forward(self, images) -> Any: """Forward-pass images into the network to extract encoder features and compute probability. Args: From 92f433573163ba6bc717de60efe56a99d8315f67 Mon Sep 17 00:00:00 2001 From: Samet Date: Mon, 16 Jan 2023 14:55:39 -0700 Subject: [PATCH 26/85] Add type hints to csflow --- anomalib/models/csflow/anomaly_map.py | 2 +- anomalib/models/csflow/torch_model.py | 25 ++++++++++++------------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/anomalib/models/csflow/anomaly_map.py b/anomalib/models/csflow/anomaly_map.py index 918cd3c7f4..007d5b633d 100644 --- a/anomalib/models/csflow/anomaly_map.py +++ b/anomalib/models/csflow/anomaly_map.py @@ -26,7 +26,7 @@ class AnomalyMapGenerator(nn.Module): mode (AnomalyMapMode): Anomaly map mode. Defaults to AnomalyMapMode.ALL. """ - def __init__(self, input_dims: Tuple[int, int, int], mode: AnomalyMapMode = AnomalyMapMode.ALL): + def __init__(self, input_dims: Tuple[int, int, int], mode: AnomalyMapMode = AnomalyMapMode.ALL) -> None: super().__init__() self.mode = mode self.input_dims = input_dims diff --git a/anomalib/models/csflow/torch_model.py b/anomalib/models/csflow/torch_model.py index 8c58eddc64..5a86547e4c 100644 --- a/anomalib/models/csflow/torch_model.py +++ b/anomalib/models/csflow/torch_model.py @@ -49,7 +49,7 @@ def __init__( leaky_slope: float = 0.1, batch_norm: bool = False, use_gamma: bool = True, - ): + ) -> None: super().__init__() pad = kernel_size // 2 @@ -199,7 +199,7 @@ class ParallelPermute(InvertibleModule): seed (Optional[float]=None): Seed for the random permutation. """ - def __init__(self, dims_in: List[Tuple[int]], seed: Optional[float] = None): + def __init__(self, dims_in: List[Tuple[int]], seed: Optional[float] = None) -> None: super().__init__(dims_in) self.n_inputs: int = len(dims_in) self.in_channels = [dims_in[i][0] for i in range(self.n_inputs)] @@ -263,7 +263,7 @@ class ParallelGlowCouplingLayer(InvertibleModule): clamp (float): clamp value for the output of the subnet """ - def __init__(self, dims_in: List[Tuple[int]], subnet_args: Dict, clamp: float = 5.0): + def __init__(self, dims_in: List[Tuple[int]], subnet_args: Dict, clamp: float = 5.0) -> None: super().__init__(dims_in) channels = dims_in[0][0] self.ndims = len(dims_in[0]) @@ -279,20 +279,19 @@ def __init__(self, dims_in: List[Tuple[int]], subnet_args: Dict, clamp: float = self.cross_convolution1 = CrossConvolutions(self.split_len1, self.split_len2 * 2, **subnet_args) self.cross_convolution2 = CrossConvolutions(self.split_len2, self.split_len1 * 2, **subnet_args) - def exp(self, input_tensor): + def exp(self, input_tensor: Tensor) -> Tensor: """Exponentiates the input and, optionally, clamps it to avoid numerical issues.""" if self.clamp > 0: return torch.exp(self.log_e(input_tensor)) return torch.exp(input_tensor) - def log_e(self, input_tensor): + def log_e(self, input_tensor: Tensor) -> Tensor: """Returns log of input. And optionally clamped to avoid numerical issues.""" if self.clamp > 0: return self.clamp * 0.636 * torch.atan(input_tensor / self.clamp) return input_tensor - # pylint: disable=unused-argument - def forward(self, input_tensor: List[Tensor], rev=False, jac=True): + def forward(self, input_tensor: List[Tensor], rev=False, jac=True) -> Tuple[List[Tensor], Tensor]: """Applies GLOW coupling for the three scales.""" # Even channel split. The two splits are used by cross-scale convolution to compute scale and transform @@ -374,7 +373,7 @@ def forward(self, input_tensor: List[Tensor], rev=False, jac=True): # Since Jacobians are only used for computing loss and summed in the loss, the idea is to sum them here return [z_dist0, z_dist1, z_dist2], torch.stack([jac0, jac1, jac2], dim=1).sum() - def output_dims(self, input_dims: List[Tuple[int]]): + def output_dims(self, input_dims: List[Tuple[int]]) -> List[Tuple[int]]: """Output dimensions of the module.""" return input_dims @@ -391,7 +390,7 @@ class CrossScaleFlow(nn.Module): def __init__( self, input_dims: Tuple[int, int, int], n_coupling_blocks: int, clamp: float, cross_conv_hidden_channels: int - ): + ) -> None: super().__init__() self.input_dims = input_dims self.n_coupling_blocks = n_coupling_blocks @@ -400,7 +399,7 @@ def __init__( self.cross_conv_hidden_channels = cross_conv_hidden_channels self.graph = self._create_graph() - def _create_graph(self): + def _create_graph(self) -> GraphINN: nodes = [] # 304 is the number of features extracted from EfficientNet-B5 feature extractor nodes.append(InputNode(304, (self.input_dims[1] // 32), (self.input_dims[2] // 32), name="input")) @@ -458,7 +457,7 @@ class MultiScaleFeatureExtractor(nn.Module): input_size (Tuple[int, int]): Size of input image. """ - def __init__(self, n_scales: int, input_size: Tuple[int, int]): + def __init__(self, n_scales: int, input_size: Tuple[int, int]) -> None: super().__init__() self.n_scales = n_scales @@ -509,7 +508,7 @@ def __init__( n_coupling_blocks: int = 4, clamp: int = 3, num_channels: int = 3, - ): + ) -> None: super().__init__() self.input_dims = (num_channels, *input_size) @@ -524,7 +523,7 @@ def __init__( ) self.anomaly_map_generator = AnomalyMapGenerator(input_dims=self.input_dims, mode=AnomalyMapMode.ALL) - def forward(self, images) -> Tuple[Tensor, Tensor]: + def forward(self, images: Tensor) -> Tuple[Tensor, Tensor]: """Forward method of the model. Args: From b6549825007249f11fcf352cf333bb0ffc18725a Mon Sep 17 00:00:00 2001 From: Samet Date: Mon, 16 Jan 2023 15:00:07 -0700 Subject: [PATCH 27/85] Add type hints to dfkde --- anomalib/models/dfkde/lightning_model.py | 14 +++++++------- anomalib/models/dfkde/torch_model.py | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/anomalib/models/dfkde/lightning_model.py b/anomalib/models/dfkde/lightning_model.py index e740996c86..a18d56aba2 100644 --- a/anomalib/models/dfkde/lightning_model.py +++ b/anomalib/models/dfkde/lightning_model.py @@ -4,11 +4,12 @@ # SPDX-License-Identifier: Apache-2.0 import logging -from typing import List, Union +from typing import Dict, List, Optional, Union import torch from omegaconf import DictConfig, ListConfig from pytorch_lightning.utilities.cli import MODEL_REGISTRY +from pytorch_lightning.utilities.types import STEP_OUTPUT from torch import Tensor from anomalib.models.components import AnomalyModule @@ -44,7 +45,7 @@ def __init__( n_pca_components: int = 16, feature_scaling_method: FeatureScalingMethod = FeatureScalingMethod.SCALE, max_training_points: int = 40000, - ): + ) -> None: super().__init__() self.model = DfkdeModel( @@ -59,16 +60,15 @@ def __init__( self.embeddings: List[Tensor] = [] @staticmethod - def configure_optimizers(): # pylint: disable=arguments-differ + def configure_optimizers() -> None: # pylint: disable=arguments-differ """DFKDE doesn't require optimization, therefore returns no optimizers.""" return None - def training_step(self, batch, _batch_idx): # pylint: disable=arguments-differ + def training_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> STEP_OUTPUT: """Training Step of DFKDE. For each batch, features are extracted from the CNN. Args: - batch (Dict[str, Any]): Batch containing image filename, image, label and mask - _batch_idx: Index of the batch. + batch (batch: Dict[str, Union[str, Tensor]]): Batch containing image filename, image, label and mask Returns: Deep CNN features. @@ -92,7 +92,7 @@ def on_validation_start(self) -> None: logger.info("Fitting a KDE model to the embedding collected from the training set.") self.model.classifier.fit(embeddings) - def validation_step(self, batch, _): # pylint: disable=arguments-differ + def validation_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> Optional[STEP_OUTPUT]: """Validation Step of DFKDE. Similar to the training step, features are extracted from the CNN for each batch. diff --git a/anomalib/models/dfkde/torch_model.py b/anomalib/models/dfkde/torch_model.py index dfc71c47a0..bf541f9dca 100644 --- a/anomalib/models/dfkde/torch_model.py +++ b/anomalib/models/dfkde/torch_model.py @@ -41,7 +41,7 @@ def __init__( n_pca_components: int = 16, feature_scaling_method: FeatureScalingMethod = FeatureScalingMethod.SCALE, max_training_points: int = 40000, - ): + ) -> None: super().__init__() self.feature_extractor = FeatureExtractor(backbone=backbone, pre_trained=pre_trained, layers=layers).eval() From 0f6269001db2d134e524e909d6ff8c8b8677fc56 Mon Sep 17 00:00:00 2001 From: Samet Date: Mon, 16 Jan 2023 15:03:52 -0700 Subject: [PATCH 28/85] Add type hints to dfm --- anomalib/models/dfm/lightning_model.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/anomalib/models/dfm/lightning_model.py b/anomalib/models/dfm/lightning_model.py index b90525ca86..0d6f6beb77 100644 --- a/anomalib/models/dfm/lightning_model.py +++ b/anomalib/models/dfm/lightning_model.py @@ -4,11 +4,12 @@ # SPDX-License-Identifier: Apache-2.0 import logging -from typing import List, Tuple, Union +from typing import Dict, List, Optional, Tuple, Union import torch from omegaconf import DictConfig, ListConfig from pytorch_lightning.utilities.cli import MODEL_REGISTRY +from pytorch_lightning.utilities.types import STEP_OUTPUT from torch import Tensor from anomalib.models.components import AnomalyModule @@ -45,7 +46,7 @@ def __init__( pooling_kernel_size: int = 4, pca_level: float = 0.97, score_type: str = "fre", - ): + ) -> None: super().__init__() self.model: DFMModel = DFMModel( @@ -65,7 +66,7 @@ def configure_optimizers() -> None: # pylint: disable=arguments-differ """DFM doesn't require optimization, therefore returns no optimizers.""" return None - def training_step(self, batch, _): # pylint: disable=arguments-differ + def training_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> None: """Training Step of DFM. For each batch, features are extracted from the CNN. @@ -96,13 +97,13 @@ def on_validation_start(self) -> None: logger.info("Fitting a PCA and a Gaussian model to dataset.") self.model.fit(embeddings) - def validation_step(self, batch, _): # pylint: disable=arguments-differ + def validation_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> Optional[STEP_OUTPUT]: """Validation Step of DFM. Similar to the training step, features are extracted from the CNN for each batch. Args: - batch (List[Dict[str, Any]]): Input batch + batch (Dict[str, Union[str, Tensor]]): Input batch Returns: Dictionary containing FRE anomaly scores and anomaly maps. From 8cb435c23dd368d8377320701096aa1d154bd624 Mon Sep 17 00:00:00 2001 From: Samet Date: Mon, 16 Jan 2023 15:04:20 -0700 Subject: [PATCH 29/85] Add type hints to dfm --- anomalib/models/dfkde/lightning_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/anomalib/models/dfkde/lightning_model.py b/anomalib/models/dfkde/lightning_model.py index a18d56aba2..963e203adc 100644 --- a/anomalib/models/dfkde/lightning_model.py +++ b/anomalib/models/dfkde/lightning_model.py @@ -64,7 +64,7 @@ def configure_optimizers() -> None: # pylint: disable=arguments-differ """DFKDE doesn't require optimization, therefore returns no optimizers.""" return None - def training_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> STEP_OUTPUT: + def training_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> None: """Training Step of DFKDE. For each batch, features are extracted from the CNN. Args: From 9e8e37af30a9dbbaf49bfc674d7169a403a0dcab Mon Sep 17 00:00:00 2001 From: Samet Date: Mon, 16 Jan 2023 15:09:56 -0700 Subject: [PATCH 30/85] Add type hints to draem --- anomalib/models/draem/lightning_model.py | 21 +++++++++++---------- anomalib/models/draem/loss.py | 6 +++--- anomalib/models/draem/torch_model.py | 14 +++++++------- 3 files changed, 21 insertions(+), 20 deletions(-) diff --git a/anomalib/models/draem/lightning_model.py b/anomalib/models/draem/lightning_model.py index 63933c8255..44e4a38f42 100644 --- a/anomalib/models/draem/lightning_model.py +++ b/anomalib/models/draem/lightning_model.py @@ -6,12 +6,13 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -from typing import Callable, Dict, Optional, Union +from typing import Callable, Dict, List, Optional, Union import torch from omegaconf import DictConfig, ListConfig from pytorch_lightning.callbacks import EarlyStopping from pytorch_lightning.utilities.cli import MODEL_REGISTRY +from pytorch_lightning.utilities.types import STEP_OUTPUT from torch import Tensor, nn from anomalib.data.utils import Augmenter @@ -33,7 +34,7 @@ class Draem(AnomalyModule): def __init__( self, enable_sspcab: bool = False, sspcab_lambda: float = 0.1, anomaly_source_path: Optional[str] = None - ): + ) -> None: super().__init__() self.augmenter = Augmenter(anomaly_source_path) @@ -47,7 +48,7 @@ def __init__( self.sspcab_loss = nn.MSELoss() self.sspcab_lambda = sspcab_lambda - def setup_sspcab(self): + def setup_sspcab(self) -> None: """Prepare the model for the SSPCAB training step by adding forward hooks for the SSPCAB layer activations.""" def get_activation(name: str) -> Callable: @@ -57,7 +58,7 @@ def get_activation(name: str) -> Callable: name (str): Identifier for the retrieved activations. """ - def hook(_, __, output: Tensor): + def hook(_, __, output: Tensor) -> None: """Hook for retrieving the activations.""" self.sspcab_activations[name] = output @@ -66,14 +67,14 @@ def hook(_, __, output: Tensor): self.model.reconstructive_subnetwork.encoder.mp4.register_forward_hook(get_activation("input")) self.model.reconstructive_subnetwork.encoder.block5.register_forward_hook(get_activation("output")) - def training_step(self, batch, _): # pylint: disable=arguments-differ + def training_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> STEP_OUTPUT: """Training Step of DRAEM. Feeds the original image and the simulated anomaly image through the network and computes the training loss. Args: - batch (Dict[str, Any]): Batch containing image filename, image, label and mask + batch (batch: Dict[str, Union[str, Tensor]]): Batch containing image filename, image, label and mask Returns: Loss dictionary @@ -94,7 +95,7 @@ def training_step(self, batch, _): # pylint: disable=arguments-differ self.log("train_loss", loss.item(), on_epoch=True, prog_bar=True, logger=True) return {"loss": loss} - def validation_step(self, batch, _): + def validation_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> Optional[STEP_OUTPUT]: """Validation step of DRAEM. The Softmax predictions of the anomalous class are used as anomaly map. Args: @@ -115,7 +116,7 @@ class DraemLightning(Draem): hparams (Union[DictConfig, ListConfig]): Model parameters """ - def __init__(self, hparams: Union[DictConfig, ListConfig]): + def __init__(self, hparams: Union[DictConfig, ListConfig]) -> None: super().__init__( enable_sspcab=hparams.model.enable_sspcab, sspcab_lambda=hparams.model.sspcab_lambda, @@ -124,7 +125,7 @@ def __init__(self, hparams: Union[DictConfig, ListConfig]): self.hparams: Union[DictConfig, ListConfig] # type: ignore self.save_hyperparameters(hparams) - def configure_callbacks(self): + def configure_callbacks(self) -> List[EarlyStopping]: """Configure model-specific callbacks. Note: @@ -140,6 +141,6 @@ def configure_callbacks(self): ) return [early_stopping] - def configure_optimizers(self): # pylint: disable=arguments-differ + def configure_optimizers(self): """Configure the Adam optimizer.""" return torch.optim.Adam(params=self.model.parameters(), lr=self.hparams.model.lr) diff --git a/anomalib/models/draem/loss.py b/anomalib/models/draem/loss.py index 1e05bfa073..f035af769f 100644 --- a/anomalib/models/draem/loss.py +++ b/anomalib/models/draem/loss.py @@ -4,7 +4,7 @@ # SPDX-License-Identifier: Apache-2.0 from kornia.losses import FocalLoss, SSIMLoss -from torch import nn +from torch import Tensor, nn class DraemLoss(nn.Module): @@ -14,14 +14,14 @@ class DraemLoss(nn.Module): image, and the Structural Similarity loss between the predicted and GT anomaly masks. """ - def __init__(self): + def __init__(self) -> None: super().__init__() self.l2_loss = nn.modules.loss.MSELoss() self.focal_loss = FocalLoss(alpha=1, reduction="mean") self.ssim_loss = SSIMLoss(window_size=11) - def forward(self, input_image, reconstruction, anomaly_mask, prediction): + def forward(self, input_image: Tensor, reconstruction: Tensor, anomaly_mask: Tensor, prediction: Tensor) -> Tensor: """Compute the loss over a batch for the DRAEM model.""" l2_loss_val = self.l2_loss(reconstruction, input_image) focal_loss_val = self.focal_loss(prediction, anomaly_mask.squeeze(1).long()) diff --git a/anomalib/models/draem/torch_model.py b/anomalib/models/draem/torch_model.py index e8d92bcb42..2ca7608a89 100644 --- a/anomalib/models/draem/torch_model.py +++ b/anomalib/models/draem/torch_model.py @@ -20,7 +20,7 @@ class DraemModel(nn.Module): """DRAEM PyTorch model consisting of the reconstructive and discriminative sub networks.""" - def __init__(self, sspcab: bool = False): + def __init__(self, sspcab: bool = False) -> None: super().__init__() self.reconstructive_subnetwork = ReconstructiveSubNetwork(sspcab=sspcab) self.discriminative_subnetwork = DiscriminativeSubNetwork(in_channels=6, out_channels=2) @@ -52,7 +52,7 @@ class ReconstructiveSubNetwork(nn.Module): base_width (int): Base dimensionality of the layers of the autoencoder. """ - def __init__(self, in_channels: int = 3, out_channels: int = 3, base_width=128, sspcab: bool = False): + def __init__(self, in_channels: int = 3, out_channels: int = 3, base_width=128, sspcab: bool = False) -> None: super().__init__() self.encoder = EncoderReconstructive(in_channels, base_width, sspcab=sspcab) self.decoder = DecoderReconstructive(base_width, out_channels=out_channels) @@ -80,7 +80,7 @@ class DiscriminativeSubNetwork(nn.Module): base_width (int): Base dimensionality of the layers of the autoencoder. """ - def __init__(self, in_channels: int = 3, out_channels: int = 3, base_width: int = 64): + def __init__(self, in_channels: int = 3, out_channels: int = 3, base_width: int = 64) -> None: super().__init__() self.encoder_segment = EncoderDiscriminative(in_channels, base_width) self.decoder_segment = DecoderDiscriminative(base_width, out_channels=out_channels) @@ -108,7 +108,7 @@ class EncoderDiscriminative(nn.Module): base_width (int): Base dimensionality of the layers of the autoencoder. """ - def __init__(self, in_channels: int, base_width: int): + def __init__(self, in_channels: int, base_width: int) -> None: super().__init__() self.block1 = nn.Sequential( nn.Conv2d(in_channels, base_width, kernel_size=3, padding=1), @@ -197,7 +197,7 @@ class DecoderDiscriminative(nn.Module): out_channels (int): Number of output channels. """ - def __init__(self, base_width: int, out_channels: int = 1): + def __init__(self, base_width: int, out_channels: int = 1) -> None: super().__init__() self.up_b = nn.Sequential( @@ -323,7 +323,7 @@ class EncoderReconstructive(nn.Module): base_width (int): Base dimensionality of the layers of the autoencoder. """ - def __init__(self, in_channels: int, base_width: int, sspcab: bool = False): + def __init__(self, in_channels: int, base_width: int, sspcab: bool = False) -> None: super().__init__() self.block1 = nn.Sequential( nn.Conv2d(in_channels, base_width, kernel_size=3, padding=1), @@ -402,7 +402,7 @@ class DecoderReconstructive(nn.Module): out_channels (int): Number of output channels. """ - def __init__(self, base_width: int, out_channels: int = 1): + def __init__(self, base_width: int, out_channels: int = 1) -> None: super().__init__() self.up1 = nn.Sequential( From 7fb21d3015c4bc182acc9b5594659535af1fcff8 Mon Sep 17 00:00:00 2001 From: Samet Date: Mon, 16 Jan 2023 15:13:11 -0700 Subject: [PATCH 31/85] Add type hints to fastflow --- anomalib/models/fastflow/anomaly_map.py | 2 +- anomalib/models/fastflow/lightning_model.py | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/anomalib/models/fastflow/anomaly_map.py b/anomalib/models/fastflow/anomaly_map.py index ea1ecdabd1..c001222f0d 100644 --- a/anomalib/models/fastflow/anomaly_map.py +++ b/anomalib/models/fastflow/anomaly_map.py @@ -14,7 +14,7 @@ class AnomalyMapGenerator(nn.Module): """Generate Anomaly Heatmap.""" - def __init__(self, input_size: Union[ListConfig, Tuple]): + def __init__(self, input_size: Union[ListConfig, Tuple]) -> None: super().__init__() self.input_size = input_size if isinstance(input_size, tuple) else tuple(input_size) diff --git a/anomalib/models/fastflow/lightning_model.py b/anomalib/models/fastflow/lightning_model.py index 9ae64e4cfb..520a2385c3 100644 --- a/anomalib/models/fastflow/lightning_model.py +++ b/anomalib/models/fastflow/lightning_model.py @@ -3,13 +3,14 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -from typing import Tuple, Union +from typing import Dict, Optional, Tuple, Union import torch from omegaconf import DictConfig, ListConfig from pytorch_lightning.callbacks import EarlyStopping from pytorch_lightning.utilities.cli import MODEL_REGISTRY -from torch import optim +from pytorch_lightning.utilities.types import STEP_OUTPUT +from torch import Tensor, optim from anomalib.models.components import AnomalyModule from anomalib.models.fastflow.loss import FastflowLoss @@ -37,7 +38,7 @@ def __init__( flow_steps: int = 8, conv3x3_only: bool = False, hidden_ratio: float = 1.0, - ): + ) -> None: super().__init__() self.model = FastflowModel( @@ -50,11 +51,11 @@ def __init__( ) self.loss = FastflowLoss() - def training_step(self, batch, _): # pylint: disable=arguments-differ + def training_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> STEP_OUTPUT: """Forward-pass input and return the loss. Args: - batch (Tensor): Input batch + batch (batch: Dict[str, Union[str, Tensor]]): Input batch _batch_idx: Index of the batch. Returns: @@ -65,15 +66,14 @@ def training_step(self, batch, _): # pylint: disable=arguments-differ self.log("train_loss", loss.item(), on_epoch=True, prog_bar=True, logger=True) return {"loss": loss} - def validation_step(self, batch, _): # pylint: disable=arguments-differ + def validation_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> Optional[STEP_OUTPUT]: """Forward-pass the input and return the anomaly map. Args: - batch (Tensor): Input batch - _batch_idx: Index of the batch. + batch (Dict[str, Union[str, Tensor]]): Input batch Returns: - dict: batch dictionary containing anomaly-maps. + Optional[STEP_OUTPUT]: batch dictionary containing anomaly-maps. """ anomaly_maps = self.model(batch["image"]) batch["anomaly_maps"] = anomaly_maps From f78cf4a79ec5f0fca1c2d3d7181dfea339102e29 Mon Sep 17 00:00:00 2001 From: Samet Date: Mon, 16 Jan 2023 15:19:08 -0700 Subject: [PATCH 32/85] Add type hints to ganmaly --- anomalib/models/ganomaly/lightning_model.py | 22 ++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/anomalib/models/ganomaly/lightning_model.py b/anomalib/models/ganomaly/lightning_model.py index e4e7a84b1d..e2af460a3a 100644 --- a/anomalib/models/ganomaly/lightning_model.py +++ b/anomalib/models/ganomaly/lightning_model.py @@ -7,12 +7,13 @@ # SPDX-License-Identifier: Apache-2.0 import logging -from typing import Dict, List, Tuple, Union +from typing import Dict, List, Optional, Tuple, Union import torch from omegaconf import DictConfig, ListConfig from pytorch_lightning.callbacks import EarlyStopping from pytorch_lightning.utilities.cli import MODEL_REGISTRY +from pytorch_lightning.utilities.types import STEP_OUTPUT from torch import Tensor, optim from anomalib.models.components import AnomalyModule @@ -53,7 +54,7 @@ def __init__( lr: float = 0.0002, beta1: float = 0.5, beta2: float = 0.999, - ): + ) -> None: super().__init__() self.model: GanomalyModel = GanomalyModel( @@ -80,7 +81,7 @@ def __init__( self.beta1 = beta1 self.beta2 = beta2 - def _reset_min_max(self): + def _reset_min_max(self) -> None: """Resets min_max scores.""" self.min_scores = torch.tensor(float("inf"), dtype=torch.float32) # pylint: disable=not-callable self.max_scores = torch.tensor(float("-inf"), dtype=torch.float32) # pylint: disable=not-callable @@ -109,15 +110,14 @@ def configure_optimizers(self) -> List[optim.Optimizer]: ) return [optimizer_d, optimizer_g] - def training_step(self, batch, _, optimizer_idx): # pylint: disable=arguments-differ + def training_step(self, batch: Dict[str, Union[str, Tensor]], optimizer_idx: int, *args, **kwargs) -> STEP_OUTPUT: """Training step. Args: - batch (Dict): Input batch containing images. - optimizer_idx (int): Optimizer which is being called for current training step. + batch (Dict[str, Union[str, Tensor]]): Input batch containing images. Returns: - Dict[str, Tensor]: Loss + STEP_OUTPUT: Loss """ # forward pass padded, fake, latent_i, latent_o = self.model(batch["image"]) @@ -138,7 +138,7 @@ def on_validation_start(self) -> None: self._reset_min_max() return super().on_validation_start() - def validation_step(self, batch, _) -> Dict[str, Tensor]: # type: ignore # pylint: disable=arguments-differ + def validation_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> Optional[STEP_OUTPUT]: """Update min and max scores from the current step. Args: @@ -165,9 +165,9 @@ def on_test_start(self) -> None: self._reset_min_max() return super().on_test_start() - def test_step(self, batch, _): + def test_step(self, batch: Dict[str, Union[str, Tensor]], batch_idx: int, *args, **kwargs) -> Optional[STEP_OUTPUT]: """Update min and max scores from the current step.""" - super().test_step(batch, _) + super().test_step(batch, batch_idx) self.max_scores = max(self.max_scores, torch.max(batch["pred_scores"])) self.min_scores = min(self.min_scores, torch.min(batch["pred_scores"])) return batch @@ -221,7 +221,7 @@ def __init__(self, hparams: Union[DictConfig, ListConfig]) -> None: self.hparams: Union[DictConfig, ListConfig] # type: ignore self.save_hyperparameters(hparams) - def configure_callbacks(self): + def configure_callbacks(self) -> List[EarlyStopping]: """Configure model-specific callbacks. Note: From af51361260a89154f4340caac0601e63c41d7872 Mon Sep 17 00:00:00 2001 From: Samet Date: Mon, 16 Jan 2023 15:26:32 -0700 Subject: [PATCH 33/85] Add type hints to padim and patchcore --- anomalib/models/padim/anomaly_map.py | 4 ++-- anomalib/models/padim/lightning_model.py | 12 ++++++------ anomalib/models/padim/torch_model.py | 2 +- anomalib/models/patchcore/anomaly_map.py | 5 ++--- anomalib/models/patchcore/lightning_model.py | 14 +++++++------- 5 files changed, 18 insertions(+), 19 deletions(-) diff --git a/anomalib/models/padim/anomaly_map.py b/anomalib/models/padim/anomaly_map.py index b706cbad14..4d43f49b8d 100644 --- a/anomalib/models/padim/anomaly_map.py +++ b/anomalib/models/padim/anomaly_map.py @@ -21,7 +21,7 @@ class AnomalyMapGenerator(nn.Module): sigma (int, optional): Standard deviation for Gaussian Kernel. Defaults to 4. """ - def __init__(self, image_size: Union[ListConfig, Tuple], sigma: int = 4): + def __init__(self, image_size: Union[ListConfig, Tuple], sigma: int = 4) -> None: super().__init__() self.image_size = image_size if isinstance(image_size, tuple) else tuple(image_size) kernel_size = 2 * int(4.0 * sigma + 0.5) + 1 @@ -109,7 +109,7 @@ def compute_anomaly_map(self, embedding: Tensor, mean: Tensor, inv_covariance: T return smoothed_anomaly_map - def forward(self, **kwargs): + def forward(self, **kwargs) -> Tensor: """Returns anomaly_map. Expects `embedding`, `mean` and `covariance` keywords to be passed explicitly. diff --git a/anomalib/models/padim/lightning_model.py b/anomalib/models/padim/lightning_model.py index e9b1e70278..484ad1580d 100644 --- a/anomalib/models/padim/lightning_model.py +++ b/anomalib/models/padim/lightning_model.py @@ -7,11 +7,12 @@ # SPDX-License-Identifier: Apache-2.0 import logging -from typing import List, Optional, Tuple, Union +from typing import Dict, List, Optional, Tuple, Union import torch from omegaconf import DictConfig, ListConfig from pytorch_lightning.utilities.cli import MODEL_REGISTRY +from pytorch_lightning.utilities.types import STEP_OUTPUT from torch import Tensor from anomalib.models.components import AnomalyModule @@ -42,7 +43,7 @@ def __init__( backbone: str, pre_trained: bool = True, n_features: Optional[int] = None, - ): + ) -> None: super().__init__() self.layers = layers @@ -58,11 +59,11 @@ def __init__( self.embeddings: List[Tensor] = [] @staticmethod - def configure_optimizers(): # pylint: disable=arguments-differ + def configure_optimizers() -> None: # pylint: disable=arguments-differ """PADIM doesn't require optimization, therefore returns no optimizers.""" return None - def training_step(self, batch, _batch_idx): # pylint: disable=arguments-differ + def training_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> None: """Training Step of PADIM. For each batch, hierarchical features are extracted from the CNN. Args: @@ -92,14 +93,13 @@ def on_validation_start(self) -> None: logger.info("Fitting a Gaussian to the embedding collected from the training set.") self.stats = self.model.gaussian.fit(embeddings) - def validation_step(self, batch, _): # pylint: disable=arguments-differ + def validation_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> Optional[STEP_OUTPUT]: """Validation Step of PADIM. Similar to the training step, hierarchical features are extracted from the CNN for each batch. Args: batch: Input batch - _: Index of the batch. Returns: Dictionary containing images, features, true labels and masks. diff --git a/anomalib/models/padim/torch_model.py b/anomalib/models/padim/torch_model.py index 98cdada3c2..ac3cd85198 100644 --- a/anomalib/models/padim/torch_model.py +++ b/anomalib/models/padim/torch_model.py @@ -64,7 +64,7 @@ def __init__( backbone: str = "resnet18", pre_trained: bool = True, n_features: Optional[int] = None, - ): + ) -> None: super().__init__() self.tiler: Optional[Tiler] = None diff --git a/anomalib/models/patchcore/anomaly_map.py b/anomalib/models/patchcore/anomaly_map.py index 7b29f83ef3..e9fb6ee024 100644 --- a/anomalib/models/patchcore/anomaly_map.py +++ b/anomalib/models/patchcore/anomaly_map.py @@ -5,7 +5,6 @@ from typing import Tuple, Union -import torch import torch.nn.functional as F from omegaconf import ListConfig from torch import Tensor, nn @@ -26,14 +25,14 @@ def __init__( kernel_size = 2 * int(4.0 * sigma + 0.5) + 1 self.blur = GaussianBlur2d(kernel_size=(kernel_size, kernel_size), sigma=(sigma, sigma), channels=1) - def compute_anomaly_map(self, patch_scores: Tensor) -> torch.Tensor: + def compute_anomaly_map(self, patch_scores: Tensor) -> Tensor: """Pixel Level Anomaly Heatmap. Args: patch_scores (Tensor): Patch-level anomaly scores Returns: - torch.Tensor: Map of the pixel-level anomaly scores + Tensor: Map of the pixel-level anomaly scores """ anomaly_map = F.interpolate(patch_scores, size=(self.input_size[0], self.input_size[1])) anomaly_map = self.blur(anomaly_map) diff --git a/anomalib/models/patchcore/lightning_model.py b/anomalib/models/patchcore/lightning_model.py index 5a5583d42b..1380de2de5 100644 --- a/anomalib/models/patchcore/lightning_model.py +++ b/anomalib/models/patchcore/lightning_model.py @@ -7,11 +7,12 @@ # SPDX-License-Identifier: Apache-2.0 import logging -from typing import List, Tuple, Union +from typing import Dict, List, Optional, Tuple, Union import torch from omegaconf import DictConfig, ListConfig from pytorch_lightning.utilities.cli import MODEL_REGISTRY +from pytorch_lightning.utilities.types import STEP_OUTPUT from torch import Tensor from anomalib.models.components import AnomalyModule @@ -63,11 +64,11 @@ def configure_optimizers(self) -> None: """ return None - def training_step(self, batch, _batch_idx): # pylint: disable=arguments-differ + def training_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> None: """Generate feature embedding of the batch. Args: - batch (Dict[str, Any]): Batch containing image filename, image, label and mask + batch (Dict[str, Union[str, Tensor]]): Batch containing image filename, image, label and mask _batch_idx (int): Batch Index Returns: @@ -93,13 +94,12 @@ def on_validation_start(self) -> None: logger.info("Applying core-set subsampling to get the embedding.") self.model.subsample_embedding(embeddings, self.coreset_sampling_ratio) - def validation_step(self, batch, _): # pylint: disable=arguments-differ + def validation_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> Optional[STEP_OUTPUT]: """Get batch of anomaly maps from input image batch. Args: - batch (Dict[str, Any]): Batch containing image filename, - image, label and mask - _ (int): Batch Index + batch (Dict[str, Union[str, Tensor]]): Batch containing image filename, + image, label and mask Returns: Dict[str, Any]: Image filenames, test images, GT and predicted label/masks From fa679f099e47332b33282278381f2f1d297a5d18 Mon Sep 17 00:00:00 2001 From: Samet Date: Mon, 16 Jan 2023 15:33:37 -0700 Subject: [PATCH 34/85] Add type hints to the rest of the models --- anomalib/models/fastflow/lightning_model.py | 4 ++-- .../models/reverse_distillation/anomaly_map.py | 2 +- .../reverse_distillation/lightning_model.py | 17 +++++++++-------- .../models/reverse_distillation/torch_model.py | 2 +- anomalib/models/rkde/lightning_model.py | 16 +++++++++------- anomalib/models/rkde/torch_model.py | 2 +- 6 files changed, 23 insertions(+), 20 deletions(-) diff --git a/anomalib/models/fastflow/lightning_model.py b/anomalib/models/fastflow/lightning_model.py index 520a2385c3..0fbfeb682a 100644 --- a/anomalib/models/fastflow/lightning_model.py +++ b/anomalib/models/fastflow/lightning_model.py @@ -3,7 +3,7 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -from typing import Dict, Optional, Tuple, Union +from typing import Dict, List, Optional, Tuple, Union import torch from omegaconf import DictConfig, ListConfig @@ -99,7 +99,7 @@ def __init__(self, hparams: Union[DictConfig, ListConfig]) -> None: self.hparams: Union[DictConfig, ListConfig] # type: ignore self.save_hyperparameters(hparams) - def configure_callbacks(self): + def configure_callbacks(self) -> List[EarlyStopping]: """Configure model-specific callbacks. Note: diff --git a/anomalib/models/reverse_distillation/anomaly_map.py b/anomalib/models/reverse_distillation/anomaly_map.py index c245981cba..11cd0b4cb9 100644 --- a/anomalib/models/reverse_distillation/anomaly_map.py +++ b/anomalib/models/reverse_distillation/anomaly_map.py @@ -31,7 +31,7 @@ class AnomalyMapGenerator(nn.Module): ValueError: In case modes other than multiply and add are passed. """ - def __init__(self, image_size: Union[ListConfig, Tuple], sigma: int = 4, mode: str = "multiply"): + def __init__(self, image_size: Union[ListConfig, Tuple], sigma: int = 4, mode: str = "multiply") -> None: super().__init__() self.image_size = image_size if isinstance(image_size, tuple) else tuple(image_size) self.sigma = sigma diff --git a/anomalib/models/reverse_distillation/lightning_model.py b/anomalib/models/reverse_distillation/lightning_model.py index 209b9ff13a..7b7558679a 100644 --- a/anomalib/models/reverse_distillation/lightning_model.py +++ b/anomalib/models/reverse_distillation/lightning_model.py @@ -6,11 +6,12 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -from typing import Dict, List, Tuple, Union +from typing import Dict, List, Optional, Tuple, Union from omegaconf import DictConfig, ListConfig from pytorch_lightning.callbacks import EarlyStopping from pytorch_lightning.utilities.cli import MODEL_REGISTRY +from pytorch_lightning.utilities.types import STEP_OUTPUT from torch import Tensor, optim from anomalib.models.components import AnomalyModule @@ -40,7 +41,7 @@ def __init__( beta1: float, beta2: float, pre_trained: bool = True, - ): + ) -> None: super().__init__() self.model = ReverseDistillationModel( backbone=backbone, @@ -56,7 +57,7 @@ def __init__( self.beta1 = beta1 self.beta2 = beta2 - def configure_optimizers(self): + def configure_optimizers(self) -> optim.Adam: """Configures optimizers for decoder and bottleneck. Note: @@ -74,7 +75,7 @@ def configure_optimizers(self): betas=(self.beta1, self.beta2), ) - def training_step(self, batch, _) -> Dict[str, Tensor]: # type: ignore + def training_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> STEP_OUTPUT: """Training Step of Reverse Distillation Model. Features are extracted from three layers of the Encoder model. These are passed to the bottleneck layer @@ -82,7 +83,7 @@ def training_step(self, batch, _) -> Dict[str, Tensor]: # type: ignore encoder and decoder features. Args: - batch (Tensor): Input batch + batch (batch: Dict[str, Union[str, Tensor]]): Input batch _: Index of the batch. Returns: @@ -92,7 +93,7 @@ def training_step(self, batch, _) -> Dict[str, Tensor]: # type: ignore self.log("train_loss", loss.item(), on_epoch=True, prog_bar=True, logger=True) return {"loss": loss} - def validation_step(self, batch, _): # pylint: disable=arguments-differ + def validation_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> Optional[STEP_OUTPUT]: """Validation Step of Reverse Distillation Model. Similar to the training step, encoder/decoder features are extracted from the CNN for each batch, and @@ -117,7 +118,7 @@ class ReverseDistillationLightning(ReverseDistillation): hparams(Union[DictConfig, ListConfig]): Model parameters """ - def __init__(self, hparams: Union[DictConfig, ListConfig]): + def __init__(self, hparams: Union[DictConfig, ListConfig]) -> None: super().__init__( input_size=hparams.model.input_size, backbone=hparams.model.backbone, @@ -131,7 +132,7 @@ def __init__(self, hparams: Union[DictConfig, ListConfig]): self.hparams: Union[DictConfig, ListConfig] # type: ignore self.save_hyperparameters(hparams) - def configure_callbacks(self): + def configure_callbacks(self) -> List[EarlyStopping]: """Configure model-specific callbacks. Note: diff --git a/anomalib/models/reverse_distillation/torch_model.py b/anomalib/models/reverse_distillation/torch_model.py index 312d4fd2ab..1700c7ada3 100644 --- a/anomalib/models/reverse_distillation/torch_model.py +++ b/anomalib/models/reverse_distillation/torch_model.py @@ -34,7 +34,7 @@ def __init__( layers: List[str], anomaly_map_mode: str, pre_trained: bool = True, - ): + ) -> None: super().__init__() self.tiler: Optional[Tiler] = None diff --git a/anomalib/models/rkde/lightning_model.py b/anomalib/models/rkde/lightning_model.py index 3082eb526e..dc867b4707 100644 --- a/anomalib/models/rkde/lightning_model.py +++ b/anomalib/models/rkde/lightning_model.py @@ -4,11 +4,12 @@ # SPDX-License-Identifier: Apache-2.0 import logging -from typing import List, Union +from typing import Dict, List, Optional, Union import torch from omegaconf import DictConfig, ListConfig from pytorch_lightning.utilities.cli import MODEL_REGISTRY +from pytorch_lightning.utilities.types import STEP_OUTPUT from torch import Tensor from anomalib.models.components import AnomalyModule @@ -47,7 +48,7 @@ def __init__( n_pca_components: int = 16, feature_scaling_method: FeatureScalingMethod = FeatureScalingMethod.SCALE, max_training_points: int = 40000, - ): + ) -> None: super().__init__() self.model: RkdeModel = RkdeModel( @@ -63,15 +64,15 @@ def __init__( self.embeddings: List[Tensor] = [] @staticmethod - def configure_optimizers(): + def configure_optimizers() -> None: """RKDE doesn't require optimization, therefore returns no optimizers.""" return None - def training_step(self, batch, _batch_idx): + def training_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> None: """Training Step of RKDE. For each batch, features are extracted from the CNN. Args: - batch (Dict[str, Any]): Batch containing image filename, image, label and mask + batch (Dict[str, Union[str, Tensor]]): Batch containing image filename, image, label and mask _batch_idx: Index of the batch. Returns: @@ -87,7 +88,7 @@ def on_validation_start(self) -> None: logger.info("Fitting a KDE model to the embedding collected from the training set.") self.model.fit(embeddings) - def validation_step(self, batch, _): + def validation_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> Optional[STEP_OUTPUT]: """Validation Step of RKde. Similar to the training step, features are extracted from the CNN for each batch. @@ -103,7 +104,8 @@ def validation_step(self, batch, _): boxes, scores = self.model(batch["image"]) # convert batched predictions to list format - batch_size = batch["image"].shape[0] + image: Tensor = batch["image"] + batch_size = image.shape[0] indices = boxes[:, 0] batch["pred_boxes"] = [boxes[indices == i, 1:] for i in range(batch_size)] batch["box_scores"] = [scores[indices == i] for i in range(batch_size)] diff --git a/anomalib/models/rkde/torch_model.py b/anomalib/models/rkde/torch_model.py index 711858bbb0..a8a135456d 100644 --- a/anomalib/models/rkde/torch_model.py +++ b/anomalib/models/rkde/torch_model.py @@ -47,7 +47,7 @@ def __init__( n_pca_components: int = 16, feature_scaling_method: FeatureScalingMethod = FeatureScalingMethod.SCALE, max_training_points: int = 40000, - ): + ) -> None: super().__init__() self.region_extractor = RegionExtractor( From 6667af7e1687f389bba206c7719a73499197f8c2 Mon Sep 17 00:00:00 2001 From: Samet Date: Mon, 16 Jan 2023 15:43:10 -0700 Subject: [PATCH 35/85] Add type hints to stfpm --- anomalib/models/stfpm/lightning_model.py | 16 ++++++++-------- anomalib/models/stfpm/loss.py | 2 +- anomalib/models/stfpm/torch_model.py | 6 +++--- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/anomalib/models/stfpm/lightning_model.py b/anomalib/models/stfpm/lightning_model.py index 545a6e45b9..944361fc2f 100644 --- a/anomalib/models/stfpm/lightning_model.py +++ b/anomalib/models/stfpm/lightning_model.py @@ -6,13 +6,14 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -from typing import List, Tuple, Union +from typing import Dict, List, Optional, Tuple, Union import torch from omegaconf import DictConfig, ListConfig from pytorch_lightning.callbacks import EarlyStopping from pytorch_lightning.utilities.cli import MODEL_REGISTRY -from torch import optim +from pytorch_lightning.utilities.types import STEP_OUTPUT +from torch import Tensor, optim from anomalib.models.components import AnomalyModule from anomalib.models.stfpm.loss import STFPMLoss @@ -36,7 +37,7 @@ def __init__( input_size: Tuple[int, int], backbone: str, layers: List[str], - ): + ) -> None: super().__init__() self.model = STFPMModel( @@ -46,7 +47,7 @@ def __init__( ) self.loss = STFPMLoss() - def training_step(self, batch, _): # pylint: disable=arguments-differ + def training_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> STEP_OUTPUT: """Training Step of STFPM. For each batch, teacher and student and teacher features are extracted from the CNN. @@ -64,15 +65,14 @@ def training_step(self, batch, _): # pylint: disable=arguments-differ self.log("train_loss", loss.item(), on_epoch=True, prog_bar=True, logger=True) return {"loss": loss} - def validation_step(self, batch, _): # pylint: disable=arguments-differ + def validation_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> Optional[STEP_OUTPUT]: """Validation Step of STFPM. Similar to the training step, student/teacher features are extracted from the CNN for each batch, and anomaly map is computed. Args: - batch (Tensor): Input batch - _: Index of the batch. + batch (Dict[str, Union[str, Tensor]]): Input batch Returns: Dictionary containing images, anomaly maps, true labels and masks. @@ -99,7 +99,7 @@ def __init__(self, hparams: Union[DictConfig, ListConfig]) -> None: self.hparams: Union[DictConfig, ListConfig] # type: ignore self.save_hyperparameters(hparams) - def configure_callbacks(self): + def configure_callbacks(self) -> List[EarlyStopping]: """Configure model-specific callbacks. Note: diff --git a/anomalib/models/stfpm/loss.py b/anomalib/models/stfpm/loss.py index c675baad5c..ee130835a6 100644 --- a/anomalib/models/stfpm/loss.py +++ b/anomalib/models/stfpm/loss.py @@ -30,7 +30,7 @@ class STFPMLoss(nn.Module): tensor(51.2015, grad_fn=) """ - def __init__(self): + def __init__(self) -> None: super().__init__() self.mse_loss = nn.MSELoss(reduction="sum") diff --git a/anomalib/models/stfpm/torch_model.py b/anomalib/models/stfpm/torch_model.py index 8a7da966e0..b8428c12f3 100644 --- a/anomalib/models/stfpm/torch_model.py +++ b/anomalib/models/stfpm/torch_model.py @@ -3,7 +3,7 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -from typing import Dict, List, Optional, Tuple +from typing import Dict, List, Optional, Tuple, Union from torch import Tensor, nn @@ -26,7 +26,7 @@ def __init__( layers: List[str], input_size: Tuple[int, int], backbone: str = "resnet18", - ): + ) -> None: super().__init__() self.tiler: Optional[Tiler] = None @@ -48,7 +48,7 @@ def __init__( image_size = input_size self.anomaly_map_generator = AnomalyMapGenerator(image_size=tuple(image_size)) - def forward(self, images): + def forward(self, images: Tensor) -> Union[Tuple[Dict[str, Tensor], Dict[str, Tensor]], Tensor]: """Forward-pass images into the network. During the training mode the model extracts the features from the teacher and student networks. From c9ea131a46335c4b5752c762034b90782627e400 Mon Sep 17 00:00:00 2001 From: Samet Date: Mon, 16 Jan 2023 23:52:44 -0700 Subject: [PATCH 36/85] Add type hints to validation step --- .../models/components/base/anomaly_module.py | 73 +++++++++++-------- 1 file changed, 41 insertions(+), 32 deletions(-) diff --git a/anomalib/models/components/base/anomaly_module.py b/anomalib/models/components/base/anomaly_module.py index 46853795d1..c083eb8774 100644 --- a/anomalib/models/components/base/anomaly_module.py +++ b/anomalib/models/components/base/anomaly_module.py @@ -5,13 +5,13 @@ import logging from abc import ABC -from typing import Any, Dict, List, Optional, OrderedDict, Union +from typing import Any, Dict, List, Optional, OrderedDict, Tuple, Union from warnings import warn import pytorch_lightning as pl import torch from pytorch_lightning.callbacks.base import Callback -from pytorch_lightning.utilities.types import STEP_OUTPUT +from pytorch_lightning.utilities.types import EPOCH_OUTPUT, STEP_OUTPUT from torch import Tensor, nn from torchmetrics import Metric @@ -62,7 +62,7 @@ def forward(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> Any: """ return self.model(batch) - def validation_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> Optional[STEP_OUTPUT]: + def validation_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> STEP_OUTPUT: """To be implemented in the subclasses.""" raise NotImplementedError @@ -82,9 +82,9 @@ def predict_step(self, batch: Any, batch_idx: int, dataloader_idx: int = 0) -> A """ del batch_idx, dataloader_idx # These variables are not used. - outputs = self.validation_step(batch) + outputs: Union[torch.Tensor, Dict[str, Any]] = self.validation_step(batch) self._post_process(outputs) - if outputs is not None: + if outputs is not None and isinstance(outputs, dict): outputs["pred_labels"] = outputs["pred_scores"] >= self.image_threshold.value if "anomaly_maps" in outputs.keys(): outputs["pred_masks"] = outputs["anomaly_maps"] >= self.pixel_threshold.value @@ -104,7 +104,7 @@ def test_step(self, batch: Dict[str, Union[str, Tensor]], batch_idx: int, *args, """Calls validation_step for anomaly map/score calculation. Args: - batch (Tensor): Input batch + batch (Dict[str, Union[str, Tensor]]): Input batch batch_idx (int): Batch index Returns: @@ -113,19 +113,19 @@ def test_step(self, batch: Dict[str, Union[str, Tensor]], batch_idx: int, *args, """ return self.predict_step(batch, batch_idx) - def validation_step_end(self, val_step_outputs): # pylint: disable=arguments-differ + def validation_step_end(self, val_step_outputs: STEP_OUTPUT, *args, **kwargs) -> STEP_OUTPUT: """Called at the end of each validation step.""" self._outputs_to_cpu(val_step_outputs) self._post_process(val_step_outputs) return val_step_outputs - def test_step_end(self, test_step_outputs): # pylint: disable=arguments-differ + def test_step_end(self, test_step_outputs: STEP_OUTPUT, *args, **kwargs) -> STEP_OUTPUT: """Called at the end of each test step.""" self._outputs_to_cpu(test_step_outputs) self._post_process(test_step_outputs) return test_step_outputs - def validation_epoch_end(self, outputs): + def validation_epoch_end(self, outputs: EPOCH_OUTPUT) -> None: """Compute threshold and performance metrics. Args: @@ -136,7 +136,7 @@ def validation_epoch_end(self, outputs): self._collect_outputs(self.image_metrics, self.pixel_metrics, outputs) self._log_metrics() - def test_epoch_end(self, outputs): + def test_epoch_end(self, outputs: EPOCH_OUTPUT) -> None: """Compute and save anomaly scores of the test set. Args: @@ -145,7 +145,7 @@ def test_epoch_end(self, outputs): self._collect_outputs(self.image_metrics, self.pixel_metrics, outputs) self._log_metrics() - def _compute_adaptive_threshold(self, outputs): + def _compute_adaptive_threshold(self, outputs: EPOCH_OUTPUT) -> None: self.image_threshold.reset() self.pixel_threshold.reset() self._collect_outputs(self.image_threshold, self.pixel_threshold, outputs) @@ -159,7 +159,11 @@ def _compute_adaptive_threshold(self, outputs): self.pixel_metrics.set_threshold(self.pixel_threshold.value.item()) @staticmethod - def _collect_outputs(image_metric, pixel_metric, outputs): + def _collect_outputs( + image_metric: AnomalibMetricCollection, + pixel_metric: AnomalibMetricCollection, + outputs: EPOCH_OUTPUT, + ) -> None: for output in outputs: image_metric.cpu() image_metric.update(output["pred_scores"], output["label"].int()) @@ -168,25 +172,30 @@ def _collect_outputs(image_metric, pixel_metric, outputs): pixel_metric.update(output["anomaly_maps"], output["mask"].int()) @staticmethod - def _post_process(outputs): + def _post_process(outputs: STEP_OUTPUT) -> None: """Compute labels based on model predictions.""" - if "pred_scores" not in outputs and "anomaly_maps" in outputs: - # infer image scores from anomaly maps - outputs["pred_scores"] = ( - outputs["anomaly_maps"].reshape(outputs["anomaly_maps"].shape[0], -1).max(dim=1).values - ) - elif "pred_scores" not in outputs and "box_scores" in outputs: - # infer image score from bbox confidence scores - outputs["pred_scores"] = torch.zeros_like(outputs["label"]).float() - for idx, (boxes, scores) in enumerate(zip(outputs["pred_boxes"], outputs["box_scores"])): - if boxes.numel(): - outputs["pred_scores"][idx] = scores.max().item() - - if "pred_boxes" in outputs and "anomaly_maps" not in outputs: - # create anomaly maps from bbox predictions for thresholding and evaluation - image_size = tuple(outputs["image"].shape[-2:]) - outputs["anomaly_maps"] = boxes_to_anomaly_maps(outputs["pred_boxes"], outputs["box_scores"], image_size) - outputs["mask"] = boxes_to_masks(outputs["boxes"], image_size) + if isinstance(outputs, dict): + if "pred_scores" not in outputs and "anomaly_maps" in outputs: + # infer image scores from anomaly maps + outputs["pred_scores"] = ( + outputs["anomaly_maps"].reshape(outputs["anomaly_maps"].shape[0], -1).max(dim=1).values + ) + elif "pred_scores" not in outputs and "box_scores" in outputs: + # infer image score from bbox confidence scores + outputs["pred_scores"] = torch.zeros_like(outputs["label"]).float() + for idx, (boxes, scores) in enumerate(zip(outputs["pred_boxes"], outputs["box_scores"])): + if boxes.numel(): + outputs["pred_scores"][idx] = scores.max().item() + + if "pred_boxes" in outputs and "anomaly_maps" not in outputs: + # create anomaly maps from bbox predictions for thresholding and evaluation + image_size: Tuple[int, int] = outputs["image"].shape[-2:] + true_boxes: List[Tensor] = outputs["boxes"] + pred_boxes: Tensor = outputs["pred_boxes"] + box_scores: Tensor = outputs["box_scores"] + + outputs["anomaly_maps"] = boxes_to_anomaly_maps(pred_boxes, box_scores, image_size) + outputs["mask"] = boxes_to_masks(true_boxes, image_size) def _outputs_to_cpu(self, output): if isinstance(output, Dict): @@ -198,7 +207,7 @@ def _outputs_to_cpu(self, output): output = output.cpu() return output - def _log_metrics(self): + def _log_metrics(self) -> None: """Log computed performance metrics.""" if self.pixel_metrics.update_called: self.log_dict(self.pixel_metrics, prog_bar=True) @@ -206,7 +215,7 @@ def _log_metrics(self): else: self.log_dict(self.image_metrics, prog_bar=True) - def _load_normalization_class(self, state_dict: OrderedDict[str, Tensor]): + def _load_normalization_class(self, state_dict: OrderedDict[str, Tensor]) -> None: """Assigns the normalization method to use.""" if "normalization_metrics.max" in state_dict.keys(): self.normalization_metrics = MinMax() From 228eaed2feb9fc009b094f903797da9ef83d8eeb Mon Sep 17 00:00:00 2001 From: Samet Date: Tue, 17 Jan 2023 00:04:00 -0700 Subject: [PATCH 37/85] Add type hints to validation step --- anomalib/models/components/base/anomaly_module.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/anomalib/models/components/base/anomaly_module.py b/anomalib/models/components/base/anomaly_module.py index c083eb8774..a4b0d45b80 100644 --- a/anomalib/models/components/base/anomaly_module.py +++ b/anomalib/models/components/base/anomaly_module.py @@ -5,7 +5,7 @@ import logging from abc import ABC -from typing import Any, Dict, List, Optional, OrderedDict, Tuple, Union +from typing import Any, Dict, List, OrderedDict, Tuple, Union from warnings import warn import pytorch_lightning as pl @@ -100,7 +100,7 @@ def predict_step(self, batch: Any, batch_idx: int, dataloader_idx: int = 0) -> A outputs["box_labels"] = [labels.int() for labels in is_anomalous] return outputs - def test_step(self, batch: Dict[str, Union[str, Tensor]], batch_idx: int, *args, **kwargs) -> Optional[STEP_OUTPUT]: + def test_step(self, batch: Dict[str, Union[str, Tensor]], batch_idx: int, *args, **kwargs) -> STEP_OUTPUT: """Calls validation_step for anomaly map/score calculation. Args: From f50886a26a598276b42f148e46b45848bc6fd188 Mon Sep 17 00:00:00 2001 From: Samet Date: Tue, 17 Jan 2023 00:09:41 -0700 Subject: [PATCH 38/85] Add type hints to cfa validation step --- anomalib/models/cfa/lightning_model.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/anomalib/models/cfa/lightning_model.py b/anomalib/models/cfa/lightning_model.py index 6de9691275..86500f3739 100644 --- a/anomalib/models/cfa/lightning_model.py +++ b/anomalib/models/cfa/lightning_model.py @@ -73,11 +73,11 @@ def on_train_start(self) -> None: """Initialize the centroid for the memory bank computation.""" self.model.initialize_centroid(data_loader=self.trainer.datamodule.train_dataloader()) # type: ignore - def training_step(self, batch) -> STEP_OUTPUT: + def training_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> STEP_OUTPUT: """Training step for the CFA model. Args: - batch (dict): Batch input. + batch (Dict[str, Union[str, Tensor]]): Batch input. Returns: STEP_OUTPUT: Loss value. @@ -86,12 +86,11 @@ def training_step(self, batch) -> STEP_OUTPUT: loss = self.loss_func(distance) return {"loss": loss} - def validation_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> Optional[STEP_OUTPUT]: + def validation_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> STEP_OUTPUT: """Validation step for the CFA model. Args: - batch (dict): Input batch. - batch_idx (int): Index of the batch. + batch (Dict[str, Union[str, Tensor]]): Input batch. Returns: dict: Anomaly map computed by the model. From 2f1deaad0bea6c3c064e327c4831395a53bf8e72 Mon Sep 17 00:00:00 2001 From: Samet Date: Tue, 17 Jan 2023 00:21:25 -0700 Subject: [PATCH 39/85] Adjust the type hint for the rest of the models --- anomalib/models/cflow/lightning_model.py | 4 ++-- anomalib/models/csflow/lightning_model.py | 4 ++-- anomalib/models/dfkde/lightning_model.py | 6 +++--- anomalib/models/dfm/lightning_model.py | 6 +++--- anomalib/models/draem/lightning_model.py | 8 ++++---- anomalib/models/fastflow/lightning_model.py | 4 ++-- anomalib/models/ganomaly/lightning_model.py | 4 ++-- anomalib/models/padim/lightning_model.py | 8 ++++---- anomalib/models/patchcore/lightning_model.py | 5 ++--- anomalib/models/reverse_distillation/lightning_model.py | 8 +++----- anomalib/models/rkde/lightning_model.py | 7 +++---- anomalib/models/stfpm/lightning_model.py | 7 +++---- 12 files changed, 33 insertions(+), 38 deletions(-) diff --git a/anomalib/models/cflow/lightning_model.py b/anomalib/models/cflow/lightning_model.py index 7e80a951bd..c32470dad7 100644 --- a/anomalib/models/cflow/lightning_model.py +++ b/anomalib/models/cflow/lightning_model.py @@ -6,7 +6,7 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -from typing import Dict, List, Optional, Tuple, Union +from typing import Dict, List, Tuple, Union import einops import torch @@ -154,7 +154,7 @@ def training_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) - self.log("train_loss", avg_loss.item(), on_epoch=True, prog_bar=True, logger=True) return {"loss": avg_loss} - def validation_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> Optional[STEP_OUTPUT]: + def validation_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> STEP_OUTPUT: """Validation Step of CFLOW. Similar to the training step, encoder features diff --git a/anomalib/models/csflow/lightning_model.py b/anomalib/models/csflow/lightning_model.py index eac4747ba2..6796ec4f60 100644 --- a/anomalib/models/csflow/lightning_model.py +++ b/anomalib/models/csflow/lightning_model.py @@ -7,7 +7,7 @@ # SPDX-License-Identifier: Apache-2.0 import logging -from typing import Dict, List, Optional, Tuple, Union +from typing import Dict, List, Tuple, Union import torch from omegaconf import DictConfig, ListConfig @@ -72,7 +72,7 @@ def training_step(self, batch, _) -> Dict[str, Tensor]: self.log("train_loss", loss.item(), on_epoch=True, prog_bar=True, logger=True) return {"loss": loss} - def validation_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> Optional[STEP_OUTPUT]: + def validation_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> STEP_OUTPUT: """Validation step for CS Flow. Args: diff --git a/anomalib/models/dfkde/lightning_model.py b/anomalib/models/dfkde/lightning_model.py index 963e203adc..e7e15fc120 100644 --- a/anomalib/models/dfkde/lightning_model.py +++ b/anomalib/models/dfkde/lightning_model.py @@ -4,7 +4,7 @@ # SPDX-License-Identifier: Apache-2.0 import logging -from typing import Dict, List, Optional, Union +from typing import Dict, List, Union import torch from omegaconf import DictConfig, ListConfig @@ -92,13 +92,13 @@ def on_validation_start(self) -> None: logger.info("Fitting a KDE model to the embedding collected from the training set.") self.model.classifier.fit(embeddings) - def validation_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> Optional[STEP_OUTPUT]: + def validation_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> STEP_OUTPUT: """Validation Step of DFKDE. Similar to the training step, features are extracted from the CNN for each batch. Args: - batch: Input batch + batch (Dict[str, Union[str, Tensor]]): Input batch Returns: Dictionary containing probability, prediction and ground truth values. diff --git a/anomalib/models/dfm/lightning_model.py b/anomalib/models/dfm/lightning_model.py index 0d6f6beb77..52b74fb68a 100644 --- a/anomalib/models/dfm/lightning_model.py +++ b/anomalib/models/dfm/lightning_model.py @@ -4,7 +4,7 @@ # SPDX-License-Identifier: Apache-2.0 import logging -from typing import Dict, List, Optional, Tuple, Union +from typing import Dict, List, Tuple, Union import torch from omegaconf import DictConfig, ListConfig @@ -72,7 +72,7 @@ def training_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) - For each batch, features are extracted from the CNN. Args: - batch (Dict[str, Tensor]): Input batch + batch (Dict[str, Union[str, Tensor]]): Input batch _: Index of the batch. Returns: @@ -97,7 +97,7 @@ def on_validation_start(self) -> None: logger.info("Fitting a PCA and a Gaussian model to dataset.") self.model.fit(embeddings) - def validation_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> Optional[STEP_OUTPUT]: + def validation_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> STEP_OUTPUT: """Validation Step of DFM. Similar to the training step, features are extracted from the CNN for each batch. diff --git a/anomalib/models/draem/lightning_model.py b/anomalib/models/draem/lightning_model.py index 44e4a38f42..5ba43193e8 100644 --- a/anomalib/models/draem/lightning_model.py +++ b/anomalib/models/draem/lightning_model.py @@ -74,7 +74,7 @@ def training_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) - image through the network and computes the training loss. Args: - batch (batch: Dict[str, Union[str, Tensor]]): Batch containing image filename, image, label and mask + batch (Dict[str, Union[str, Tensor]]): Batch containing image filename, image, label and mask Returns: Loss dictionary @@ -95,11 +95,11 @@ def training_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) - self.log("train_loss", loss.item(), on_epoch=True, prog_bar=True, logger=True) return {"loss": loss} - def validation_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> Optional[STEP_OUTPUT]: + def validation_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> STEP_OUTPUT: """Validation step of DRAEM. The Softmax predictions of the anomalous class are used as anomaly map. Args: - batch: Batch of input images + batch (Dict[str, Union[str, Tensor]]): Batch of input images Returns: Dictionary to which predicted anomaly maps have been added. @@ -141,6 +141,6 @@ def configure_callbacks(self) -> List[EarlyStopping]: ) return [early_stopping] - def configure_optimizers(self): + def configure_optimizers(self) -> torch.optim.Optimizer: """Configure the Adam optimizer.""" return torch.optim.Adam(params=self.model.parameters(), lr=self.hparams.model.lr) diff --git a/anomalib/models/fastflow/lightning_model.py b/anomalib/models/fastflow/lightning_model.py index 0fbfeb682a..00c58c748a 100644 --- a/anomalib/models/fastflow/lightning_model.py +++ b/anomalib/models/fastflow/lightning_model.py @@ -3,7 +3,7 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -from typing import Dict, List, Optional, Tuple, Union +from typing import Dict, List, Tuple, Union import torch from omegaconf import DictConfig, ListConfig @@ -66,7 +66,7 @@ def training_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) - self.log("train_loss", loss.item(), on_epoch=True, prog_bar=True, logger=True) return {"loss": loss} - def validation_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> Optional[STEP_OUTPUT]: + def validation_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> STEP_OUTPUT: """Forward-pass the input and return the anomaly map. Args: diff --git a/anomalib/models/ganomaly/lightning_model.py b/anomalib/models/ganomaly/lightning_model.py index e2af460a3a..0e44ef6acd 100644 --- a/anomalib/models/ganomaly/lightning_model.py +++ b/anomalib/models/ganomaly/lightning_model.py @@ -138,11 +138,11 @@ def on_validation_start(self) -> None: self._reset_min_max() return super().on_validation_start() - def validation_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> Optional[STEP_OUTPUT]: + def validation_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> STEP_OUTPUT: """Update min and max scores from the current step. Args: - batch (Dict[str, Tensor]): Predicted difference between z and z_hat. + batch (Dict[str, Union[str, Tensor]]): Predicted difference between z and z_hat. Returns: Dict[str, Tensor]: batch diff --git a/anomalib/models/padim/lightning_model.py b/anomalib/models/padim/lightning_model.py index 484ad1580d..177056481f 100644 --- a/anomalib/models/padim/lightning_model.py +++ b/anomalib/models/padim/lightning_model.py @@ -67,7 +67,7 @@ def training_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) - """Training Step of PADIM. For each batch, hierarchical features are extracted from the CNN. Args: - batch (Dict[str, Any]): Batch containing image filename, image, label and mask + batch (Dict[str, Union[str, Tensor]]): Batch containing image filename, image, label and mask _batch_idx: Index of the batch. Returns: @@ -93,13 +93,13 @@ def on_validation_start(self) -> None: logger.info("Fitting a Gaussian to the embedding collected from the training set.") self.stats = self.model.gaussian.fit(embeddings) - def validation_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> Optional[STEP_OUTPUT]: + def validation_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> STEP_OUTPUT: """Validation Step of PADIM. Similar to the training step, hierarchical features are extracted from the CNN for each batch. Args: - batch: Input batch + batch (Dict[str, Union[str, Tensor]]): Input batch Returns: Dictionary containing images, features, true labels and masks. @@ -117,7 +117,7 @@ class PadimLightning(Padim): hparams (Union[DictConfig, ListConfig]): Model params """ - def __init__(self, hparams: Union[DictConfig, ListConfig]): + def __init__(self, hparams: Union[DictConfig, ListConfig]) -> None: super().__init__( input_size=hparams.model.input_size, layers=hparams.model.layers, diff --git a/anomalib/models/patchcore/lightning_model.py b/anomalib/models/patchcore/lightning_model.py index 1380de2de5..6dd45415f9 100644 --- a/anomalib/models/patchcore/lightning_model.py +++ b/anomalib/models/patchcore/lightning_model.py @@ -7,7 +7,7 @@ # SPDX-License-Identifier: Apache-2.0 import logging -from typing import Dict, List, Optional, Tuple, Union +from typing import Dict, List, Tuple, Union import torch from omegaconf import DictConfig, ListConfig @@ -69,7 +69,6 @@ def training_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) - Args: batch (Dict[str, Union[str, Tensor]]): Batch containing image filename, image, label and mask - _batch_idx (int): Batch Index Returns: Dict[str, np.ndarray]: Embedding Vector @@ -94,7 +93,7 @@ def on_validation_start(self) -> None: logger.info("Applying core-set subsampling to get the embedding.") self.model.subsample_embedding(embeddings, self.coreset_sampling_ratio) - def validation_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> Optional[STEP_OUTPUT]: + def validation_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> STEP_OUTPUT: """Get batch of anomaly maps from input image batch. Args: diff --git a/anomalib/models/reverse_distillation/lightning_model.py b/anomalib/models/reverse_distillation/lightning_model.py index 7b7558679a..4546781bb2 100644 --- a/anomalib/models/reverse_distillation/lightning_model.py +++ b/anomalib/models/reverse_distillation/lightning_model.py @@ -6,7 +6,7 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -from typing import Dict, List, Optional, Tuple, Union +from typing import Dict, List, Tuple, Union from omegaconf import DictConfig, ListConfig from pytorch_lightning.callbacks import EarlyStopping @@ -84,7 +84,6 @@ def training_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) - Args: batch (batch: Dict[str, Union[str, Tensor]]): Input batch - _: Index of the batch. Returns: Feature Map @@ -93,15 +92,14 @@ def training_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) - self.log("train_loss", loss.item(), on_epoch=True, prog_bar=True, logger=True) return {"loss": loss} - def validation_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> Optional[STEP_OUTPUT]: + def validation_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> STEP_OUTPUT: """Validation Step of Reverse Distillation Model. Similar to the training step, encoder/decoder features are extracted from the CNN for each batch, and anomaly map is computed. Args: - batch (Tensor): Input batch - _: Index of the batch. + batch (Dict[str, Union[str, Tensor]]): Input batch Returns: Dictionary containing images, anomaly maps, true labels and masks. diff --git a/anomalib/models/rkde/lightning_model.py b/anomalib/models/rkde/lightning_model.py index dc867b4707..4ecbb58623 100644 --- a/anomalib/models/rkde/lightning_model.py +++ b/anomalib/models/rkde/lightning_model.py @@ -4,7 +4,7 @@ # SPDX-License-Identifier: Apache-2.0 import logging -from typing import Dict, List, Optional, Union +from typing import Dict, List, Union import torch from omegaconf import DictConfig, ListConfig @@ -73,7 +73,6 @@ def training_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) - Args: batch (Dict[str, Union[str, Tensor]]): Batch containing image filename, image, label and mask - _batch_idx: Index of the batch. Returns: Deep CNN features. @@ -88,13 +87,13 @@ def on_validation_start(self) -> None: logger.info("Fitting a KDE model to the embedding collected from the training set.") self.model.fit(embeddings) - def validation_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> Optional[STEP_OUTPUT]: + def validation_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> STEP_OUTPUT: """Validation Step of RKde. Similar to the training step, features are extracted from the CNN for each batch. Args: - batch: Input batch + batch (Dict[str, Union[str, Tensor]]): Batch containing image filename, image, label and mask Returns: Dictionary containing probability, prediction and ground truth values. diff --git a/anomalib/models/stfpm/lightning_model.py b/anomalib/models/stfpm/lightning_model.py index 944361fc2f..6bf4e97769 100644 --- a/anomalib/models/stfpm/lightning_model.py +++ b/anomalib/models/stfpm/lightning_model.py @@ -6,7 +6,7 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -from typing import Dict, List, Optional, Tuple, Union +from typing import Dict, List, Tuple, Union import torch from omegaconf import DictConfig, ListConfig @@ -53,8 +53,7 @@ def training_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) - For each batch, teacher and student and teacher features are extracted from the CNN. Args: - batch (Tensor): Input batch - _: Index of the batch. + batch (Dict[str, Union[str, Tensor]]): Input batch Returns: Loss value @@ -65,7 +64,7 @@ def training_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) - self.log("train_loss", loss.item(), on_epoch=True, prog_bar=True, logger=True) return {"loss": loss} - def validation_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> Optional[STEP_OUTPUT]: + def validation_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> STEP_OUTPUT: """Validation Step of STFPM. Similar to the training step, student/teacher features are extracted from the CNN for each batch, and From 76df2ee27af0555b17fa2b15c9db8c32a3a26f82 Mon Sep 17 00:00:00 2001 From: Samet Date: Tue, 17 Jan 2023 00:24:31 -0700 Subject: [PATCH 40/85] Fix the type hint for self.loss --- anomalib/models/cfa/lightning_model.py | 4 ++-- anomalib/models/components/base/anomaly_module.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/anomalib/models/cfa/lightning_model.py b/anomalib/models/cfa/lightning_model.py index 86500f3739..ab20f046a2 100644 --- a/anomalib/models/cfa/lightning_model.py +++ b/anomalib/models/cfa/lightning_model.py @@ -63,7 +63,7 @@ def __init__( num_hard_negative_features=num_hard_negative_features, radius=radius, ) - self.loss_func = CfaLoss( + self.loss = CfaLoss( num_nearest_neighbors=num_nearest_neighbors, num_hard_negative_features=num_hard_negative_features, radius=radius, @@ -83,7 +83,7 @@ def training_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) - STEP_OUTPUT: Loss value. """ distance = self.model(batch["image"]) - loss = self.loss_func(distance) + loss = self.loss(distance) return {"loss": loss} def validation_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> STEP_OUTPUT: diff --git a/anomalib/models/components/base/anomaly_module.py b/anomalib/models/components/base/anomaly_module.py index a4b0d45b80..b745070fd6 100644 --- a/anomalib/models/components/base/anomaly_module.py +++ b/anomalib/models/components/base/anomaly_module.py @@ -39,7 +39,7 @@ def __init__(self) -> None: self.save_hyperparameters() self.model: nn.Module - self.loss: Tensor + self.loss: nn.Module self.callbacks: List[Callback] self.threshold_method: ThresholdMethod From 9e6d4af495303647c756ee61f8f95e22a4b0c86c Mon Sep 17 00:00:00 2001 From: Samet Date: Tue, 17 Jan 2023 03:44:36 -0700 Subject: [PATCH 41/85] Metrics are updated --- anomalib/utils/metrics/anomaly_score_distribution.py | 7 ++++--- anomalib/utils/metrics/min_max.py | 5 +++-- anomalib/utils/metrics/optimal_f1.py | 5 +++-- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/anomalib/utils/metrics/anomaly_score_distribution.py b/anomalib/utils/metrics/anomaly_score_distribution.py index 80188ab4f8..e0b6ebfc8b 100644 --- a/anomalib/utils/metrics/anomaly_score_distribution.py +++ b/anomalib/utils/metrics/anomaly_score_distribution.py @@ -28,11 +28,12 @@ def __init__(self, **kwargs) -> None: self.pixel_mean = torch.empty(0) self.pixel_std = torch.empty(0) - # pylint: disable=arguments-differ - def update( # type: ignore - self, anomaly_scores: Optional[Tensor] = None, anomaly_maps: Optional[Tensor] = None + def update( + self, *args, anomaly_scores: Optional[Tensor] = None, anomaly_maps: Optional[Tensor] = None, **kwargs ) -> None: """Update the precision-recall curve metric.""" + del args, kwargs # These variables are not used. + if anomaly_maps is not None: self.anomaly_maps.append(anomaly_maps) if anomaly_scores is not None: diff --git a/anomalib/utils/metrics/min_max.py b/anomalib/utils/metrics/min_max.py index 809b371934..a256395be1 100644 --- a/anomalib/utils/metrics/min_max.py +++ b/anomalib/utils/metrics/min_max.py @@ -23,9 +23,10 @@ def __init__(self, **kwargs) -> None: self.min = torch.tensor(float("inf")) # pylint: disable=not-callable self.max = torch.tensor(float("-inf")) # pylint: disable=not-callable - # pylint: disable=arguments-differ - def update(self, predictions: Tensor) -> None: # type: ignore + def update(self, predictions: Tensor, *args, **kwargs) -> None: """Update the min and max values.""" + del args, kwargs # These variables are not used. + self.max = torch.max(self.max, torch.max(predictions)) self.min = torch.min(self.min, torch.min(predictions)) diff --git a/anomalib/utils/metrics/optimal_f1.py b/anomalib/utils/metrics/optimal_f1.py index f5598f0e31..18af32b892 100644 --- a/anomalib/utils/metrics/optimal_f1.py +++ b/anomalib/utils/metrics/optimal_f1.py @@ -33,9 +33,10 @@ def __init__(self, num_classes: int, **kwargs) -> None: self.threshold: Tensor - # pylint: disable=arguments-differ - def update(self, preds: Tensor, target: Tensor) -> None: # type: ignore + def update(self, preds: Tensor, target: Tensor, *args, **kwargs) -> None: """Update the precision-recall curve metric.""" + del args, kwargs # These variables are not used. + self.precision_recall_curve.update(preds, target) def compute(self) -> Tensor: From 8a1e39bc70924c8eeeda69be61c2ec29c54d1214 Mon Sep 17 00:00:00 2001 From: Samet Date: Tue, 17 Jan 2023 03:51:04 -0700 Subject: [PATCH 42/85] Run pyupgrade for the first time. --- .pre-commit-config.yaml | 5 +++++ anomalib/data/base/datamodule.py | 16 ++++++++-------- anomalib/data/base/dataset.py | 4 ++-- anomalib/data/utils/split.py | 12 ++++++------ anomalib/data/visa.py | 2 +- anomalib/utils/metrics/aupro.py | 6 ++---- setup.py | 5 +++-- 7 files changed, 27 insertions(+), 23 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 399b0a7d5e..9a62d20c0f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -63,6 +63,11 @@ repos: types: [python] exclude: "tests|docs" + - repo: https://github.com/asottile/pyupgrade + rev: v3.3.1 + hooks: + - id: pyupgrade + # notebooks. - repo: https://github.com/nbQA-dev/nbQA rev: 1.4.0 diff --git a/anomalib/data/base/datamodule.py b/anomalib/data/base/datamodule.py index 41dc66d7e3..7c3a15eaad 100644 --- a/anomalib/data/base/datamodule.py +++ b/anomalib/data/base/datamodule.py @@ -7,7 +7,7 @@ import logging from abc import ABC -from typing import Any, Dict, List, Optional +from typing import Any from pandas import DataFrame from pytorch_lightning import LightningDataModule @@ -26,7 +26,7 @@ logger = logging.getLogger(__name__) -def collate_fn(batch: List) -> Dict[str, Any]: +def collate_fn(batch: list) -> dict[str, Any]: """Custom collate function that collates bounding boxes as lists. Bounding boxes are collated as a list of tensors, while the default collate function is used for all other entries. @@ -72,9 +72,9 @@ def __init__( num_workers: int, val_split_mode: ValSplitMode, val_split_ratio: float, - test_split_mode: Optional[TestSplitMode] = None, - test_split_ratio: Optional[float] = None, - seed: Optional[int] = None, + test_split_mode: TestSplitMode | None = None, + test_split_ratio: float | None = None, + seed: int | None = None, ) -> None: super().__init__() self.train_batch_size = train_batch_size @@ -90,9 +90,9 @@ def __init__( self.val_data: AnomalibDataset self.test_data: AnomalibDataset - self._samples: Optional[DataFrame] = None + self._samples: DataFrame | None = None - def setup(self, stage: Optional[str] = None) -> None: + def setup(self, stage: str | None = None) -> None: """Setup train, validation and test data. Args: @@ -102,7 +102,7 @@ def setup(self, stage: Optional[str] = None) -> None: self._setup(stage) assert self.is_setup - def _setup(self, _stage: Optional[str] = None) -> None: + def _setup(self, _stage: str | None = None) -> None: """Set up the datasets and perform dynamic subset splitting. This method may be overridden in subclass for custom splitting behaviour. diff --git a/anomalib/data/base/dataset.py b/anomalib/data/base/dataset.py index e4a36604ff..5946ac229a 100644 --- a/anomalib/data/base/dataset.py +++ b/anomalib/data/base/dataset.py @@ -9,7 +9,7 @@ import logging from abc import ABC, abstractmethod from pathlib import Path -from typing import Dict, Sequence, Union +from typing import Sequence import albumentations as A import cv2 @@ -102,7 +102,7 @@ def has_anomalous(self) -> bool: """Check if the dataset contains any anomalous samples.""" return 1 in list(self.samples.label_index) - def __getitem__(self, index: int) -> Dict[str, Union[str, Tensor]]: + def __getitem__(self, index: int) -> dict[str, str | Tensor]: """Get dataset item for the index ``index``. Args: diff --git a/anomalib/data/utils/split.py b/anomalib/data/utils/split.py index 60f8b7f0e1..8f87a6d300 100644 --- a/anomalib/data/utils/split.py +++ b/anomalib/data/utils/split.py @@ -16,7 +16,7 @@ import math import warnings from enum import Enum -from typing import TYPE_CHECKING, List, Optional, Sequence, Tuple, Union +from typing import TYPE_CHECKING, Sequence import torch @@ -66,10 +66,10 @@ def concatenate_datasets(datasets: Sequence[AnomalibDataset]) -> AnomalibDataset def random_split( dataset: AnomalibDataset, - split_ratio: Union[float, Sequence[float]], + split_ratio: float | Sequence[float], label_aware: bool = False, - seed: Optional[int] = None, -) -> List[AnomalibDataset]: + seed: int | None = None, +) -> list[AnomalibDataset]: """Perform a random split of a dataset. Args: @@ -98,7 +98,7 @@ def random_split( per_label_datasets = [dataset] # outer list: per-label unique, inner list: random subsets with the given ratio - subsets: List[List[AnomalibDataset]] = [] + subsets: list[list[AnomalibDataset]] = [] # split each (label-aware) subset of source data for label_dataset in per_label_datasets: # get subset lengths @@ -127,7 +127,7 @@ def random_split( return [concatenate_datasets(subset) for subset in subsets] -def split_by_label(dataset: AnomalibDataset) -> Tuple[AnomalibDataset, AnomalibDataset]: +def split_by_label(dataset: AnomalibDataset) -> tuple[AnomalibDataset, AnomalibDataset]: """Splits the dataset into the normal and anomalous subsets.""" samples = dataset.samples normal_indices = samples[samples.label_index == 0].index diff --git a/anomalib/data/visa.py b/anomalib/data/visa.py index f378bee031..017a45b373 100644 --- a/anomalib/data/visa.py +++ b/anomalib/data/visa.py @@ -218,7 +218,7 @@ def apply_cls1_split(self) -> None: test_img_bad_folder.mkdir(parents=True, exist_ok=True) test_mask_bad_folder.mkdir(parents=True, exist_ok=True) - with open(split_file, "r", encoding="utf-8") as file: + with open(split_file, encoding="utf-8") as file: csvreader = csv.reader(file) next(csvreader) for row in csvreader: diff --git a/anomalib/utils/metrics/aupro.py b/anomalib/utils/metrics/aupro.py index 58f69d4e3d..670be1f93d 100644 --- a/anomalib/utils/metrics/aupro.py +++ b/anomalib/utils/metrics/aupro.py @@ -77,10 +77,8 @@ def _compute(self) -> Tuple[Tensor, Tensor]: # check and prepare target for labeling via kornia if target.min() < 0 or target.max() > 1: raise ValueError( - ( - f"kornia.contrib.connected_components expects input to lie in the interval [0, 1], but found " - f"interval was [{target.min()}, {target.max()}]." - ) + f"kornia.contrib.connected_components expects input to lie in the interval [0, 1], but found " + f"interval was [{target.min()}, {target.max()}]." ) target = target.unsqueeze(1) # kornia expects N1HW format target = target.type(torch.float) # kornia expects FloatTensor diff --git a/setup.py b/setup.py index 881015130c..a6e2f01e2c 100644 --- a/setup.py +++ b/setup.py @@ -5,12 +5,13 @@ from importlib.util import module_from_spec, spec_from_file_location from pathlib import Path +from types import ModuleType from typing import List from setuptools import find_packages, setup -def load_module(name: str = "anomalib/__init__.py"): +def load_module(name: str = "anomalib/__init__.py") -> ModuleType: """Load Python Module. Args: @@ -68,7 +69,7 @@ def get_required_packages(requirement_files: List[str]) -> List[str]: required_packages: List[str] = [] for requirement_file in requirement_files: - with open(f"requirements/{requirement_file}.txt", "r", encoding="utf8") as file: + with open(f"requirements/{requirement_file}.txt", encoding="utf8") as file: for line in file: package = line.strip() if package and not package.startswith(("#", "-f")): From 0d246ad7cddf8285a9eb2f6e4c5f617639427d24 Mon Sep 17 00:00:00 2001 From: Samet Date: Tue, 17 Jan 2023 03:59:52 -0700 Subject: [PATCH 43/85] Edited config to the new annotation --- anomalib/config/config.py | 53 ++++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/anomalib/config/config.py b/anomalib/config/config.py index 918400a6d8..2b1ca131fb 100644 --- a/anomalib/config/config.py +++ b/anomalib/config/config.py @@ -6,10 +6,11 @@ # TODO: This would require a new design. # TODO: https://jira.devtools.intel.com/browse/IAAALD-149 +from __future__ import annotations + import time from datetime import datetime from pathlib import Path -from typing import List, Optional, Union from warnings import warn from omegaconf import DictConfig, ListConfig, OmegaConf @@ -22,17 +23,17 @@ def _get_now_str(timestamp: float) -> str: return datetime.fromtimestamp(timestamp).strftime("%Y-%m-%d_%H-%M-%S") -def update_input_size_config(config: Union[DictConfig, ListConfig]) -> Union[DictConfig, ListConfig]: +def update_input_size_config(config: DictConfig | ListConfig) -> DictConfig | ListConfig: """Update config with image size as tuple, effective input size and tiling stride. Convert integer image size parameters into tuples, calculate the effective input size based on image size and crop size, and set tiling stride if undefined. Args: - config (Union[DictConfig, ListConfig]): Configurable parameters object + config (DictConfig | ListConfig): Configurable parameters object Returns: - Union[DictConfig, ListConfig]: Configurable parameters with updated values + DictConfig | ListConfig: Configurable parameters with updated values """ # Image size: Ensure value is in the form [height, width] image_size = config.dataset.get("image_size") @@ -65,14 +66,14 @@ def update_input_size_config(config: Union[DictConfig, ListConfig]) -> Union[Dic return config -def update_nncf_config(config: Union[DictConfig, ListConfig]) -> Union[DictConfig, ListConfig]: +def update_nncf_config(config: DictConfig | ListConfig) -> DictConfig | ListConfig: """Set the NNCF input size based on the value of the crop_size parameter in the configurable parameters object. - Args: - config (Union[DictConfig, ListConfig]): Configurable parameters of the current run. + Args + config (DictConfig | ListConfig): Configurable parameters of the current run. Returns: - Union[DictConfig, ListConfig]: Updated configurable parameters in DictConfig object. + DictConfig | ListConfig: Updated configurable parameters in DictConfig object. """ crop_size = config.dataset.image_size sample_size = (crop_size, crop_size) if isinstance(crop_size, int) else crop_size @@ -87,19 +88,19 @@ def update_nncf_config(config: Union[DictConfig, ListConfig]) -> Union[DictConfi return config -def update_multi_gpu_training_config(config: Union[DictConfig, ListConfig]) -> Union[DictConfig, ListConfig]: +def update_multi_gpu_training_config(config: DictConfig | ListConfig) -> DictConfig | ListConfig: """Updates the config to change learning rate based on number of gpus assigned. Current behaviour is to ensure only ddp accelerator is used. Args: - config (Union[DictConfig, ListConfig]): Configurable parameters for the current run + config (DictConfig | ListConfig): Configurable parameters for the current run Raises: ValueError: If unsupported accelerator is passed Returns: - Union[DictConfig, ListConfig]: Updated config + DictConfig | ListConfig: Updated config """ # validate accelerator if config.trainer.accelerator is not None: @@ -119,7 +120,7 @@ def update_multi_gpu_training_config(config: Union[DictConfig, ListConfig]) -> U # increase the learning rate by the number of devices if "lr" in config.model: # Number of GPUs can either be passed as gpus: 2 or gpus: [0,1] - n_gpus: Union[int, List] = 1 + n_gpus: int | list = 1 if "trainer" in config and "gpus" in config.trainer: n_gpus = config.trainer.gpus lr_scaler = n_gpus if isinstance(n_gpus, int) else len(n_gpus) @@ -127,14 +128,14 @@ def update_multi_gpu_training_config(config: Union[DictConfig, ListConfig]) -> U return config -def update_datasets_config(config: Union[DictConfig, ListConfig]) -> Union[DictConfig, ListConfig]: +def update_datasets_config(config: DictConfig | ListConfig) -> DictConfig | ListConfig: """Updates the dataset section of the config. Args: - config (Union[DictConfig, ListConfig]): Configurable parameters for the current run. + config (DictConfig | ListConfig): Configurable parameters for the current run. Returns: - Union[DictConfig, ListConfig]: Updated config + DictConfig | ListConfig: Updated config """ if "format" not in config.dataset.keys(): config.dataset.format = "mvtec" @@ -200,23 +201,23 @@ def update_datasets_config(config: Union[DictConfig, ListConfig]) -> Union[DictC def get_configurable_parameters( - model_name: Optional[str] = None, - config_path: Optional[Union[Path, str]] = None, - weight_file: Optional[str] = None, - config_filename: Optional[str] = "config", - config_file_extension: Optional[str] = "yaml", -) -> Union[DictConfig, ListConfig]: + model_name: str | None = None, + config_path: Path | str | None = None, + weight_file: str | None = None, + config_filename: str | None = "config", + config_file_extension: str | None = "yaml", +) -> DictConfig | ListConfig: """Get configurable parameters. Args: - model_name: Optional[str]: (Default value = None) - config_path: Optional[Union[Path, str]]: (Default value = None) + model_name: str | None: (Default value = None) + config_path: Path | str | None: (Default value = None) weight_file: Path to the weight file - config_filename: Optional[str]: (Default value = "config") - config_file_extension: Optional[str]: (Default value = "yaml") + config_filename: str | None: (Default value = "config") + config_file_extension: str | None: (Default value = "yaml") Returns: - Union[DictConfig, ListConfig]: Configurable parameters in DictConfig object. + DictConfig | ListConfig: Configurable parameters in DictConfig object. """ if model_name is None and config_path is None: raise ValueError( From 00b45cb345673483d96644442c43ba3201980028 Mon Sep 17 00:00:00 2001 From: Samet Date: Tue, 17 Jan 2023 04:45:03 -0700 Subject: [PATCH 44/85] Converted video.py to new annotation format --- anomalib/data/base/video.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/anomalib/data/base/video.py b/anomalib/data/base/video.py index 22148c5786..9b5bcfb4aa 100644 --- a/anomalib/data/base/video.py +++ b/anomalib/data/base/video.py @@ -1,7 +1,9 @@ """Base Video Dataset.""" +from __future__ import annotations + from abc import ABC -from typing import Callable, Dict, Optional, Union +from typing import Callable import albumentations as A import torch @@ -25,15 +27,17 @@ class AnomalibVideoDataset(AnomalibDataset, ABC): frames_between_clips (int): Number of frames between each consecutive video clip. """ - def __init__(self, task: TaskType, transform: A.Compose, clip_length_in_frames: int, frames_between_clips: int): + def __init__( + self, task: TaskType, transform: A.Compose, clip_length_in_frames: int, frames_between_clips: int + ) -> None: super().__init__(task, transform) self.clip_length_in_frames = clip_length_in_frames self.frames_between_clips = frames_between_clips self.transform = transform - self.indexer: Optional[ClipsIndexer] = None - self.indexer_cls: Optional[Callable] = None + self.indexer: ClipsIndexer | None = None + self.indexer_cls: Callable | None = None def __len__(self) -> int: """Get length of the dataset.""" @@ -64,7 +68,7 @@ def _setup_clips(self) -> None: frames_between_clips=self.frames_between_clips, ) - def __getitem__(self, index: int) -> Dict[str, Union[str, Tensor]]: + def __getitem__(self, index: int) -> dict[str, str | Tensor]: """Return mask, clip and file system information.""" assert isinstance(self.indexer, ClipsIndexer) @@ -98,7 +102,7 @@ def __getitem__(self, index: int) -> Dict[str, Union[str, Tensor]]: class AnomalibVideoDataModule(AnomalibDataModule): """Base class for video data modules.""" - def _setup(self, _stage: Optional[str] = None) -> None: + def _setup(self, _stage: str | None = None) -> None: """Set up the datasets and perform dynamic subset splitting. This method may be overridden in subclass for custom splitting behaviour. From 86e55ce70af4a851698e607a7c3ba62839e68c77 Mon Sep 17 00:00:00 2001 From: Samet Date: Tue, 17 Jan 2023 04:54:10 -0700 Subject: [PATCH 45/85] Converted avenue.py to new annotation format --- anomalib/data/avenue.py | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/anomalib/data/avenue.py b/anomalib/data/avenue.py index b95a973c5e..f52d6f89a4 100644 --- a/anomalib/data/avenue.py +++ b/anomalib/data/avenue.py @@ -13,18 +13,19 @@ # Copyright (C) 2023 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import logging import math from pathlib import Path from shutil import move -from typing import Callable, Optional, Tuple, Union +from typing import Callable import albumentations as A import cv2 import numpy as np import scipy.io from pandas import DataFrame -from torch import Tensor from anomalib.data.base import AnomalibVideoDataModule, AnomalibVideoDataset from anomalib.data.task_type import TaskType @@ -52,7 +53,7 @@ ) -def make_avenue_dataset(root: Path, gt_dir: Path, split: Optional[Union[Split, str]] = None) -> DataFrame: +def make_avenue_dataset(root: Path, gt_dir: Path, split: Split | str | None = None) -> DataFrame: """Create CUHK Avenue dataset by parsing the file structure. The files are expected to follow the structure: @@ -62,7 +63,7 @@ def make_avenue_dataset(root: Path, gt_dir: Path, split: Optional[Union[Split, s Args: root (Path): Path to dataset gt_dir (Path): Path to the ground truth - split (Optional[Union[Split, str]], optional): Dataset split (ie., either train or test). Defaults to None. + split (Split | str | None = None, optional): Dataset split (ie., either train or test). Defaults to None. Example: The following example shows how to get testing samples from Avenue dataset: @@ -106,7 +107,7 @@ def make_avenue_dataset(root: Path, gt_dir: Path, split: Optional[Union[Split, s class AvenueClipsIndexer(ClipsIndexer): """Clips class for UCSDped dataset.""" - def get_mask(self, idx) -> Optional[Tensor]: + def get_mask(self, idx) -> np.ndarray | None: """Retrieve the masks from the file system.""" video_idx, frames_idx = self.get_clip_location(idx) @@ -133,10 +134,10 @@ class AvenueDataset(AnomalibVideoDataset): Args: task (TaskType): Task type, 'classification', 'detection' or 'segmentation' - root (str): Path to the root of the dataset + root (Path | str): Path to the root of the dataset gt_dir (str): Path to the ground truth files transform (A.Compose): Albumentations Compose object describing the transforms that are applied to the inputs. - split (Optional[Union[Split, str]]): Split of the dataset, usually Split.TRAIN or Split.TEST + split (Split): Split of the dataset, usually Split.TRAIN or Split.TEST clip_length_in_frames (int, optional): Number of video frames in each clip. frames_between_clips (int, optional): Number of frames between each consecutive video clip. """ @@ -144,7 +145,7 @@ class AvenueDataset(AnomalibVideoDataset): def __init__( self, task: TaskType, - root: Union[Path, str], + root: Path | str, gt_dir: str, transform: A.Compose, split: Split, @@ -172,23 +173,23 @@ class Avenue(AnomalibVideoDataModule): clip_length_in_frames (int, optional): Number of video frames in each clip. frames_between_clips (int, optional): Number of frames between each consecutive video clip. task TaskType): Task type, 'classification', 'detection' or 'segmentation' - image_size (Optional[Union[int, Tuple[int, int]]], optional): Size of the input image. + image_size (int | tuple[int, int] | None, optional): Size of the input image. Defaults to None. - center_crop (Optional[Union[int, Tuple[int, int]]], optional): When provided, the images will be center-cropped + center_crop (int | tuple[int, int] | None, optional): When provided, the images will be center-cropped to the provided dimensions. normalize (bool): When True, the images will be normalized to the ImageNet statistics. train_batch_size (int, optional): Training batch size. Defaults to 32. eval_batch_size (int, optional): Test batch size. Defaults to 32. num_workers (int, optional): Number of workers. Defaults to 8. - transform_config_train (Optional[Union[str, A.Compose]], optional): Config for pre-processing + transform_config_train (str | A.Compose | None, optional): Config for pre-processing during training. Defaults to None. - transform_config_val (Optional[Union[str, A.Compose]], optional): Config for pre-processing + transform_config_val (str | A.Compose | None, optional): Config for pre-processing during validation. Defaults to None. val_split_mode (ValSplitMode): Setting that determines how the validation subset is obtained. val_split_ratio (float): Fraction of train or test images that will be reserved for validation. - seed (Optional[int], optional): Seed which may be set to a fixed value for reproducibility. + seed (int | None, optional): Seed which may be set to a fixed value for reproducibility. """ def __init__( @@ -198,17 +199,17 @@ def __init__( clip_length_in_frames: int = 1, frames_between_clips: int = 1, task: TaskType = TaskType.SEGMENTATION, - image_size: Optional[Union[int, Tuple[int, int]]] = None, - center_crop: Optional[Union[int, Tuple[int, int]]] = None, - normalization: Union[InputNormalizationMethod, str] = InputNormalizationMethod.IMAGENET, + image_size: int | tuple[int, int] | None = None, + center_crop: int | tuple[int, int] | None = None, + normalization: str | InputNormalizationMethod = InputNormalizationMethod.IMAGENET, train_batch_size: int = 32, eval_batch_size: int = 32, num_workers: int = 8, - transform_config_train: Optional[Union[str, A.Compose]] = None, - transform_config_eval: Optional[Union[str, A.Compose]] = None, + transform_config_train: str | A.Compose | None = None, + transform_config_eval: str | A.Compose | None = None, val_split_mode: ValSplitMode = ValSplitMode.FROM_TEST, val_split_ratio: float = 0.5, - seed: Optional[int] = None, + seed: int | None = None, ) -> None: super().__init__( train_batch_size=train_batch_size, From 591b74a532efccfd8d14f4b32457e22e1a34e51d Mon Sep 17 00:00:00 2001 From: Samet Date: Tue, 17 Jan 2023 05:00:03 -0700 Subject: [PATCH 46/85] Converted btech.py to new annotation format --- anomalib/data/btech.py | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/anomalib/data/btech.py b/anomalib/data/btech.py index 7e6ae08d07..ae42df03e9 100644 --- a/anomalib/data/btech.py +++ b/anomalib/data/btech.py @@ -9,10 +9,11 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import logging import shutil from pathlib import Path -from typing import Optional, Tuple, Union import albumentations as A import cv2 @@ -40,7 +41,7 @@ ) -def make_btech_dataset(path: Path, split: Optional[Union[Split, str]] = None) -> DataFrame: +def make_btech_dataset(path: Path, split: str | Split | None = None) -> DataFrame: """Create BTech samples by parsing the BTech data file structure. The files are expected to follow the structure: @@ -49,7 +50,7 @@ def make_btech_dataset(path: Path, split: Optional[Union[Split, str]] = None) -> Args: path (Path): Path to dataset - split (Optional[Union[Split, str]], optional): Dataset split (ie., either train or test). Defaults to None. + split (str | Split | None, optional): Dataset split (ie., either train or test). Defaults to None. split_ratio (float, optional): Ratio to split normal training images and add to the test set in case test set doesn't contain any normal images. Defaults to 0.1. @@ -159,10 +160,10 @@ class BTechDataset(AnomalibDataset): def __init__( self, - root: Union[Path, str], + root: str | Path, category: str, transform: A.Compose, - split: Optional[Union[Split, str]] = None, + split: str | Split | None = None, task: TaskType = TaskType.SEGMENTATION, ) -> None: super().__init__(task, transform) @@ -182,7 +183,7 @@ class BTech(AnomalibDataModule): root: Path to the BTech dataset category: Name of the BTech category. image_size: Variable to which image is resized. - center_crop (Optional[Union[int, Tuple[int, int]]], optional): When provided, the images will be center-cropped + center_crop (int | tuple[int, int] | None, optional): When provided, the images will be center-cropped to the provided dimensions. normalize (bool): When True, the images will be normalized to the ImageNet statistics. train_batch_size: Training batch size. @@ -192,12 +193,12 @@ class BTech(AnomalibDataModule): transform_config_train: Config for pre-processing during training. transform_config_val: Config for pre-processing during validation. create_validation_set: Create a validation subset in addition to the train and test subsets - seed (Optional[int], optional): Seed used during random subset splitting. + seed (int | None, optional): Seed used during random subset splitting. test_split_mode (TestSplitMode): Setting that determines how the testing subset is obtained. test_split_ratio (float): Fraction of images from the train set that will be reserved for testing. val_split_mode (ValSplitMode): Setting that determines how the validation subset is obtained. val_split_ratio (float): Fraction of train or test images that will be reserved for validation. - seed (Optional[int], optional): Seed which may be set to a fixed value for reproducibility. + seed (int | None, optional): Seed which may be set to a fixed value for reproducibility. Examples: >>> from anomalib.data import BTech @@ -230,20 +231,20 @@ def __init__( self, root: str, category: str, - image_size: Optional[Union[int, Tuple[int, int]]] = None, - center_crop: Optional[Union[int, Tuple[int, int]]] = None, - normalization: Union[InputNormalizationMethod, str] = InputNormalizationMethod.IMAGENET, + image_size: int | tuple[int, int] | None = None, + center_crop: int | tuple[int, int] | None = None, + normalization: str | InputNormalizationMethod = InputNormalizationMethod.IMAGENET, train_batch_size: int = 32, eval_batch_size: int = 32, num_workers: int = 8, task: TaskType = TaskType.SEGMENTATION, - transform_config_train: Optional[Union[str, A.Compose]] = None, - transform_config_eval: Optional[Union[str, A.Compose]] = None, + transform_config_train: str | A.Compose | None = None, + transform_config_eval: str | A.Compose | None = None, test_split_mode: TestSplitMode = TestSplitMode.FROM_DIR, test_split_ratio: float = 0.2, val_split_mode: ValSplitMode = ValSplitMode.SAME_AS_TEST, val_split_ratio: float = 0.5, - seed: Optional[int] = None, + seed: int | None = None, ) -> None: super().__init__( train_batch_size=train_batch_size, From aec74947987cb52ee0319a3a864099e21272bc1b Mon Sep 17 00:00:00 2001 From: Samet Date: Tue, 17 Jan 2023 05:05:39 -0700 Subject: [PATCH 47/85] Convert to new annotation format - folder --- anomalib/data/folder.py | 123 ++++++++++++++++++++-------------------- 1 file changed, 62 insertions(+), 61 deletions(-) diff --git a/anomalib/data/folder.py b/anomalib/data/folder.py index 60e97be839..bf43d9a1ee 100644 --- a/anomalib/data/folder.py +++ b/anomalib/data/folder.py @@ -6,8 +6,9 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + from pathlib import Path -from typing import Optional, Tuple, Union import albumentations as A from pandas import DataFrame @@ -24,11 +25,11 @@ ) -def _check_and_convert_path(path: Union[str, Path]) -> Path: +def _check_and_convert_path(path: str | Path) -> Path: """Check an input path, and convert to Pathlib object. Args: - path (Union[str, Path]): Input path. + path (str | Path): Input path. Returns: Path: Output path converted to pathlib object. @@ -39,14 +40,14 @@ def _check_and_convert_path(path: Union[str, Path]) -> Path: def _prepare_files_labels( - path: Union[str, Path], path_type: str, extensions: Optional[Tuple[str, ...]] = None -) -> Tuple[list, list]: + path: str | Path, path_type: str, extensions: tuple[str, ...] | None = None +) -> tuple[list, list]: """Return a list of filenames and list corresponding labels. Args: - path (Union[str, Path]): Path to the directory containing images. + path (str | Path): Path to the directory containing images. path_type (str): Type of images in the provided path ("normal", "abnormal", "normal_test") - extensions (Optional[Tuple[str, ...]], optional): Type of the image extensions to read from the + extensions (tuple[str, ...] | None, optional): Type of the image extensions to read from the directory. Returns: @@ -68,15 +69,15 @@ def _prepare_files_labels( return filenames, labels -def _resolve_path(folder: Union[Path, str], root: Optional[Union[Path, str]] = None) -> Path: +def _resolve_path(folder: str | Path, root: str | Path | None = None) -> Path: """Combines root and folder and returns the absolute path. This allows users to pass either a root directory and relative paths, or absolute paths to each of the image sources. This function makes sure that the samples dataframe always contains absolute paths. Args: - folder (Optional[Union[Path, str]]): Folder location containing image or mask data. - root (Optional[Union[Path, str]]): Root directory for the dataset. + folder (str | Path | None): Folder location containing image or mask data. + root (str | Path | None): Root directory for the dataset. """ folder = Path(folder) if folder.is_absolute(): @@ -93,28 +94,28 @@ def _resolve_path(folder: Union[Path, str], root: Optional[Union[Path, str]] = N def make_folder_dataset( - normal_dir: Union[str, Path], - root: Optional[Union[str, Path]] = None, - abnormal_dir: Optional[Union[str, Path]] = None, - normal_test_dir: Optional[Union[str, Path]] = None, - mask_dir: Optional[Union[str, Path]] = None, - split: Optional[Union[Split, str]] = None, - extensions: Optional[Tuple[str, ...]] = None, + normal_dir: str | Path, + root: str | Path | None = None, + abnormal_dir: str | Path | None = None, + normal_test_dir: str | Path | None = None, + mask_dir: str | Path | None = None, + split: str | Split | None = None, + extensions: tuple[str, ...] | None = None, ) -> DataFrame: """Make Folder Dataset. Args: - normal_dir (Union[str, Path]): Path to the directory containing normal images. - root (Optional[Union[str, Path]]): Path to the root directory of the dataset. - abnormal_dir (Optional[Union[str, Path]], optional): Path to the directory containing abnormal images. - normal_test_dir (Optional[Union[str, Path]], optional): Path to the directory containing + normal_dir (str | Path): Path to the directory containing normal images. + root (str | Path | None): Path to the root directory of the dataset. + abnormal_dir (str | Path | None, optional): Path to the directory containing abnormal images. + normal_test_dir (str | Path | None, optional): Path to the directory containing normal images for the test dataset. Normal test images will be a split of `normal_dir` if `None`. Defaults to None. - mask_dir (Optional[Union[str, Path]], optional): Path to the directory containing + mask_dir (str | Path | None, optional): Path to the directory containing the mask annotations. Defaults to None. - split (Optional[Union[Split, str]], optional): Dataset split (ie., Split.FULL, Split.TRAIN or Split.TEST). + split (str | Split | None, optional): Dataset split (ie., Split.FULL, Split.TRAIN or Split.TEST). Defaults to None. - extensions (Optional[Tuple[str, ...]], optional): Type of the image extensions to read from the + extensions (tuple[str, ...] | None, optional): Type of the image extensions to read from the directory. Returns: @@ -186,17 +187,17 @@ class FolderDataset(AnomalibDataset): Args: task (TaskType): Task type. (``classification``, ``detection`` or ``segmentation``). transform (A.Compose): Albumentations Compose object describing the transforms that are applied to the inputs. - split (Optional[Union[Split, str]]): Fixed subset split that follows from folder structure on file system. + split (str | Split | None): Fixed subset split that follows from folder structure on file system. Choose from [Split.FULL, Split.TRAIN, Split.TEST] - normal_dir (Union[str, Path]): Path to the directory containing normal images. - root (Optional[Union[str, Path]]): Root folder of the dataset. - abnormal_dir (Optional[Union[str, Path]], optional): Path to the directory containing abnormal images. - normal_test_dir (Optional[Union[str, Path]], optional): Path to the directory containing + normal_dir (str | Path): Path to the directory containing normal images. + root (str | Path | None): Root folder of the dataset. + abnormal_dir (str | Path | None, optional): Path to the directory containing abnormal images. + normal_test_dir (str | Path | None, optional): Path to the directory containing normal images for the test dataset. Defaults to None. - mask_dir (Optional[Union[str, Path]], optional): Path to the directory containing + mask_dir (str | Path | None, optional): Path to the directory containing the mask annotations. Defaults to None. - extensions (Optional[Tuple[str, ...]], optional): Type of the image extensions to read from the + extensions (tuple[str, ...] | None, optional): Type of the image extensions to read from the directory. val_split_mode (ValSplitMode): Setting that determines how the validation subset is obtained. @@ -209,13 +210,13 @@ def __init__( self, task: TaskType, transform: A.Compose, - normal_dir: Union[str, Path], - root: Optional[Union[str, Path]] = None, - abnormal_dir: Optional[Union[str, Path]] = None, - normal_test_dir: Optional[Union[str, Path]] = None, - mask_dir: Optional[Union[str, Path]] = None, - split: Optional[Union[Split, str]] = None, - extensions: Optional[Tuple[str, ...]] = None, + normal_dir: str | Path, + root: str | Path | None = None, + abnormal_dir: str | Path | None = None, + normal_test_dir: str | Path | None = None, + mask_dir: str | Path | None = None, + split: str | Split | None = None, + extensions: tuple[str, ...] | None = None, ) -> None: super().__init__(task, transform) @@ -244,23 +245,23 @@ class Folder(AnomalibDataModule): """Folder DataModule. Args: - normal_dir (Union[str, Path]): Name of the directory containing normal images. + normal_dir (str | Path): Name of the directory containing normal images. Defaults to "normal". - root (Optional[Union[str, Path]]): Path to the root folder containing normal and abnormal dirs. - abnormal_dir (Optional[Union[str, Path]]): Name of the directory containing abnormal images. + root (str | Path | None): Path to the root folder containing normal and abnormal dirs. + abnormal_dir (str | Path | None): Name of the directory containing abnormal images. Defaults to "abnormal". - normal_test_dir (Optional[Union[str, Path]], optional): Path to the directory containing + normal_test_dir (str | Path | None, optional): Path to the directory containing normal images for the test dataset. Defaults to None. - mask_dir (Optional[Union[str, Path]], optional): Path to the directory containing + mask_dir (str | Path | None, optional): Path to the directory containing the mask annotations. Defaults to None. normal_split_ratio (float, optional): Ratio to split normal training images and add to the test set in case test set doesn't contain any normal images. Defaults to 0.2. - extensions (Optional[Tuple[str, ...]], optional): Type of the image extensions to read from the + extensions (tuple[str, ...] | None, optional): Type of the image extensions to read from the directory. Defaults to None. - image_size (Optional[Union[int, Tuple[int, int]]], optional): Size of the input image. + image_size (int | tuple[int, int] | None, optional): Size of the input image. Defaults to None. - center_crop (Optional[Union[int, Tuple[int, int]]], optional): When provided, the images will be center-cropped + center_crop (int | tuple[int, int] | None, optional): When provided, the images will be center-cropped to the provided dimensions. normalize (bool): When True, the images will be normalized to the ImageNet statistics. train_batch_size (int, optional): Training batch size. Defaults to 32. @@ -268,42 +269,42 @@ class Folder(AnomalibDataModule): num_workers (int, optional): Number of workers. Defaults to 8. task (TaskType, optional): Task type. Could be ``classification``, ``detection`` or ``segmentation``. Defaults to segmentation. - transform_config_train (Optional[Union[str, A.Compose]], optional): Config for pre-processing + transform_config_train (str | A.Compose | None, optional): Config for pre-processing during training. Defaults to None. - transform_config_val (Optional[Union[str, A.Compose]], optional): Config for pre-processing + transform_config_val (str | A.Compose | None, optional): Config for pre-processing during validation. Defaults to None. test_split_mode (TestSplitMode): Setting that determines how the testing subset is obtained. test_split_ratio (float): Fraction of images from the train set that will be reserved for testing. val_split_mode (ValSplitMode): Setting that determines how the validation subset is obtained. val_split_ratio (float): Fraction of train or test images that will be reserved for validation. - seed (Optional[int], optional): Seed used during random subset splitting. + seed (int | None, optional): Seed used during random subset splitting. """ def __init__( self, - normal_dir: Union[str, Path], - root: Optional[Union[str, Path]] = None, - abnormal_dir: Optional[Union[str, Path]] = None, - normal_test_dir: Optional[Union[str, Path]] = None, - mask_dir: Optional[Union[str, Path]] = None, + normal_dir: str | Path, + root: str | Path | None = None, + abnormal_dir: str | Path | None = None, + normal_test_dir: str | Path | None = None, + mask_dir: str | Path | None = None, normal_split_ratio: float = 0.2, - extensions: Optional[Tuple[str]] = None, - image_size: Optional[Union[int, Tuple[int, int]]] = None, - center_crop: Optional[Union[int, Tuple[int, int]]] = None, - normalization: Union[InputNormalizationMethod, str] = InputNormalizationMethod.IMAGENET, + extensions: tuple[str] | None = None, + image_size: int | tuple[int, int] | None = None, + center_crop: int | tuple[int, int] | None = None, + normalization: str | InputNormalizationMethod = InputNormalizationMethod.IMAGENET, train_batch_size: int = 32, eval_batch_size: int = 32, num_workers: int = 8, task: TaskType = TaskType.SEGMENTATION, - transform_config_train: Optional[Union[str, A.Compose]] = None, - transform_config_eval: Optional[Union[str, A.Compose]] = None, + transform_config_train: str | A.Compose | None = None, + transform_config_eval: str | A.Compose | None = None, test_split_mode: TestSplitMode = TestSplitMode.FROM_DIR, test_split_ratio: float = 0.2, val_split_mode: ValSplitMode = ValSplitMode.FROM_TEST, val_split_ratio: float = 0.5, - seed: Optional[int] = None, + seed: int | None = None, ) -> None: super().__init__( train_batch_size=train_batch_size, From ee2f284d73e22bb8bb09ef13984228e556cac5a5 Mon Sep 17 00:00:00 2001 From: Samet Date: Tue, 17 Jan 2023 05:07:08 -0700 Subject: [PATCH 48/85] Convert to new annotation format - inference data --- anomalib/data/inference.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/anomalib/data/inference.py b/anomalib/data/inference.py index ebb98e2287..ba43ce0d75 100644 --- a/anomalib/data/inference.py +++ b/anomalib/data/inference.py @@ -3,8 +3,10 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + from pathlib import Path -from typing import Any, Optional, Tuple, Union +from typing import Any import albumentations as A from torch.utils.data.dataset import Dataset @@ -16,18 +18,18 @@ class InferenceDataset(Dataset): """Inference Dataset to perform prediction. Args: - path (Union[str, Path]): Path to an image or image-folder. - transform (Optional[A.Compose], optional): Albumentations Compose object describing the transforms that are + path (str | Path): Path to an image or image-folder. + transform (A.Compose | None, optional): Albumentations Compose object describing the transforms that are applied to the inputs. - image_size (Optional[Union[int, Tuple[int, int]]], optional): Target image size + image_size (int | tuple[int, int] | None, optional): Target image size to resize the original image. Defaults to None. """ def __init__( self, - path: Union[str, Path], - transform: Optional[A.Compose] = None, - image_size: Optional[Union[int, Tuple[int, int]]] = None, + path: str | Path, + transform: A.Compose | None = None, + image_size: int | tuple[int, int] | None = None, ) -> None: super().__init__() From 507ddf3283586e76f4086683a90ea5641c55498d Mon Sep 17 00:00:00 2001 From: Samet Date: Tue, 17 Jan 2023 05:08:24 -0700 Subject: [PATCH 49/85] Convert to new annotation format - init data --- anomalib/data/__init__.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/anomalib/data/__init__.py b/anomalib/data/__init__.py index b89fad0e0c..09544ed139 100644 --- a/anomalib/data/__init__.py +++ b/anomalib/data/__init__.py @@ -3,8 +3,9 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import logging -from typing import Union from omegaconf import DictConfig, ListConfig @@ -21,11 +22,11 @@ logger = logging.getLogger(__name__) -def get_datamodule(config: Union[DictConfig, ListConfig]) -> AnomalibDataModule: +def get_datamodule(config: DictConfig | ListConfig) -> AnomalibDataModule: """Get Anomaly Datamodule. Args: - config (Union[DictConfig, ListConfig]): Configuration of the anomaly model. + config (DictConfig | ListConfig): Configuration of the anomaly model. Returns: PyTorch Lightning DataModule From c78ccb1faf732ebdfd43f81b072e96eaab30a0ec Mon Sep 17 00:00:00 2001 From: Samet Date: Tue, 17 Jan 2023 05:09:54 -0700 Subject: [PATCH 50/85] Convert to new annotation format - mvtecdata --- anomalib/data/mvtec.py | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/anomalib/data/mvtec.py b/anomalib/data/mvtec.py index f319d7babd..f3ec3f8152 100644 --- a/anomalib/data/mvtec.py +++ b/anomalib/data/mvtec.py @@ -23,9 +23,11 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import logging from pathlib import Path -from typing import Optional, Sequence, Tuple, Union +from typing import Sequence import albumentations as A from pandas import DataFrame @@ -56,7 +58,7 @@ def make_mvtec_dataset( - root: Union[str, Path], split: Optional[Union[Split, str]] = None, extensions: Optional[Sequence[str]] = None + root: str | Path, split: str | Split | None = None, extensions: Sequence[str] | None = None ) -> DataFrame: """Create MVTec AD samples by parsing the MVTec AD data file structure. @@ -73,7 +75,7 @@ def make_mvtec_dataset( Args: path (Path): Path to dataset - split (Optional[Union[Split, str]], optional): Dataset split (ie., either train or test). Defaults to None. + split (str | Split | None, optional): Dataset split (ie., either train or test). Defaults to None. split_ratio (float, optional): Ratio to split normal training images and add to the test set in case test set doesn't contain any normal images. Defaults to 0.1. @@ -150,7 +152,7 @@ class MVTecDataset(AnomalibDataset): Args: task (TaskType): Task type, ``classification``, ``detection`` or ``segmentation`` transform (A.Compose): Albumentations Compose object describing the transforms that are applied to the inputs. - split (Optional[Union[Split, str]]): Split of the dataset, usually Split.TRAIN or Split.TEST + split (str | Split | None): Split of the dataset, usually Split.TRAIN or Split.TEST root (str): Path to the root of the dataset category (str): Sub-category of the dataset, e.g. 'bottle' """ @@ -161,7 +163,7 @@ def __init__( transform: A.Compose, root: str, category: str, - split: Optional[Union[Split, str]] = None, + split: str | Split | None = None, ) -> None: super().__init__(task=task, transform=transform) @@ -178,46 +180,46 @@ class MVTec(AnomalibDataModule): Args: root (str): Path to the root of the dataset category (str): Category of the MVTec dataset (e.g. "bottle" or "cable"). - image_size (Optional[Union[int, Tuple[int, int]]], optional): Size of the input image. + image_size (int | tuple[int, int] | None, optional): Size of the input image. Defaults to None. - center_crop (Optional[Union[int, Tuple[int, int]]], optional): When provided, the images will be center-cropped + center_crop (int | tuple[int, int] | None, optional): When provided, the images will be center-cropped to the provided dimensions. normalize (bool): When True, the images will be normalized to the ImageNet statistics. train_batch_size (int, optional): Training batch size. Defaults to 32. eval_batch_size (int, optional): Test batch size. Defaults to 32. num_workers (int, optional): Number of workers. Defaults to 8. task TaskType): Task type, 'classification', 'detection' or 'segmentation' - transform_config_train (Optional[Union[str, A.Compose]], optional): Config for pre-processing + transform_config_train (str | A.Compose | None, optional): Config for pre-processing during training. Defaults to None. - transform_config_val (Optional[Union[str, A.Compose]], optional): Config for pre-processing + transform_config_val (str | A.Compose | None, optional): Config for pre-processing during validation. Defaults to None. test_split_mode (TestSplitMode): Setting that determines how the testing subset is obtained. test_split_ratio (float): Fraction of images from the train set that will be reserved for testing. val_split_mode (ValSplitMode): Setting that determines how the validation subset is obtained. val_split_ratio (float): Fraction of train or test images that will be reserved for validation. - seed (Optional[int], optional): Seed which may be set to a fixed value for reproducibility. + seed (int | None, optional): Seed which may be set to a fixed value for reproducibility. """ def __init__( self, root: str, category: str, - image_size: Optional[Union[int, Tuple[int, int]]] = None, - center_crop: Optional[Union[int, Tuple[int, int]]] = None, - normalization: Union[InputNormalizationMethod, str] = InputNormalizationMethod.IMAGENET, + image_size: int | tuple[int, int] | None = None, + center_crop: int | tuple[int, int] | None = None, + normalization: str | InputNormalizationMethod = InputNormalizationMethod.IMAGENET, train_batch_size: int = 32, eval_batch_size: int = 32, num_workers: int = 8, task: TaskType = TaskType.SEGMENTATION, - transform_config_train: Optional[Union[str, A.Compose]] = None, - transform_config_eval: Optional[Union[str, A.Compose]] = None, + transform_config_train: str | A.Compose | None = None, + transform_config_eval: str | A.Compose | None = None, test_split_mode: TestSplitMode = TestSplitMode.FROM_DIR, test_split_ratio: float = 0.2, val_split_mode: ValSplitMode = ValSplitMode.SAME_AS_TEST, val_split_ratio: float = 0.5, - seed: Optional[int] = None, + seed: int | None = None, ) -> None: super().__init__( train_batch_size=train_batch_size, From aedfeeda987da3adad3bd475dc75e86892f48467 Mon Sep 17 00:00:00 2001 From: Samet Date: Tue, 17 Jan 2023 05:11:58 -0700 Subject: [PATCH 51/85] Convert to new annotation format - synthetic data --- anomalib/data/synthetic.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/anomalib/data/synthetic.py b/anomalib/data/synthetic.py index 2ddf38180b..6287a88d2c 100644 --- a/anomalib/data/synthetic.py +++ b/anomalib/data/synthetic.py @@ -6,13 +6,14 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import logging import math import shutil from copy import deepcopy from pathlib import Path from tempfile import mkdtemp -from typing import Dict import albumentations as A import cv2 @@ -130,7 +131,7 @@ def __init__(self, task: TaskType, transform: A.Compose, source_samples: DataFra self.setup() @classmethod - def from_dataset(cls, dataset: AnomalibDataset) -> "SyntheticAnomalyDataset": + def from_dataset(cls, dataset: AnomalibDataset) -> SyntheticAnomalyDataset: """Create a synthetic anomaly dataset from an existing dataset of normal images. Args: @@ -139,7 +140,7 @@ def from_dataset(cls, dataset: AnomalibDataset) -> "SyntheticAnomalyDataset": """ return cls(task=dataset.task, transform=dataset.transform, source_samples=dataset.samples) - def __copy__(self) -> "SyntheticAnomalyDataset": + def __copy__(self) -> SyntheticAnomalyDataset: """Returns a shallow copy of the dataset object and prevents cleanup when original object is deleted.""" cls = self.__class__ new = cls.__new__(cls) @@ -147,7 +148,7 @@ def __copy__(self) -> "SyntheticAnomalyDataset": self._cleanup = False return new - def __deepcopy__(self, _memo: Dict) -> "SyntheticAnomalyDataset": + def __deepcopy__(self, _memo: dict) -> SyntheticAnomalyDataset: """Returns a deep copy of the dataset object and prevents cleanup when original object is deleted.""" cls = self.__class__ new = cls.__new__(cls) From d0e507b4fea5b9f1906b60fdcbd3953c6ab20d93 Mon Sep 17 00:00:00 2001 From: Samet Date: Tue, 17 Jan 2023 05:16:03 -0700 Subject: [PATCH 52/85] Convert to new annotation format - ucsd data --- anomalib/data/ucsd_ped.py | 42 ++++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/anomalib/data/ucsd_ped.py b/anomalib/data/ucsd_ped.py index 9774956c36..e775627921 100644 --- a/anomalib/data/ucsd_ped.py +++ b/anomalib/data/ucsd_ped.py @@ -3,10 +3,12 @@ # Copyright (C) 2023 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import logging from pathlib import Path from shutil import move -from typing import Any, Callable, Dict, Optional, Tuple, Union +from typing import Any, Callable import albumentations as A import cv2 @@ -37,7 +39,7 @@ ) -def make_ucsd_dataset(path: Path, split: Optional[Union[Split, str]] = None) -> DataFrame: +def make_ucsd_dataset(path: Path, split: str | Split | None = None) -> DataFrame: """Create UCSD Pedestrian dataset by parsing the file structure. The files are expected to follow the structure: @@ -46,7 +48,7 @@ def make_ucsd_dataset(path: Path, split: Optional[Union[Split, str]] = None) -> Args: root (Path): Path to dataset - split (Optional[Union[Split, str]], optional): Dataset split (ie., either train or test). Defaults to None. + split (str | Split | None, optional): Dataset split (ie., either train or test). Defaults to None. Example: The following example shows how to get testing samples from UCSDped2 category: @@ -92,7 +94,7 @@ def make_ucsd_dataset(path: Path, split: Optional[Union[Split, str]] = None) -> class UCSDpedClipsIndexer(ClipsIndexer): """Clips class for UCSDped dataset.""" - def get_mask(self, idx) -> Optional[Tensor]: + def get_mask(self, idx) -> np.ndarray | None: """Retrieve the masks from the file system.""" video_idx, frames_idx = self.get_clip_location(idx) @@ -116,7 +118,7 @@ def _compute_frame_pts(self) -> None: self.video_fps = [None] * len(self.video_paths) # fps information cannot be inferred from folder structure - def get_clip(self, idx: int) -> Tuple[Tensor, Tensor, Dict[str, Any], int]: + def get_clip(self, idx: int) -> tuple[Tensor, Tensor, dict[str, Any], int]: """Gets a subclip from a list of videos. Args: @@ -125,7 +127,7 @@ def get_clip(self, idx: int) -> Tuple[Tensor, Tensor, Dict[str, Any], int]: Returns: video (Tensor) audio (Tensor) - info (Dict) + info (dict) video_idx (int): index of the video in `video_paths` """ if idx >= self.num_clips(): @@ -150,7 +152,7 @@ class UCSDpedDataset(AnomalibVideoDataset): root (str): Path to the root of the dataset category (str): Sub-category of the dataset, e.g. 'bottle' transform (A.Compose): Albumentations Compose object describing the transforms that are applied to the inputs. - split (Optional[Union[Split, str]]): Split of the dataset, usually Split.TRAIN or Split.TEST + split (str | Split | None): Split of the dataset, usually Split.TRAIN or Split.TEST clip_length_in_frames (int, optional): Number of video frames in each clip. frames_between_clips (int, optional): Number of frames between each consecutive video clip. """ @@ -158,7 +160,7 @@ class UCSDpedDataset(AnomalibVideoDataset): def __init__( self, task: TaskType, - root: Union[Path, str], + root: str | Path, category: str, transform: A.Compose, split: Split, @@ -185,26 +187,26 @@ class UCSDped(AnomalibVideoDataModule): clip_length_in_frames (int, optional): Number of video frames in each clip. frames_between_clips (int, optional): Number of frames between each consecutive video clip. task (TaskType): Task type, 'classification', 'detection' or 'segmentation' - image_size (Optional[Union[int, Tuple[int, int]]], optional): Size of the input image. + image_size (int | tuple[int, int] | None, optional): Size of the input image. Defaults to None. - center_crop (Optional[Union[int, Tuple[int, int]]], optional): When provided, the images will be center-cropped + center_crop (int | tuple[int, int] | None, optional): When provided, the images will be center-cropped to the provided dimensions. normalize (bool): When True, the images will be normalized to the ImageNet statistics. - center_crop (Optional[Union[int, Tuple[int, int]]], optional): When provided, the images will be center-cropped + center_crop (int | tuple[int, int] | None, optional): When provided, the images will be center-cropped to the provided dimensions. normalize (bool): When True, the images will be normalized to the ImageNet statistics. train_batch_size (int, optional): Training batch size. Defaults to 32. eval_batch_size (int, optional): Test batch size. Defaults to 32. num_workers (int, optional): Number of workers. Defaults to 8. - transform_config_train (Optional[Union[str, A.Compose]], optional): Config for pre-processing + transform_config_train (str | A.Compose | None, optional): Config for pre-processing during training. Defaults to None. - transform_config_val (Optional[Union[str, A.Compose]], optional): Config for pre-processing + transform_config_val (str | A.Compose | None, optional): Config for pre-processing during validation. Defaults to None. val_split_mode (ValSplitMode): Setting that determines how the validation subset is obtained. val_split_ratio (float): Fraction of train or test images that will be reserved for validation. - seed (Optional[int], optional): Seed which may be set to a fixed value for reproducibility. + seed (int | None, optional): Seed which may be set to a fixed value for reproducibility. """ def __init__( @@ -214,17 +216,17 @@ def __init__( clip_length_in_frames: int = 1, frames_between_clips: int = 1, task: TaskType = TaskType.SEGMENTATION, - image_size: Optional[Union[int, Tuple[int, int]]] = None, - center_crop: Optional[Union[int, Tuple[int, int]]] = None, - normalization: Union[InputNormalizationMethod, str] = InputNormalizationMethod.IMAGENET, + image_size: int | tuple[int, int] | None = None, + center_crop: int | tuple[int, int] | None = None, + normalization: str | InputNormalizationMethod = InputNormalizationMethod.IMAGENET, train_batch_size: int = 32, eval_batch_size: int = 32, num_workers: int = 8, - transform_config_train: Optional[Union[str, A.Compose]] = None, - transform_config_eval: Optional[Union[str, A.Compose]] = None, + transform_config_train: str | A.Compose | None = None, + transform_config_eval: str | A.Compose | None = None, val_split_mode: ValSplitMode = ValSplitMode.FROM_TEST, val_split_ratio: float = 0.5, - seed: Optional[int] = None, + seed: int | None = None, ) -> None: super().__init__( train_batch_size=train_batch_size, From 78f2e52293920643c1b2ec60a39d9100d6ac2531 Mon Sep 17 00:00:00 2001 From: Samet Date: Tue, 17 Jan 2023 05:17:00 -0700 Subject: [PATCH 53/85] Convert to new annotation format - visa data --- anomalib/data/visa.py | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/anomalib/data/visa.py b/anomalib/data/visa.py index 017a45b373..d18ff4fdd9 100644 --- a/anomalib/data/visa.py +++ b/anomalib/data/visa.py @@ -21,11 +21,12 @@ # Subset splitting code adapted from https://github.com/amazon-science/spot-diff # Original licence: Apache-2.0 +from __future__ import annotations + import csv import logging import shutil from pathlib import Path -from typing import Optional, Tuple, Union import albumentations as A import cv2 @@ -61,8 +62,8 @@ class VisaDataset(AnomalibDataset): Args: task (TaskType): Task type, ``classification``, ``detection`` or ``segmentation`` transform (A.Compose): Albumentations Compose object describing the transforms that are applied to the inputs. - split (Optional[Union[Split, str]]): Split of the dataset, usually Split.TRAIN or Split.TEST - root (Union[str, Path]): Path to the root of the dataset + split (str | Split | None): Split of the dataset, usually Split.TRAIN or Split.TEST + root (str | Path): Path to the root of the dataset category (str): Sub-category of the dataset, e.g. 'candle' """ @@ -70,9 +71,9 @@ def __init__( self, task: TaskType, transform: A.Compose, - root: Union[str, Path], + root: str | Path, category: str, - split: Optional[Union[Split, str]] = None, + split: str | Split | None = None, ) -> None: super().__init__(task=task, transform=transform) @@ -89,46 +90,46 @@ class Visa(AnomalibDataModule): Args: root (str): Path to the root of the dataset category (str): Category of the MVTec dataset (e.g. "bottle" or "cable"). - image_size (Optional[Union[int, Tuple[int, int]]], optional): Size of the input image. + image_size (int | tuple[int, int] | None, optional): Size of the input image. Defaults to None. - center_crop (Optional[Union[int, Tuple[int, int]]], optional): When provided, the images will be center-cropped + center_crop (int | tuple[int, int] | None, optional): When provided, the images will be center-cropped to the provided dimensions. normalize (bool): When True, the images will be normalized to the ImageNet statistics. train_batch_size (int, optional): Training batch size. Defaults to 32. eval_batch_size (int, optional): Test batch size. Defaults to 32. num_workers (int, optional): Number of workers. Defaults to 8. task (TaskType): Task type, 'classification', 'detection' or 'segmentation' - transform_config_train (Optional[Union[str, A.Compose]], optional): Config for pre-processing + transform_config_train (str | A.Compose | None, optional): Config for pre-processing during training. Defaults to None. - transform_config_val (Optional[Union[str, A.Compose]], optional): Config for pre-processing + transform_config_val (str | A.Compose | None, optional): Config for pre-processing during validation. Defaults to None. test_split_mode (TestSplitMode): Setting that determines how the testing subset is obtained. test_split_ratio (float): Fraction of images from the train set that will be reserved for testing. val_split_mode (ValSplitMode): Setting that determines how the validation subset is obtained. val_split_ratio (float): Fraction of train or test images that will be reserved for validation. - seed (Optional[int], optional): Seed which may be set to a fixed value for reproducibility. + seed (int | None, optional): Seed which may be set to a fixed value for reproducibility. """ def __init__( self, root: str, category: str, - image_size: Optional[Union[int, Tuple[int, int]]] = None, - center_crop: Optional[Union[int, Tuple[int, int]]] = None, - normalization: Union[InputNormalizationMethod, str] = InputNormalizationMethod.IMAGENET, + image_size: int | tuple[int, int] | None = None, + center_crop: int | tuple[int, int] | None = None, + normalization: str | InputNormalizationMethod = InputNormalizationMethod.IMAGENET, train_batch_size: int = 32, eval_batch_size: int = 32, num_workers: int = 8, task: TaskType = TaskType.SEGMENTATION, - transform_config_train: Optional[Union[str, A.Compose]] = None, - transform_config_eval: Optional[Union[str, A.Compose]] = None, + transform_config_train: str | A.Compose | None = None, + transform_config_eval: str | A.Compose | None = None, test_split_mode: TestSplitMode = TestSplitMode.FROM_DIR, test_split_ratio: float = 0.2, val_split_mode: ValSplitMode = ValSplitMode.SAME_AS_TEST, val_split_ratio: float = 0.5, - seed: Optional[int] = None, + seed: int | None = None, ) -> None: super().__init__( train_batch_size=train_batch_size, From 048c4f68db38e2d655acb0e794379ee4f1013e3b Mon Sep 17 00:00:00 2001 From: Samet Date: Tue, 17 Jan 2023 05:45:56 -0700 Subject: [PATCH 54/85] Convert to new annotation format - inferencer --- .../deploy/inferencers/base_inferencer.py | 42 +++++++++---------- .../deploy/inferencers/openvino_inferencer.py | 36 ++++++++-------- .../deploy/inferencers/torch_inferencer.py | 40 +++++++++--------- 3 files changed, 60 insertions(+), 58 deletions(-) diff --git a/anomalib/deploy/inferencers/base_inferencer.py b/anomalib/deploy/inferencers/base_inferencer.py index c6005525bb..03c76f1973 100644 --- a/anomalib/deploy/inferencers/base_inferencer.py +++ b/anomalib/deploy/inferencers/base_inferencer.py @@ -3,9 +3,11 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + from abc import ABC, abstractmethod from pathlib import Path -from typing import Any, Dict, Optional, Tuple, Union, cast +from typing import Any, cast import cv2 import numpy as np @@ -30,31 +32,29 @@ class Inferencer(ABC): """ @abstractmethod - def load_model(self, path: Union[str, Path]) -> Any: + def load_model(self, path: str | Path) -> Any: """Load Model.""" raise NotImplementedError @abstractmethod - def pre_process(self, image: np.ndarray) -> Union[np.ndarray, Tensor]: + def pre_process(self, image: np.ndarray) -> np.ndarray | Tensor: """Pre-process.""" raise NotImplementedError @abstractmethod - def forward(self, image: Union[np.ndarray, Tensor]) -> Union[np.ndarray, Tensor]: + def forward(self, image: np.ndarray | Tensor) -> np.ndarray | Tensor: """Forward-Pass input to model.""" raise NotImplementedError @abstractmethod - def post_process( - self, predictions: Union[np.ndarray, Tensor], meta_data: Optional[Dict[str, Any]] - ) -> Dict[str, Any]: + def post_process(self, predictions: np.ndarray | Tensor, meta_data: dict[str, Any] | None) -> dict[str, Any]: """Post-Process.""" raise NotImplementedError def predict( self, - image: Union[str, np.ndarray, Path], - meta_data: Optional[Dict[str, Any]] = None, + image: str | Path | np.ndarray, + meta_data: dict[str, Any] | None = None, ) -> ImageResult: """Perform a prediction for a given input image. @@ -128,20 +128,20 @@ def __call__(self, image: np.ndarray) -> ImageResult: @staticmethod def _normalize( - pred_scores: Union[Tensor, np.float32], - meta_data: Union[Dict, DictConfig], - anomaly_maps: Optional[Union[Tensor, np.ndarray]] = None, - ) -> Tuple[Optional[Union[np.ndarray, Tensor]], float]: + pred_scores: Tensor | np.float32, + meta_data: dict | DictConfig, + anomaly_maps: Tensor | np.ndarray | None = None, + ) -> tuple[np.ndarray | Tensor | None, float]: """Applies normalization and resizes the image. Args: - pred_scores (Union[Tensor, np.float32]): Predicted anomaly score - meta_data (Dict): Meta data. Post-processing step sometimes requires + pred_scores (Tensor | np.float32): Predicted anomaly score + meta_data (dict | DictConfig): Meta data. Post-processing step sometimes requires additional meta data such as image shape. This variable comprises such info. - anomaly_maps (Optional[Union[Tensor, np.ndarray]]): Predicted raw anomaly map. + anomaly_maps (Tensor | np.ndarray | None): Predicted raw anomaly map. Returns: - Tuple[Optional[Union[np.ndarray, Tensor], float]]: Post processed predictions that are ready to be + tuple[np.ndarray | Tensor | None, float]: Post processed predictions that are ready to be visualized and predicted scores. """ @@ -170,17 +170,17 @@ def _normalize( return anomaly_maps, float(pred_scores) - def _load_meta_data(self, path: Optional[Union[str, Path]] = None) -> Union[DictConfig, Dict]: + def _load_meta_data(self, path: str | Path | None = None) -> dict | DictConfig: """Loads the meta data from the given path. Args: - path (Optional[Union[str, Path]], optional): Path to JSON file containing the metadata. + path (str | Path | None, optional): Path to JSON file containing the metadata. If no path is provided, it returns an empty dict. Defaults to None. Returns: - Union[DictConfig, Dict]: Dictionary containing the metadata. + dict | DictConfig: Dictionary containing the metadata. """ - meta_data: Union[DictConfig, Dict[str, Union[float, np.ndarray, Tensor]]] = {} + meta_data: dict[str, float | np.ndarray | Tensor] | DictConfig = {} if path is not None: config = OmegaConf.load(path) meta_data = cast(DictConfig, config) diff --git a/anomalib/deploy/inferencers/openvino_inferencer.py b/anomalib/deploy/inferencers/openvino_inferencer.py index c151a2da3b..b027c15609 100644 --- a/anomalib/deploy/inferencers/openvino_inferencer.py +++ b/anomalib/deploy/inferencers/openvino_inferencer.py @@ -3,9 +3,11 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + from importlib.util import find_spec from pathlib import Path -from typing import Any, Dict, Optional, Tuple, Union +from typing import Any import cv2 import numpy as np @@ -27,18 +29,18 @@ class OpenVINOInferencer(Inferencer): """OpenVINO implementation for the inference. Args: - config (Union[str, Path, DictConfig, ListConfig]): Configurable parameters that are used + config (str | Path | DictConfig | ListConfig): Configurable parameters that are used during the training stage. - path (Union[str, Path]): Path to the openvino onnx, xml or bin file. - meta_data_path (Union[str, Path], optional): Path to metadata file. Defaults to None. + path (str | Path): Path to the openvino onnx, xml or bin file. + meta_data_path (str | Path, optional): Path to metadata file. Defaults to None. """ def __init__( self, - config: Union[str, Path, DictConfig, ListConfig], - path: Union[str, Path, Tuple[bytes, bytes]], - meta_data_path: Optional[Union[str, Path]] = None, - device: Optional[str] = "CPU", + config: str | Path | DictConfig | ListConfig, + path: str | Path | tuple[bytes, bytes], + meta_data_path: str | Path | None = None, + device: str | None = "CPU", ) -> None: # Check and load the configuration if isinstance(config, (str, Path)): @@ -52,15 +54,15 @@ def __init__( self.input_blob, self.output_blob, self.network = self.load_model(path) self.meta_data = super()._load_meta_data(meta_data_path) - def load_model(self, path: Union[str, Path, Tuple[bytes, bytes]]): + def load_model(self, path: str | Path | tuple[bytes, bytes]): """Load the OpenVINO model. Args: - path (Union[str, Path, Tuple[bytes, bytes]]): Path to the onnx or xml and bin files + path (str | Path | tuple[bytes, bytes]): Path to the onnx or xml and bin files or tuple of .xml and .bin data as bytes. Returns: - [Tuple[str, str, ExecutableNetwork]]: Input and Output blob names + [tuple[str, str, ExecutableNetwork]]: Input and Output blob names together with the Executable network. """ ie_core = IECore() @@ -129,9 +131,7 @@ def forward(self, image: np.ndarray) -> np.ndarray: """ return self.network.infer(inputs={self.input_blob: image}) - def post_process( - self, predictions: np.ndarray, meta_data: Optional[Union[Dict, DictConfig]] = None - ) -> Dict[str, Any]: + def post_process(self, predictions: np.ndarray, meta_data: dict | DictConfig | None = None) -> dict[str, Any]: """Post process the output predictions. Args: @@ -141,7 +141,7 @@ def post_process( Defaults to None. Returns: - Dict[str, Any]: Post processed prediction results. + dict[str, Any]: Post processed prediction results. """ if meta_data is None: meta_data = self.meta_data @@ -149,9 +149,9 @@ def post_process( predictions = predictions[self.output_blob] # Initialize the result variables. - anomaly_map: Optional[np.ndarray] = None - pred_label: Optional[float] = None - pred_mask: Optional[float] = None + anomaly_map: np.ndarray | None = None + pred_label: float | None = None + pred_mask: float | None = None # If predictions returns a single value, this means that the task is # classification, and the value is the classification prediction score. diff --git a/anomalib/deploy/inferencers/torch_inferencer.py b/anomalib/deploy/inferencers/torch_inferencer.py index f5102089aa..924e9d1713 100644 --- a/anomalib/deploy/inferencers/torch_inferencer.py +++ b/anomalib/deploy/inferencers/torch_inferencer.py @@ -3,8 +3,10 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + from pathlib import Path -from typing import Any, Dict, Optional, Union +from typing import Any import cv2 import numpy as np @@ -27,19 +29,19 @@ class TorchInferencer(Inferencer): """PyTorch implementation for the inference. Args: - config (Union[str, Path, DictConfig, ListConfig]): Configurable parameters that are used + config (str | Path | DictConfig | ListConfig): Configurable parameters that are used during the training stage. - model_source (Union[str, Path, AnomalyModule]): Path to the model ckpt file or the Anomaly model. - meta_data_path (Union[str, Path], optional): Path to metadata file. If none, it tries to load the params + model_source (str | Path | AnomalyModule): Path to the model ckpt file or the Anomaly model. + meta_data_path (str | Path, optional): Path to metadata file. If none, it tries to load the params from the model state_dict. Defaults to None. - device (Optional[str], optional): Device to use for inference. Options are auto, cpu, cuda. Defaults to "auto". + device (str | None, optional): Device to use for inference. Options are auto, cpu, cuda. Defaults to "auto". """ def __init__( self, - config: Union[str, Path, DictConfig, ListConfig], - model_source: Union[str, Path, AnomalyModule], - meta_data_path: Optional[Union[str, Path]] = None, + config: str | Path | DictConfig | ListConfig, + model_source: str | Path | AnomalyModule, + meta_data_path: str | Path | None = None, device: str = "auto", ) -> None: @@ -80,28 +82,28 @@ def _get_device(device: str) -> torch.device: device = "cuda" return torch.device(device) - def _load_meta_data(self, path: Optional[Union[str, Path]] = None) -> Union[Dict, DictConfig]: + def _load_meta_data(self, path: str | Path | None = None) -> dict | DictConfig: """Load metadata from file or from model state dict. Args: - path (Optional[Union[str, Path]], optional): Path to metadata file. If none, it tries to load the params + path (str | Path | None, optional): Path to metadata file. If none, it tries to load the params from the model state_dict. Defaults to None. Returns: - Dict: Dictionary containing the meta_data. + dict: Dictionary containing the meta_data. """ - meta_data: Union[DictConfig, Dict[str, Union[float, Tensor, np.ndarray]]] + meta_data: dict[str, float | np.ndarray | Tensor] | DictConfig if path is None: meta_data = get_model_metadata(self.model) else: meta_data = super()._load_meta_data(path) return meta_data - def load_model(self, path: Union[str, Path]) -> AnomalyModule: + def load_model(self, path: str | Path) -> AnomalyModule: """Load the PyTorch model. Args: - path (Union[str, Path]): Path to model ckpt file. + path (str | Path): Path to model ckpt file. Returns: (AnomalyModule): PyTorch Lightning model. @@ -150,17 +152,17 @@ def forward(self, image: Tensor) -> Tensor: """ return self.model(image) - def post_process(self, predictions: Tensor, meta_data: Optional[Union[Dict, DictConfig]] = None) -> Dict[str, Any]: + def post_process(self, predictions: Tensor, meta_data: dict | DictConfig | None = None) -> dict[str, Any]: """Post process the output predictions. Args: predictions (Tensor): Raw output predicted by the model. - meta_data (Dict, optional): Meta data. Post-processing step sometimes requires + meta_data (dict, optional): Meta data. Post-processing step sometimes requires additional meta data such as image shape. This variable comprises such info. Defaults to None. Returns: - Dict[str, Union[str, float, np.ndarray]]: Post processed prediction results. + dict[str, str | float | np.ndarray]: Post processed prediction results. """ if meta_data is None: meta_data = self.meta_data @@ -184,12 +186,12 @@ def post_process(self, predictions: Tensor, meta_data: Optional[Union[Dict, Dict # Common practice in anomaly detection is to assign anomalous # label to the prediction if the prediction score is greater # than the image threshold. - pred_label: Optional[str] = None + pred_label: str | None = None if "image_threshold" in meta_data: pred_idx = pred_score >= meta_data["image_threshold"] pred_label = "Anomalous" if pred_idx else "Normal" - pred_mask: Optional[np.ndarray] = None + pred_mask: np.ndarray | None = None if "pixel_threshold" in meta_data: pred_mask = (anomaly_map >= meta_data["pixel_threshold"]).squeeze().astype(np.uint8) From 24bccbefb60c640b1b8b692dbc48b0f4c1f29a8b Mon Sep 17 00:00:00 2001 From: Samet Date: Tue, 17 Jan 2023 06:06:47 -0700 Subject: [PATCH 55/85] Convert to new annotation format - cfa model --- anomalib/models/cfa/anomaly_map.py | 10 ++++----- anomalib/models/cfa/lightning_model.py | 31 +++++++++++++------------- anomalib/models/cfa/torch_model.py | 24 ++++++++++---------- 3 files changed, 32 insertions(+), 33 deletions(-) diff --git a/anomalib/models/cfa/anomaly_map.py b/anomalib/models/cfa/anomaly_map.py index 087ceab8e6..15ecf4321d 100644 --- a/anomalib/models/cfa/anomaly_map.py +++ b/anomalib/models/cfa/anomaly_map.py @@ -4,7 +4,7 @@ # SPDX-License-Identifier: Apache-2.0 -from typing import Tuple, Union +from __future__ import annotations import torch import torch.nn.functional as F @@ -20,7 +20,7 @@ class AnomalyMapGenerator(nn.Module): def __init__( self, - image_size: Union[ListConfig, Tuple], + image_size: ListConfig | tuple, num_nearest_neighbors: int, sigma: int = 4, ) -> None: @@ -29,13 +29,13 @@ def __init__( self.num_nearest_neighbors = num_nearest_neighbors self.sigma = sigma - def compute_score(self, distance: Tensor, scale: Tuple[int, int]) -> Tensor: + def compute_score(self, distance: Tensor, scale: tuple[int, int]) -> Tensor: """Compute score based on the distance. Args: distance (Tensor): Distance tensor computed using target oriented features. - scale (Tuple[int, int]): Height and width of the largest feature + scale (tuple[int, int]): Height and width of the largest feature map. Returns: @@ -78,7 +78,7 @@ def forward(self, **kwargs) -> Tensor: raise ValueError(f"Expected keys `distance` and `scale. Found {kwargs.keys()}") distance: Tensor = kwargs["distance"] - scale: Tuple[int, int] = kwargs["scale"] + scale: tuple[int, int] = kwargs["scale"] score = self.compute_score(distance=distance, scale=scale) anomaly_map = self.compute_anomaly_map(score) diff --git a/anomalib/models/cfa/lightning_model.py b/anomalib/models/cfa/lightning_model.py index ab20f046a2..51d65a1326 100644 --- a/anomalib/models/cfa/lightning_model.py +++ b/anomalib/models/cfa/lightning_model.py @@ -8,8 +8,9 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import logging -from typing import Dict, List, Optional, Tuple, Union import torch from omegaconf import DictConfig, ListConfig @@ -34,7 +35,7 @@ class Cfa(AnomalyModule): """CFA: Coupled-hypersphere-based Feature Adaptation for Target-Oriented Anomaly Localization. Args: - input_size (Tuple[int, int]): Size of the model input. + input_size (tuple[int, int]): Size of the model input. backbone (str): Backbone CNN network gamma_c (int, optional): gamma_c value from the paper. Defaults to 1. gamma_d (int, optional): gamma_d value from the paper. Defaults to 1. @@ -45,7 +46,7 @@ class Cfa(AnomalyModule): def __init__( self, - input_size: Tuple[int, int], + input_size: tuple[int, int], backbone: str, gamma_c: int = 1, gamma_d: int = 1, @@ -73,11 +74,11 @@ def on_train_start(self) -> None: """Initialize the centroid for the memory bank computation.""" self.model.initialize_centroid(data_loader=self.trainer.datamodule.train_dataloader()) # type: ignore - def training_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> STEP_OUTPUT: + def training_step(self, batch: dict[str, str | Tensor], *args, **kwargs) -> STEP_OUTPUT: """Training step for the CFA model. Args: - batch (Dict[str, Union[str, Tensor]]): Batch input. + batch (dict[str, str | Tensor]): Batch input. Returns: STEP_OUTPUT: Loss value. @@ -86,11 +87,11 @@ def training_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) - loss = self.loss(distance) return {"loss": loss} - def validation_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> STEP_OUTPUT: + def validation_step(self, batch: dict[str, str | Tensor], *args, **kwargs) -> STEP_OUTPUT: """Validation step for the CFA model. Args: - batch (Dict[str, Union[str, Tensor]]): Input batch. + batch (dict[str, str | Tensor]): Input batch. Returns: dict: Anomaly map computed by the model. @@ -98,15 +99,13 @@ def validation_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) batch["anomaly_maps"] = self.model(batch["image"]) return batch - def backward( - self, loss: Tensor, optimizer: Optional[Optimizer], optimizer_idx: Optional[int], *args, **kwargs - ) -> None: + def backward(self, loss: Tensor, optimizer: Optimizer | None, optimizer_idx: int | None, *args, **kwargs) -> None: """Backward step for the CFA model. Args: loss (Tensor): Loss value. - optimizer (Optional[Optimizer]): Optimizer. - optimizer_idx (Optional[int]): Optimizer index. + optimizer (Optimizer | None): Optimizer. + optimizer_idx (int | None): Optimizer index. """ del optimizer, optimizer_idx # These variables are not used. # TODO: Investigate why retain_graph is needed. @@ -117,20 +116,20 @@ class CfaLightning(Cfa): """PL Lightning Module for the CFA model. Args: - hparams (Union[DictConfig, ListConfig]): Model params + hparams (DictConfig | ListConfig): Model params """ - def __init__(self, hparams: Union[DictConfig, ListConfig]) -> None: + def __init__(self, hparams: DictConfig | ListConfig) -> None: super().__init__( input_size=hparams.model.input_size, backbone=hparams.model.backbone, gamma_c=hparams.model.gamma_c, gamma_d=hparams.model.gamma_d, ) - self.hparams: Union[DictConfig, ListConfig] # type: ignore + self.hparams: DictConfig | ListConfig # type: ignore self.save_hyperparameters(hparams) - def configure_callbacks(self) -> List[Callback]: + def configure_callbacks(self) -> list[Callback]: """Configure model-specific callbacks. Note: diff --git a/anomalib/models/cfa/torch_model.py b/anomalib/models/cfa/torch_model.py index 5a1d731b56..f523c36531 100644 --- a/anomalib/models/cfa/torch_model.py +++ b/anomalib/models/cfa/torch_model.py @@ -8,7 +8,7 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -from typing import Dict, List, Optional, Tuple, Union +from __future__ import annotations import torch import torch.nn.functional as F @@ -29,7 +29,7 @@ SUPPORTED_BACKBONES = ("vgg19_bn", "resnet18", "wide_resnet50_2", "efficientnet_b5") -def get_return_nodes(backbone: str) -> List[str]: +def get_return_nodes(backbone: str) -> list[str]: """Get the return nodes for a given backbone. Args: @@ -41,12 +41,12 @@ def get_return_nodes(backbone: str) -> List[str]: ValueError: If the backbone is not one of the supported backbones. Returns: - List[str]: A list of return nodes for the given backbone. + list[str]: A list of return nodes for the given backbone. """ if backbone == "efficientnet_b5": raise NotImplementedError("EfficientNet feature extractor has not implemented yet.") - return_nodes: List[str] + return_nodes: list[str] if backbone in ("resnet18", "wide_resnet50_2"): return_nodes = ["layer1", "layer2", "layer3"] elif backbone == "vgg19_bn": @@ -57,12 +57,12 @@ def get_return_nodes(backbone: str) -> List[str]: # TODO: Replace this with the new torchfx feature extractor. -def get_feature_extractor(backbone: str, return_nodes: List[str]) -> GraphModule: +def get_feature_extractor(backbone: str, return_nodes: list[str]) -> GraphModule: """Get the feature extractor from the backbone CNN. Args: backbone (str): Backbone CNN network - return_nodes (List[str]): A list of return nodes for the given backbone. + return_nodes (list[str]): A list of return nodes for the given backbone. Raises: NotImplementedError: When the backbone is efficientnet_b5 @@ -82,7 +82,7 @@ class CfaModel(DynamicBufferModule): """Torch implementation of the CFA Model. Args: - input_size: (Tuple[int, int]): Input size of the image tensor. + input_size: (tuple[int, int]): Input size of the image tensor. backbone (str): Backbone CNN network. gamma_c (int): gamma_c parameter from the paper. gamma_d (int): gamma_d parameter from the paper. @@ -93,7 +93,7 @@ class CfaModel(DynamicBufferModule): def __init__( self, - input_size: Tuple[int, int], + input_size: tuple[int, int], backbone: str, gamma_c: int, gamma_d: int, @@ -129,7 +129,7 @@ def __init__( self.scale = resolution else: raise ValueError( - f"Unknown type {type(resolution)} for `resolution`. Expected types are either int or Tuple[int, int]." + f"Unknown type {type(resolution)} for `resolution`. Expected types are either int or tuple[int, int]." ) self.descriptor = Descriptor(self.gamma_d, backbone) @@ -234,12 +234,12 @@ def __init__(self, gamma_d: int, backbone: str) -> None: self.layer = CoordConv2d(in_channels=dim, out_channels=out_channels, kernel_size=1) - def forward(self, features: Union[List[Tensor], Dict[str, Tensor]]) -> Tensor: + def forward(self, features: list[Tensor] | dict[str, Tensor]) -> Tensor: """Forward pass.""" if isinstance(features, dict): features = list(features.values()) - patch_features: Optional[Tensor] = None + patch_features: Tensor | None = None for i in features: i = F.avg_pool2d(i, 3, 1, 1) / i.size(1) if self.backbone == "efficientnet_b5" else F.avg_pool2d(i, 3, 1, 1) patch_features = ( @@ -268,7 +268,7 @@ def __init__( out_channels: int, kernel_size: _size_2_t, stride: _size_2_t = 1, - padding: Union[str, _size_2_t] = 0, + padding: str | _size_2_t = 0, dilation: _size_2_t = 1, groups: int = 1, bias: bool = True, From 5cd724c7597c55702f667545c04014c45bf3881d Mon Sep 17 00:00:00 2001 From: Samet Date: Tue, 17 Jan 2023 06:20:54 -0700 Subject: [PATCH 56/85] Convert to new annotation format - cflow model --- anomalib/models/cflow/anomaly_map.py | 22 ++++++++++++---------- anomalib/models/cflow/lightning_model.py | 22 +++++++++++----------- anomalib/models/cflow/torch_model.py | 12 +++++++----- 3 files changed, 30 insertions(+), 26 deletions(-) diff --git a/anomalib/models/cflow/anomaly_map.py b/anomalib/models/cflow/anomaly_map.py index b56f2cdc27..006c6b17a6 100644 --- a/anomalib/models/cflow/anomaly_map.py +++ b/anomalib/models/cflow/anomaly_map.py @@ -3,7 +3,9 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -from typing import List, Tuple, Union, cast +from __future__ import annotations + +from typing import List, cast import torch import torch.nn.functional as F @@ -16,15 +18,15 @@ class AnomalyMapGenerator(nn.Module): def __init__( self, - image_size: Union[ListConfig, Tuple], - pool_layers: List[str], + image_size: ListConfig | tuple, + pool_layers: list[str], ) -> None: super().__init__() self.distance = torch.nn.PairwiseDistance(p=2, keepdim=True) self.image_size = image_size if isinstance(image_size, tuple) else tuple(image_size) - self.pool_layers: List[str] = pool_layers + self.pool_layers: list[str] = pool_layers - def compute_anomaly_map(self, distribution: List[Tensor], height: List[int], width: List[int]) -> Tensor: + def compute_anomaly_map(self, distribution: list[Tensor], height: list[int], width: list[int]) -> Tensor: """Compute the layer map based on likelihood estimation. Args: @@ -36,7 +38,7 @@ def compute_anomaly_map(self, distribution: List[Tensor], height: List[int], wid Final Anomaly Map """ - layer_maps: List[Tensor] = [] + layer_maps: list[Tensor] = [] for layer_idx in range(len(self.pool_layers)): layer_distribution = distribution[layer_idx].clone().detach() # Normalize the likelihoods to (-Inf:0] and convert to probs in range [0:1] @@ -58,7 +60,7 @@ def compute_anomaly_map(self, distribution: List[Tensor], height: List[int], wid return anomaly_map - def forward(self, **kwargs: Union[List[Tensor], List[int], List[List]]) -> Tensor: + def forward(self, **kwargs: list[Tensor] | list[int] | list[list]) -> Tensor: """Returns anomaly_map. Expects `distribution`, `height` and 'width' keywords to be passed explicitly @@ -78,7 +80,7 @@ def forward(self, **kwargs: Union[List[Tensor], List[int], List[List]]) -> Tenso raise KeyError(f"Expected keys `distribution`, `height` and `width`. Found {kwargs.keys()}") # placate mypy - distribution: List[Tensor] = cast(List[Tensor], kwargs["distribution"]) - height: List[int] = cast(List[int], kwargs["height"]) - width: List[int] = cast(List[int], kwargs["width"]) + distribution: list[Tensor] = cast(List[Tensor], kwargs["distribution"]) + height: list[int] = cast(List[int], kwargs["height"]) + width: list[int] = cast(List[int], kwargs["width"]) return self.compute_anomaly_map(distribution, height, width) diff --git a/anomalib/models/cflow/lightning_model.py b/anomalib/models/cflow/lightning_model.py index c32470dad7..aa2f45637e 100644 --- a/anomalib/models/cflow/lightning_model.py +++ b/anomalib/models/cflow/lightning_model.py @@ -6,7 +6,7 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -from typing import Dict, List, Tuple, Union +from __future__ import annotations import einops import torch @@ -31,9 +31,9 @@ class Cflow(AnomalyModule): def __init__( self, - input_size: Tuple[int, int], + input_size: tuple[int, int], backbone: str, - layers: List[str], + layers: list[str], pre_trained: bool = True, fiber_batch_size: int = 64, decoder: str = "freia-cflow", @@ -84,7 +84,7 @@ def configure_optimizers(self) -> Optimizer: ) return optimizer - def training_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> STEP_OUTPUT: + def training_step(self, batch: dict[str, str | Tensor], *args, **kwargs) -> STEP_OUTPUT: """Training Step of CFLOW. For each batch, decoder layers are trained with a dynamic fiber batch size. @@ -92,7 +92,7 @@ def training_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) - per batch of input images Args: - batch (Dict[str, Union[str, Tensor]]): Input batch + batch (dict[str, str | Tensor]): Input batch Returns: Loss value for the batch @@ -154,7 +154,7 @@ def training_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) - self.log("train_loss", avg_loss.item(), on_epoch=True, prog_bar=True, logger=True) return {"loss": avg_loss} - def validation_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> STEP_OUTPUT: + def validation_step(self, batch: dict[str, str | Tensor], *args, **kwargs) -> STEP_OUTPUT: """Validation Step of CFLOW. Similar to the training step, encoder features @@ -162,7 +162,7 @@ def validation_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) map is computed. Args: - batch (Dict[str, Union[str, Tensor]]): Input batch + batch (dict[str, str | Tensor]): Input batch Returns: Dictionary containing images, anomaly maps, true labels and masks. @@ -178,10 +178,10 @@ class CflowLightning(Cflow): """PL Lightning Module for the CFLOW algorithm. Args: - hparams (Union[DictConfig, ListConfig]): Model params + hparams (DictConfig | ListConfig): Model params """ - def __init__(self, hparams: Union[DictConfig, ListConfig]) -> None: + def __init__(self, hparams: DictConfig | ListConfig) -> None: super().__init__( input_size=hparams.model.input_size, backbone=hparams.model.backbone, @@ -194,10 +194,10 @@ def __init__(self, hparams: Union[DictConfig, ListConfig]) -> None: clamp_alpha=hparams.model.clamp_alpha, permute_soft=hparams.model.permute_soft, ) - self.hparams: Union[DictConfig, ListConfig] # type: ignore + self.hparams: DictConfig | ListConfig # type: ignore self.save_hyperparameters(hparams) - def configure_callbacks(self) -> List[EarlyStopping]: + def configure_callbacks(self) -> list[EarlyStopping]: """Configure model-specific callbacks. Note: diff --git a/anomalib/models/cflow/torch_model.py b/anomalib/models/cflow/torch_model.py index a45b2df776..1e7b3333c7 100644 --- a/anomalib/models/cflow/torch_model.py +++ b/anomalib/models/cflow/torch_model.py @@ -3,7 +3,9 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -from typing import Any, List, Tuple +from __future__ import annotations + +from typing import Any import einops import torch @@ -19,9 +21,9 @@ class CflowModel(nn.Module): def __init__( self, - input_size: Tuple[int, int], + input_size: tuple[int, int], backbone: str, - layers: List[str], + layers: list[str], pre_trained: bool = True, fiber_batch_size: int = 64, decoder: str = "freia-cflow", @@ -77,8 +79,8 @@ def forward(self, images) -> Any: distribution = [torch.Tensor(0).to(images.device) for _ in self.pool_layers] - height: List[int] = [] - width: List[int] = [] + height: list[int] = [] + width: list[int] = [] for layer_idx, layer in enumerate(self.pool_layers): encoder_activations = activation[layer] # BxCxHxW From 4df638396a8cf6fbc4d506871b7ee5df2ef28ac0 Mon Sep 17 00:00:00 2001 From: Samet Date: Tue, 17 Jan 2023 06:43:20 -0700 Subject: [PATCH 57/85] Convert to new annotation format - model commponents --- .../models/components/base/anomaly_module.py | 26 ++++++++------- .../classification/kde_classifier.py | 11 ++++--- .../dimensionality_reduction/pca.py | 4 +-- .../random_projection.py | 8 ++--- .../components/feature_extractors/timm.py | 11 ++++--- .../components/feature_extractors/torchfx.py | 32 ++++++++++--------- .../components/feature_extractors/utils.py | 14 ++++---- anomalib/models/components/filters/blur.py | 10 +++--- .../components/sampling/k_center_greedy.py | 12 +++---- anomalib/models/components/stats/kde.py | 7 ++-- .../stats/multi_variate_gaussian.py | 12 ++++--- 11 files changed, 78 insertions(+), 69 deletions(-) diff --git a/anomalib/models/components/base/anomaly_module.py b/anomalib/models/components/base/anomaly_module.py index b745070fd6..df7785853c 100644 --- a/anomalib/models/components/base/anomaly_module.py +++ b/anomalib/models/components/base/anomaly_module.py @@ -3,9 +3,11 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import logging from abc import ABC -from typing import Any, Dict, List, OrderedDict, Tuple, Union +from typing import Any, OrderedDict from warnings import warn import pytorch_lightning as pl @@ -40,7 +42,7 @@ def __init__(self) -> None: self.save_hyperparameters() self.model: nn.Module self.loss: nn.Module - self.callbacks: List[Callback] + self.callbacks: list[Callback] self.threshold_method: ThresholdMethod self.image_threshold = AnomalyScoreThreshold().cpu() @@ -51,18 +53,18 @@ def __init__(self) -> None: self.image_metrics: AnomalibMetricCollection self.pixel_metrics: AnomalibMetricCollection - def forward(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> Any: + def forward(self, batch: dict[str, str | Tensor], *args, **kwargs) -> Any: """Forward-pass input tensor to the module. Args: - batch (Dict[str, Union[str, Tensor]]): Input batch. + batch (dict[str, str | Tensor]): Input batch. Returns: Tensor: Output tensor from the model. """ return self.model(batch) - def validation_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> STEP_OUTPUT: + def validation_step(self, batch: dict[str, str | Tensor], *args, **kwargs) -> STEP_OUTPUT: """To be implemented in the subclasses.""" raise NotImplementedError @@ -82,7 +84,7 @@ def predict_step(self, batch: Any, batch_idx: int, dataloader_idx: int = 0) -> A """ del batch_idx, dataloader_idx # These variables are not used. - outputs: Union[torch.Tensor, Dict[str, Any]] = self.validation_step(batch) + outputs: Tensor | dict[str, Any] = self.validation_step(batch) self._post_process(outputs) if outputs is not None and isinstance(outputs, dict): outputs["pred_labels"] = outputs["pred_scores"] >= self.image_threshold.value @@ -100,11 +102,11 @@ def predict_step(self, batch: Any, batch_idx: int, dataloader_idx: int = 0) -> A outputs["box_labels"] = [labels.int() for labels in is_anomalous] return outputs - def test_step(self, batch: Dict[str, Union[str, Tensor]], batch_idx: int, *args, **kwargs) -> STEP_OUTPUT: + def test_step(self, batch: dict[str, str | Tensor], batch_idx: int, *args, **kwargs) -> STEP_OUTPUT: """Calls validation_step for anomaly map/score calculation. Args: - batch (Dict[str, Union[str, Tensor]]): Input batch + batch (dict[str, str | Tensor]): Input batch batch_idx (int): Batch index Returns: @@ -189,8 +191,8 @@ def _post_process(outputs: STEP_OUTPUT) -> None: if "pred_boxes" in outputs and "anomaly_maps" not in outputs: # create anomaly maps from bbox predictions for thresholding and evaluation - image_size: Tuple[int, int] = outputs["image"].shape[-2:] - true_boxes: List[Tensor] = outputs["boxes"] + image_size: tuple[int, int] = outputs["image"].shape[-2:] + true_boxes: list[Tensor] = outputs["boxes"] pred_boxes: Tensor = outputs["pred_boxes"] box_scores: Tensor = outputs["box_scores"] @@ -198,10 +200,10 @@ def _post_process(outputs: STEP_OUTPUT) -> None: outputs["mask"] = boxes_to_masks(true_boxes, image_size) def _outputs_to_cpu(self, output): - if isinstance(output, Dict): + if isinstance(output, dict): for key, value in output.items(): output[key] = self._outputs_to_cpu(value) - elif isinstance(output, List): + elif isinstance(output, list): output = [self._outputs_to_cpu(item) for item in output] elif isinstance(output, Tensor): output = output.cpu() diff --git a/anomalib/models/components/classification/kde_classifier.py b/anomalib/models/components/classification/kde_classifier.py index ce6d743dca..8f1160c5ef 100644 --- a/anomalib/models/components/classification/kde_classifier.py +++ b/anomalib/models/components/classification/kde_classifier.py @@ -3,10 +3,11 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import logging import random from enum import Enum -from typing import Optional, Tuple import torch from torch import Tensor, nn @@ -52,12 +53,12 @@ def __init__( self.register_buffer("max_length", torch.empty([])) self.max_length = torch.empty([]) - def pre_process(self, feature_stack: Tensor, max_length: Optional[Tensor] = None) -> Tuple[Tensor, Tensor]: + def pre_process(self, feature_stack: Tensor, max_length: Tensor | None = None) -> tuple[Tensor, Tensor]: """Pre-process the CNN features. Args: feature_stack (Tensor): Features extracted from CNN - max_length (Optional[Tensor]): Used to unit normalize the feature_stack vector. If ``max_len`` is not + max_length (Tensor | None): Used to unit normalize the feature_stack vector. If ``max_len`` is not provided, the length is calculated from the ``feature_stack``. Defaults to None. Returns: @@ -103,7 +104,7 @@ def fit(self, embeddings: Tensor) -> bool: return True - def compute_kde_scores(self, features: Tensor, as_log_likelihood: Optional[bool] = False) -> Tensor: + def compute_kde_scores(self, features: Tensor, as_log_likelihood: bool | None = False) -> Tensor: """Compute the KDE scores. The scores calculated from the KDE model are converted to densities. If `as_log_likelihood` is set to true then @@ -111,7 +112,7 @@ def compute_kde_scores(self, features: Tensor, as_log_likelihood: Optional[bool] Args: features (Tensor): Features to which the PCA model is fit. - as_log_likelihood (Optional[bool], optional): If true, gets log likelihood scores. Defaults to False. + as_log_likelihood (bool | None, optional): If true, gets log likelihood scores. Defaults to False. Returns: (Tensor): Score diff --git a/anomalib/models/components/dimensionality_reduction/pca.py b/anomalib/models/components/dimensionality_reduction/pca.py index b9e2c7c9bb..7ff85d9296 100644 --- a/anomalib/models/components/dimensionality_reduction/pca.py +++ b/anomalib/models/components/dimensionality_reduction/pca.py @@ -3,7 +3,7 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -from typing import Union +from __future__ import annotations import torch from torch import Tensor @@ -19,7 +19,7 @@ class PCA(DynamicBufferModule): or a ratio between 0-1. """ - def __init__(self, n_components: Union[float, int]): + def __init__(self, n_components: int | float): super().__init__() self.n_components = n_components diff --git a/anomalib/models/components/dimensionality_reduction/random_projection.py b/anomalib/models/components/dimensionality_reduction/random_projection.py index f239595d2a..f9c6e66139 100644 --- a/anomalib/models/components/dimensionality_reduction/random_projection.py +++ b/anomalib/models/components/dimensionality_reduction/random_projection.py @@ -7,7 +7,7 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -from typing import Optional +from __future__ import annotations import numpy as np import torch @@ -25,11 +25,11 @@ class SparseRandomProjection: Args: eps (float, optional): Minimum distortion rate parameter for calculating Johnson-Lindenstrauss minimum dimensions. Defaults to 0.1. - random_state (Optional[int], optional): Uses the seed to set the random + random_state (int | None, optional): Uses the seed to set the random state for sample_without_replacement function. Defaults to None. """ - def __init__(self, eps: float = 0.1, random_state: Optional[int] = None) -> None: + def __init__(self, eps: float = 0.1, random_state: int | None = None) -> None: self.n_components: int self.sparse_random_matrix: Tensor self.eps = eps @@ -91,7 +91,7 @@ def johnson_lindenstrauss_min_dim(self, n_samples: int, eps: float = 0.1): denominator = (eps**2 / 2) - (eps**3 / 3) return (4 * np.log(n_samples) / denominator).astype(np.int64) - def fit(self, embedding: Tensor) -> "SparseRandomProjection": + def fit(self, embedding: Tensor) -> SparseRandomProjection: """Generates sparse matrix from the embedding tensor. Args: diff --git a/anomalib/models/components/feature_extractors/timm.py b/anomalib/models/components/feature_extractors/timm.py index 3a12baf337..df79a5600c 100644 --- a/anomalib/models/components/feature_extractors/timm.py +++ b/anomalib/models/components/feature_extractors/timm.py @@ -6,9 +6,10 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import logging import warnings -from typing import Dict, List import timm import torch @@ -22,7 +23,7 @@ class TimmFeatureExtractor(nn.Module): Args: backbone (nn.Module): The backbone to which the feature extraction hooks are attached. - layers (Iterable[str]): List of layer names of the backbone to which the hooks are attached. + layers (Iterable[str]): list of layer names of the backbone to which the hooks are attached. pre_trained (bool): Whether to use a pre-trained backbone. Defaults to True. requires_grad (bool): Whether to require gradients for the backbone. Defaults to False. Models like ``stfpm`` use the feature extractor model as a trainable network. In such cases gradient @@ -42,7 +43,7 @@ class TimmFeatureExtractor(nn.Module): [torch.Size([32, 64, 64, 64]), torch.Size([32, 128, 32, 32]), torch.Size([32, 256, 16, 16])] """ - def __init__(self, backbone: str, layers: List[str], pre_trained: bool = True, requires_grad: bool = False): + def __init__(self, backbone: str, layers: list[str], pre_trained: bool = True, requires_grad: bool = False): super().__init__() self.backbone = backbone self.layers = layers @@ -58,7 +59,7 @@ def __init__(self, backbone: str, layers: List[str], pre_trained: bool = True, r self.out_dims = self.feature_extractor.feature_info.channels() self._features = {layer: torch.empty(0) for layer in self.layers} - def _map_layer_to_idx(self, offset: int = 3) -> List[int]: + def _map_layer_to_idx(self, offset: int = 3) -> list[int]: """Maps set of layer names to indices of model. Args: @@ -84,7 +85,7 @@ def _map_layer_to_idx(self, offset: int = 3) -> List[int]: return idx - def forward(self, inputs: Tensor) -> Dict[str, Tensor]: + def forward(self, inputs: Tensor) -> dict[str, Tensor]: """Forward-pass input tensor into the CNN. Args: diff --git a/anomalib/models/components/feature_extractors/torchfx.py b/anomalib/models/components/feature_extractors/torchfx.py index 6f0972c8bf..9971cde32f 100644 --- a/anomalib/models/components/feature_extractors/torchfx.py +++ b/anomalib/models/components/feature_extractors/torchfx.py @@ -3,9 +3,11 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import importlib from dataclasses import dataclass, field -from typing import Callable, Dict, List, Optional, Union +from typing import Callable import torch from torch import Tensor, nn @@ -18,20 +20,20 @@ class BackboneParams: """Used for serializing the backbone.""" - class_path: Union[str, nn.Module] - init_args: Dict = field(default_factory=dict) + class_path: str | nn.Module + init_args: dict = field(default_factory=dict) class TorchFXFeatureExtractor(nn.Module): """Extract features from a CNN. Args: - backbone (Union[str, BackboneParams, Dict, nn.Module]): The backbone to which the feature extraction hooks are + backbone (str | BackboneParams | dict | nn.Module): The backbone to which the feature extraction hooks are attached. If the name is provided, the model is loaded from torchvision. Otherwise, the model class can be provided and it will try to load the weights from the provided weights file. return_nodes (Iterable[str]): List of layer names of the backbone to which the hooks are attached. You can find the names of these nodes by using ``get_graph_node_names`` function. - weights (Optional[Union[WeightsEnum,str]]): Weights enum to use for the model. Torchvision models require + weights (str | WeightsEnum | None): Weights enum to use for the model. Torchvision models require ``WeightsEnum``. These enums are defined in ``torchvision.models.``. You can pass the weights path for custom models. requires_grad (bool): Models like ``stfpm`` use the feature extractor for training. In such cases we should @@ -69,9 +71,9 @@ class TorchFXFeatureExtractor(nn.Module): def __init__( self, - backbone: Union[str, BackboneParams, Dict, nn.Module], - return_nodes: List[str], - weights: Optional[Union[WeightsEnum, str]] = None, + backbone: str | BackboneParams | dict | nn.Module, + return_nodes: list[str], + weights: str | WeightsEnum | None = None, requires_grad: bool = False, ): super().__init__() @@ -85,19 +87,19 @@ def __init__( def initialize_feature_extractor( self, backbone: BackboneParams, - return_nodes: List[str], - weights: Optional[Union[WeightsEnum, str]] = None, + return_nodes: list[str], + weights: str | WeightsEnum | None = None, requires_grad: bool = False, - ) -> Union[GraphModule, nn.Module]: + ) -> GraphModule | nn.Module: """Extract features from a CNN. Args: - backbone (Union[str, BackboneParams]): The backbone to which the feature extraction hooks are attached. + backbone (BackboneParams): The backbone to which the feature extraction hooks are attached. If the name is provided, the model is loaded from torchvision. Otherwise, the model class can be provided and it will try to load the weights from the provided weights file. return_nodes (Iterable[str]): List of layer names of the backbone to which the hooks are attached. You can find the names of these nodes by using ``get_graph_node_names`` function. - weights (Optional[Union[WeightsEnum,str]]): Weights enum to use for the model. Torchvision models require + weights (str | WeightsEnum | None): Weights enum to use for the model. Torchvision models require ``WeightsEnum``. These enums are defined in ``torchvision.models.``. You can pass the weights path for custom models. requires_grad (bool): Models like ``stfpm`` use the feature extractor for training. In such cases we should @@ -141,7 +143,7 @@ def _get_backbone_class(backbone: str) -> Callable[..., nn.Module]: >>> TorchFXFeatureExtractor._get_backbone_class("efficientnet_b5") torchvision.models.efficientnet.EfficientNet> @@ -170,6 +172,6 @@ def _get_backbone_class(backbone: str) -> Callable[..., nn.Module]: return backbone_class - def forward(self, inputs: Tensor) -> Dict[str, Tensor]: + def forward(self, inputs: Tensor) -> dict[str, Tensor]: """Extract features from the input.""" return self.feature_extractor(inputs) diff --git a/anomalib/models/components/feature_extractors/utils.py b/anomalib/models/components/feature_extractors/utils.py index a9336821fa..5780ad3304 100644 --- a/anomalib/models/components/feature_extractors/utils.py +++ b/anomalib/models/components/feature_extractors/utils.py @@ -1,6 +1,6 @@ """Utility functions to manipulate feature extractors.""" -from typing import Dict, List, Tuple, Union +from __future__ import annotations import torch from torch.fx.graph_module import GraphModule @@ -9,15 +9,15 @@ def dryrun_find_featuremap_dims( - feature_extractor: Union[FeatureExtractor, GraphModule], - input_size: Tuple[int, int], - layers: List[str], -) -> Dict[str, Dict[str, Union[int, Tuple[int, int]]]]: + feature_extractor: FeatureExtractor | GraphModule, + input_size: tuple[int, int], + layers: list[str], +) -> dict[str, dict[str, int | tuple[int, int]]]: """Dry run an empty image of `input_size` size to get the featuremap tensors' dimensions (num_features, resolution). Returns: - Tuple[int, int]: maping of `layer -> dimensions dict` - Each `dimension dict` has two keys: `num_features` (int) and `resolution`(Tuple[int, int]). + tuple[int, int]: maping of `layer -> dimensions dict` + Each `dimension dict` has two keys: `num_features` (int) and `resolution`(tuple[int, int]). """ dryrun_input = torch.empty(1, 3, *input_size) diff --git a/anomalib/models/components/filters/blur.py b/anomalib/models/components/filters/blur.py index 5edadc81d7..e56e3eba58 100644 --- a/anomalib/models/components/filters/blur.py +++ b/anomalib/models/components/filters/blur.py @@ -1,6 +1,6 @@ """Gaussian blurring via pytorch.""" -from typing import Optional, Tuple, Union +from __future__ import annotations from kornia.filters import get_gaussian_kernel2d from kornia.filters.filter import _compute_padding @@ -31,9 +31,9 @@ class GaussianBlur2d(nn.Module): def __init__( self, - sigma: Union[Tuple[float, float], float], + sigma: float | tuple[float, float], channels: int = 1, - kernel_size: Optional[Union[Tuple[int, int], int]] = None, + kernel_size: int | tuple[int, int] | None = None, normalize: bool = True, border_type: str = "reflect", padding: str = "same", @@ -41,9 +41,9 @@ def __init__( """Initialize model, setup kernel etc.. Args: - sigma (Union[Tuple[float, float], float]): standard deviation to use for constructing the Gaussian kernel. + sigma (float | tuple[float, float]): standard deviation to use for constructing the Gaussian kernel. channels (int): channels of the input. Defaults to 1. - kernel_size (Optional[Union[Tuple[int, int], int]]): size of the Gaussian kernel to use. Defaults to None. + kernel_size (int | tuple[int, int] | None): size of the Gaussian kernel to use. Defaults to None. normalize (bool, optional): Whether to normalize the kernel or not (i.e. all elements sum to 1). Defaults to True. border_type (str, optional): Border type to use for padding of the input. Defaults to "reflect". diff --git a/anomalib/models/components/sampling/k_center_greedy.py b/anomalib/models/components/sampling/k_center_greedy.py index 1703c74149..920b16a5d0 100644 --- a/anomalib/models/components/sampling/k_center_greedy.py +++ b/anomalib/models/components/sampling/k_center_greedy.py @@ -5,7 +5,7 @@ . https://arxiv.org/abs/1708.00489 """ -from typing import List, Optional +from __future__ import annotations import torch import torch.nn.functional as F @@ -44,11 +44,11 @@ def reset_distances(self) -> None: """Reset minimum distances.""" self.min_distances = None - def update_distances(self, cluster_centers: List[int]) -> None: + def update_distances(self, cluster_centers: list[int]) -> None: """Update min distances given cluster centers. Args: - cluster_centers (List[int]): indices of cluster centers + cluster_centers (list[int]): indices of cluster centers """ if cluster_centers: @@ -77,7 +77,7 @@ def get_new_idx(self) -> int: return idx - def select_coreset_idxs(self, selected_idxs: Optional[List[int]] = None) -> List[int]: + def select_coreset_idxs(self, selected_idxs: list[int] | None = None) -> list[int]: """Greedily form a coreset to minimize the maximum distance of a cluster. Args: @@ -98,7 +98,7 @@ def select_coreset_idxs(self, selected_idxs: Optional[List[int]] = None) -> List self.features = self.embedding.reshape(self.embedding.shape[0], -1) self.update_distances(cluster_centers=selected_idxs) - selected_coreset_idxs: List[int] = [] + selected_coreset_idxs: list[int] = [] idx = int(torch.randint(high=self.n_observations, size=(1,)).item()) for _ in range(self.coreset_size): self.update_distances(cluster_centers=[idx]) @@ -110,7 +110,7 @@ def select_coreset_idxs(self, selected_idxs: Optional[List[int]] = None) -> List return selected_coreset_idxs - def sample_coreset(self, selected_idxs: Optional[List[int]] = None) -> Tensor: + def sample_coreset(self, selected_idxs: list[int] | None = None) -> Tensor: """Select coreset from the embedding. Args: diff --git a/anomalib/models/components/stats/kde.py b/anomalib/models/components/stats/kde.py index 0929165869..5fbe4bbaff 100644 --- a/anomalib/models/components/stats/kde.py +++ b/anomalib/models/components/stats/kde.py @@ -3,8 +3,9 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import math -from typing import Optional import torch from torch import Tensor @@ -16,10 +17,10 @@ class GaussianKDE(DynamicBufferModule): """Gaussian Kernel Density Estimation. Args: - dataset (Optional[Tensor], optional): Dataset on which to fit the KDE model. Defaults to None. + dataset (Tensor | None, optional): Dataset on which to fit the KDE model. Defaults to None. """ - def __init__(self, dataset: Optional[Tensor] = None): + def __init__(self, dataset: Tensor | None = None): super().__init__() if dataset is not None: diff --git a/anomalib/models/components/stats/multi_variate_gaussian.py b/anomalib/models/components/stats/multi_variate_gaussian.py index 14eb8f3c73..d6ba6f8baa 100644 --- a/anomalib/models/components/stats/multi_variate_gaussian.py +++ b/anomalib/models/components/stats/multi_variate_gaussian.py @@ -3,7 +3,9 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -from typing import Any, List, Optional +from __future__ import annotations + +from typing import Any import torch from torch import Tensor, nn @@ -26,7 +28,7 @@ def _cov( observations: Tensor, rowvar: bool = False, bias: bool = False, - ddof: Optional[int] = None, + ddof: int | None = None, aweights: Tensor = None, ) -> Tensor: """Estimates covariance matrix like numpy.cov. @@ -43,7 +45,7 @@ def _cov( number of observations given (unbiased estimate). If `bias` is True, then normalization is by ``N``. These values can be overridden by using the keyword ``ddof`` in numpy versions >= 1.5. Defaults to False - ddof (Optional, int): If not ``None`` the default value implied by `bias` is overridden. + ddof (int | None): If not ``None`` the default value implied by `bias` is overridden. Note that ``ddof=1`` will return the unbiased estimate, even if both `fweights` and `aweights` are specified, and ``ddof=0`` will return the simple average. See the notes for the details. The default value @@ -104,7 +106,7 @@ def _cov( return covariance.squeeze() - def forward(self, embedding: Tensor) -> List[Tensor]: + def forward(self, embedding: Tensor) -> list[Tensor]: """Calculate multivariate Gaussian distribution. Args: @@ -128,7 +130,7 @@ def forward(self, embedding: Tensor) -> List[Tensor]: return [self.mean, self.inv_covariance] - def fit(self, embedding: Tensor) -> List[Tensor]: + def fit(self, embedding: Tensor) -> list[Tensor]: """Fit multi-variate gaussian distribution to the input embedding. Args: From b94c876d27e7d3f6faa4b4f196fc9c3683ef12b5 Mon Sep 17 00:00:00 2001 From: Samet Date: Tue, 17 Jan 2023 06:53:49 -0700 Subject: [PATCH 58/85] Convert to new annotation format - cflow model --- anomalib/models/csflow/anomaly_map.py | 7 +-- anomalib/models/csflow/lightning_model.py | 21 ++++---- anomalib/models/csflow/torch_model.py | 61 ++++++++++++----------- 3 files changed, 46 insertions(+), 43 deletions(-) diff --git a/anomalib/models/csflow/anomaly_map.py b/anomalib/models/csflow/anomaly_map.py index 007d5b633d..3fced453b8 100644 --- a/anomalib/models/csflow/anomaly_map.py +++ b/anomalib/models/csflow/anomaly_map.py @@ -3,8 +3,9 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + from enum import Enum -from typing import Tuple import torch import torch.nn.functional as F @@ -22,11 +23,11 @@ class AnomalyMapGenerator(nn.Module): """Anomaly Map Generator for CS-Flow model. Args: - input_dims (Tuple[int, int, int]): Input dimensions. + input_dims (tuple[int, int, int]): Input dimensions. mode (AnomalyMapMode): Anomaly map mode. Defaults to AnomalyMapMode.ALL. """ - def __init__(self, input_dims: Tuple[int, int, int], mode: AnomalyMapMode = AnomalyMapMode.ALL) -> None: + def __init__(self, input_dims: tuple[int, int, int], mode: AnomalyMapMode = AnomalyMapMode.ALL) -> None: super().__init__() self.mode = mode self.input_dims = input_dims diff --git a/anomalib/models/csflow/lightning_model.py b/anomalib/models/csflow/lightning_model.py index 6796ec4f60..19e4a9a8f6 100644 --- a/anomalib/models/csflow/lightning_model.py +++ b/anomalib/models/csflow/lightning_model.py @@ -6,8 +6,9 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import logging -from typing import Dict, List, Tuple, Union import torch from omegaconf import DictConfig, ListConfig @@ -31,7 +32,7 @@ class Csflow(AnomalyModule): """Fully Convolutional Cross-Scale-Flows for Image-based Defect Detection. Args: - input_size (Tuple[int, int]): Size of the model input. + input_size (tuple[int, int]): Size of the model input. n_coupling_blocks (int): Number of coupling blocks in the model. cross_conv_hidden_channels (int): Number of hidden channels in the cross convolution. clamp (int): Clamp value for glow layer. @@ -40,7 +41,7 @@ class Csflow(AnomalyModule): def __init__( self, - input_size: Tuple[int, int], + input_size: tuple[int, int], cross_conv_hidden_channels: int, n_coupling_blocks: int, clamp: int, @@ -56,7 +57,7 @@ def __init__( ) self.loss = CsFlowLoss() - def training_step(self, batch, _) -> Dict[str, Tensor]: + def training_step(self, batch, _) -> dict[str, Tensor]: """Training Step of CS-Flow. Args: @@ -72,14 +73,14 @@ def training_step(self, batch, _) -> Dict[str, Tensor]: self.log("train_loss", loss.item(), on_epoch=True, prog_bar=True, logger=True) return {"loss": loss} - def validation_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> STEP_OUTPUT: + def validation_step(self, batch: dict[str, str | Tensor], *args, **kwargs) -> STEP_OUTPUT: """Validation step for CS Flow. Args: batch (Tensor): Input batch Returns: - Dict[str, Tensor]: Dictionary containing the anomaly map, scores, etc. + dict[str, Tensor]: Dictionary containing the anomaly map, scores, etc. """ anomaly_maps, anomaly_scores = self.model(batch["image"]) batch["anomaly_maps"] = anomaly_maps @@ -91,10 +92,10 @@ class CsflowLightning(Csflow): """Fully Convolutional Cross-Scale-Flows for Image-based Defect Detection. Args: - hprams (Union[DictConfig, ListConfig]): Model params + hprams (DictConfig | ListConfig): Model params """ - def __init__(self, hparams: Union[DictConfig, ListConfig]) -> None: + def __init__(self, hparams: DictConfig | ListConfig) -> None: super().__init__( input_size=hparams.model.input_size, n_coupling_blocks=hparams.model.n_coupling_blocks, @@ -102,10 +103,10 @@ def __init__(self, hparams: Union[DictConfig, ListConfig]) -> None: clamp=hparams.model.clamp, num_channels=3, ) - self.hparams: Union[DictConfig, ListConfig] # type: ignore + self.hparams: DictConfig | ListConfig # type: ignore self.save_hyperparameters(hparams) - def configure_callbacks(self) -> List[EarlyStopping]: + def configure_callbacks(self) -> list[EarlyStopping]: """Configure model-specific callbacks. Note: diff --git a/anomalib/models/csflow/torch_model.py b/anomalib/models/csflow/torch_model.py index 5a86547e4c..aa785ce12f 100644 --- a/anomalib/models/csflow/torch_model.py +++ b/anomalib/models/csflow/torch_model.py @@ -11,8 +11,9 @@ # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + from math import exp -from typing import Dict, List, Optional, Tuple import numpy as np import torch @@ -146,13 +147,13 @@ def __init__( self.leaky_relu = nn.LeakyReLU(self.leaky_slope) - def forward(self, scale0, scale1, scale2) -> Tuple[Tensor, Tensor, Tensor]: + def forward(self, scale0, scale1, scale2) -> tuple[Tensor, Tensor, Tensor]: """Applies the cross convolution to the three scales. This block is represented in figure 4 of the paper. Returns: - Tuple[Tensor, Tensor, Tensor]: Tensors indicating scale and transform parameters as a single tensor for + tuple[Tensor, Tensor, Tensor]: Tensors indicating scale and transform parameters as a single tensor for each scale. The scale parameters are the first part across channel dimension and the transform parameters are the second. """ @@ -195,11 +196,11 @@ class ParallelPermute(InvertibleModule): """Permutes input vector in a random but fixed way. Args: - dim (List[Tuple[int]]): Dimension of the input vector. - seed (Optional[float]=None): Seed for the random permutation. + dim (list[tuple[int]]): Dimension of the input vector. + seed (float | None=None): Seed for the random permutation. """ - def __init__(self, dims_in: List[Tuple[int]], seed: Optional[float] = None) -> None: + def __init__(self, dims_in: list[tuple[int]], seed: float | None = None) -> None: super().__init__(dims_in) self.n_inputs: int = len(dims_in) self.in_channels = [dims_in[i][0] for i in range(self.n_inputs)] @@ -214,14 +215,14 @@ def __init__(self, dims_in: List[Tuple[int]], seed: Optional[float] = None) -> N self.perm.append(perm) self.perm_inv.append(perm_inv) - def get_random_perm(self, index: int) -> Tuple[Tensor, Tensor]: + def get_random_perm(self, index: int) -> tuple[Tensor, Tensor]: """Returns a random permutation of the channels for each input. Args: i: index of the input Returns: - Tuple[Tensor, Tensor]: permutation and inverse permutation + tuple[Tensor, Tensor]: permutation and inverse permutation """ perm = np.random.permutation(self.in_channels[index]) perm_inv = np.zeros_like(perm) @@ -233,7 +234,7 @@ def get_random_perm(self, index: int) -> Tuple[Tensor, Tensor]: return perm, perm_inv # pylint: disable=unused-argument - def forward(self, input_tensor: List[Tensor], rev=False, jac=True) -> Tuple[List[Tensor], float]: + def forward(self, input_tensor: list[Tensor], rev=False, jac=True) -> tuple[list[Tensor], float]: """Applies the permutation to the input. Args: @@ -242,14 +243,14 @@ def forward(self, input_tensor: List[Tensor], rev=False, jac=True) -> Tuple[List jac: (unused) if True, computes the log determinant of the Jacobian Returns: - Tuple[Tensor, Tensor]: output tensor and log determinant of the Jacobian + tuple[Tensor, Tensor]: output tensor and log determinant of the Jacobian """ if not rev: return [input_tensor[i][:, self.perm[i]] for i in range(self.n_inputs)], 0.0 return [input_tensor[i][:, self.perm_inv[i]] for i in range(self.n_inputs)], 0.0 - def output_dims(self, input_dims: List[Tuple[int]]) -> List[Tuple[int]]: + def output_dims(self, input_dims: list[tuple[int]]) -> list[tuple[int]]: """Returns the output dimensions of the module.""" return input_dims @@ -258,12 +259,12 @@ class ParallelGlowCouplingLayer(InvertibleModule): """Coupling block that follows the GLOW design but is applied to all the scales in parallel. Args: - dims_in (List[Tuple[int]]): list of dimensions of the input tensors - subnet_args (Dict): arguments of the subnet + dims_in (list[tuple[int]]): list of dimensions of the input tensors + subnet_args (dict): arguments of the subnet clamp (float): clamp value for the output of the subnet """ - def __init__(self, dims_in: List[Tuple[int]], subnet_args: Dict, clamp: float = 5.0) -> None: + def __init__(self, dims_in: list[tuple[int]], subnet_args: dict, clamp: float = 5.0) -> None: super().__init__(dims_in) channels = dims_in[0][0] self.ndims = len(dims_in[0]) @@ -291,7 +292,7 @@ def log_e(self, input_tensor: Tensor) -> Tensor: return self.clamp * 0.636 * torch.atan(input_tensor / self.clamp) return input_tensor - def forward(self, input_tensor: List[Tensor], rev=False, jac=True) -> Tuple[List[Tensor], Tensor]: + def forward(self, input_tensor: list[Tensor], rev=False, jac=True) -> tuple[list[Tensor], Tensor]: """Applies GLOW coupling for the three scales.""" # Even channel split. The two splits are used by cross-scale convolution to compute scale and transform @@ -373,7 +374,7 @@ def forward(self, input_tensor: List[Tensor], rev=False, jac=True) -> Tuple[List # Since Jacobians are only used for computing loss and summed in the loss, the idea is to sum them here return [z_dist0, z_dist1, z_dist2], torch.stack([jac0, jac1, jac2], dim=1).sum() - def output_dims(self, input_dims: List[Tuple[int]]) -> List[Tuple[int]]: + def output_dims(self, input_dims: list[tuple[int]]) -> list[tuple[int]]: """Output dimensions of the module.""" return input_dims @@ -382,14 +383,14 @@ class CrossScaleFlow(nn.Module): """Cross scale coupling layer. Args: - input_dims (Tuple[int, int, int]): Input dimensions of the module. + input_dims (tuple[int, int, int]): Input dimensions of the module. n_coupling_blocks (int): Number of coupling blocks. clamp (float): Clamp value for the inputs. corss_conv_hidden_channels (int): Number of hidden channels in the cross convolution. """ def __init__( - self, input_dims: Tuple[int, int, int], n_coupling_blocks: int, clamp: float, cross_conv_hidden_channels: int + self, input_dims: tuple[int, int, int], n_coupling_blocks: int, clamp: float, cross_conv_hidden_channels: int ) -> None: super().__init__() self.input_dims = input_dims @@ -435,14 +436,14 @@ def _create_graph(self) -> GraphINN: nodes.append(OutputNode([nodes[-3].out2], name="output_end2")) return GraphINN(nodes) - def forward(self, inputs: Tensor) -> Tuple[Tensor, Tensor]: + def forward(self, inputs: Tensor) -> tuple[Tensor, Tensor]: """Forward pass. Args: inputs (Tensor): Input tensor. Returns: - Tuple[Tensor, Tensor]: Output tensor and log determinant of Jacobian. + tuple[Tensor, Tensor]: Output tensor and log determinant of Jacobian. """ return self.graph(inputs) @@ -454,10 +455,10 @@ class MultiScaleFeatureExtractor(nn.Module): Args: n_scales (int): Number of scales for input image. - input_size (Tuple[int, int]): Size of input image. + input_size (tuple[int, int]): Size of input image. """ - def __init__(self, n_scales: int, input_size: Tuple[int, int]) -> None: + def __init__(self, n_scales: int, input_size: tuple[int, int]) -> None: super().__init__() self.n_scales = n_scales @@ -466,14 +467,14 @@ def __init__(self, n_scales: int, input_size: Tuple[int, int]) -> None: backbone="efficientnet_b5", weights=EfficientNet_B5_Weights.DEFAULT, return_nodes=["features.6.8"] ) - def forward(self, input_tensor: Tensor) -> List[Tensor]: + def forward(self, input_tensor: Tensor) -> list[Tensor]: """Extracts features at three scales. Args: input_tensor (Tensor): Input images. Returns: - List[Tensor]: List of tensors containing features at three scales. + list[Tensor]: List of tensors containing features at three scales. """ output = [] for scale in range(self.n_scales): @@ -494,7 +495,7 @@ class CsFlowModel(nn.Module): """CS Flow Module. Args: - input_size (Tuple[int, int]): Input image size. + input_size (tuple[int, int]): Input image size. cross_conv_hidden_channels (int): Number of hidden channels in the cross convolution. n_coupling_blocks (int): Number of coupling blocks. clamp (float): Clamp value for the coupling blocks. @@ -503,7 +504,7 @@ class CsFlowModel(nn.Module): def __init__( self, - input_size: Tuple[int, int], + input_size: tuple[int, int], cross_conv_hidden_channels: int, n_coupling_blocks: int = 4, clamp: int = 3, @@ -523,15 +524,15 @@ def __init__( ) self.anomaly_map_generator = AnomalyMapGenerator(input_dims=self.input_dims, mode=AnomalyMapMode.ALL) - def forward(self, images: Tensor) -> Tuple[Tensor, Tensor]: + def forward(self, images: Tensor) -> tuple[Tensor, Tensor]: """Forward method of the model. Args: images (Tensor): Input images. Returns: - Tuple[Tensor, Tensor]: During training: Tuple containing the z_distribution for three scales and the sum - of log determinant of the Jacobian. During evaluation: Tuple containing anomaly maps and anomaly scores + tuple[Tensor, Tensor]: During training: tuple containing the z_distribution for three scales and the sum + of log determinant of the Jacobian. During evaluation: tuple containing anomaly maps and anomaly scores """ features = self.feature_extractor(images) if self.training: @@ -553,7 +554,7 @@ def _compute_anomaly_scores(self, z_dists: Tensor) -> Tensor: Tensor: Anomaly scores. """ # z_dist is a 3 length list of tensors with shape b x 304 x fx x fy - flat_maps: List[Tensor] = [] + flat_maps: list[Tensor] = [] for z_dist in z_dists: flat_maps.append(z_dist.reshape(z_dist.shape[0], -1)) flat_maps_tensor = torch.cat(flat_maps, dim=1) From c54475915a8f51eaf3768d618315ede044ae56d2 Mon Sep 17 00:00:00 2001 From: Samet Date: Tue, 17 Jan 2023 06:54:17 -0700 Subject: [PATCH 59/85] Convert to new annotation format - dfkde model --- anomalib/models/dfkde/lightning_model.py | 21 +++++++++++---------- anomalib/models/dfkde/torch_model.py | 5 +++-- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/anomalib/models/dfkde/lightning_model.py b/anomalib/models/dfkde/lightning_model.py index e7e15fc120..dc39586179 100644 --- a/anomalib/models/dfkde/lightning_model.py +++ b/anomalib/models/dfkde/lightning_model.py @@ -3,8 +3,9 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import logging -from typing import Dict, List, Union import torch from omegaconf import DictConfig, ListConfig @@ -39,7 +40,7 @@ class Dfkde(AnomalyModule): def __init__( self, - layers: List[str], + layers: list[str], backbone: str, pre_trained: bool = True, n_pca_components: int = 16, @@ -57,18 +58,18 @@ def __init__( max_training_points=max_training_points, ) - self.embeddings: List[Tensor] = [] + self.embeddings: list[Tensor] = [] @staticmethod def configure_optimizers() -> None: # pylint: disable=arguments-differ """DFKDE doesn't require optimization, therefore returns no optimizers.""" return None - def training_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> None: + def training_step(self, batch: dict[str, str | Tensor], *args, **kwargs) -> None: """Training Step of DFKDE. For each batch, features are extracted from the CNN. Args: - batch (batch: Dict[str, Union[str, Tensor]]): Batch containing image filename, image, label and mask + batch (batch: dict[str, str | Tensor]): Batch containing image filename, image, label and mask Returns: Deep CNN features. @@ -92,13 +93,13 @@ def on_validation_start(self) -> None: logger.info("Fitting a KDE model to the embedding collected from the training set.") self.model.classifier.fit(embeddings) - def validation_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> STEP_OUTPUT: + def validation_step(self, batch: dict[str, str | Tensor], *args, **kwargs) -> STEP_OUTPUT: """Validation Step of DFKDE. Similar to the training step, features are extracted from the CNN for each batch. Args: - batch (Dict[str, Union[str, Tensor]]): Input batch + batch (dict[str, str | Tensor]): Input batch Returns: Dictionary containing probability, prediction and ground truth values. @@ -112,10 +113,10 @@ class DfkdeLightning(Dfkde): """DFKDE: Deep Feature Kernel Density Estimation. Args: - hparams (Union[DictConfig, ListConfig]): Model params + hparams (DictConfig | ListConfig): Model params """ - def __init__(self, hparams: Union[DictConfig, ListConfig]) -> None: + def __init__(self, hparams: DictConfig | ListConfig) -> None: super().__init__( layers=hparams.model.layers, backbone=hparams.model.backbone, @@ -124,5 +125,5 @@ def __init__(self, hparams: Union[DictConfig, ListConfig]) -> None: feature_scaling_method=FeatureScalingMethod(hparams.model.feature_scaling_method), max_training_points=hparams.model.max_training_points, ) - self.hparams: Union[DictConfig, ListConfig] # type: ignore + self.hparams: DictConfig | ListConfig # type: ignore self.save_hyperparameters(hparams) diff --git a/anomalib/models/dfkde/torch_model.py b/anomalib/models/dfkde/torch_model.py index bf541f9dca..5746442820 100644 --- a/anomalib/models/dfkde/torch_model.py +++ b/anomalib/models/dfkde/torch_model.py @@ -3,8 +3,9 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import logging -from typing import List import torch import torch.nn.functional as F @@ -35,7 +36,7 @@ class DfkdeModel(nn.Module): def __init__( self, - layers: List[str], + layers: list[str], backbone: str, pre_trained: bool = True, n_pca_components: int = 16, From fae5fb66528a5dbb978f4c7b3bd44edc049c324c Mon Sep 17 00:00:00 2001 From: Samet Date: Tue, 17 Jan 2023 06:54:39 -0700 Subject: [PATCH 60/85] Convert to new annotation format - dfm model --- anomalib/models/dfm/lightning_model.py | 23 ++++++++++++----------- anomalib/models/dfm/torch_model.py | 7 ++++--- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/anomalib/models/dfm/lightning_model.py b/anomalib/models/dfm/lightning_model.py index 52b74fb68a..c83ad7a1c5 100644 --- a/anomalib/models/dfm/lightning_model.py +++ b/anomalib/models/dfm/lightning_model.py @@ -3,8 +3,9 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import logging -from typing import Dict, List, Tuple, Union import torch from omegaconf import DictConfig, ListConfig @@ -26,7 +27,7 @@ class Dfm(AnomalyModule): Args: backbone (str): Backbone CNN network layer (str): Layer to extract features from the backbone CNN - input_size (Tuple[int, int]): Input size for the model. + input_size (tuple[int, int]): Input size for the model. pre_trained (bool, optional): Boolean to check whether to use a pre_trained backbone. pooling_kernel_size (int, optional): Kernel size to pool features extracted from the CNN. Defaults to 4. @@ -41,7 +42,7 @@ def __init__( self, backbone: str, layer: str, - input_size: Tuple[int, int], + input_size: tuple[int, int], pre_trained: bool = True, pooling_kernel_size: int = 4, pca_level: float = 0.97, @@ -58,7 +59,7 @@ def __init__( n_comps=pca_level, score_type=score_type, ) - self.embeddings: List[Tensor] = [] + self.embeddings: list[Tensor] = [] self.score_type = score_type @staticmethod @@ -66,13 +67,13 @@ def configure_optimizers() -> None: # pylint: disable=arguments-differ """DFM doesn't require optimization, therefore returns no optimizers.""" return None - def training_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> None: + def training_step(self, batch: dict[str, str | Tensor], *args, **kwargs) -> None: """Training Step of DFM. For each batch, features are extracted from the CNN. Args: - batch (Dict[str, Union[str, Tensor]]): Input batch + batch (dict[str, str | Tensor]): Input batch _: Index of the batch. Returns: @@ -97,13 +98,13 @@ def on_validation_start(self) -> None: logger.info("Fitting a PCA and a Gaussian model to dataset.") self.model.fit(embeddings) - def validation_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> STEP_OUTPUT: + def validation_step(self, batch: dict[str, str | Tensor], *args, **kwargs) -> STEP_OUTPUT: """Validation Step of DFM. Similar to the training step, features are extracted from the CNN for each batch. Args: - batch (Dict[str, Union[str, Tensor]]): Input batch + batch (dict[str, str | Tensor]): Input batch Returns: Dictionary containing FRE anomaly scores and anomaly maps. @@ -120,10 +121,10 @@ class DfmLightning(Dfm): """DFM: Deep Featured Kernel Density Estimation. Args: - hparams (Union[DictConfig, ListConfig]): Model params + hparams (DictConfig | ListConfig): Model params """ - def __init__(self, hparams: Union[DictConfig, ListConfig]) -> None: + def __init__(self, hparams: DictConfig | ListConfig) -> None: super().__init__( input_size=hparams.model.input_size, backbone=hparams.model.backbone, @@ -133,5 +134,5 @@ def __init__(self, hparams: Union[DictConfig, ListConfig]) -> None: pca_level=hparams.model.pca_level, score_type=hparams.model.score_type, ) - self.hparams: Union[DictConfig, ListConfig] # type: ignore + self.hparams: DictConfig | ListConfig # type: ignore self.save_hyperparameters(hparams) diff --git a/anomalib/models/dfm/torch_model.py b/anomalib/models/dfm/torch_model.py index ec47613b01..b8e719abb8 100644 --- a/anomalib/models/dfm/torch_model.py +++ b/anomalib/models/dfm/torch_model.py @@ -3,8 +3,9 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import math -from typing import Tuple import torch import torch.nn.functional as F @@ -76,7 +77,7 @@ class DFMModel(nn.Module): Args: backbone (str): Pre-trained model backbone. layer (str): Layer from which to extract features. - input_size (Tuple[int, int]): Input size for the model. + input_size (tuple[int, int]): Input size for the model. pre_trained (bool, optional): Boolean to check whether to use a pre_trained backbone. pooling_kernel_size (int, optional): Kernel size to pool features extracted from the CNN. n_comps (float, optional): Ratio from which number of components for PCA are calculated. Defaults to 0.97. @@ -88,7 +89,7 @@ def __init__( self, backbone: str, layer: str, - input_size: Tuple[int, int], + input_size: tuple[int, int], pre_trained: bool = True, pooling_kernel_size: int = 4, n_comps: float = 0.97, From de6c411ffdde56b5af43ff19e54319d301b6b348 Mon Sep 17 00:00:00 2001 From: Samet Date: Tue, 17 Jan 2023 06:59:38 -0700 Subject: [PATCH 61/85] Convert to new annotation format - draem model --- anomalib/models/draem/lightning_model.py | 26 +++++++++++++----------- anomalib/models/draem/torch_model.py | 6 +++--- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/anomalib/models/draem/lightning_model.py b/anomalib/models/draem/lightning_model.py index 5ba43193e8..ac7f55bd2c 100644 --- a/anomalib/models/draem/lightning_model.py +++ b/anomalib/models/draem/lightning_model.py @@ -6,7 +6,9 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -from typing import Callable, Dict, List, Optional, Union +from __future__ import annotations + +from typing import Callable import torch from omegaconf import DictConfig, ListConfig @@ -28,12 +30,12 @@ class Draem(AnomalyModule): """DRÆM: A discriminatively trained reconstruction embedding for surface anomaly detection. Args: - anomaly_source_path (Optional[str]): Path to folder that contains the anomaly source images. Random noise will + anomaly_source_path (str | None): Path to folder that contains the anomaly source images. Random noise will be used if left empty. """ def __init__( - self, enable_sspcab: bool = False, sspcab_lambda: float = 0.1, anomaly_source_path: Optional[str] = None + self, enable_sspcab: bool = False, sspcab_lambda: float = 0.1, anomaly_source_path: str | None = None ) -> None: super().__init__() @@ -43,7 +45,7 @@ def __init__( self.sspcab = enable_sspcab if self.sspcab: - self.sspcab_activations: Dict = {} + self.sspcab_activations: dict = {} self.setup_sspcab() self.sspcab_loss = nn.MSELoss() self.sspcab_lambda = sspcab_lambda @@ -67,14 +69,14 @@ def hook(_, __, output: Tensor) -> None: self.model.reconstructive_subnetwork.encoder.mp4.register_forward_hook(get_activation("input")) self.model.reconstructive_subnetwork.encoder.block5.register_forward_hook(get_activation("output")) - def training_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> STEP_OUTPUT: + def training_step(self, batch: dict[str, str | Tensor], *args, **kwargs) -> STEP_OUTPUT: """Training Step of DRAEM. Feeds the original image and the simulated anomaly image through the network and computes the training loss. Args: - batch (Dict[str, Union[str, Tensor]]): Batch containing image filename, image, label and mask + batch (dict[str, str | Tensor]): Batch containing image filename, image, label and mask Returns: Loss dictionary @@ -95,11 +97,11 @@ def training_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) - self.log("train_loss", loss.item(), on_epoch=True, prog_bar=True, logger=True) return {"loss": loss} - def validation_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> STEP_OUTPUT: + def validation_step(self, batch: dict[str, str | Tensor], *args, **kwargs) -> STEP_OUTPUT: """Validation step of DRAEM. The Softmax predictions of the anomalous class are used as anomaly map. Args: - batch (Dict[str, Union[str, Tensor]]): Batch of input images + batch (dict[str, str | Tensor]): Batch of input images Returns: Dictionary to which predicted anomaly maps have been added. @@ -113,19 +115,19 @@ class DraemLightning(Draem): """DRÆM: A discriminatively trained reconstruction embedding for surface anomaly detection. Args: - hparams (Union[DictConfig, ListConfig]): Model parameters + hparams (DictConfig | ListConfig): Model parameters """ - def __init__(self, hparams: Union[DictConfig, ListConfig]) -> None: + def __init__(self, hparams: DictConfig | ListConfig) -> None: super().__init__( enable_sspcab=hparams.model.enable_sspcab, sspcab_lambda=hparams.model.sspcab_lambda, anomaly_source_path=hparams.model.anomaly_source_path, ) - self.hparams: Union[DictConfig, ListConfig] # type: ignore + self.hparams: DictConfig | ListConfig # type: ignore self.save_hyperparameters(hparams) - def configure_callbacks(self) -> List[EarlyStopping]: + def configure_callbacks(self) -> list[EarlyStopping]: """Configure model-specific callbacks. Note: diff --git a/anomalib/models/draem/torch_model.py b/anomalib/models/draem/torch_model.py index 2ca7608a89..6d74953a33 100644 --- a/anomalib/models/draem/torch_model.py +++ b/anomalib/models/draem/torch_model.py @@ -9,7 +9,7 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -from typing import Tuple, Union +from __future__ import annotations import torch from torch import Tensor, nn @@ -25,7 +25,7 @@ def __init__(self, sspcab: bool = False) -> None: self.reconstructive_subnetwork = ReconstructiveSubNetwork(sspcab=sspcab) self.discriminative_subnetwork = DiscriminativeSubNetwork(in_channels=6, out_channels=2) - def forward(self, batch: Tensor) -> Union[Tensor, Tuple[Tensor, Tensor]]: + def forward(self, batch: Tensor) -> Tensor | tuple[Tensor, Tensor]: """Compute the reconstruction and anomaly mask from an input image. Args: @@ -165,7 +165,7 @@ def __init__(self, in_channels: int, base_width: int) -> None: nn.ReLU(inplace=True), ) - def forward(self, batch: Tensor) -> Tuple[Tensor, Tensor, Tensor, Tensor, Tensor, Tensor]: + def forward(self, batch: Tensor) -> tuple[Tensor, Tensor, Tensor, Tensor, Tensor, Tensor]: """Convert the inputs to the salient space by running them through the encoder network. Args: From aa4959e704f0322399ed451bb5428e56b762ef85 Mon Sep 17 00:00:00 2001 From: Samet Date: Tue, 17 Jan 2023 07:09:18 -0700 Subject: [PATCH 62/85] Convert to new annotation format - fastflow model --- anomalib/models/fastflow/anomaly_map.py | 10 +++--- anomalib/models/fastflow/lightning_model.py | 22 ++++++------- anomalib/models/fastflow/loss.py | 8 ++--- anomalib/models/fastflow/torch_model.py | 34 +++++++++++---------- 4 files changed, 38 insertions(+), 36 deletions(-) diff --git a/anomalib/models/fastflow/anomaly_map.py b/anomalib/models/fastflow/anomaly_map.py index c001222f0d..12ee734a0d 100644 --- a/anomalib/models/fastflow/anomaly_map.py +++ b/anomalib/models/fastflow/anomaly_map.py @@ -3,7 +3,7 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -from typing import List, Tuple, Union +from __future__ import annotations import torch import torch.nn.functional as F @@ -14,11 +14,11 @@ class AnomalyMapGenerator(nn.Module): """Generate Anomaly Heatmap.""" - def __init__(self, input_size: Union[ListConfig, Tuple]) -> None: + def __init__(self, input_size: ListConfig | tuple) -> None: super().__init__() self.input_size = input_size if isinstance(input_size, tuple) else tuple(input_size) - def forward(self, hidden_variables: List[Tensor]) -> Tensor: + def forward(self, hidden_variables: list[Tensor]) -> Tensor: """Generate Anomaly Heatmap. This implementation generates the heatmap based on the flow maps @@ -27,12 +27,12 @@ def forward(self, hidden_variables: List[Tensor]) -> Tensor: map. Args: - hidden_variables (List[Tensor]): List of hidden variables from each NF FastFlow block. + hidden_variables (list[Tensor]): List of hidden variables from each NF FastFlow block. Returns: Tensor: Anomaly Map. """ - flow_maps: List[Tensor] = [] + flow_maps: list[Tensor] = [] for hidden_variable in hidden_variables: log_prob = -torch.mean(hidden_variable**2, dim=1, keepdim=True) * 0.5 prob = torch.exp(log_prob) diff --git a/anomalib/models/fastflow/lightning_model.py b/anomalib/models/fastflow/lightning_model.py index 00c58c748a..c3a681a88c 100644 --- a/anomalib/models/fastflow/lightning_model.py +++ b/anomalib/models/fastflow/lightning_model.py @@ -3,7 +3,7 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -from typing import Dict, List, Tuple, Union +from __future__ import annotations import torch from omegaconf import DictConfig, ListConfig @@ -22,7 +22,7 @@ class Fastflow(AnomalyModule): """PL Lightning Module for the FastFlow algorithm. Args: - input_size (Tuple[int, int]): Model input size. + input_size (tuple[int, int]): Model input size. backbone (str): Backbone CNN network pre_trained (bool, optional): Boolean to check whether to use a pre_trained backbone. flow_steps (int, optional): Flow steps. @@ -32,7 +32,7 @@ class Fastflow(AnomalyModule): def __init__( self, - input_size: Tuple[int, int], + input_size: tuple[int, int], backbone: str, pre_trained: bool = True, flow_steps: int = 8, @@ -51,11 +51,11 @@ def __init__( ) self.loss = FastflowLoss() - def training_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> STEP_OUTPUT: + def training_step(self, batch: dict[str, str | Tensor], *args, **kwargs) -> STEP_OUTPUT: """Forward-pass input and return the loss. Args: - batch (batch: Dict[str, Union[str, Tensor]]): Input batch + batch (batch: dict[str, str | Tensor]): Input batch _batch_idx: Index of the batch. Returns: @@ -66,11 +66,11 @@ def training_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) - self.log("train_loss", loss.item(), on_epoch=True, prog_bar=True, logger=True) return {"loss": loss} - def validation_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> STEP_OUTPUT: + def validation_step(self, batch: dict[str, str | Tensor], *args, **kwargs) -> STEP_OUTPUT: """Forward-pass the input and return the anomaly map. Args: - batch (Dict[str, Union[str, Tensor]]): Input batch + batch (dict[str, str | Tensor]): Input batch Returns: Optional[STEP_OUTPUT]: batch dictionary containing anomaly-maps. @@ -84,10 +84,10 @@ class FastflowLightning(Fastflow): """PL Lightning Module for the FastFlow algorithm. Args: - hparams (Union[DictConfig, ListConfig]): Model params + hparams (DictConfig | ListConfig): Model params """ - def __init__(self, hparams: Union[DictConfig, ListConfig]) -> None: + def __init__(self, hparams: DictConfig | ListConfig) -> None: super().__init__( input_size=hparams.model.input_size, backbone=hparams.model.backbone, @@ -96,10 +96,10 @@ def __init__(self, hparams: Union[DictConfig, ListConfig]) -> None: conv3x3_only=hparams.model.conv3x3_only, hidden_ratio=hparams.model.hidden_ratio, ) - self.hparams: Union[DictConfig, ListConfig] # type: ignore + self.hparams: DictConfig | ListConfig # type: ignore self.save_hyperparameters(hparams) - def configure_callbacks(self) -> List[EarlyStopping]: + def configure_callbacks(self) -> list[EarlyStopping]: """Configure model-specific callbacks. Note: diff --git a/anomalib/models/fastflow/loss.py b/anomalib/models/fastflow/loss.py index 608b0cfc87..848f86980f 100644 --- a/anomalib/models/fastflow/loss.py +++ b/anomalib/models/fastflow/loss.py @@ -3,7 +3,7 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -from typing import List +from __future__ import annotations import torch from torch import Tensor, nn @@ -12,12 +12,12 @@ class FastflowLoss(nn.Module): """FastFlow Loss.""" - def forward(self, hidden_variables: List[Tensor], jacobians: List[Tensor]) -> Tensor: + def forward(self, hidden_variables: list[Tensor], jacobians: list[Tensor]) -> Tensor: """Calculate the Fastflow loss. Args: - hidden_variables (List[Tensor]): Hidden variables from the fastflow model. f: X -> Z - jacobians (List[Tensor]): Log of the jacobian determinants from the fastflow model. + hidden_variables (list[Tensor]): Hidden variables from the fastflow model. f: X -> Z + jacobians (list[Tensor]): Log of the jacobian determinants from the fastflow model. Returns: Tensor: Fastflow loss computed based on the hidden variables and the log of the Jacobians. diff --git a/anomalib/models/fastflow/torch_model.py b/anomalib/models/fastflow/torch_model.py index 3a96a46377..ca8a3c78a7 100644 --- a/anomalib/models/fastflow/torch_model.py +++ b/anomalib/models/fastflow/torch_model.py @@ -9,7 +9,9 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -from typing import Callable, List, Tuple, Union +from __future__ import annotations + +from typing import Callable import timm import torch @@ -49,7 +51,7 @@ def subnet_conv(in_channels: int, out_channels: int) -> nn.Sequential: def create_fast_flow_block( - input_dimensions: List[int], + input_dimensions: list[int], conv3x3_only: bool, hidden_ratio: float, flow_steps: int, @@ -61,7 +63,7 @@ def create_fast_flow_block( Figure 2 and Section 3.3 in the paper. Args: - input_dimensions (List[int]): Input dimensions (Channel, Height, Width) + input_dimensions (list[int]): Input dimensions (Channel, Height, Width) conv3x3_only (bool): Boolean whether to use conv3x3 only or conv3x3 and conv1x1. hidden_ratio (float): Ratio for the hidden layer channels. flow_steps (int): Flow steps. @@ -91,7 +93,7 @@ class FastflowModel(nn.Module): Unsupervised Anomaly Detection and Localization via 2D Normalizing Flows. Args: - input_size (Tuple[int, int]): Model input size. + input_size (tuple[int, int]): Model input size. backbone (str): Backbone CNN network pre_trained (bool, optional): Boolean to check whether to use a pre_trained backbone. flow_steps (int, optional): Flow steps. @@ -104,7 +106,7 @@ class FastflowModel(nn.Module): def __init__( self, - input_size: Tuple[int, int], + input_size: tuple[int, int], backbone: str, pre_trained: bool = True, flow_steps: int = 8, @@ -160,19 +162,19 @@ def __init__( ) self.anomaly_map_generator = AnomalyMapGenerator(input_size=input_size) - def forward(self, input_tensor: Tensor) -> Union[Tuple[List[Tensor], List[Tensor]], Tensor]: + def forward(self, input_tensor: Tensor) -> Tensor | list[Tensor] | tuple[list[Tensor]]: """Forward-Pass the input to the FastFlow Model. Args: input_tensor (Tensor): Input tensor. Returns: - Union[Tuple[Tensor, Tensor], Tensor]: During training, return + Tensor | list[Tensor] | tuple[list[Tensor]]: During training, return (hidden_variables, log-of-the-jacobian-determinants). During the validation/test, return the anomaly map. """ - return_val: Union[Tuple[List[Tensor], List[Tensor]], Tensor] + return_val: Tensor | list[Tensor] | tuple[list[Tensor]] self.feature_extractor.eval() if isinstance(self.feature_extractor, VisionTransformer): @@ -185,8 +187,8 @@ def forward(self, input_tensor: Tensor) -> Union[Tuple[List[Tensor], List[Tensor # Compute the hidden variable f: X -> Z and log-likelihood of the jacobian # (See Section 3.3 in the paper.) # NOTE: output variable has z, and jacobian tuple for each fast-flow blocks. - hidden_variables: List[Tensor] = [] - log_jacobians: List[Tensor] = [] + hidden_variables: list[Tensor] = [] + log_jacobians: list[Tensor] = [] for fast_flow_block, feature in zip(self.fast_flow_blocks, features): hidden_variable, log_jacobian = fast_flow_block(feature) hidden_variables.append(hidden_variable) @@ -199,27 +201,27 @@ def forward(self, input_tensor: Tensor) -> Union[Tuple[List[Tensor], List[Tensor return return_val - def _get_cnn_features(self, input_tensor: Tensor) -> List[Tensor]: + def _get_cnn_features(self, input_tensor: Tensor) -> list[Tensor]: """Get CNN-based features. Args: input_tensor (Tensor): Input Tensor. Returns: - List[Tensor]: List of features. + list[Tensor]: List of features. """ features = self.feature_extractor(input_tensor) features = [self.norms[i](feature) for i, feature in enumerate(features)] return features - def _get_cait_features(self, input_tensor: Tensor) -> List[Tensor]: + def _get_cait_features(self, input_tensor: Tensor) -> list[Tensor]: """Get Class-Attention-Image-Transformers (CaiT) features. Args: input_tensor (Tensor): Input Tensor. Returns: - List[Tensor]: List of features. + list[Tensor]: List of features. """ feature = self.feature_extractor.patch_embed(input_tensor) feature = feature + self.feature_extractor.pos_embed @@ -233,14 +235,14 @@ def _get_cait_features(self, input_tensor: Tensor) -> List[Tensor]: features = [feature] return features - def _get_vit_features(self, input_tensor: Tensor) -> List[Tensor]: + def _get_vit_features(self, input_tensor: Tensor) -> list[Tensor]: """Get Vision Transformers (ViT) features. Args: input_tensor (Tensor): Input Tensor. Returns: - List[Tensor]: List of features. + list[Tensor]: List of features. """ feature = self.feature_extractor.patch_embed(input_tensor) cls_token = self.feature_extractor.cls_token.expand(feature.shape[0], -1, -1) From 02ad225f5daf89f64f3d01ae4668eb28d9d2d57c Mon Sep 17 00:00:00 2001 From: Samet Date: Tue, 17 Jan 2023 07:38:18 -0700 Subject: [PATCH 63/85] Convert to new annotation format - ganomaly model --- anomalib/models/ganomaly/lightning_model.py | 29 +++++++------- anomalib/models/ganomaly/torch_model.py | 43 +++++++++++---------- 2 files changed, 38 insertions(+), 34 deletions(-) diff --git a/anomalib/models/ganomaly/lightning_model.py b/anomalib/models/ganomaly/lightning_model.py index 0e44ef6acd..675b5bf767 100644 --- a/anomalib/models/ganomaly/lightning_model.py +++ b/anomalib/models/ganomaly/lightning_model.py @@ -6,8 +6,9 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import logging -from typing import Dict, List, Optional, Tuple, Union import torch from omegaconf import DictConfig, ListConfig @@ -30,7 +31,7 @@ class Ganomaly(AnomalyModule): Args: batch_size (int): Batch size. - input_size (Tuple[int,int]): Input dimension. + input_size (tuple[int,int]): Input dimension. n_features (int): Number of features layers in the CNNs. latent_vec_size (int): Size of autoencoder latent vector. extra_layers (int, optional): Number of extra layers for encoder/decoder. Defaults to 0. @@ -43,7 +44,7 @@ class Ganomaly(AnomalyModule): def __init__( self, batch_size: int, - input_size: Tuple[int, int], + input_size: tuple[int, int], n_features: int, latent_vec_size: int, extra_layers: int = 0, @@ -86,7 +87,7 @@ def _reset_min_max(self) -> None: self.min_scores = torch.tensor(float("inf"), dtype=torch.float32) # pylint: disable=not-callable self.max_scores = torch.tensor(float("-inf"), dtype=torch.float32) # pylint: disable=not-callable - def configure_optimizers(self) -> List[optim.Optimizer]: + def configure_optimizers(self) -> list[optim.Optimizer]: """Configures optimizers for each decoder. Note: @@ -110,11 +111,11 @@ def configure_optimizers(self) -> List[optim.Optimizer]: ) return [optimizer_d, optimizer_g] - def training_step(self, batch: Dict[str, Union[str, Tensor]], optimizer_idx: int, *args, **kwargs) -> STEP_OUTPUT: + def training_step(self, batch: dict[str, str | Tensor], optimizer_idx: int, *args, **kwargs) -> STEP_OUTPUT: """Training step. Args: - batch (Dict[str, Union[str, Tensor]]): Input batch containing images. + batch (dict[str, str | Tensor]): Input batch containing images. Returns: STEP_OUTPUT: Loss @@ -138,14 +139,14 @@ def on_validation_start(self) -> None: self._reset_min_max() return super().on_validation_start() - def validation_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> STEP_OUTPUT: + def validation_step(self, batch: dict[str, str | Tensor], *args, **kwargs) -> STEP_OUTPUT: """Update min and max scores from the current step. Args: - batch (Dict[str, Union[str, Tensor]]): Predicted difference between z and z_hat. + batch (dict[str, str | Tensor]): Predicted difference between z and z_hat. Returns: - Dict[str, Tensor]: batch + dict[str, Tensor]: batch """ batch["pred_scores"] = self.model(batch["image"]) self.max_scores = max(self.max_scores, torch.max(batch["pred_scores"])) @@ -165,7 +166,7 @@ def on_test_start(self) -> None: self._reset_min_max() return super().on_test_start() - def test_step(self, batch: Dict[str, Union[str, Tensor]], batch_idx: int, *args, **kwargs) -> Optional[STEP_OUTPUT]: + def test_step(self, batch: dict[str, str | Tensor], batch_idx: int, *args, **kwargs) -> STEP_OUTPUT: """Update min and max scores from the current step.""" super().test_step(batch, batch_idx) self.max_scores = max(self.max_scores, torch.max(batch["pred_scores"])) @@ -199,10 +200,10 @@ class GanomalyLightning(Ganomaly): """PL Lightning Module for the GANomaly Algorithm. Args: - hparams (Union[DictConfig, ListConfig]): Model params + hparams (DictConfig | ListConfig): Model params """ - def __init__(self, hparams: Union[DictConfig, ListConfig]) -> None: + def __init__(self, hparams: DictConfig | ListConfig) -> None: super().__init__( batch_size=hparams.dataset.train_batch_size, @@ -218,10 +219,10 @@ def __init__(self, hparams: Union[DictConfig, ListConfig]) -> None: beta1=hparams.model.beta1, beta2=hparams.model.beta2, ) - self.hparams: Union[DictConfig, ListConfig] # type: ignore + self.hparams: DictConfig | ListConfig # type: ignore self.save_hyperparameters(hparams) - def configure_callbacks(self) -> List[EarlyStopping]: + def configure_callbacks(self) -> list[EarlyStopping]: """Configure model-specific callbacks. Note: diff --git a/anomalib/models/ganomaly/torch_model.py b/anomalib/models/ganomaly/torch_model.py index fbf67fc1b5..bb96293ebc 100644 --- a/anomalib/models/ganomaly/torch_model.py +++ b/anomalib/models/ganomaly/torch_model.py @@ -9,8 +9,9 @@ # Copyright (C) 2020-2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import math -from typing import Tuple, Union import torch from torch import Tensor, nn @@ -22,7 +23,7 @@ class Encoder(nn.Module): """Encoder Network. Args: - input_size (Tuple[int, int]): Size of input image + input_size (tuple[int, int]): Size of input image latent_vec_size (int): Size of latent vector z num_input_channels (int): Number of input channels in the image n_features (int): Number of features per convolution layer @@ -32,13 +33,13 @@ class Encoder(nn.Module): def __init__( self, - input_size: Tuple[int, int], + input_size: tuple[int, int], latent_vec_size: int, num_input_channels: int, n_features: int, extra_layers: int = 0, add_final_conv_layer: bool = True, - ): + ) -> None: super().__init__() self.input_layers = nn.Sequential() @@ -85,7 +86,7 @@ def __init__( bias=False, ) - def forward(self, input_tensor: Tensor): + def forward(self, input_tensor: Tensor) -> Tensor: """Return latent vectors.""" output = self.input_layers(input_tensor) @@ -101,7 +102,7 @@ class Decoder(nn.Module): """Decoder Network. Args: - input_size (Tuple[int, int]): Size of input image + input_size (tuple[int, int]): Size of input image latent_vec_size (int): Size of latent vector z num_input_channels (int): Number of input channels in the image n_features (int): Number of features per convolution layer @@ -111,12 +112,12 @@ class Decoder(nn.Module): def __init__( self, - input_size: Tuple[int, int], + input_size: tuple[int, int], latent_vec_size: int, num_input_channels: int, n_features: int, extra_layers: int = 0, - ): + ) -> None: super().__init__() self.latent_input = nn.Sequential() @@ -191,7 +192,7 @@ def __init__( ) self.final_layers.add_module(f"final-{num_input_channels}-tanh", nn.Tanh()) - def forward(self, input_tensor): + def forward(self, input_tensor: Tensor) -> Tensor: """Return generated image.""" output = self.latent_input(input_tensor) output = self.inverse_pyramid(output) @@ -206,13 +207,15 @@ class Discriminator(nn.Module): Made of only one encoder layer which takes x and x_hat to produce a score. Args: - input_size (Tuple[int,int]): Input image size. + input_size (tuple[int,int]): Input image size. num_input_channels (int): Number of image channels. n_features (int): Number of feature maps in each convolution layer. extra_layers (int, optional): Add extra intermediate layers. Defaults to 0. """ - def __init__(self, input_size: Tuple[int, int], num_input_channels: int, n_features: int, extra_layers: int = 0): + def __init__( + self, input_size: tuple[int, int], num_input_channels: int, n_features: int, extra_layers: int = 0 + ) -> None: super().__init__() encoder = Encoder(input_size, 1, num_input_channels, n_features, extra_layers) layers = [] @@ -226,7 +229,7 @@ def __init__(self, input_size: Tuple[int, int], num_input_channels: int, n_featu self.classifier = nn.Sequential(layers[-1]) self.classifier.add_module("Sigmoid", nn.Sigmoid()) - def forward(self, input_tensor): + def forward(self, input_tensor: Tensor) -> tuple[Tensor, Tensor]: """Return class of object and features.""" features = self.features(input_tensor) classifier = self.classifier(features) @@ -240,7 +243,7 @@ class Generator(nn.Module): Made of an encoder-decoder-encoder architecture. Args: - input_size (Tuple[int,int]): Size of input data. + input_size (tuple[int,int]): Size of input data. latent_vec_size (int): Dimension of latent vector produced between the first encoder-decoder. num_input_channels (int): Number of channels in input image. n_features (int): Number of feature maps in each convolution layer. @@ -250,13 +253,13 @@ class Generator(nn.Module): def __init__( self, - input_size: Tuple[int, int], + input_size: tuple[int, int], latent_vec_size: int, num_input_channels: int, n_features: int, extra_layers: int = 0, add_final_conv_layer: bool = True, - ): + ) -> None: super().__init__() self.encoder1 = Encoder( input_size, latent_vec_size, num_input_channels, n_features, extra_layers, add_final_conv_layer @@ -266,7 +269,7 @@ def __init__( input_size, latent_vec_size, num_input_channels, n_features, extra_layers, add_final_conv_layer ) - def forward(self, input_tensor): + def forward(self, input_tensor: Tensor) -> tuple[Tensor, Tensor, Tensor]: """Return generated image and the latent vectors.""" latent_i = self.encoder1(input_tensor) gen_image = self.decoder(latent_i) @@ -278,7 +281,7 @@ class GanomalyModel(nn.Module): """Ganomaly Model. Args: - input_size (Tuple[int,int]): Input dimension. + input_size (tuple[int,int]): Input dimension. num_input_channels (int): Number of input channels. n_features (int): Number of features layers in the CNNs. latent_vec_size (int): Size of autoencoder latent vector. @@ -288,7 +291,7 @@ class GanomalyModel(nn.Module): def __init__( self, - input_size: Tuple[int, int], + input_size: tuple[int, int], num_input_channels: int, n_features: int, latent_vec_size: int, @@ -314,7 +317,7 @@ def __init__( self.weights_init(self.discriminator) @staticmethod - def weights_init(module: nn.Module): + def weights_init(module: nn.Module) -> None: """Initialize DCGAN weights. Args: @@ -327,7 +330,7 @@ def weights_init(module: nn.Module): nn.init.normal_(module.weight.data, 1.0, 0.02) nn.init.constant_(module.bias.data, 0) - def forward(self, batch: Tensor) -> Union[Tuple[Tensor, Tensor, Tensor, Tensor], Tensor]: + def forward(self, batch: Tensor) -> Tensor | tuple[Tensor, Tensor, Tensor, Tensor]: """Get scores for batch. Args: From 8ce58a92719442040db49a12a40b34f00a18095a Mon Sep 17 00:00:00 2001 From: Samet Date: Tue, 17 Jan 2023 07:42:53 -0700 Subject: [PATCH 64/85] Convert to new annotation format - padim model --- anomalib/models/padim/anomaly_map.py | 10 ++++---- anomalib/models/padim/lightning_model.py | 31 ++++++++++++------------ anomalib/models/padim/torch_model.py | 25 ++++++++++--------- 3 files changed, 34 insertions(+), 32 deletions(-) diff --git a/anomalib/models/padim/anomaly_map.py b/anomalib/models/padim/anomaly_map.py index 4d43f49b8d..08df61cbce 100644 --- a/anomalib/models/padim/anomaly_map.py +++ b/anomalib/models/padim/anomaly_map.py @@ -3,7 +3,7 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -from typing import List, Tuple, Union +from __future__ import annotations import torch import torch.nn.functional as F @@ -17,25 +17,25 @@ class AnomalyMapGenerator(nn.Module): """Generate Anomaly Heatmap. Args: - image_size (Union[ListConfig, Tuple]): Size of the input image. The anomaly map is upsampled to this dimension. + image_size (ListConfig, tuple): Size of the input image. The anomaly map is upsampled to this dimension. sigma (int, optional): Standard deviation for Gaussian Kernel. Defaults to 4. """ - def __init__(self, image_size: Union[ListConfig, Tuple], sigma: int = 4) -> None: + def __init__(self, image_size: ListConfig | tuple, sigma: int = 4) -> None: super().__init__() self.image_size = image_size if isinstance(image_size, tuple) else tuple(image_size) kernel_size = 2 * int(4.0 * sigma + 0.5) + 1 self.blur = GaussianBlur2d(kernel_size=(kernel_size, kernel_size), sigma=(sigma, sigma), channels=1) @staticmethod - def compute_distance(embedding: Tensor, stats: List[Tensor]) -> Tensor: + def compute_distance(embedding: Tensor, stats: list[Tensor]) -> Tensor: """Compute anomaly score to the patch in position(i,j) of a test image. Ref: Equation (2), Section III-C of the paper. Args: embedding (Tensor): Embedding Vector - stats (List[Tensor]): Mean and Covariance Matrix of the multivariate Gaussian distribution + stats (list[Tensor]): Mean and Covariance Matrix of the multivariate Gaussian distribution Returns: Anomaly score of a test image via mahalanobis distance. diff --git a/anomalib/models/padim/lightning_model.py b/anomalib/models/padim/lightning_model.py index 177056481f..ec1b4f073d 100644 --- a/anomalib/models/padim/lightning_model.py +++ b/anomalib/models/padim/lightning_model.py @@ -6,8 +6,9 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import logging -from typing import Dict, List, Optional, Tuple, Union import torch from omegaconf import DictConfig, ListConfig @@ -28,8 +29,8 @@ class Padim(AnomalyModule): """PaDiM: a Patch Distribution Modeling Framework for Anomaly Detection and Localization. Args: - layers (List[str]): Layers to extract features from the backbone CNN - input_size (Tuple[int, int]): Size of the model input. + layers (list[str]): Layers to extract features from the backbone CNN + input_size (tuple[int, int]): Size of the model input. backbone (str): Backbone CNN network pre_trained (bool, optional): Boolean to check whether to use a pre_trained backbone. n_features (int, optional): Number of features to retain in the dimension reduction step. @@ -38,11 +39,11 @@ class Padim(AnomalyModule): def __init__( self, - layers: List[str], - input_size: Tuple[int, int], + layers: list[str], + input_size: tuple[int, int], backbone: str, pre_trained: bool = True, - n_features: Optional[int] = None, + n_features: int | None = None, ) -> None: super().__init__() @@ -55,19 +56,19 @@ def __init__( n_features=n_features, ).eval() - self.stats: List[Tensor] = [] - self.embeddings: List[Tensor] = [] + self.stats: list[Tensor] = [] + self.embeddings: list[Tensor] = [] @staticmethod def configure_optimizers() -> None: # pylint: disable=arguments-differ """PADIM doesn't require optimization, therefore returns no optimizers.""" return None - def training_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> None: + def training_step(self, batch: dict[str, str | Tensor], *args, **kwargs) -> None: """Training Step of PADIM. For each batch, hierarchical features are extracted from the CNN. Args: - batch (Dict[str, Union[str, Tensor]]): Batch containing image filename, image, label and mask + batch (dict[str, str | Tensor]): Batch containing image filename, image, label and mask _batch_idx: Index of the batch. Returns: @@ -93,13 +94,13 @@ def on_validation_start(self) -> None: logger.info("Fitting a Gaussian to the embedding collected from the training set.") self.stats = self.model.gaussian.fit(embeddings) - def validation_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> STEP_OUTPUT: + def validation_step(self, batch: dict[str, str | Tensor], *args, **kwargs) -> STEP_OUTPUT: """Validation Step of PADIM. Similar to the training step, hierarchical features are extracted from the CNN for each batch. Args: - batch (Dict[str, Union[str, Tensor]]): Input batch + batch (dict[str, str | Tensor]): Input batch Returns: Dictionary containing images, features, true labels and masks. @@ -114,10 +115,10 @@ class PadimLightning(Padim): """PaDiM: a Patch Distribution Modeling Framework for Anomaly Detection and Localization. Args: - hparams (Union[DictConfig, ListConfig]): Model params + hparams (DictConfig | ListConfig): Model params """ - def __init__(self, hparams: Union[DictConfig, ListConfig]) -> None: + def __init__(self, hparams: DictConfig | ListConfig) -> None: super().__init__( input_size=hparams.model.input_size, layers=hparams.model.layers, @@ -125,5 +126,5 @@ def __init__(self, hparams: Union[DictConfig, ListConfig]) -> None: pre_trained=hparams.model.pre_trained, n_features=hparams.model.n_features if "n_features" in hparams.model else None, ) - self.hparams: Union[DictConfig, ListConfig] # type: ignore + self.hparams: DictConfig | ListConfig # type: ignore self.save_hyperparameters(hparams) diff --git a/anomalib/models/padim/torch_model.py b/anomalib/models/padim/torch_model.py index ac3cd85198..702a788bfb 100644 --- a/anomalib/models/padim/torch_model.py +++ b/anomalib/models/padim/torch_model.py @@ -3,8 +3,9 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + from random import sample -from typing import Dict, List, Optional, Tuple import torch import torch.nn.functional as F @@ -23,15 +24,15 @@ def _deduce_dims( - feature_extractor: FeatureExtractor, input_size: Tuple[int, int], layers: List[str] -) -> Tuple[int, int]: + feature_extractor: FeatureExtractor, input_size: tuple[int, int], layers: list[str] +) -> tuple[int, int]: """Run a dry run to deduce the dimensions of the extracted features. Important: `layers` is assumed to be ordered and the first (layers[0]) is assumed to be the layer with largest resolution. Returns: - Tuple[int, int]: Dimensions of the extracted features: (n_dims_original, n_patches) + tuple[int, int]: Dimensions of the extracted features: (n_dims_original, n_patches) """ dimensions_mapping = dryrun_find_featuremap_dims(feature_extractor, input_size, layers) @@ -49,8 +50,8 @@ class PadimModel(nn.Module): """Padim Module. Args: - input_size (Tuple[int, int]): Input size for the model. - layers (List[str]): Layers used for feature extraction + input_size (tuple[int, int]): Input size for the model. + layers (list[str]): Layers used for feature extraction backbone (str, optional): Pre-trained model backbone. Defaults to "resnet18". pre_trained (bool, optional): Boolean to check whether to use a pre_trained backbone. n_features (int, optional): Number of features to retain in the dimension reduction step. @@ -59,14 +60,14 @@ class PadimModel(nn.Module): def __init__( self, - input_size: Tuple[int, int], - layers: List[str], + input_size: tuple[int, int], + layers: list[str], backbone: str = "resnet18", pre_trained: bool = True, - n_features: Optional[int] = None, + n_features: int | None = None, ) -> None: super().__init__() - self.tiler: Optional[Tiler] = None + self.tiler: Tiler | None = None self.backbone = backbone self.layers = layers @@ -139,11 +140,11 @@ def forward(self, input_tensor: Tensor) -> Tensor: ) return output - def generate_embedding(self, features: Dict[str, Tensor]) -> Tensor: + def generate_embedding(self, features: dict[str, Tensor]) -> Tensor: """Generate embedding from hierarchical feature map. Args: - features (Dict[str, Tensor]): Hierarchical feature map from a CNN (ResNet18 or WideResnet) + features (dict[str, Tensor]): Hierarchical feature map from a CNN (ResNet18 or WideResnet) Returns: Embedding vector From 0ea8e08dc8de609b663e186d4fe5141ddd833b02 Mon Sep 17 00:00:00 2001 From: Samet Date: Tue, 17 Jan 2023 07:46:34 -0700 Subject: [PATCH 65/85] Convert to new annotation format - patchcore model --- anomalib/models/patchcore/anomaly_map.py | 4 +-- anomalib/models/patchcore/lightning_model.py | 29 ++++++++++---------- anomalib/models/patchcore/torch_model.py | 18 ++++++------ 3 files changed, 26 insertions(+), 25 deletions(-) diff --git a/anomalib/models/patchcore/anomaly_map.py b/anomalib/models/patchcore/anomaly_map.py index e9fb6ee024..18b5238582 100644 --- a/anomalib/models/patchcore/anomaly_map.py +++ b/anomalib/models/patchcore/anomaly_map.py @@ -3,7 +3,7 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -from typing import Tuple, Union +from __future__ import annotations import torch.nn.functional as F from omegaconf import ListConfig @@ -17,7 +17,7 @@ class AnomalyMapGenerator(nn.Module): def __init__( self, - input_size: Union[ListConfig, Tuple], + input_size: ListConfig | tuple, sigma: int = 4, ) -> None: super().__init__() diff --git a/anomalib/models/patchcore/lightning_model.py b/anomalib/models/patchcore/lightning_model.py index 6dd45415f9..5e34c301e4 100644 --- a/anomalib/models/patchcore/lightning_model.py +++ b/anomalib/models/patchcore/lightning_model.py @@ -6,8 +6,9 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import logging -from typing import Dict, List, Tuple, Union import torch from omegaconf import DictConfig, ListConfig @@ -26,9 +27,9 @@ class Patchcore(AnomalyModule): """PatchcoreLightning Module to train PatchCore algorithm. Args: - input_size (Tuple[int, int]): Size of the model input. + input_size (tuple[int, int]): Size of the model input. backbone (str): Backbone CNN network - layers (List[str]): Layers to extract features from the backbone CNN + layers (list[str]): Layers to extract features from the backbone CNN pre_trained (bool, optional): Boolean to check whether to use a pre_trained backbone. coreset_sampling_ratio (float, optional): Coreset sampling ratio to subsample embedding. Defaults to 0.1. @@ -37,9 +38,9 @@ class Patchcore(AnomalyModule): def __init__( self, - input_size: Tuple[int, int], + input_size: tuple[int, int], backbone: str, - layers: List[str], + layers: list[str], pre_trained: bool = True, coreset_sampling_ratio: float = 0.1, num_neighbors: int = 9, @@ -54,7 +55,7 @@ def __init__( num_neighbors=num_neighbors, ) self.coreset_sampling_ratio = coreset_sampling_ratio - self.embeddings: List[Tensor] = [] + self.embeddings: list[Tensor] = [] def configure_optimizers(self) -> None: """Configure optimizers. @@ -64,14 +65,14 @@ def configure_optimizers(self) -> None: """ return None - def training_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> None: + def training_step(self, batch: dict[str, str | Tensor], *args, **kwargs) -> None: """Generate feature embedding of the batch. Args: - batch (Dict[str, Union[str, Tensor]]): Batch containing image filename, image, label and mask + batch (dict[str, str | Tensor]): Batch containing image filename, image, label and mask Returns: - Dict[str, np.ndarray]: Embedding Vector + dict[str, np.ndarray]: Embedding Vector """ self.model.feature_extractor.eval() embedding = self.model(batch["image"]) @@ -93,15 +94,15 @@ def on_validation_start(self) -> None: logger.info("Applying core-set subsampling to get the embedding.") self.model.subsample_embedding(embeddings, self.coreset_sampling_ratio) - def validation_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> STEP_OUTPUT: + def validation_step(self, batch: dict[str, str | Tensor], *args, **kwargs) -> STEP_OUTPUT: """Get batch of anomaly maps from input image batch. Args: - batch (Dict[str, Union[str, Tensor]]): Batch containing image filename, + batch (dict[str, str | Tensor]): Batch containing image filename, image, label and mask Returns: - Dict[str, Any]: Image filenames, test images, GT and predicted label/masks + dict[str, Any]: Image filenames, test images, GT and predicted label/masks """ anomaly_maps, anomaly_score = self.model(batch["image"]) @@ -115,7 +116,7 @@ class PatchcoreLightning(Patchcore): """PatchcoreLightning Module to train PatchCore algorithm. Args: - hparams (Union[DictConfig, ListConfig]): Model params + hparams (DictConfig | ListConfig): Model params """ def __init__(self, hparams) -> None: @@ -127,5 +128,5 @@ def __init__(self, hparams) -> None: coreset_sampling_ratio=hparams.model.coreset_sampling_ratio, num_neighbors=hparams.model.num_neighbors, ) - self.hparams: Union[DictConfig, ListConfig] # type: ignore + self.hparams: DictConfig | ListConfig # type: ignore self.save_hyperparameters(hparams) diff --git a/anomalib/models/patchcore/torch_model.py b/anomalib/models/patchcore/torch_model.py index 819fd83945..7f4f11f5fc 100644 --- a/anomalib/models/patchcore/torch_model.py +++ b/anomalib/models/patchcore/torch_model.py @@ -3,7 +3,7 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -from typing import Dict, List, Optional, Tuple, Union +from __future__ import annotations import torch import torch.nn.functional as F @@ -23,14 +23,14 @@ class PatchcoreModel(DynamicBufferModule, nn.Module): def __init__( self, - input_size: Tuple[int, int], - layers: List[str], + input_size: tuple[int, int], + layers: list[str], backbone: str = "wide_resnet50_2", pre_trained: bool = True, num_neighbors: int = 9, ) -> None: super().__init__() - self.tiler: Optional[Tiler] = None + self.tiler: Tiler | None = None self.backbone = backbone self.layers = layers @@ -44,7 +44,7 @@ def __init__( self.register_buffer("memory_bank", Tensor()) self.memory_bank: Tensor - def forward(self, input_tensor: Tensor) -> Union[Tensor, Tuple[Tensor, Tensor]]: + def forward(self, input_tensor: Tensor) -> Tensor | tuple[Tensor, Tensor]: """Return Embedding during training, or a tuple of anomaly map and anomaly score during testing. Steps performed: @@ -56,7 +56,7 @@ def forward(self, input_tensor: Tensor) -> Union[Tensor, Tuple[Tensor, Tensor]]: input_tensor (Tensor): Input tensor Returns: - Union[Tensor, Tuple[Tensor, Tensor]]: Embedding for training, + Tensor | tuple[Tensor, Tensor]: Embedding for training, anomaly map and anomaly score for testing. """ if self.tiler: @@ -93,12 +93,12 @@ def forward(self, input_tensor: Tensor) -> Union[Tensor, Tuple[Tensor, Tensor]]: return output - def generate_embedding(self, features: Dict[str, Tensor]) -> Tensor: + def generate_embedding(self, features: dict[str, Tensor]) -> Tensor: """Generate embedding from hierarchical feature map. Args: features: Hierarchical feature map from a CNN (ResNet18 or WideResnet) - features: Dict[str:Tensor]: + features: dict[str:Tensor]: Returns: Embedding vector @@ -142,7 +142,7 @@ def subsample_embedding(self, embedding: Tensor, sampling_ratio: float) -> None: coreset = sampler.sample_coreset() self.memory_bank = coreset - def nearest_neighbors(self, embedding: Tensor, n_neighbors: int) -> Tuple[Tensor, Tensor]: + def nearest_neighbors(self, embedding: Tensor, n_neighbors: int) -> tuple[Tensor, Tensor]: """Nearest Neighbours using brute force method and euclidean norm. Args: From 71f8f999b9e4af02a229109c24134800a830f734 Mon Sep 17 00:00:00 2001 From: Samet Date: Tue, 17 Jan 2023 08:12:34 -0700 Subject: [PATCH 66/85] Convert to new annotation format - reverse distillation model --- .../reverse_distillation/anomaly_map.py | 12 +++--- .../components/bottleneck.py | 18 +++++---- .../components/de_resnet.py | 38 ++++++++++--------- .../reverse_distillation/lightning_model.py | 26 ++++++------- anomalib/models/reverse_distillation/loss.py | 12 +++--- .../reverse_distillation/torch_model.py | 16 ++++---- 6 files changed, 63 insertions(+), 59 deletions(-) diff --git a/anomalib/models/reverse_distillation/anomaly_map.py b/anomalib/models/reverse_distillation/anomaly_map.py index 11cd0b4cb9..52e2778feb 100644 --- a/anomalib/models/reverse_distillation/anomaly_map.py +++ b/anomalib/models/reverse_distillation/anomaly_map.py @@ -9,7 +9,7 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -from typing import List, Tuple, Union +from __future__ import annotations import torch import torch.nn.functional as F @@ -22,7 +22,7 @@ class AnomalyMapGenerator(nn.Module): """Generate Anomaly Heatmap. Args: - image_size (Union[ListConfig, Tuple]): Size of original image used for upscaling the anomaly map. + image_size (ListConfig, tuple): Size of original image used for upscaling the anomaly map. sigma (int): Standard deviation of the gaussian kernel used to smooth anomaly map. mode (str, optional): Operation used to generate anomaly map. Options are `add` and `multiply`. Defaults to "multiply". @@ -31,7 +31,7 @@ class AnomalyMapGenerator(nn.Module): ValueError: In case modes other than multiply and add are passed. """ - def __init__(self, image_size: Union[ListConfig, Tuple], sigma: int = 4, mode: str = "multiply") -> None: + def __init__(self, image_size: ListConfig | tuple, sigma: int = 4, mode: str = "multiply") -> None: super().__init__() self.image_size = image_size if isinstance(image_size, tuple) else tuple(image_size) self.sigma = sigma @@ -41,12 +41,12 @@ def __init__(self, image_size: Union[ListConfig, Tuple], sigma: int = 4, mode: s raise ValueError(f"Found mode {mode}. Only multiply and add are supported.") self.mode = mode - def forward(self, student_features: List[Tensor], teacher_features: List[Tensor]) -> Tensor: + def forward(self, student_features: list[Tensor], teacher_features: list[Tensor]) -> Tensor: """Computes anomaly map given encoder and decoder features. Args: - student_features (List[Tensor]): List of encoder features - teacher_features (List[Tensor]): List of decoder features + student_features (list[Tensor]): List of encoder features + teacher_features (list[Tensor]): List of decoder features Returns: Tensor: Anomaly maps of length batch. diff --git a/anomalib/models/reverse_distillation/components/bottleneck.py b/anomalib/models/reverse_distillation/components/bottleneck.py index 8000473e03..7b29c9363f 100644 --- a/anomalib/models/reverse_distillation/components/bottleneck.py +++ b/anomalib/models/reverse_distillation/components/bottleneck.py @@ -10,7 +10,9 @@ # SPDX-License-Identifier: Apache-2.0 -from typing import Callable, List, Optional, Type, Union +from __future__ import annotations + +from typing import Callable import torch from torch import Tensor, nn @@ -45,17 +47,17 @@ class OCBE(nn.Module): groups (int, optional): Number of blocked connections from input channels to output channels. Defaults to 1. width_per_group (int, optional): Number of layers in each intermediate convolution layer. Defaults to 64. - norm_layer (Optional[Callable[..., nn.Module]], optional): Batch norm layer to use. Defaults to None. + norm_layer (Callable[..., nn.Module] | None, optional): Batch norm layer to use. Defaults to None. """ def __init__( self, - block: Type[Union[Bottleneck, BasicBlock]], + block: type[Bottleneck | BasicBlock], layers: int, groups: int = 1, width_per_group: int = 64, - norm_layer: Optional[Callable[..., nn.Module]] = None, - ): + norm_layer: Callable[..., nn.Module] | None = None, + ) -> None: super().__init__() if norm_layer is None: norm_layer = nn.BatchNorm2d @@ -88,7 +90,7 @@ def __init__( def _make_layer( self, - block: Type[Union[Bottleneck, BasicBlock]], + block: type[Bottleneck | BasicBlock], planes: int, blocks: int, stride: int = 1, @@ -134,11 +136,11 @@ def _make_layer( return nn.Sequential(*layers) - def forward(self, features: List[Tensor]) -> Tensor: + def forward(self, features: list[Tensor]) -> Tensor: """Forward-pass of Bottleneck layer. Args: - features (List[Tensor]): List of features extracted from the encoder. + features (list[Tensor]): List of features extracted from the encoder. Returns: Tensor: Output of the bottleneck layer diff --git a/anomalib/models/reverse_distillation/components/de_resnet.py b/anomalib/models/reverse_distillation/components/de_resnet.py index 6d0072a766..9baa64e6e8 100644 --- a/anomalib/models/reverse_distillation/components/de_resnet.py +++ b/anomalib/models/reverse_distillation/components/de_resnet.py @@ -9,7 +9,9 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -from typing import Any, Callable, List, Optional, Type, Union +from __future__ import annotations + +from typing import Any, Callable from torch import Tensor, nn from torchvision.models.resnet import conv1x1, conv3x3 @@ -22,12 +24,12 @@ class DecoderBasicBlock(nn.Module): inplanes (int): Number of input channels. planes (int): Number of output channels. stride (int, optional): Stride for convolution and de-convolution layers. Defaults to 1. - upsample (Optional[nn.Module], optional): Module used for upsampling output. Defaults to None. + upsample (nn.Module | None, optional): Module used for upsampling output. Defaults to None. groups (int, optional): Number of blocked connections from input channels to output channels. Defaults to 1. base_width (int, optional): Number of layers in each intermediate convolution layer. Defaults to 64. dilation (int, optional): Spacing between kernel elements. Defaults to 1. - norm_layer (Optional[Callable[..., nn.Module]], optional): Batch norm layer to use.Defaults to None. + norm_layer (Callable[..., nn.Module] | None, optional): Batch norm layer to use.Defaults to None. Raises: ValueError: If groups are not equal to 1 and base width is not 64. @@ -41,11 +43,11 @@ def __init__( inplanes: int, planes: int, stride: int = 1, - upsample: Optional[nn.Module] = None, + upsample: nn.Module | None = None, groups: int = 1, base_width: int = 64, dilation: int = 1, - norm_layer: Optional[Callable[..., nn.Module]] = None, + norm_layer: Callable[..., nn.Module] | None = None, ) -> None: super().__init__() if norm_layer is None: @@ -95,12 +97,12 @@ class DecoderBottleneck(nn.Module): inplanes (int): Number of input channels. planes (int): Number of output channels. stride (int, optional): Stride for convolution and de-convolution layers. Defaults to 1. - upsample (Optional[nn.Module], optional): Module used for upsampling output. Defaults to None. + upsample (nn.Module | None, optional): Module used for upsampling output. Defaults to None. groups (int, optional): Number of blocked connections from input channels to output channels. Defaults to 1. base_width (int, optional): Number of layers in each intermediate convolution layer. Defaults to 64. dilation (int, optional): Spacing between kernel elements. Defaults to 1. - norm_layer (Optional[Callable[..., nn.Module]], optional): Batch norm layer to use.Defaults to None. + norm_layer (Callable[..., nn.Module] | None, optional): Batch norm layer to use.Defaults to None. """ expansion: int = 4 @@ -110,11 +112,11 @@ def __init__( inplanes: int, planes: int, stride: int = 1, - upsample: Optional[nn.Module] = None, + upsample: nn.Module | None = None, groups: int = 1, base_width: int = 64, dilation: int = 1, - norm_layer: Optional[Callable[..., nn.Module]] = None, + norm_layer: Callable[..., nn.Module] | None = None, ) -> None: super().__init__() if norm_layer is None: @@ -164,24 +166,24 @@ class ResNet(nn.Module): """ResNet model for decoder. Args: - block (Type[Union[DecoderBasicBlock, DecoderBottleneck]]): Type of block to use in a layer. - layers (List[int]): List to specify number for blocks per layer. + block (Type[DecoderBasicBlock | DecoderBottleneck]): Type of block to use in a layer. + layers (list[int]): List to specify number for blocks per layer. zero_init_residual (bool, optional): If true, initializes the last batch norm in each layer to zero. Defaults to False. groups (int, optional): Number of blocked connections per layer from input channels to output channels. Defaults to 1. width_per_group (int, optional): Number of layers in each intermediate convolution layer.. Defaults to 64. - norm_layer (Optional[Callable[..., nn.Module]], optional): Batch norm layer to use. Defaults to None. + norm_layer (Callable[..., nn.Module] | None, optional): Batch norm layer to use. Defaults to None. """ def __init__( self, - block: Type[Union[DecoderBasicBlock, DecoderBottleneck]], - layers: List[int], + block: type[DecoderBasicBlock | DecoderBottleneck], + layers: list[int], zero_init_residual: bool = False, groups: int = 1, width_per_group: int = 64, - norm_layer: Optional[Callable[..., nn.Module]] = None, + norm_layer: Callable[..., nn.Module] | None = None, ) -> None: super().__init__() if norm_layer is None: @@ -215,7 +217,7 @@ def __init__( def _make_layer( self, - block: Type[Union[DecoderBasicBlock, DecoderBottleneck]], + block: type[DecoderBasicBlock | DecoderBottleneck], planes: int, blocks: int, stride: int = 1, @@ -256,7 +258,7 @@ def _make_layer( return nn.Sequential(*layers) - def forward(self, batch: Tensor) -> List[Tensor]: + def forward(self, batch: Tensor) -> list[Tensor]: """Forward pass for Decoder ResNet. Returns list of features.""" feature_a = self.layer1(batch) # 512*8*8->256*16*16 feature_b = self.layer2(feature_a) # 256*16*16->128*32*32 @@ -265,7 +267,7 @@ def forward(self, batch: Tensor) -> List[Tensor]: return [feature_c, feature_b, feature_a] -def _resnet(block: Type[Union[DecoderBasicBlock, DecoderBottleneck]], layers: List[int], **kwargs: Any) -> ResNet: +def _resnet(block: type[DecoderBasicBlock | DecoderBottleneck], layers: list[int], **kwargs: Any) -> ResNet: model = ResNet(block, layers, **kwargs) return model diff --git a/anomalib/models/reverse_distillation/lightning_model.py b/anomalib/models/reverse_distillation/lightning_model.py index 4546781bb2..732c27185b 100644 --- a/anomalib/models/reverse_distillation/lightning_model.py +++ b/anomalib/models/reverse_distillation/lightning_model.py @@ -6,7 +6,7 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -from typing import Dict, List, Tuple, Union +from __future__ import annotations from omegaconf import DictConfig, ListConfig from pytorch_lightning.callbacks import EarlyStopping @@ -25,17 +25,17 @@ class ReverseDistillation(AnomalyModule): """PL Lightning Module for Reverse Distillation Algorithm. Args: - input_size (Tuple[int, int]): Size of model input + input_size (tuple[int, int]): Size of model input backbone (str): Backbone of CNN network - layers (List[str]): Layers to extract features from the backbone CNN + layers (list[str]): Layers to extract features from the backbone CNN pre_trained (bool, optional): Boolean to check whether to use a pre_trained backbone. """ def __init__( self, - input_size: Tuple[int, int], + input_size: tuple[int, int], backbone: str, - layers: List[str], + layers: list[str], anomaly_map_mode: str, lr: float, beta1: float, @@ -75,7 +75,7 @@ def configure_optimizers(self) -> optim.Adam: betas=(self.beta1, self.beta2), ) - def training_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> STEP_OUTPUT: + def training_step(self, batch: dict[str, str | Tensor], *args, **kwargs) -> STEP_OUTPUT: """Training Step of Reverse Distillation Model. Features are extracted from three layers of the Encoder model. These are passed to the bottleneck layer @@ -83,7 +83,7 @@ def training_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) - encoder and decoder features. Args: - batch (batch: Dict[str, Union[str, Tensor]]): Input batch + batch (batch: dict[str, str | Tensor]): Input batch Returns: Feature Map @@ -92,14 +92,14 @@ def training_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) - self.log("train_loss", loss.item(), on_epoch=True, prog_bar=True, logger=True) return {"loss": loss} - def validation_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> STEP_OUTPUT: + def validation_step(self, batch: dict[str, str | Tensor], *args, **kwargs) -> STEP_OUTPUT: """Validation Step of Reverse Distillation Model. Similar to the training step, encoder/decoder features are extracted from the CNN for each batch, and anomaly map is computed. Args: - batch (Dict[str, Union[str, Tensor]]): Input batch + batch (dict[str, str | Tensor]): Input batch Returns: Dictionary containing images, anomaly maps, true labels and masks. @@ -113,10 +113,10 @@ class ReverseDistillationLightning(ReverseDistillation): """PL Lightning Module for Reverse Distillation Algorithm. Args: - hparams(Union[DictConfig, ListConfig]): Model parameters + hparams(DictConfig | ListConfig): Model parameters """ - def __init__(self, hparams: Union[DictConfig, ListConfig]) -> None: + def __init__(self, hparams: DictConfig | ListConfig) -> None: super().__init__( input_size=hparams.model.input_size, backbone=hparams.model.backbone, @@ -127,10 +127,10 @@ def __init__(self, hparams: Union[DictConfig, ListConfig]) -> None: beta1=hparams.model.beta1, beta2=hparams.model.beta2, ) - self.hparams: Union[DictConfig, ListConfig] # type: ignore + self.hparams: DictConfig | ListConfig # type: ignore self.save_hyperparameters(hparams) - def configure_callbacks(self) -> List[EarlyStopping]: + def configure_callbacks(self) -> list[EarlyStopping]: """Configure model-specific callbacks. Note: diff --git a/anomalib/models/reverse_distillation/loss.py b/anomalib/models/reverse_distillation/loss.py index 3fd6d270a1..e58955f175 100644 --- a/anomalib/models/reverse_distillation/loss.py +++ b/anomalib/models/reverse_distillation/loss.py @@ -3,21 +3,21 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -from typing import List +from __future__ import annotations import torch -from torch import Tensor +from torch import Tensor, nn -class ReverseDistillationLoss: +class ReverseDistillationLoss(nn.Module): """Loss function for Reverse Distillation.""" - def __call__(self, encoder_features: List[Tensor], decoder_features: List[Tensor]) -> Tensor: + def forward(self, encoder_features: list[Tensor], decoder_features: list[Tensor]) -> Tensor: """Computes cosine similarity loss based on features from encoder and decoder. Args: - encoder_features (List[Tensor]): List of features extracted from encoder - decoder_features (List[Tensor]): List of features extracted from decoder + encoder_features (list[Tensor]): List of features extracted from encoder + decoder_features (list[Tensor]): List of features extracted from decoder Returns: Tensor: Cosine similarity loss diff --git a/anomalib/models/reverse_distillation/torch_model.py b/anomalib/models/reverse_distillation/torch_model.py index 1700c7ada3..086120390a 100644 --- a/anomalib/models/reverse_distillation/torch_model.py +++ b/anomalib/models/reverse_distillation/torch_model.py @@ -3,7 +3,7 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -from typing import List, Optional, Tuple, Union +from __future__ import annotations from torch import Tensor, nn @@ -21,8 +21,8 @@ class ReverseDistillationModel(nn.Module): Args: backbone (str): Name of the backbone used for encoder and decoder - input_size (Tuple[int, int]): Size of input image - layers (List[str]): Name of layers from which the features are extracted. + input_size (tuple[int, int]): Size of input image + layers (list[str]): Name of layers from which the features are extracted. anomaly_map_mode (str): Mode used to generate anomaly map. Options are between ``multiply`` and ``add``. pre_trained (bool, optional): Boolean to check whether to use a pre_trained backbone. """ @@ -30,13 +30,13 @@ class ReverseDistillationModel(nn.Module): def __init__( self, backbone: str, - input_size: Tuple[int, int], - layers: List[str], + input_size: tuple[int, int], + layers: list[str], anomaly_map_mode: str, pre_trained: bool = True, ) -> None: super().__init__() - self.tiler: Optional[Tiler] = None + self.tiler: Tiler | None = None encoder_backbone = backbone self.encoder = FeatureExtractor(backbone=encoder_backbone, pre_trained=pre_trained, layers=layers) @@ -50,7 +50,7 @@ def __init__( self.anomaly_map_generator = AnomalyMapGenerator(image_size=tuple(image_size), mode=anomaly_map_mode) - def forward(self, images: Tensor) -> Union[Tensor, Tuple[List[Tensor], List[Tensor]]]: + def forward(self, images: Tensor) -> Tensor | list[Tensor] | tuple[list[Tensor]]: """Forward-pass images to the network. During the training mode the model extracts features from encoder and decoder networks. @@ -60,7 +60,7 @@ def forward(self, images: Tensor) -> Union[Tensor, Tuple[List[Tensor], List[Tens images (Tensor): Batch of images Returns: - Union[Tensor, Tuple[List[Tensor],List[Tensor]]]: Encoder and decoder features in training mode, + Tensor | list[Tensor] | tuple[list[Tensor]]: Encoder and decoder features in training mode, else anomaly maps. """ self.encoder.eval() From fe440981f1a57ae232fe8650857f418518a10318 Mon Sep 17 00:00:00 2001 From: Samet Date: Tue, 17 Jan 2023 08:15:02 -0700 Subject: [PATCH 67/85] Convert to new annotation format - rkde model --- anomalib/models/rkde/lightning_model.py | 19 ++++++++++--------- anomalib/models/rkde/region_extractor.py | 9 +++++---- anomalib/models/rkde/torch_model.py | 7 ++++--- 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/anomalib/models/rkde/lightning_model.py b/anomalib/models/rkde/lightning_model.py index 4ecbb58623..f0a721f4de 100644 --- a/anomalib/models/rkde/lightning_model.py +++ b/anomalib/models/rkde/lightning_model.py @@ -3,8 +3,9 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import logging -from typing import Dict, List, Union import torch from omegaconf import DictConfig, ListConfig @@ -61,18 +62,18 @@ def __init__( feature_scaling_method=feature_scaling_method, max_training_points=max_training_points, ) - self.embeddings: List[Tensor] = [] + self.embeddings: list[Tensor] = [] @staticmethod def configure_optimizers() -> None: """RKDE doesn't require optimization, therefore returns no optimizers.""" return None - def training_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> None: + def training_step(self, batch: dict[str, str | Tensor], *args, **kwargs) -> None: """Training Step of RKDE. For each batch, features are extracted from the CNN. Args: - batch (Dict[str, Union[str, Tensor]]): Batch containing image filename, image, label and mask + batch (dict[str, str | Tensor]): Batch containing image filename, image, label and mask Returns: Deep CNN features. @@ -87,13 +88,13 @@ def on_validation_start(self) -> None: logger.info("Fitting a KDE model to the embedding collected from the training set.") self.model.fit(embeddings) - def validation_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> STEP_OUTPUT: + def validation_step(self, batch: dict[str, str | Tensor], *args, **kwargs) -> STEP_OUTPUT: """Validation Step of RKde. Similar to the training step, features are extracted from the CNN for each batch. Args: - batch (Dict[str, Union[str, Tensor]]): Batch containing image filename, image, label and mask + batch (dict[str, str | Tensor]): Batch containing image filename, image, label and mask Returns: Dictionary containing probability, prediction and ground truth values. @@ -116,10 +117,10 @@ class RkdeLightning(Rkde): """Rkde: Deep Feature Kernel Density Estimation. Args: - hparams (Union[DictConfig, ListConfig]): Model params + hparams (DictConfig | ListConfig): Model params """ - def __init__(self, hparams: Union[DictConfig, ListConfig]) -> None: + def __init__(self, hparams: DictConfig | ListConfig) -> None: super().__init__( roi_stage=RoiStage(hparams.model.roi_stage), roi_score_threshold=hparams.model.roi_score_threshold, @@ -130,5 +131,5 @@ def __init__(self, hparams: Union[DictConfig, ListConfig]) -> None: feature_scaling_method=FeatureScalingMethod(hparams.model.feature_scaling_method), max_training_points=hparams.model.max_training_points, ) - self.hparams: Union[DictConfig, ListConfig] # type: ignore + self.hparams: DictConfig | ListConfig # type: ignore self.save_hyperparameters(hparams) diff --git a/anomalib/models/rkde/region_extractor.py b/anomalib/models/rkde/region_extractor.py index ab37377dc4..2a61695678 100644 --- a/anomalib/models/rkde/region_extractor.py +++ b/anomalib/models/rkde/region_extractor.py @@ -6,8 +6,9 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + from enum import Enum -from typing import List import torch from torch import Tensor, nn @@ -106,7 +107,7 @@ def forward(self, batch: Tensor) -> Tensor: regions = torch.cat([indices.unsqueeze(1).to(batch.device), torch.cat(regions)], dim=1) return regions - def post_process_box_predictions(self, pred_boxes: Tensor, pred_scores: Tensor) -> List[Tensor]: + def post_process_box_predictions(self, pred_boxes: Tensor, pred_scores: Tensor) -> list[Tensor]: """Post-processes the box predictions. The post-processing consists of removing small boxes, applying nms, and @@ -117,10 +118,10 @@ def post_process_box_predictions(self, pred_boxes: Tensor, pred_scores: Tensor) pred_scores (Tensor): Tensor of shape () with a confidence score for each box prediction. Returns: - List[Tensor]: Post-processed box predictions of shape (N, 4). + list[Tensor]: Post-processed box predictions of shape (N, 4). """ - processed_boxes: List[Tensor] = [] + processed_boxes: list[Tensor] = [] for boxes, scores in zip(pred_boxes, pred_scores): # remove small boxes diff --git a/anomalib/models/rkde/torch_model.py b/anomalib/models/rkde/torch_model.py index a8a135456d..237c6d15c7 100644 --- a/anomalib/models/rkde/torch_model.py +++ b/anomalib/models/rkde/torch_model.py @@ -3,8 +3,9 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import logging -from typing import Tuple, Union import torch from torch import Tensor, nn @@ -77,14 +78,14 @@ def fit(self, embeddings: Tensor) -> bool: """ return self.classifier.fit(embeddings) - def forward(self, batch: Tensor) -> Union[Tensor, Tuple[Tensor, Tensor]]: + def forward(self, batch: Tensor) -> Tensor | tuple[Tensor, Tensor]: """Prediction by normality model. Args: input (Tensor): Input images. Returns: - Union[Tensor, Tuple[Tensor, Tensor]]: The extracted features (when in training mode), or the predicted rois + Tensor | tuple[Tensor, Tensor]: The extracted features (when in training mode), or the predicted rois and corresponding anomaly scores. """ self.region_extractor.eval() From 5ad22c4c42cb2bdf543e4021cdae94330beb8f99 Mon Sep 17 00:00:00 2001 From: Samet Date: Tue, 17 Jan 2023 08:19:02 -0700 Subject: [PATCH 68/85] Convert to new annotation format - stfpm model --- anomalib/models/stfpm/anomaly_map.py | 19 ++++++++--------- anomalib/models/stfpm/lightning_model.py | 26 ++++++++++++------------ anomalib/models/stfpm/loss.py | 10 ++++----- anomalib/models/stfpm/torch_model.py | 18 ++++++++-------- 4 files changed, 35 insertions(+), 38 deletions(-) diff --git a/anomalib/models/stfpm/anomaly_map.py b/anomalib/models/stfpm/anomaly_map.py index 53dd0fa32a..41b43f3663 100644 --- a/anomalib/models/stfpm/anomaly_map.py +++ b/anomalib/models/stfpm/anomaly_map.py @@ -3,7 +3,7 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -from typing import Dict, Tuple, Union +from __future__ import annotations import torch import torch.nn.functional as F @@ -14,10 +14,7 @@ class AnomalyMapGenerator(nn.Module): """Generate Anomaly Heatmap.""" - def __init__( - self, - image_size: Union[ListConfig, Tuple], - ): + def __init__(self, image_size: ListConfig | tuple) -> None: super().__init__() self.distance = torch.nn.PairwiseDistance(p=2, keepdim=True) self.image_size = image_size if isinstance(image_size, tuple) else tuple(image_size) @@ -40,13 +37,13 @@ def compute_layer_map(self, teacher_features: Tensor, student_features: Tensor) return layer_map def compute_anomaly_map( - self, teacher_features: Dict[str, Tensor], student_features: Dict[str, Tensor] + self, teacher_features: dict[str, Tensor], student_features: dict[str, Tensor] ) -> torch.Tensor: """Compute the overall anomaly map via element-wise production the interpolated anomaly maps. Args: - teacher_features (Dict[str, Tensor]): Teacher features - student_features (Dict[str, Tensor]): Student features + teacher_features (dict[str, Tensor]): Teacher features + student_features (dict[str, Tensor]): Student features Returns: Final anomaly map @@ -60,7 +57,7 @@ def compute_anomaly_map( return anomaly_map - def forward(self, **kwargs: Dict[str, Tensor]) -> torch.Tensor: + def forward(self, **kwargs: dict[str, Tensor]) -> torch.Tensor: """Returns anomaly map. Expects `teach_features` and `student_features` keywords to be passed explicitly. @@ -82,7 +79,7 @@ def forward(self, **kwargs: Dict[str, Tensor]) -> torch.Tensor: if not ("teacher_features" in kwargs and "student_features" in kwargs): raise ValueError(f"Expected keys `teacher_features` and `student_features. Found {kwargs.keys()}") - teacher_features: Dict[str, Tensor] = kwargs["teacher_features"] - student_features: Dict[str, Tensor] = kwargs["student_features"] + teacher_features: dict[str, Tensor] = kwargs["teacher_features"] + student_features: dict[str, Tensor] = kwargs["student_features"] return self.compute_anomaly_map(teacher_features, student_features) diff --git a/anomalib/models/stfpm/lightning_model.py b/anomalib/models/stfpm/lightning_model.py index 6bf4e97769..ad847987da 100644 --- a/anomalib/models/stfpm/lightning_model.py +++ b/anomalib/models/stfpm/lightning_model.py @@ -6,7 +6,7 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -from typing import Dict, List, Tuple, Union +from __future__ import annotations import torch from omegaconf import DictConfig, ListConfig @@ -27,16 +27,16 @@ class Stfpm(AnomalyModule): """PL Lightning Module for the STFPM algorithm. Args: - input_size (Tuple[int, int]): Size of the model input. + input_size (tuple[int, int]): Size of the model input. backbone (str): Backbone CNN network - layers (List[str]): Layers to extract features from the backbone CNN + layers (list[str]): Layers to extract features from the backbone CNN """ def __init__( self, - input_size: Tuple[int, int], + input_size: tuple[int, int], backbone: str, - layers: List[str], + layers: list[str], ) -> None: super().__init__() @@ -47,13 +47,13 @@ def __init__( ) self.loss = STFPMLoss() - def training_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> STEP_OUTPUT: + def training_step(self, batch: dict[str, str | Tensor], *args, **kwargs) -> STEP_OUTPUT: """Training Step of STFPM. For each batch, teacher and student and teacher features are extracted from the CNN. Args: - batch (Dict[str, Union[str, Tensor]]): Input batch + batch (dict[str, str | Tensor]): Input batch Returns: Loss value @@ -64,14 +64,14 @@ def training_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) - self.log("train_loss", loss.item(), on_epoch=True, prog_bar=True, logger=True) return {"loss": loss} - def validation_step(self, batch: Dict[str, Union[str, Tensor]], *args, **kwargs) -> STEP_OUTPUT: + def validation_step(self, batch: dict[str, str | Tensor], *args, **kwargs) -> STEP_OUTPUT: """Validation Step of STFPM. Similar to the training step, student/teacher features are extracted from the CNN for each batch, and anomaly map is computed. Args: - batch (Dict[str, Union[str, Tensor]]): Input batch + batch (dict[str, str | Tensor]): Input batch Returns: Dictionary containing images, anomaly maps, true labels and masks. @@ -86,19 +86,19 @@ class StfpmLightning(Stfpm): """PL Lightning Module for the STFPM algorithm. Args: - hparams (Union[DictConfig, ListConfig]): Model params + hparams (DictConfig | ListConfig): Model params """ - def __init__(self, hparams: Union[DictConfig, ListConfig]) -> None: + def __init__(self, hparams: DictConfig | ListConfig) -> None: super().__init__( input_size=hparams.model.input_size, backbone=hparams.model.backbone, layers=hparams.model.layers, ) - self.hparams: Union[DictConfig, ListConfig] # type: ignore + self.hparams: DictConfig | ListConfig # type: ignore self.save_hyperparameters(hparams) - def configure_callbacks(self) -> List[EarlyStopping]: + def configure_callbacks(self) -> list[EarlyStopping]: """Configure model-specific callbacks. Note: diff --git a/anomalib/models/stfpm/loss.py b/anomalib/models/stfpm/loss.py index ee130835a6..21172e694c 100644 --- a/anomalib/models/stfpm/loss.py +++ b/anomalib/models/stfpm/loss.py @@ -3,7 +3,7 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -from typing import Dict, List +from __future__ import annotations import torch import torch.nn.functional as F @@ -53,18 +53,18 @@ def compute_layer_loss(self, teacher_feats: Tensor, student_feats: Tensor) -> Te return layer_loss - def forward(self, teacher_features: Dict[str, Tensor], student_features: Dict[str, Tensor]) -> Tensor: + def forward(self, teacher_features: dict[str, Tensor], student_features: dict[str, Tensor]) -> Tensor: """Compute the overall loss via the weighted average of the layer losses computed by the cosine similarity. Args: - teacher_features (Dict[str, Tensor]): Teacher features - student_features (Dict[str, Tensor]): Student features + teacher_features (dict[str, Tensor]): Teacher features + student_features (dict[str, Tensor]): Student features Returns: Total loss, which is the weighted average of the layer losses. """ - layer_losses: List[Tensor] = [] + layer_losses: list[Tensor] = [] for layer in teacher_features.keys(): loss = self.compute_layer_loss(teacher_features[layer], student_features[layer]) layer_losses.append(loss) diff --git a/anomalib/models/stfpm/torch_model.py b/anomalib/models/stfpm/torch_model.py index b8428c12f3..dfe6cc15d2 100644 --- a/anomalib/models/stfpm/torch_model.py +++ b/anomalib/models/stfpm/torch_model.py @@ -3,7 +3,7 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -from typing import Dict, List, Optional, Tuple, Union +from __future__ import annotations from torch import Tensor, nn @@ -16,19 +16,19 @@ class STFPMModel(nn.Module): """STFPM: Student-Teacher Feature Pyramid Matching for Unsupervised Anomaly Detection. Args: - layers (List[str]): Layers used for feature extraction - input_size (Tuple[int, int]): Input size for the model. + layers (list[str]): Layers used for feature extraction + input_size (tuple[int, int]): Input size for the model. backbone (str, optional): Pre-trained model backbone. Defaults to "resnet18". """ def __init__( self, - layers: List[str], - input_size: Tuple[int, int], + layers: list[str], + input_size: tuple[int, int], backbone: str = "resnet18", ) -> None: super().__init__() - self.tiler: Optional[Tiler] = None + self.tiler: Tiler | None = None self.backbone = backbone self.teacher_model = FeatureExtractor(backbone=self.backbone, pre_trained=True, layers=layers) @@ -48,7 +48,7 @@ def __init__( image_size = input_size self.anomaly_map_generator = AnomalyMapGenerator(image_size=tuple(image_size)) - def forward(self, images: Tensor) -> Union[Tuple[Dict[str, Tensor], Dict[str, Tensor]], Tensor]: + def forward(self, images: Tensor) -> Tensor | dict[str, Tensor] | tuple[dict[str, Tensor]]: """Forward-pass images into the network. During the training mode the model extracts the features from the teacher and student networks. @@ -62,8 +62,8 @@ def forward(self, images: Tensor) -> Union[Tuple[Dict[str, Tensor], Dict[str, Te """ if self.tiler: images = self.tiler.tile(images) - teacher_features: Dict[str, Tensor] = self.teacher_model(images) - student_features: Dict[str, Tensor] = self.student_model(images) + teacher_features: dict[str, Tensor] = self.teacher_model(images) + student_features: dict[str, Tensor] = self.student_model(images) if self.training: output = teacher_features, student_features else: From e99399c270b5b9135eb6a2756aa97e26f8afe665 Mon Sep 17 00:00:00 2001 From: Samet Date: Tue, 17 Jan 2023 08:31:26 -0700 Subject: [PATCH 69/85] Convert to new annotation format - data utils --- anomalib/data/base/datamodule.py | 6 +- anomalib/data/base/dataset.py | 2 +- anomalib/data/utils/augmenter.py | 17 ++-- anomalib/data/utils/boxes.py | 24 ++--- anomalib/data/utils/download.py | 110 ++++++++++++----------- anomalib/data/utils/generators/perlin.py | 15 ++-- anomalib/data/utils/image.py | 33 +++---- anomalib/data/utils/split.py | 2 +- anomalib/data/utils/transform.py | 13 +-- anomalib/data/utils/video.py | 16 ++-- setup.py | 11 +-- 11 files changed, 129 insertions(+), 120 deletions(-) diff --git a/anomalib/data/base/datamodule.py b/anomalib/data/base/datamodule.py index 7c3a15eaad..f3dfbbf256 100644 --- a/anomalib/data/base/datamodule.py +++ b/anomalib/data/base/datamodule.py @@ -35,7 +35,7 @@ def collate_fn(batch: list) -> dict[str, Any]: batch (List): list of items in the batch where len(batch) is equal to the batch size. Returns: - Dict[str, Any]: Dictionary containing the collated batch information. + dict[str, Any]: Dictionary containing the collated batch information. """ elem = batch[0] # sample an element from the batch to check the type. out_dict = {} @@ -62,7 +62,7 @@ class AnomalibDataModule(LightningDataModule, ABC): val_split_mode (ValSplitMode): Determines how the validation split is obtained. Options: [none, same_as_test, from_test, synthetic] val_split_ratio (float): Fraction of the train or test images held our for validation. - seed (Optional[int], optional): Seed used during random subset splitting. + seed (int | None, optional): Seed used during random subset splitting. """ def __init__( @@ -96,7 +96,7 @@ def setup(self, stage: str | None = None) -> None: """Setup train, validation and test data. Args: - stage: Optional[str]: Train/Val/Test stages. (Default value = None) + stage: str | None: Train/Val/Test stages. (Default value = None) """ if not self.is_setup: self._setup(stage) diff --git a/anomalib/data/base/dataset.py b/anomalib/data/base/dataset.py index 5946ac229a..b0e3f70da9 100644 --- a/anomalib/data/base/dataset.py +++ b/anomalib/data/base/dataset.py @@ -109,7 +109,7 @@ def __getitem__(self, index: int) -> dict[str, str | Tensor]: index (int): Index to get the item. Returns: - Union[Dict[str, Tensor], Dict[str, Union[str, Tensor]]]: Dict of image tensor during training. + Union[dict[str, Tensor], dict[str, str | Tensor]]: Dict of image tensor during training. Otherwise, Dict containing image path, target path, image tensor, label and transformed bounding box. """ diff --git a/anomalib/data/utils/augmenter.py b/anomalib/data/utils/augmenter.py index ed687652aa..6d41acb98e 100644 --- a/anomalib/data/utils/augmenter.py +++ b/anomalib/data/utils/augmenter.py @@ -10,10 +10,11 @@ # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import glob import math import random -from typing import Optional, Tuple, Union import cv2 import imgaug.augmenters as iaa @@ -34,7 +35,7 @@ class Augmenter: """Class that generates noisy augmentations of input images. Args: - anomaly_source_path (Optional[str]): Path to a folder of images that will be used as source of the anomalous + anomaly_source_path (str | None): Path to a folder of images that will be used as source of the anomalous noise. If not specified, random noise will be used instead. p_anomalous (float): Probability that the anomalous perturbation will be applied to a given image. beta (float): Parameter that determines the opacity of the noise mask. @@ -42,9 +43,9 @@ class Augmenter: def __init__( self, - anomaly_source_path: Optional[str] = None, + anomaly_source_path: str | None = None, p_anomalous: float = 0.5, - beta: Union[float, Tuple[float, float]] = (0.2, 1.0), + beta: float | tuple[float, float] = (0.2, 1.0), ): self.p_anomalous = p_anomalous @@ -80,14 +81,14 @@ def rand_augmenter(self) -> iaa.Sequential: return aug def generate_perturbation( - self, height: int, width: int, anomaly_source_path: Optional[str] - ) -> Tuple[np.ndarray, np.ndarray]: + self, height: int, width: int, anomaly_source_path: str | None + ) -> tuple[np.ndarray, np.ndarray]: """Generate an image containing a random anomalous perturbation using a source image. Args: height (int): height of the generated image. width: (int): width of the generated image. - anomaly_source_path (Optional[str]): Path to an image file. If not provided, random noise will be used + anomaly_source_path (str | None): Path to an image file. If not provided, random noise will be used instead. Returns: @@ -126,7 +127,7 @@ def generate_perturbation( return perturbation, mask - def augment_batch(self, batch: Tensor) -> Tuple[Tensor, Tensor]: + def augment_batch(self, batch: Tensor) -> tuple[Tensor, Tensor]: """Generate anomalous augmentations for a batch of input images. Args: diff --git a/anomalib/data/utils/boxes.py b/anomalib/data/utils/boxes.py index 6ef9a42d82..f75a5b6a9c 100644 --- a/anomalib/data/utils/boxes.py +++ b/anomalib/data/utils/boxes.py @@ -3,7 +3,7 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -from typing import List, Optional, Tuple +from __future__ import annotations import torch from torch import Tensor @@ -11,18 +11,18 @@ from anomalib.utils.cv import connected_components_cpu, connected_components_gpu -def masks_to_boxes(masks: Tensor, anomaly_maps: Optional[Tensor] = None) -> Tuple[List[Tensor], List[Tensor]]: +def masks_to_boxes(masks: Tensor, anomaly_maps: Tensor | None = None) -> tuple[list[Tensor], list[Tensor]]: """Convert a batch of segmentation masks to bounding box coordinates. Args: masks (Tensor): Input tensor of shape (B, 1, H, W), (B, H, W) or (H, W) - anomaly_maps (Optional[Tensor], optional): Anomaly maps of shape (B, 1, H, W), (B, H, W) or (H, W) which are + anomaly_maps (Tensor | None, optional): Anomaly maps of shape (B, 1, H, W), (B, H, W) or (H, W) which are used to determine an anomaly score for the converted bounding boxes. Returns: - List[Tensor]: A list of length B where each element is a tensor of shape (N, 4) containing the bounding box + list[Tensor]: A list of length B where each element is a tensor of shape (N, 4) containing the bounding box coordinates of the objects in the masks in xyxy format. - List[Tensor]: A list of length B where each element is a tensor of length (N) containing an anomaly score for + list[Tensor]: A list of length B where each element is a tensor of length (N) containing an anomaly score for each of the converted boxes. """ height, width = masks.shape[-2:] @@ -53,13 +53,13 @@ def masks_to_boxes(masks: Tensor, anomaly_maps: Optional[Tensor] = None) -> Tupl return batch_boxes, batch_scores -def boxes_to_masks(boxes: List[Tensor], image_size: Tuple[int, int]) -> Tensor: +def boxes_to_masks(boxes: list[Tensor], image_size: tuple[int, int]) -> Tensor: """Convert bounding boxes to segmentations masks. Args: - boxes (List[Tensor]): A list of length B where each element is a tensor of shape (N, 4) containing the bounding + boxes (list[Tensor]): A list of length B where each element is a tensor of shape (N, 4) containing the bounding box coordinates of the regions of interest in xyxy format. - image_size (Tuple[int, int]): Image size of the output masks in (H, W) format. + image_size (tuple[int, int]): Image size of the output masks in (H, W) format. Returns: Tensor: Tensor of shape (B, H, W) in which each slice is a binary mask showing the pixels contained by a @@ -73,15 +73,15 @@ def boxes_to_masks(boxes: List[Tensor], image_size: Tuple[int, int]) -> Tensor: return masks -def boxes_to_anomaly_maps(boxes: Tensor, scores: Tensor, image_size: Tuple[int, int]) -> Tensor: +def boxes_to_anomaly_maps(boxes: Tensor, scores: Tensor, image_size: tuple[int, int]) -> Tensor: """Convert bounding box coordinates to anomaly heatmaps. Args: - boxes (List[Tensor]): A list of length B where each element is a tensor of shape (N, 4) containing the bounding + boxes (list[Tensor]): A list of length B where each element is a tensor of shape (N, 4) containing the bounding box coordinates of the regions of interest in xyxy format. - scores (List[Tensor]): A list of length B where each element is a 1D tensor of length N containing the anomaly + scores (list[Tensor]): A list of length B where each element is a 1D tensor of length N containing the anomaly scores for each region of interest. - image_size (Tuple[int, int]): Image size of the output masks in (H, W) format. + image_size (tuple[int, int]): Image size of the output masks in (H, W) format. Returns: Tensor: Tensor of shape (B, H, W). The pixel locations within each bounding box are collectively assigned the diff --git a/anomalib/data/utils/download.py b/anomalib/data/utils/download.py index bfe36ed5b8..0cfd7ce21b 100644 --- a/anomalib/data/utils/download.py +++ b/anomalib/data/utils/download.py @@ -3,13 +3,15 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import hashlib import io import logging import tarfile from dataclasses import dataclass from pathlib import Path -from typing import Dict, Iterable, Optional, Union +from typing import Iterable from urllib.request import urlretrieve from zipfile import ZipFile @@ -33,55 +35,55 @@ class DownloadProgressBar(tqdm): For information about the parameters in constructor, refer to `tqdm`'s documentation. Args: - iterable (Optional[Iterable]): Iterable to decorate with a progressbar. + iterable (Iterable | None): Iterable to decorate with a progressbar. Leave blank to manually manage the updates. - desc (Optional[str]): Prefix for the progressbar. - total (Optional[Union[int, float]]): The number of expected iterations. If unspecified, + desc (str | None): Prefix for the progressbar. + total (int | float | None): The number of expected iterations. If unspecified, len(iterable) is used if possible. If float("inf") or as a last resort, only basic progress statistics are displayed (no ETA, no progressbar). If `gui` is True and this parameter needs subsequent updating, specify an initial arbitrary large positive number, e.g. 9e9. - leave (Optional[bool]): upon termination of iteration. If `None`, will leave only if `position` is `0`. - file (Optional[Union[io.TextIOWrapper, io.StringIO]]): Specifies where to output the progress messages + leave (bool | None): upon termination of iteration. If `None`, will leave only if `position` is `0`. + file (io.TextIOWrapper | io.StringIO | None): Specifies where to output the progress messages (default: sys.stderr). Uses `file.write(str)` and `file.flush()` methods. For encoding, see `write_bytes`. - ncols (Optional[int]): The width of the entire output message. If specified, + ncols (int | None): The width of the entire output message. If specified, dynamically resizes the progressbar to stay within this bound. If unspecified, attempts to use environment width. The fallback is a meter width of 10 and no limit for the counter and statistics. If 0, will not print any meter (only stats). - mininterval (Optional[float]): Minimum progress display update interval [default: 0.1] seconds. - maxinterval (Optional[float]): Maximum progress display update interval [default: 10] seconds. + mininterval (float | None): Minimum progress display update interval [default: 0.1] seconds. + maxinterval (float | None): Maximum progress display update interval [default: 10] seconds. Automatically adjusts `miniters` to correspond to `mininterval` after long display update lag. Only works if `dynamic_miniters` or monitor thread is enabled. - miniters (Optional[Union[int, float]]): Minimum progress display update interval, in iterations. + miniters (int | float | None): Minimum progress display update interval, in iterations. If 0 and `dynamic_miniters`, will automatically adjust to equal `mininterval` (more CPU efficient, good for tight loops). If > 0, will skip display of specified number of iterations. Tweak this and `mininterval` to get very efficient loops. If your progress is erratic with both fast and slow iterations (network, skipping items, etc) you should set miniters=1. - use_ascii (Optional[Union[bool, str]]): If unspecified or False, use unicode (smooth blocks) to fill + use_ascii (str | bool | None): If unspecified or False, use unicode (smooth blocks) to fill the meter. The fallback is to use ASCII characters " 123456789#". - disable (Optional[bool]): Whether to disable the entire progressbar wrapper + disable (bool | None): Whether to disable the entire progressbar wrapper [default: False]. If set to None, disable on non-TTY. - unit (Optional[str]): String that will be used to define the unit of each iteration + unit (str | None): String that will be used to define the unit of each iteration [default: it]. - unit_scale (Union[bool, int, float]): If 1 or True, the number of iterations will be reduced/scaled + unit_scale (int | float | bool): If 1 or True, the number of iterations will be reduced/scaled automatically and a metric prefix following the International System of Units standard will be added (kilo, mega, etc.) [default: False]. If any other non-zero number, will scale `total` and `n`. - dynamic_ncols (Optional[bool]): If set, constantly alters `ncols` and `nrows` to the + dynamic_ncols (bool | None): If set, constantly alters `ncols` and `nrows` to the environment (allowing for window resizes) [default: False]. - smoothing (Optional[float]): Exponential moving average smoothing factor for speed estimates + smoothing (float | None): Exponential moving average smoothing factor for speed estimates (ignored in GUI mode). Ranges from 0 (average speed) to 1 (current/instantaneous speed) [default: 0.3]. - bar_format (Optional[str]): Specify a custom bar string formatting. May impact performance. + bar_format (str | None): Specify a custom bar string formatting. May impact performance. [default: '{l_bar}{bar}{r_bar}'], where l_bar='{desc}: {percentage:3.0f}%|' and r_bar='| {n_fmt}/{total_fmt} [{elapsed}<{remaining}, ' @@ -93,26 +95,26 @@ class DownloadProgressBar(tqdm): remaining, remaining_s, eta. Note that a trailing ": " is automatically removed after {desc} if the latter is empty. - initial (Optional[Union[int, float]]): The initial counter value. Useful when restarting a progress + initial (int | float | None): The initial counter value. Useful when restarting a progress bar [default: 0]. If using float, consider specifying `{n:.3f}` or similar in `bar_format`, or specifying `unit_scale`. - position (Optional[int]): Specify the line offset to print this bar (starting from 0) + position (int | None): Specify the line offset to print this bar (starting from 0) Automatic if unspecified. Useful to manage multiple bars at once (eg, from threads). - postfix (Optional[Dict]): Specify additional stats to display at the end of the bar. + postfix (dict | None): Specify additional stats to display at the end of the bar. Calls `set_postfix(**postfix)` if possible (dict). - unit_divisor (Optional[float]): [default: 1000], ignored unless `unit_scale` is True. - write_bytes (Optional[bool]): If (default: None) and `file` is unspecified, + unit_divisor (float | None): [default: 1000], ignored unless `unit_scale` is True. + write_bytes (bool | None): If (default: None) and `file` is unspecified, bytes will be written in Python 2. If `True` will also write bytes. In all other cases will default to unicode. - lock_args (Optional[tuple]): Passed to `refresh` for intermediate output + lock_args (tuple | None): Passed to `refresh` for intermediate output (initialisation, iterating, and updating). - nrows (Optional[int]): The screen height. If specified, hides nested bars + nrows (int | None): The screen height. If specified, hides nested bars outside this bound. If unspecified, attempts to use environment height. The fallback is 20. - colour (Optional[str]): Bar colour (e.g. 'green', '#00ff00'). - delay (Optional[float]): Don't display until [default: 0] seconds have elapsed. - gui (Optional[bool]): WARNING: internal parameter - do not use. + colour (str | None): Bar colour (e.g. 'green', '#00ff00'). + delay (float | None): Don't display until [default: 0] seconds have elapsed. + gui (bool | None): WARNING: internal parameter - do not use. Use tqdm.gui.tqdm(...) instead. If set, will attempt to use matplotlib animations for a graphical output [default: False]. @@ -124,32 +126,32 @@ class DownloadProgressBar(tqdm): def __init__( self, - iterable: Optional[Iterable] = None, - desc: Optional[str] = None, - total: Optional[Union[int, float]] = None, - leave: Optional[bool] = True, - file: Optional[Union[io.TextIOWrapper, io.StringIO]] = None, - ncols: Optional[int] = None, - mininterval: Optional[float] = 0.1, - maxinterval: Optional[float] = 10.0, - miniters: Optional[Union[int, float]] = None, - use_ascii: Optional[Union[bool, str]] = None, - disable: Optional[bool] = False, - unit: Optional[str] = "it", - unit_scale: Optional[Union[bool, int, float]] = False, - dynamic_ncols: Optional[bool] = False, - smoothing: Optional[float] = 0.3, - bar_format: Optional[str] = None, - initial: Optional[Union[int, float]] = 0, - position: Optional[int] = None, - postfix: Optional[Dict] = None, - unit_divisor: Optional[float] = 1000, - write_bytes: Optional[bool] = None, - lock_args: Optional[tuple] = None, - nrows: Optional[int] = None, - colour: Optional[str] = None, - delay: Optional[float] = 0, - gui: Optional[bool] = False, + iterable: Iterable | None = None, + desc: str | None = None, + total: int | float | None = None, + leave: bool | None = True, + file: io.TextIOWrapper | io.StringIO | None = None, + ncols: int | None = None, + mininterval: float | None = 0.1, + maxinterval: float | None = 10.0, + miniters: int | float | None = None, + use_ascii: bool | str | None = None, + disable: bool | None = False, + unit: str | None = "it", + unit_scale: bool | int | float | None = False, + dynamic_ncols: bool | None = False, + smoothing: float | None = 0.3, + bar_format: str | None = None, + initial: int | float | None = 0, + position: int | None = None, + postfix: dict | None = None, + unit_divisor: float | None = 1000, + write_bytes: bool | None = None, + lock_args: tuple | None = None, + nrows: int | None = None, + colour: str | None = None, + delay: float | None = 0, + gui: bool | None = False, **kwargs, ): super().__init__( @@ -181,7 +183,7 @@ def __init__( gui=gui, **kwargs, ) - self.total: Optional[Union[int, float]] + self.total: int | float | None def update_to(self, chunk_number: int = 1, max_chunk_size: int = 1, total_size=None) -> None: """Progress bar hook for tqdm. diff --git a/anomalib/data/utils/generators/perlin.py b/anomalib/data/utils/generators/perlin.py index 760222fa44..6b36ace1fd 100644 --- a/anomalib/data/utils/generators/perlin.py +++ b/anomalib/data/utils/generators/perlin.py @@ -11,8 +11,9 @@ # pylint: disable=invalid-name +from __future__ import annotations + import math -from typing import Tuple, Union import numpy as np import torch @@ -66,20 +67,20 @@ def f(t): def random_2d_perlin( - shape: Tuple, - res: Tuple[Union[int, Tensor], Union[int, Tensor]], + shape: tuple, + res: tuple[int | Tensor, int | Tensor], fade=lambda t: 6 * t**5 - 15 * t**4 + 10 * t**3, -) -> Union[np.ndarray, Tensor]: +) -> np.ndarray | Tensor: """Returns a random 2d perlin noise array. Args: - shape (Tuple): Shape of the 2d map. - res (Tuple[Union[int, Tensor]]): Tuple of scales for perlin noise for height and width dimension. + shape (tuple): Shape of the 2d map. + res (tuple[int | Tensor, int | Tensor]): Tuple of scales for perlin noise for height and width dimension. fade (_type_, optional): Function used for fading the resulting 2d map. Defaults to equation 6*t**5-15*t**4+10*t**3. Returns: - Union[np.ndarray, Tensor]: Random 2d-array/tensor generated using perlin noise. + np.ndarray | Tensor: Random 2d-array/tensor generated using perlin noise. """ if isinstance(res[0], int): result = _rand_perlin_2d_np(shape, res, fade) diff --git a/anomalib/data/utils/image.py b/anomalib/data/utils/image.py index d188033de7..1e976e7563 100644 --- a/anomalib/data/utils/image.py +++ b/anomalib/data/utils/image.py @@ -3,10 +3,11 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import math import warnings from pathlib import Path -from typing import List, Optional, Tuple, Union import cv2 import numpy as np @@ -15,17 +16,17 @@ from torchvision.datasets.folder import IMG_EXTENSIONS -def get_image_filenames(path: Union[str, Path]) -> List[Path]: +def get_image_filenames(path: str | Path) -> list[Path]: """Get image filenames. Args: - path (Union[str, Path]): Path to image or image-folder. + path (str | Path): Path to image or image-folder. Returns: - List[Path]: List of image filenames + list[Path]: List of image filenames """ - image_filenames: List[Path] + image_filenames: list[Path] if isinstance(path, str): path = Path(path) @@ -42,13 +43,13 @@ def get_image_filenames(path: Union[str, Path]) -> List[Path]: return image_filenames -def duplicate_filename(path: Union[str, Path]) -> Path: +def duplicate_filename(path: str | Path) -> Path: """Check and duplicate filename. This function checks the path and adds a suffix if it already exists on the file system. Args: - path (Union[str, Path]): Input Path + path (str | Path): Input Path Examples: >>> path = Path("datasets/MVTec/bottle/test/broken_large/000.png") @@ -76,7 +77,7 @@ def duplicate_filename(path: Union[str, Path]) -> Path: return duplicated_path -def generate_output_image_filename(input_path: Union[str, Path], output_path: Union[str, Path]) -> Path: +def generate_output_image_filename(input_path: str | Path, output_path: str | Path) -> Path: """Generate an output filename to save the inference image. This function generates an output filaname by checking the input and output filenames. Input path is @@ -89,8 +90,8 @@ def generate_output_image_filename(input_path: Union[str, Path], output_path: Un filenames of ``input_path`` to ``output_path``. Args: - input_path (Union[str, Path]): Path to the input image to infer. - output_path (Union[str, Path]): Path to output to save the predictions. + input_path (str | Path): Path to the input image to infer. + output_path (str | Path): Path to output to save the predictions. Could be a filename or a directory. Examples: @@ -141,11 +142,11 @@ def generate_output_image_filename(input_path: Union[str, Path], output_path: Un return file_path -def get_image_height_and_width(image_size: Union[int, Tuple[int, int]]) -> Tuple[int, int]: +def get_image_height_and_width(image_size: int | tuple[int, int]) -> tuple[int, int]: """Get image height and width from ``image_size`` variable. Args: - image_size (Optional[Union[int, Tuple[int, int]]], optional): Input image size. + image_size (int | tuple[int, int] | None, optional): Input image size. Raises: ValueError: Image size not None, int or tuple. @@ -164,22 +165,22 @@ def get_image_height_and_width(image_size: Union[int, Tuple[int, int]]) -> Tuple Traceback (most recent call last): File "", line 1, in File "", line 18, in get_image_height_and_width - ValueError: ``image_size`` could be either int or Tuple[int, int] + ValueError: ``image_size`` could be either int or tuple[int, int] Returns: - Tuple[Optional[int], Optional[int]]: A tuple containing image height and width values. + Tuple[int | None, int | None]: A tuple containing image height and width values. """ if isinstance(image_size, int): height_and_width = (image_size, image_size) elif isinstance(image_size, tuple): height_and_width = int(image_size[0]), int(image_size[1]) else: - raise ValueError("``image_size`` could be either int or Tuple[int, int]") + raise ValueError("``image_size`` could be either int or tuple[int, int]") return height_and_width -def read_image(path: Union[str, Path], image_size: Optional[Union[int, Tuple[int, int]]] = None) -> np.ndarray: +def read_image(path: str | Path, image_size: int | tuple[int, int] | None = None) -> np.ndarray: """Read image from disk in RGB format. Args: diff --git a/anomalib/data/utils/split.py b/anomalib/data/utils/split.py index 8f87a6d300..33e95a863d 100644 --- a/anomalib/data/utils/split.py +++ b/anomalib/data/utils/split.py @@ -79,7 +79,7 @@ def random_split( [1-split_ratio, split_ratio]. label_aware (bool): When True, the relative occurrence of the different class labels of the source dataset will be maintained in each of the subsets. - seed (Optional[int], optional): Seed that can be passed if results need to be reproducible + seed (int | None, optional): Seed that can be passed if results need to be reproducible """ if isinstance(split_ratio, float): diff --git a/anomalib/data/utils/transform.py b/anomalib/data/utils/transform.py index ebf548d9a1..cbfa3b372b 100644 --- a/anomalib/data/utils/transform.py +++ b/anomalib/data/utils/transform.py @@ -3,9 +3,10 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import logging from enum import Enum -from typing import Optional, Tuple, Union import albumentations as A from albumentations.pytorch import ToTensorV2 @@ -23,18 +24,18 @@ class InputNormalizationMethod(str, Enum): def get_transforms( - config: Optional[Union[str, A.Compose]] = None, - image_size: Optional[Union[int, Tuple[int, int]]] = None, - center_crop: Optional[Union[int, Tuple[int, int]]] = None, + config: str | A.Compose | None = None, + image_size: int | tuple[int, int] | None = None, + center_crop: int | tuple[int, int] | None = None, normalization: InputNormalizationMethod = InputNormalizationMethod.IMAGENET, to_tensor: bool = True, ) -> A.Compose: """Get transforms from config or image size. Args: - config (Optional[Union[str, A.Compose]], optional): Albumentations transforms. + config (str | A.Compose | None, optional): Albumentations transforms. Either config or albumentations ``Compose`` object. Defaults to None. - image_size (Optional[Union[int, Tuple]], optional): Image size to transform. Defaults to None. + image_size (int | tuple | None, optional): Image size to transform. Defaults to None. to_tensor (bool, optional): Boolean to convert the final transforms into Torch tensor. Defaults to True. Raises: diff --git a/anomalib/data/utils/video.py b/anomalib/data/utils/video.py index 7e8dfcf5e0..76c756f822 100644 --- a/anomalib/data/utils/video.py +++ b/anomalib/data/utils/video.py @@ -3,9 +3,11 @@ # Copyright (C) 2023 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import warnings from abc import ABC, abstractmethod -from typing import Any, Dict, List, Optional +from typing import Any from torch import Tensor from torchvision.datasets.video_utils import VideoClips @@ -19,14 +21,14 @@ class ClipsIndexer(VideoClips, ABC): of folders with single-frame images), the subclass should implement at least get_clip and _compute_frame_pts. Args: - video_paths (List[str]): List of video paths that make up the dataset. - mask_paths (List[str]): List of paths to the masks for each video in the dataset. + video_paths (list[str]): List of video paths that make up the dataset. + mask_paths (list[str]): List of paths to the masks for each video in the dataset. """ def __init__( self, - video_paths: List[str], - mask_paths: List[str], + video_paths: list[str], + mask_paths: list[str], clip_length_in_frames: int = 1, frames_between_clips: int = 1, ) -> None: @@ -42,11 +44,11 @@ def last_frame_idx(self, video_idx: int) -> int: return self.clips[video_idx][-1][-1].item() @abstractmethod - def get_mask(self, idx: int) -> Optional[Tensor]: + def get_mask(self, idx: int) -> Tensor | None: """Return the masks for the given index.""" raise NotImplementedError - def get_item(self, idx: int) -> Dict[str, Any]: + def get_item(self, idx: int) -> dict[str, Any]: """Return a dictionary containing the clip, mask, video path and frame indices.""" with warnings.catch_warnings(): # silence warning caused by bug in torchvision, see https://github.com/pytorch/vision/issues/5787 diff --git a/setup.py b/setup.py index a6e2f01e2c..1d93d3058a 100644 --- a/setup.py +++ b/setup.py @@ -3,10 +3,11 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + from importlib.util import module_from_spec, spec_from_file_location from pathlib import Path from types import ModuleType -from typing import List from setuptools import find_packages, setup @@ -49,13 +50,13 @@ def get_version() -> str: return version -def get_required_packages(requirement_files: List[str]) -> List[str]: +def get_required_packages(requirement_files: list[str]) -> list[str]: """Get packages from requirements.txt file. This function returns list of required packages from requirement files. Args: - requirement_files (List[str]): txt files that contains list of required + requirement_files (list[str]): txt files that contains list of required packages. Example: @@ -63,10 +64,10 @@ def get_required_packages(requirement_files: List[str]) -> List[str]: ['onnx>=1.8.1', 'networkx~=2.5', 'openvino-dev==2021.4.1', ...] Returns: - List[str]: List of required packages + list[str]: List of required packages """ - required_packages: List[str] = [] + required_packages: list[str] = [] for requirement_file in requirement_files: with open(f"requirements/{requirement_file}.txt", encoding="utf8") as file: From bb7efdb5fa7811104497c580b6f833d56fdac36e Mon Sep 17 00:00:00 2001 From: Samet Date: Tue, 17 Jan 2023 08:33:27 -0700 Subject: [PATCH 70/85] Convert to new annotation format - deploy utils --- anomalib/deploy/export.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/anomalib/deploy/export.py b/anomalib/deploy/export.py index d7c4fe83c5..558dcb1477 100644 --- a/anomalib/deploy/export.py +++ b/anomalib/deploy/export.py @@ -3,11 +3,12 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import json import subprocess # nosec from enum import Enum from pathlib import Path -from typing import Dict, List, Tuple, Union import numpy as np import torch @@ -24,17 +25,17 @@ class ExportMode(str, Enum): OPENVINO = "openvino" -def get_model_metadata(model: AnomalyModule) -> Dict[str, Tensor]: +def get_model_metadata(model: AnomalyModule) -> dict[str, Tensor]: """Get meta data related to normalization from model. Args: model (AnomalyModule): Anomaly model which contains metadata related to normalization. Returns: - Dict[str, Tensor]: metadata + dict[str, Tensor]: metadata """ meta_data = {} - cached_meta_data: Dict[str, Union[Number, Tensor]] = { + cached_meta_data: dict[str, Number | Tensor] = { "image_threshold": model.image_threshold.cpu().value.item(), "pixel_threshold": model.pixel_threshold.cpu().value.item(), } @@ -51,9 +52,9 @@ def get_model_metadata(model: AnomalyModule) -> Dict[str, Tensor]: def export( model: AnomalyModule, - input_size: Union[List[int], Tuple[int, int]], + input_size: list[int] | tuple[int, int], export_mode: ExportMode, - export_root: Union[str, Path], + export_root: str | Path, ) -> None: """Export the model to onnx format and (optionally) convert to OpenVINO IR if export mode is set to OpenVINO. @@ -61,8 +62,8 @@ def export( Args: model (AnomalyModule): Model to convert. - input_size (Union[List[int], Tuple[int, int]]): Image size used as the input for onnx converter. - export_root (Union[str, Path]): Path to exported ONNX/OpenVINO IR. + input_size (list[int] | tuple[int, int]): Image size used as the input for onnx converter. + export_root (str | Path): Path to exported ONNX/OpenVINO IR. export_mode (ExportMode): Mode to export the model. ONNX or OpenVINO. """ # Write metadata to json file. The file is written in the same directory as the target model. @@ -81,12 +82,12 @@ def export( _export_to_openvino(export_path, onnx_path) -def _export_to_onnx(model: AnomalyModule, input_size: Union[List[int], Tuple[int, int]], export_path: Path) -> Path: +def _export_to_onnx(model: AnomalyModule, input_size: list[int] | tuple[int, int], export_path: Path) -> Path: """Export model to onnx. Args: model (AnomalyModule): Model to export. - input_size (Union[List[int], Tuple[int, int]]): Image size used as the input for onnx converter. + input_size (list[int] | tuple[int, int]): Image size used as the input for onnx converter. export_path (Path): Path to the root folder of the exported model. Returns: @@ -105,11 +106,11 @@ def _export_to_onnx(model: AnomalyModule, input_size: Union[List[int], Tuple[int return onnx_path -def _export_to_openvino(export_path: Union[str, Path], onnx_path: Path) -> None: +def _export_to_openvino(export_path: str | Path, onnx_path: Path) -> None: """Convert onnx model to OpenVINO IR. Args: - export_path (Union[str, Path]): Path to the root folder of the exported model. + export_path (str | Path): Path to the root folder of the exported model. onnx_path (Path): Path to the exported onnx model. """ optimize_command = ["mo", "--input_model", str(onnx_path), "--output_dir", str(export_path)] From 7faf3a8905da977fa35e7c238012cf386ec00667 Mon Sep 17 00:00:00 2001 From: Samet Date: Tue, 17 Jan 2023 08:34:11 -0700 Subject: [PATCH 71/85] Convert to new annotation format - model utils --- anomalib/models/__init__.py | 9 +++++---- anomalib/models/cflow/utils.py | 18 ++++++++++-------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/anomalib/models/__init__.py b/anomalib/models/__init__.py index dee8120aaa..b881cf8cf4 100644 --- a/anomalib/models/__init__.py +++ b/anomalib/models/__init__.py @@ -3,10 +3,11 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import logging import os from importlib import import_module -from typing import List, Union from omegaconf import DictConfig, ListConfig from torch import load @@ -57,7 +58,7 @@ def _snake_to_pascal_case(model_name: str) -> str: return "".join([split.capitalize() for split in model_name.split("_")]) -def get_model(config: Union[DictConfig, ListConfig]) -> AnomalyModule: +def get_model(config: DictConfig | ListConfig) -> AnomalyModule: """Load model from the configuration file. Works only when the convention for model naming is followed. @@ -67,7 +68,7 @@ def get_model(config: Union[DictConfig, ListConfig]) -> AnomalyModule: `anomalib.models.stfpm.lightning_model.StfpmLightning` Args: - config (Union[DictConfig, ListConfig]): Config.yaml loaded using OmegaConf + config (DictConfig | ListConfig): Config.yaml loaded using OmegaConf Raises: ValueError: If unsupported model is passed @@ -77,7 +78,7 @@ def get_model(config: Union[DictConfig, ListConfig]) -> AnomalyModule: """ logger.info("Loading the model.") - model_list: List[str] = [ + model_list: list[str] = [ "cfa", "cflow", "csflow", diff --git a/anomalib/models/cflow/utils.py b/anomalib/models/cflow/utils.py index dad432ec46..326668ce08 100644 --- a/anomalib/models/cflow/utils.py +++ b/anomalib/models/cflow/utils.py @@ -3,6 +3,8 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import logging import math @@ -10,28 +12,28 @@ import torch from FrEIA.framework import SequenceINN from FrEIA.modules import AllInOneBlock -from torch import nn +from torch import Tensor, nn logger = logging.getLogger(__name__) -def get_logp(dim_feature_vector: int, p_u: torch.Tensor, logdet_j: torch.Tensor) -> torch.Tensor: +def get_logp(dim_feature_vector: int, p_u: Tensor, logdet_j: Tensor) -> Tensor: """Returns the log likelihood estimation. Args: dim_feature_vector (int): Dimensions of the condition vector - p_u (torch.Tensor): Random variable u - logdet_j (torch.Tensor): log of determinant of jacobian returned from the invertable decoder + p_u (Tensor): Random variable u + logdet_j (Tensor): log of determinant of jacobian returned from the invertable decoder Returns: - torch.Tensor: Log probability + Tensor: Log probability """ ln_sqrt_2pi = -np.log(np.sqrt(2 * np.pi)) # ln(sqrt(2*pi)) logp = dim_feature_vector * ln_sqrt_2pi - 0.5 * torch.sum(p_u**2, 1) + logdet_j return logp -def positional_encoding_2d(condition_vector: int, height: int, width: int) -> torch.Tensor: +def positional_encoding_2d(condition_vector: int, height: int, width: int) -> Tensor: """Creates embedding to store relative position of the feature vector using sine and cosine functions. Args: @@ -43,7 +45,7 @@ def positional_encoding_2d(condition_vector: int, height: int, width: int) -> to ValueError: Cannot generate encoding with conditional vector length not as multiple of 4 Returns: - torch.Tensor: condition_vector x HEIGHT x WIDTH position matrix + Tensor: condition_vector x HEIGHT x WIDTH position matrix """ if condition_vector % 4 != 0: raise ValueError(f"Cannot use sin/cos positional encoding with odd dimension (got dim={condition_vector})") @@ -68,7 +70,7 @@ def positional_encoding_2d(condition_vector: int, height: int, width: int) -> to return pos_encoding -def subnet_fc(dims_in: int, dims_out: int): +def subnet_fc(dims_in: int, dims_out: int) -> nn.Sequential: """Subnetwork which predicts the affine coefficients. Args: From 97770a3c903eaa637b04f64661e9cf8312bb63af Mon Sep 17 00:00:00 2001 From: Samet Date: Tue, 17 Jan 2023 08:52:00 -0700 Subject: [PATCH 72/85] Convert to new annotation format - post processing utils --- anomalib/post_processing/normalization/cdf.py | 18 ++++++------- .../post_processing/normalization/min_max.py | 12 ++++----- anomalib/post_processing/post_process.py | 19 +++++++------- anomalib/post_processing/visualizer.py | 26 ++++++++++--------- 4 files changed, 38 insertions(+), 37 deletions(-) diff --git a/anomalib/post_processing/normalization/cdf.py b/anomalib/post_processing/normalization/cdf.py index e76425169c..00a3602ccb 100644 --- a/anomalib/post_processing/normalization/cdf.py +++ b/anomalib/post_processing/normalization/cdf.py @@ -3,7 +3,7 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -from typing import Optional, Union +from __future__ import annotations import numpy as np import torch @@ -13,11 +13,11 @@ def standardize( - targets: Union[np.ndarray, Tensor], - mean: Union[np.ndarray, Tensor, float], - std: Union[np.ndarray, Tensor, float], - center_at: Optional[float] = None, -) -> Union[np.ndarray, Tensor]: + targets: np.ndarray | Tensor, + mean: float | np.ndarray | Tensor, + std: float | np.ndarray | Tensor, + center_at: float | None = None, +) -> np.ndarray | Tensor: """Standardize the targets to the z-domain.""" if isinstance(targets, np.ndarray): targets = np.log(targets) @@ -31,9 +31,7 @@ def standardize( return standardized -def normalize( - targets: Union[np.ndarray, Tensor], threshold: Union[np.ndarray, Tensor, float] -) -> Union[np.ndarray, Tensor]: +def normalize(targets: np.ndarray | Tensor, threshold: float | np.ndarray | Tensor) -> np.ndarray | Tensor: """Normalize the targets by using the cumulative density function.""" if isinstance(targets, Tensor): return normalize_torch(targets, threshold) @@ -52,6 +50,6 @@ def normalize_torch(targets: Tensor, threshold: Tensor) -> Tensor: return normalized -def normalize_numpy(targets: np.ndarray, threshold: Union[np.ndarray, float]) -> np.ndarray: +def normalize_numpy(targets: np.ndarray, threshold: float | np.ndarray) -> np.ndarray: """Normalize the targets by using the cumulative density function, Numpy version.""" return norm.cdf(targets - threshold) diff --git a/anomalib/post_processing/normalization/min_max.py b/anomalib/post_processing/normalization/min_max.py index 7fe751433e..bab3ce9108 100644 --- a/anomalib/post_processing/normalization/min_max.py +++ b/anomalib/post_processing/normalization/min_max.py @@ -3,7 +3,7 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -from typing import Union +from __future__ import annotations import numpy as np import torch @@ -11,11 +11,11 @@ def normalize( - targets: Union[np.ndarray, Tensor, np.float32], - threshold: Union[np.ndarray, Tensor, float], - min_val: Union[np.ndarray, Tensor, float], - max_val: Union[np.ndarray, Tensor, float], -) -> Union[np.ndarray, Tensor]: + targets: np.ndarray | np.float32 | Tensor, + threshold: float | np.ndarray | Tensor, + min_val: float | np.ndarray | Tensor, + max_val: float | np.ndarray | Tensor, +) -> np.ndarray | Tensor: """Apply min-max normalization and shift the values such that the threshold value is centered at 0.5.""" normalized = ((targets - threshold) / (max_val - min_val)) + 0.5 if isinstance(targets, (np.ndarray, np.float32, np.float64)): diff --git a/anomalib/post_processing/post_process.py b/anomalib/post_processing/post_process.py index c6e4577d8d..ba24f76de5 100644 --- a/anomalib/post_processing/post_process.py +++ b/anomalib/post_processing/post_process.py @@ -4,9 +4,10 @@ # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import math from enum import Enum -from typing import Optional, Tuple import cv2 import numpy as np @@ -23,8 +24,8 @@ class ThresholdMethod(str, Enum): def add_label( image: np.ndarray, label_name: str, - color: Tuple[int, int, int], - confidence: Optional[float] = None, + color: tuple[int, int, int], + confidence: float | None = None, font_scale: float = 5e-3, thickness_scale=1e-3, ) -> np.ndarray: @@ -33,8 +34,8 @@ def add_label( Args: image (np.ndarray): Input image. label_name (str): Name of the label that will be displayed on the image. - color (Tuple[int, int, int]): RGB values for background color of label. - confidence (Optional[float]): confidence score of the label. + color (tuple[int, int, int]): RGB values for background color of label. + confidence (float | None): confidence score of the label. font_scale (float): scale of the font size relative to image size. Increase for bigger font. thickness_scale (float): scale of the font thickness. Increase for thicker font. @@ -71,12 +72,12 @@ def add_label( return image -def add_normal_label(image: np.ndarray, confidence: Optional[float] = None) -> np.ndarray: +def add_normal_label(image: np.ndarray, confidence: float | None = None) -> np.ndarray: """Adds the normal label to the image.""" return add_label(image, "normal", (225, 252, 134), confidence) -def add_anomalous_label(image: np.ndarray, confidence: Optional[float] = None) -> np.ndarray: +def add_anomalous_label(image: np.ndarray, confidence: float | None = None) -> np.ndarray: """Adds the anomalous label to the image.""" return add_label(image, "anomalous", (255, 100, 100), confidence) @@ -153,13 +154,13 @@ def compute_mask(anomaly_map: np.ndarray, threshold: float, kernel_size: int = 4 return mask -def draw_boxes(image: np.ndarray, boxes: np.ndarray, color: Tuple[int, int, int]) -> np.ndarray: +def draw_boxes(image: np.ndarray, boxes: np.ndarray, color: tuple[int, int, int]) -> np.ndarray: """Draw bounding boxes on an image. Args: image (np.ndarray): Source image. boxes (np.nparray): 2D array of shape (N, 4) where each row contains the xyxy coordinates of a bounding box. - color (Tuple[int, int, int]): Color of the drawn boxes in RGB format. + color (tuple[int, int, int]): Color of the drawn boxes in RGB format. Returns: np.ndarray: Image showing the bounding boxes drawn on top of the source image. diff --git a/anomalib/post_processing/visualizer.py b/anomalib/post_processing/visualizer.py index ddc1e875cd..8ebe4a95de 100644 --- a/anomalib/post_processing/visualizer.py +++ b/anomalib/post_processing/visualizer.py @@ -3,9 +3,11 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + from dataclasses import dataclass, field from pathlib import Path -from typing import Dict, Iterator, List, Optional +from typing import Iterator import cv2 import matplotlib.figure @@ -30,12 +32,12 @@ class ImageResult: image: np.ndarray pred_score: float pred_label: str - anomaly_map: Optional[np.ndarray] = None - gt_mask: Optional[np.ndarray] = None - pred_mask: Optional[np.ndarray] = None - gt_boxes: Optional[np.ndarray] = None - pred_boxes: Optional[np.ndarray] = None - box_labels: Optional[np.ndarray] = None + anomaly_map: np.ndarray | None = None + gt_mask: np.ndarray | None = None + pred_mask: np.ndarray | None = None + gt_boxes: np.ndarray | None = None + pred_boxes: np.ndarray | None = None + box_labels: np.ndarray | None = None heat_map: np.ndarray = field(init=False) segmentations: np.ndarray = field(init=False) @@ -77,11 +79,11 @@ def __init__(self, mode: str, task: TaskType) -> None: ) self.task = task - def visualize_batch(self, batch: Dict) -> Iterator[np.ndarray]: + def visualize_batch(self, batch: dict) -> Iterator[np.ndarray]: """Generator that yields a visualization result for each item in the batch. Args: - batch (Dict): Dictionary containing the ground truth and predictions of a batch of images. + batch (dict): Dictionary containing the ground truth and predictions of a batch of images. Returns: Generator that yields a display-ready visualization for each image. @@ -234,17 +236,17 @@ class ImageGrid: """ def __init__(self) -> None: - self.images: List[Dict] = [] + self.images: list[dict] = [] self.figure: matplotlib.figure.Figure self.axis: np.ndarray - def add_image(self, image: np.ndarray, title: Optional[str] = None, color_map: Optional[str] = None) -> None: + def add_image(self, image: np.ndarray, title: str | None = None, color_map: str | None = None) -> None: """Add an image to the grid. Args: image (np.ndarray): Image which should be added to the figure. title (str): Image title shown on the plot. - color_map (Optional[str]): Name of matplotlib color map used to map scalar data to colours. Defaults to None. + color_map (str | None): Name of matplotlib color map used to map scalar data to colours. Defaults to None. """ image_data = dict(image=image, title=title, color_map=color_map) self.images.append(image_data) From b8be9bdbd148581aceea8098323f8072b0e6bddc Mon Sep 17 00:00:00 2001 From: Samet Date: Tue, 17 Jan 2023 09:01:29 -0700 Subject: [PATCH 73/85] Convert to new annotation format - pre processing utils --- anomalib/pre_processing/pre_process.py | 34 +++++++++++--------- anomalib/pre_processing/tiler.py | 30 +++++++++-------- anomalib/pre_processing/transforms/custom.py | 9 +++--- 3 files changed, 39 insertions(+), 34 deletions(-) diff --git a/anomalib/pre_processing/pre_process.py b/anomalib/pre_processing/pre_process.py index ff82bb5827..5a3cb56a9d 100644 --- a/anomalib/pre_processing/pre_process.py +++ b/anomalib/pre_processing/pre_process.py @@ -7,9 +7,11 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import logging import warnings -from typing import Any, Dict, Optional, Tuple, Union +from typing import Any import albumentations as A from albumentations.pytorch import ToTensorV2 @@ -17,11 +19,11 @@ logger = logging.getLogger(__name__) -def get_image_height_and_width(image_size: Optional[Union[int, Tuple]] = None) -> Tuple[Optional[int], Optional[int]]: +def get_image_height_and_width(image_size: int | tuple | None = None) -> tuple[int | None, int | None]: """Get image height and width from ``image_size`` variable. Args: - image_size (Optional[Union[int, Tuple[int, int]]], optional): Input image size. + image_size (int | tuple | None, optional): Input image size. Raises: ValueError: Image size not None, int or tuple. @@ -40,12 +42,12 @@ def get_image_height_and_width(image_size: Optional[Union[int, Tuple]] = None) - Traceback (most recent call last): File "", line 1, in File "", line 18, in get_image_height_and_width - ValueError: ``image_size`` could be either int or Tuple[int, int] + ValueError: ``image_size`` could be either int or tuple[int, int] Returns: - Tuple[Optional[int], Optional[int]]: A tuple containing image height and width values. + tuple[int | None, int | None]: A tuple containing image height and width values. """ - height_and_width: Tuple[Optional[int], Optional[int]] + height_and_width: tuple[int | None, int | None] if isinstance(image_size, int): height_and_width = (image_size, image_size) elif isinstance(image_size, tuple): @@ -53,22 +55,22 @@ def get_image_height_and_width(image_size: Optional[Union[int, Tuple]] = None) - elif image_size is None: height_and_width = (None, None) else: - raise ValueError("``image_size`` could be either int or Tuple[int, int]") + raise ValueError("``image_size`` could be either int or tuple[int, int]") return height_and_width def get_transforms( - config: Optional[Union[str, A.Compose]] = None, - image_size: Optional[Union[int, Tuple]] = None, + config: str | A.Compose | None = None, + image_size: int | tuple | None = None, to_tensor: bool = True, ) -> A.Compose: """Get transforms from config or image size. Args: - config (Optional[Union[str, A.Compose]], optional): Albumentations transforms. + config (str | A.Compose | None, optional): Albumentations transforms. Either config or albumentations ``Compose`` object. Defaults to None. - image_size (Optional[Union[int, Tuple]], optional): Image size to transform. Defaults to None. + image_size (int | tuple | None, optional): Image size to transform. Defaults to None. to_tensor (bool, optional): Boolean to convert the final transforms into Torch tensor. Defaults to True. Raises: @@ -168,10 +170,10 @@ class PreProcessor: For the inference it returns a numpy array. Args: - config (Optional[Union[str, A.Compose]], optional): Transformation configurations. + config (str | A.Compose | None, optional): Transformation configurations. When it is ``None``, ``PreProcessor`` only applies resizing. When it is ``str`` it loads the config via ``albumentations`` deserialisation methos . Defaults to None. - image_size (Optional[Union[int, Tuple[int, int]]], optional): When there is no config, + image_size (int | tuple | None, optional): When there is no config, ``image_size`` resizes the image. Defaults to None. to_tensor (bool, optional): Boolean to check whether the augmented image is transformed into a tensor or not. Defaults to True. @@ -213,8 +215,8 @@ class PreProcessor: def __init__( self, - config: Optional[Union[str, A.Compose]] = None, - image_size: Optional[Union[int, Tuple]] = None, + config: str | A.Compose | None = None, + image_size: int | tuple | None = None, to_tensor: bool = True, ) -> None: warnings.warn( @@ -229,6 +231,6 @@ def __init__( self.transforms = get_transforms(config, image_size, to_tensor) - def __call__(self, *args, **kwargs) -> Dict[str, Any]: + def __call__(self, *args, **kwargs) -> dict[str, Any]: """Return transformed arguments.""" return self.transforms(*args, **kwargs) diff --git a/anomalib/pre_processing/tiler.py b/anomalib/pre_processing/tiler.py index 08f4687f40..ceb69f2f9c 100644 --- a/anomalib/pre_processing/tiler.py +++ b/anomalib/pre_processing/tiler.py @@ -3,9 +3,11 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + from itertools import product from math import ceil -from typing import Optional, Sequence, Tuple, Union +from typing import Sequence import torch import torchvision.transforms as T @@ -17,15 +19,15 @@ class StrideSizeError(Exception): """StrideSizeError to raise exception when stride size is greater than the tile size.""" -def compute_new_image_size(image_size: Tuple, tile_size: Tuple, stride: Tuple) -> Tuple: +def compute_new_image_size(image_size: tuple, tile_size: tuple, stride: tuple) -> tuple: """This function checks if image size is divisible by tile size and stride. If not divisible, it resizes the image size to make it divisible. Args: - image_size (Tuple): Original image size - tile_size (Tuple): Tile size - stride (Tuple): Stride + image_size (tuple): Original image size + tile_size (tuple): Tile size + stride (tuple): Stride Examples: >>> compute_new_image_size(image_size=(512, 512), tile_size=(256, 256), stride=(128, 128)) @@ -35,7 +37,7 @@ def compute_new_image_size(image_size: Tuple, tile_size: Tuple, stride: Tuple) - (555, 555) Returns: - Tuple: Updated image size that is divisible by tile size and stride. + tuple: Updated image size that is divisible by tile size and stride. """ def __compute_new_edge_size(edge_size: int, tile_size: int, stride: int) -> int: @@ -51,12 +53,12 @@ def __compute_new_edge_size(edge_size: int, tile_size: int, stride: int) -> int: return resized_h, resized_w -def upscale_image(image: Tensor, size: Tuple, mode: str = "padding") -> Tensor: +def upscale_image(image: Tensor, size: tuple, mode: str = "padding") -> Tensor: """Upscale image to the desired size via either padding or interpolation. Args: image (Tensor): Image - size (Tuple): Tuple to which image is upscaled. + size (tuple): tuple to which image is upscaled. mode (str, optional): Upscaling mode. Defaults to "padding". Examples: @@ -90,12 +92,12 @@ def upscale_image(image: Tensor, size: Tuple, mode: str = "padding") -> Tensor: return image -def downscale_image(image: Tensor, size: Tuple, mode: str = "padding") -> Tensor: +def downscale_image(image: Tensor, size: tuple, mode: str = "padding") -> Tensor: """Opposite of upscaling. This image downscales image to a desired size. Args: image (Tensor): Input image - size (Tuple): Size to which image is down scaled. + size (tuple): Size to which image is down scaled. mode (str, optional): Downscaling mode. Defaults to "padding". Examples: @@ -146,8 +148,8 @@ class Tiler: def __init__( self, - tile_size: Union[int, Sequence], - stride: Optional[Union[int, Sequence]] = None, + tile_size: int | Sequence, + stride: int | Sequence | None = None, remove_border_count: int = 0, mode: str = "padding", tile_count: int = 4, @@ -188,7 +190,7 @@ def __init__( self.num_patches_w: int @staticmethod - def __validate_size_type(parameter: Union[int, Sequence]) -> Tuple[int, ...]: + def __validate_size_type(parameter: int | Sequence) -> tuple[int, ...]: if isinstance(parameter, int): output = (parameter, parameter) elif isinstance(parameter, Sequence): @@ -330,7 +332,7 @@ def __fold(self, tiles: Tensor) -> Tensor: return img - def tile(self, image: Tensor, use_random_tiling: Optional[bool] = False) -> Tensor: + def tile(self, image: Tensor, use_random_tiling: bool | None = False) -> Tensor: """Tiles an input image to either overlapping, non-overlapping or random patches. Args: diff --git a/anomalib/pre_processing/transforms/custom.py b/anomalib/pre_processing/transforms/custom.py index da0f5b2558..083efc5572 100644 --- a/anomalib/pre_processing/transforms/custom.py +++ b/anomalib/pre_processing/transforms/custom.py @@ -3,8 +3,9 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import warnings -from typing import List, Optional, Tuple import numpy as np from torch import Tensor @@ -13,7 +14,7 @@ class Denormalize: """Denormalize Torch Tensor into np image format.""" - def __init__(self, mean: Optional[List[float]] = None, std: Optional[List[float]] = None) -> None: + def __init__(self, mean: list[float] | None = None, std: list[float] | None = None) -> None: """Denormalize Torch Tensor into np image format. Args: @@ -60,12 +61,12 @@ def __repr__(self) -> str: class ToNumpy: """Convert Tensor into Numpy Array.""" - def __call__(self, tensor: Tensor, dims: Optional[Tuple[int, ...]] = None) -> np.ndarray: + def __call__(self, tensor: Tensor, dims: tuple[int, ...] | None = None) -> np.ndarray: """Convert Tensor into Numpy Array. Args: tensor (Tensor): Tensor to convert. Input tensor in range 0-1. - dims (Optional[Tuple[int, ...]], optional): Convert dimensions from torch to numpy format. + dims (tuple[int, ...] | None, optional): Convert dimensions from torch to numpy format. Tuple corresponding to axis permutation from torch tensor to numpy array. Defaults to None. Returns: From 72f483c18f5e806623cdd50a36a77d3c5c364356 Mon Sep 17 00:00:00 2001 From: Samet Date: Tue, 17 Jan 2023 09:13:32 -0700 Subject: [PATCH 74/85] Convert to new annotation format - model utils --- anomalib/models/fastflow/lightning_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/anomalib/models/fastflow/lightning_model.py b/anomalib/models/fastflow/lightning_model.py index c3a681a88c..7b80a1dde7 100644 --- a/anomalib/models/fastflow/lightning_model.py +++ b/anomalib/models/fastflow/lightning_model.py @@ -73,7 +73,7 @@ def validation_step(self, batch: dict[str, str | Tensor], *args, **kwargs) -> ST batch (dict[str, str | Tensor]): Input batch Returns: - Optional[STEP_OUTPUT]: batch dictionary containing anomaly-maps. + STEP_OUTPUT | None: batch dictionary containing anomaly-maps. """ anomaly_maps = self.model(batch["image"]) batch["anomaly_maps"] = anomaly_maps From 734f059c487413781227134ae6d7a5e442cd0f1b Mon Sep 17 00:00:00 2001 From: Samet Date: Tue, 17 Jan 2023 09:15:52 -0700 Subject: [PATCH 75/85] Convert to new annotation format - callbacks utils --- anomalib/utils/callbacks/__init__.py | 15 ++++++++------- anomalib/utils/callbacks/cdf_normalization.py | 16 +++++++++------- anomalib/utils/callbacks/export.py | 7 ++++--- .../utils/callbacks/metrics_configuration.py | 17 +++++++++-------- .../utils/callbacks/min_max_normalization.py | 10 ++++++---- anomalib/utils/callbacks/model_loader.py | 5 +++-- anomalib/utils/callbacks/nncf/callback.py | 12 +++++++----- anomalib/utils/callbacks/nncf/utils.py | 16 +++++++++------- .../callbacks/post_processing_configuration.py | 15 ++++++++------- anomalib/utils/callbacks/tiler_configuration.py | 16 +++++++++------- .../callbacks/visualizer/visualizer_base.py | 6 ++++-- .../callbacks/visualizer/visualizer_image.py | 12 +++++++----- .../callbacks/visualizer/visualizer_metric.py | 2 ++ 13 files changed, 85 insertions(+), 64 deletions(-) diff --git a/anomalib/utils/callbacks/__init__.py b/anomalib/utils/callbacks/__init__.py index ef1edc3e13..735a0240fa 100644 --- a/anomalib/utils/callbacks/__init__.py +++ b/anomalib/utils/callbacks/__init__.py @@ -3,11 +3,12 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import logging import os import warnings from importlib import import_module -from typing import List, Union import yaml from jsonargparse.namespace import Namespace @@ -43,18 +44,18 @@ logger = logging.getLogger(__name__) -def get_callbacks(config: Union[ListConfig, DictConfig]) -> List[Callback]: +def get_callbacks(config: DictConfig | ListConfig) -> list[Callback]: """Return base callbacks for all the lightning models. Args: config (DictConfig): Model config Return: - (List[Callback]): List of callbacks. + (list[Callback]): List of callbacks. """ logger.info("Loading the callbacks") - callbacks: List[Callback] = [] + callbacks: list[Callback] = [] monitor_metric = None if "early_stopping" not in config.model.keys() else config.model.early_stopping.metric monitor_mode = "max" if "early_stopping" not in config.model.keys() else config.model.early_stopping.mode @@ -147,12 +148,12 @@ def get_callbacks(config: Union[ListConfig, DictConfig]) -> List[Callback]: return callbacks -def add_visualizer_callback(callbacks: List[Callback], config: Union[DictConfig, ListConfig]) -> None: +def add_visualizer_callback(callbacks: list[Callback], config: DictConfig | ListConfig) -> None: """Configure the visualizer callback based on the config and add it to the list of callbacks. Args: - callbacks (List[Callback]): Current list of callbacks. - config (Union[DictConfig, ListConfig]): The config object. + callbacks (list[Callback]): Current list of callbacks. + config (DictConfig | ListConfig): The config object. """ # visualization settings assert isinstance(config, (DictConfig, Namespace)) diff --git a/anomalib/utils/callbacks/cdf_normalization.py b/anomalib/utils/callbacks/cdf_normalization.py index b8e459fff4..52ec6af226 100644 --- a/anomalib/utils/callbacks/cdf_normalization.py +++ b/anomalib/utils/callbacks/cdf_normalization.py @@ -3,8 +3,10 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import logging -from typing import Any, Dict, Optional +from typing import Any import pytorch_lightning as pl from pytorch_lightning import Callback, Trainer @@ -25,10 +27,10 @@ class CdfNormalizationCallback(Callback): """Callback that standardizes the image-level and pixel-level anomaly scores.""" def __init__(self) -> None: - self.image_dist: Optional[LogNormal] = None - self.pixel_dist: Optional[LogNormal] = None + self.image_dist: LogNormal | None = None + self.pixel_dist: LogNormal | None = None - def setup(self, trainer: pl.Trainer, pl_module: AnomalyModule, stage: Optional[str] = None) -> None: + def setup(self, trainer: pl.Trainer, pl_module: AnomalyModule, stage: str | None = None) -> None: """Adds training_distribution metrics to normalization metrics.""" del trainer, stage # These variabels are not used. @@ -63,7 +65,7 @@ def on_validation_batch_end( self, trainer: pl.Trainer, pl_module: AnomalyModule, - outputs: Optional[STEP_OUTPUT], + outputs: STEP_OUTPUT | None, batch: Any, batch_idx: int, dataloader_idx: int, @@ -77,7 +79,7 @@ def on_test_batch_end( self, trainer: pl.Trainer, pl_module: AnomalyModule, - outputs: Optional[STEP_OUTPUT], + outputs: STEP_OUTPUT | None, batch: Any, batch_idx: int, dataloader_idx: int, @@ -92,7 +94,7 @@ def on_predict_batch_end( self, trainer: pl.Trainer, pl_module: AnomalyModule, - outputs: Dict, + outputs: dict, batch: Any, batch_idx: int, dataloader_idx: int, diff --git a/anomalib/utils/callbacks/export.py b/anomalib/utils/callbacks/export.py index 4121c24af9..ffb42c8ddd 100644 --- a/anomalib/utils/callbacks/export.py +++ b/anomalib/utils/callbacks/export.py @@ -3,9 +3,10 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import logging import os -from typing import Tuple import pytorch_lightning as pl from pytorch_lightning import Callback @@ -24,12 +25,12 @@ class ExportCallback(Callback): Model is first exported to ``.onnx`` format, and then converted to OpenVINO IR. Args: - input_size (Tuple[int, int]): Tuple of image height, width + input_size (tuple[int, int]): Tuple of image height, width dirpath (str): Path for model output filename (str): Name of output model """ - def __init__(self, input_size: Tuple[int, int], dirpath: str, filename: str, export_mode: ExportMode) -> None: + def __init__(self, input_size: tuple[int, int], dirpath: str, filename: str, export_mode: ExportMode) -> None: self.input_size = input_size self.dirpath = dirpath self.filename = filename diff --git a/anomalib/utils/callbacks/metrics_configuration.py b/anomalib/utils/callbacks/metrics_configuration.py index bbab06436c..812d48e757 100644 --- a/anomalib/utils/callbacks/metrics_configuration.py +++ b/anomalib/utils/callbacks/metrics_configuration.py @@ -4,8 +4,9 @@ # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import logging -from typing import List, Optional import pytorch_lightning as pl from pytorch_lightning.callbacks import Callback @@ -27,8 +28,8 @@ class MetricsConfigurationCallback(Callback): def __init__( self, task: TaskType = TaskType.SEGMENTATION, - image_metrics: Optional[List[str]] = None, - pixel_metrics: Optional[List[str]] = None, + image_metrics: list[str] | None = None, + pixel_metrics: list[str] | None = None, ) -> None: """Create image and pixel-level AnomalibMetricsCollection. @@ -39,8 +40,8 @@ def __init__( Args: task (TaskType): Task type of the current run. - image_metrics (Optional[List[str]]): List of image-level metrics. - pixel_metrics (Optional[List[str]]): List of pixel-level metrics. + image_metrics (list[str] | None): List of image-level metrics. + pixel_metrics (list[str] | None): List of pixel-level metrics. """ self.task = task self.image_metric_names = image_metrics @@ -50,20 +51,20 @@ def setup( self, trainer: pl.Trainer, pl_module: AnomalyModule, - stage: Optional[str] = None, + stage: str | None = None, ) -> None: """Setup image and pixel-level AnomalibMetricsCollection within Anomalib Model. Args: trainer (pl.Trainer): PyTorch Lightning Trainer pl_module (AnomalyModule): Anomalib Model that inherits pl LightningModule. - stage (Optional[str], optional): fit, validate, test or predict. Defaults to None. + stage (str | None, optional): fit, validate, test or predict. Defaults to None. """ del trainer, stage # These variables are not used. image_metric_names = [] if self.image_metric_names is None else self.image_metric_names - pixel_metric_names: List[str] + pixel_metric_names: list[str] if self.pixel_metric_names is None: pixel_metric_names = [] elif self.task == TaskType.CLASSIFICATION: diff --git a/anomalib/utils/callbacks/min_max_normalization.py b/anomalib/utils/callbacks/min_max_normalization.py index 4b7c5c72ad..632ed8b872 100644 --- a/anomalib/utils/callbacks/min_max_normalization.py +++ b/anomalib/utils/callbacks/min_max_normalization.py @@ -3,7 +3,9 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -from typing import Any, Optional +from __future__ import annotations + +from typing import Any import pytorch_lightning as pl import torch @@ -20,7 +22,7 @@ class MinMaxNormalizationCallback(Callback): """Callback that normalizes the image-level and pixel-level anomaly scores using min-max normalization.""" - def setup(self, trainer: pl.Trainer, pl_module: AnomalyModule, stage: Optional[str] = None) -> None: + def setup(self, trainer: pl.Trainer, pl_module: AnomalyModule, stage: str | None = None) -> None: """Adds min_max metrics to normalization metrics.""" del trainer, stage # These variables are not used. @@ -62,9 +64,9 @@ def on_validation_batch_end( def on_test_batch_end( self, - trainer: "pl.Trainer", + trainer: pl.Trainer, pl_module: AnomalyModule, - outputs: Optional[STEP_OUTPUT], + outputs: STEP_OUTPUT | None, batch: Any, batch_idx: int, dataloader_idx: int, diff --git a/anomalib/utils/callbacks/model_loader.py b/anomalib/utils/callbacks/model_loader.py index c00a4b0d02..a32384a3ee 100644 --- a/anomalib/utils/callbacks/model_loader.py +++ b/anomalib/utils/callbacks/model_loader.py @@ -3,8 +3,9 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import logging -from typing import Optional import torch from pytorch_lightning import Callback, Trainer @@ -22,7 +23,7 @@ class LoadModelCallback(Callback): def __init__(self, weights_path) -> None: self.weights_path = weights_path - def setup(self, trainer: Trainer, pl_module: AnomalyModule, stage: Optional[str] = None) -> None: + def setup(self, trainer: Trainer, pl_module: AnomalyModule, stage: str | None = None) -> None: """Call when inference begins. Loads the model weights from ``weights_path`` into the PyTorch module. diff --git a/anomalib/utils/callbacks/nncf/callback.py b/anomalib/utils/callbacks/nncf/callback.py index b4a6f2ff62..7d4754a729 100644 --- a/anomalib/utils/callbacks/nncf/callback.py +++ b/anomalib/utils/callbacks/nncf/callback.py @@ -3,8 +3,10 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import os -from typing import Any, Dict, Optional +from typing import Any import pytorch_lightning as pl from nncf import NNCFConfig @@ -24,17 +26,17 @@ class NNCFCallback(Callback): the PyTorch module that must be compressed. Args: - config (Dict): NNCF Configuration + config (dict): NNCF Configuration export_dir (Str): Path where the export `onnx` and the OpenVINO `xml` and `bin` IR are saved. If None model will not be exported. """ - def __init__(self, config: Dict, export_dir: Optional[str] = None) -> None: + def __init__(self, config: dict, export_dir: str | None = None) -> None: self.export_dir = export_dir self.config = NNCFConfig(config) - self.nncf_ctrl: Optional[CompressionAlgorithmController] = None + self.nncf_ctrl: CompressionAlgorithmController | None = None - def setup(self, trainer: pl.Trainer, pl_module: pl.LightningModule, stage: Optional[str] = None) -> None: + def setup(self, trainer: pl.Trainer, pl_module: pl.LightningModule, stage: str | None = None) -> None: """Call when fit or test begins. Takes the pytorch model and wraps it using the compression controller diff --git a/anomalib/utils/callbacks/nncf/utils.py b/anomalib/utils/callbacks/nncf/utils.py index b387034a67..a1aafd759f 100644 --- a/anomalib/utils/callbacks/nncf/utils.py +++ b/anomalib/utils/callbacks/nncf/utils.py @@ -3,9 +3,11 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import logging from copy import copy -from typing import Any, Dict, Iterator, List, Tuple +from typing import Any, Iterator from nncf import NNCFConfig from nncf.api.compression import CompressionAlgorithmController @@ -35,11 +37,11 @@ def __next__(self) -> Any: loaded_item = next(self._data_loader_iter) return loaded_item["image"] - def get_inputs(self, dataloader_output) -> Tuple[Tuple, Dict]: + def get_inputs(self, dataloader_output) -> tuple[tuple, dict]: """Get input to model. Returns: - (dataloader_output,), {}: Tuple[Tuple, Dict]: The current model call to be made during + (dataloader_output,), {}: tuple[tuple, dict]: The current model call to be made during the initialization process """ return (dataloader_output,), {} @@ -56,8 +58,8 @@ def get_target(self, _): def wrap_nncf_model( - model: nn.Module, config: Dict, dataloader: DataLoader = None, init_state_dict: Dict = None -) -> Tuple[CompressionAlgorithmController, NNCFNetwork]: + model: nn.Module, config: dict, dataloader: DataLoader = None, init_state_dict: dict = None +) -> tuple[CompressionAlgorithmController, NNCFNetwork]: """Wrap model by NNCF. :param model: Anomalib model. @@ -95,12 +97,12 @@ def wrap_nncf_model( return nncf_ctrl, nncf_model -def is_state_nncf(state: Dict) -> bool: +def is_state_nncf(state: dict) -> bool: """The function to check if sate is the result of NNCF-compressed model.""" return bool(state.get("meta", {}).get("nncf_enable_compression", False)) -def compose_nncf_config(nncf_config: Dict, enabled_options: List[str]) -> Dict: +def compose_nncf_config(nncf_config: dict, enabled_options: list[str]) -> dict: """Compose NNCf config by selected options. :param nncf_config: diff --git a/anomalib/utils/callbacks/post_processing_configuration.py b/anomalib/utils/callbacks/post_processing_configuration.py index 42a044f4e9..2adbe144a4 100644 --- a/anomalib/utils/callbacks/post_processing_configuration.py +++ b/anomalib/utils/callbacks/post_processing_configuration.py @@ -4,8 +4,9 @@ # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import logging -from typing import Optional import torch from pytorch_lightning import Callback, LightningModule, Trainer @@ -26,16 +27,16 @@ class PostProcessingConfigurationCallback(Callback): Args: normalization_method(NormalizationMethod): Normalization method. 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. + manual_image_threshold (float | None): Default manual image threshold value. + manual_pixel_threshold (float | None): Default manual pixel threshold value. """ def __init__( self, normalization_method: NormalizationMethod = NormalizationMethod.MIN_MAX, threshold_method: ThresholdMethod = ThresholdMethod.ADAPTIVE, - manual_image_threshold: Optional[float] = None, - manual_pixel_threshold: Optional[float] = None, + manual_image_threshold: float | None = None, + manual_pixel_threshold: float | None = None, ) -> None: super().__init__() self.normalization_method = normalization_method @@ -60,13 +61,13 @@ def __init__( self.manual_image_threshold = manual_image_threshold self.manual_pixel_threshold = manual_pixel_threshold - def setup(self, trainer: Trainer, pl_module: LightningModule, stage: Optional[str] = None) -> None: + def setup(self, trainer: Trainer, pl_module: LightningModule, stage: str | None = None) -> None: """Setup post-processing configuration within Anomalib Model. Args: trainer (Trainer): PyTorch Lightning Trainer pl_module (LightningModule): Anomalib Model that inherits pl LightningModule. - stage (Optional[str], optional): fit, validate, test or predict. Defaults to None. + stage (str | None, optional): fit, validate, test or predict. Defaults to None. """ del trainer, stage # These variables are not used. diff --git a/anomalib/utils/callbacks/tiler_configuration.py b/anomalib/utils/callbacks/tiler_configuration.py index 11137267be..db5c5b710a 100644 --- a/anomalib/utils/callbacks/tiler_configuration.py +++ b/anomalib/utils/callbacks/tiler_configuration.py @@ -4,7 +4,9 @@ # SPDX-License-Identifier: Apache-2.0 -from typing import Optional, Sequence, Union +from __future__ import annotations + +from typing import Sequence import pytorch_lightning as pl from pytorch_lightning.callbacks import Callback @@ -23,8 +25,8 @@ class TilerConfigurationCallback(Callback): def __init__( self, enable: bool = False, - tile_size: Union[int, Sequence] = 256, - stride: Optional[Union[int, Sequence]] = None, + tile_size: int | Sequence = 256, + stride: int | Sequence | None = None, remove_border_count: int = 0, mode: str = "padding", tile_count: int = 4, @@ -34,9 +36,9 @@ def __init__( Args: enable (bool): Boolean to enable tiling operation. Defaults to False. - tile_size ([Union[int, Sequence]]): Tile size. + tile_size ([int | Sequence]): Tile size. Defaults to 256. - stride ([Union[int, Sequence]]): Stride to move tiles on the image. + stride ([int | Sequence]): Stride to move tiles on the image. remove_border_count (int, optional): Number of pixels to remove from the image before tiling. Defaults to 0. mode (str, optional): Up-scaling mode when untiling overlapping tiles. @@ -51,13 +53,13 @@ def __init__( self.mode = mode self.tile_count = tile_count - def setup(self, trainer: pl.Trainer, pl_module: pl.LightningModule, stage: Optional[str] = None) -> None: + def setup(self, trainer: pl.Trainer, pl_module: pl.LightningModule, stage: str | None = None) -> None: """Setup Tiler object within Anomalib Model. Args: trainer (pl.Trainer): PyTorch Lightning Trainer pl_module (pl.LightningModule): Anomalib Model that inherits pl LightningModule. - stage (Optional[str], optional): fit, validate, test or predict. Defaults to None. + stage (str | None, optional): fit, validate, test or predict. Defaults to None. Raises: ValueError: When Anomalib Model doesn't contain ``Tiler`` object, it means the model diff --git a/anomalib/utils/callbacks/visualizer/visualizer_base.py b/anomalib/utils/callbacks/visualizer/visualizer_base.py index 2233c62908..cfcf3d3c30 100644 --- a/anomalib/utils/callbacks/visualizer/visualizer_base.py +++ b/anomalib/utils/callbacks/visualizer/visualizer_base.py @@ -3,8 +3,10 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + from pathlib import Path -from typing import Union, cast +from typing import cast import numpy as np import pytorch_lightning as pl @@ -56,7 +58,7 @@ def _add_to_logger( image: np.ndarray, module: AnomalyModule, trainer: pl.Trainer, - filename: Union[Path, str], + filename: str | Path, ) -> None: """Log image from a visualizer to each of the available loggers in the project. diff --git a/anomalib/utils/callbacks/visualizer/visualizer_image.py b/anomalib/utils/callbacks/visualizer/visualizer_image.py index d0a2d9bc29..8ae244a066 100644 --- a/anomalib/utils/callbacks/visualizer/visualizer_image.py +++ b/anomalib/utils/callbacks/visualizer/visualizer_image.py @@ -3,9 +3,11 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import math from pathlib import Path -from typing import Any, Optional +from typing import Any import pytorch_lightning as pl from pytorch_lightning.utilities.cli import CALLBACK_REGISTRY @@ -31,7 +33,7 @@ def on_predict_batch_end( self, trainer: pl.Trainer, pl_module: AnomalyModule, - outputs: Optional[STEP_OUTPUT], + outputs: STEP_OUTPUT | None, batch: Any, batch_idx: int, dataloader_idx: int, @@ -42,7 +44,7 @@ def on_predict_batch_end( trainer (Trainer): Pytorch lightning trainer object (unused). pl_module (AnomalyModule): Lightning modules derived from BaseAnomalyLightning object as currently only they support logging images. - outputs (Optional[STEP_OUTPUT]): Outputs of the current test step. + outputs (STEP_OUTPUT | None): Outputs of the current test step. batch (Any): Input batch of the current test step (unused). batch_idx (int): Index of the current test batch (unused). dataloader_idx (int): Index of the dataloader that yielded the current batch (unused). @@ -62,7 +64,7 @@ def on_test_batch_end( self, trainer: pl.Trainer, pl_module: AnomalyModule, - outputs: Optional[STEP_OUTPUT], + outputs: STEP_OUTPUT | None, batch: Any, batch_idx: int, dataloader_idx: int, @@ -73,7 +75,7 @@ def on_test_batch_end( trainer (Trainer): Pytorch lightning trainer object (unused). pl_module (AnomalyModule): Lightning modules derived from BaseAnomalyLightning object as currently only they support logging images. - outputs (Optional[STEP_OUTPUT]): Outputs of the current test step. + outputs (STEP_OUTPUT | None): Outputs of the current test step. batch (Any): Input batch of the current test step (unused). batch_idx (int): Index of the current test batch (unused). dataloader_idx (int): Index of the dataloader that yielded the current batch (unused). diff --git a/anomalib/utils/callbacks/visualizer/visualizer_metric.py b/anomalib/utils/callbacks/visualizer/visualizer_metric.py index 0350cde878..0a1ccea158 100644 --- a/anomalib/utils/callbacks/visualizer/visualizer_metric.py +++ b/anomalib/utils/callbacks/visualizer/visualizer_metric.py @@ -3,6 +3,8 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + from pathlib import Path import numpy as np From cf5155b31f6798b93c745e96a19513abb2189f87 Mon Sep 17 00:00:00 2001 From: Samet Date: Tue, 17 Jan 2023 10:08:46 -0700 Subject: [PATCH 76/85] Convert to new annotation format - utils --- anomalib/data/utils/image.py | 2 +- .../callbacks/visualizer/visualizer_metric.py | 2 +- anomalib/utils/cli/cli.py | 18 +++++----- anomalib/utils/hpo/config.py | 7 ++-- anomalib/utils/hpo/runners.py | 14 ++++---- anomalib/utils/loggers/__init__.py | 16 +++++---- anomalib/utils/loggers/base.py | 6 ++-- anomalib/utils/loggers/comet.py | 26 +++++++------- anomalib/utils/loggers/tensorboard.py | 18 +++++----- anomalib/utils/loggers/wandb.py | 34 ++++++++++--------- anomalib/utils/metrics/__init__.py | 30 ++++++++-------- .../metrics/anomaly_score_distribution.py | 12 +++---- .../utils/metrics/anomaly_score_threshold.py | 2 ++ anomalib/utils/metrics/aupr.py | 9 ++--- anomalib/utils/metrics/aupro.py | 20 ++++++----- anomalib/utils/metrics/auroc.py | 8 ++--- anomalib/utils/metrics/min_max.py | 4 +-- anomalib/utils/metrics/plotting_utils.py | 14 ++++---- anomalib/utils/metrics/pro.py | 6 ++-- anomalib/utils/sweep/config.py | 20 ++++++----- anomalib/utils/sweep/helpers/callbacks.py | 10 +++--- anomalib/utils/sweep/helpers/inference.py | 13 ++++--- .../001_getting_started.ipynb | 8 +++-- tools/benchmarking/benchmark.py | 34 ++++++++++--------- tools/benchmarking/utils/metrics.py | 23 +++++++------ tools/hpo/sweep.py | 5 +-- tools/inference/gradio_inference.py | 11 +++--- 27 files changed, 199 insertions(+), 173 deletions(-) diff --git a/anomalib/data/utils/image.py b/anomalib/data/utils/image.py index 1e976e7563..b3e10d343a 100644 --- a/anomalib/data/utils/image.py +++ b/anomalib/data/utils/image.py @@ -168,7 +168,7 @@ def get_image_height_and_width(image_size: int | tuple[int, int]) -> tuple[int, ValueError: ``image_size`` could be either int or tuple[int, int] Returns: - Tuple[int | None, int | None]: A tuple containing image height and width values. + tuple[int | None, int | None]: A tuple containing image height and width values. """ if isinstance(image_size, int): height_and_width = (image_size, image_size) diff --git a/anomalib/utils/callbacks/visualizer/visualizer_metric.py b/anomalib/utils/callbacks/visualizer/visualizer_metric.py index 0a1ccea158..428fc07d32 100644 --- a/anomalib/utils/callbacks/visualizer/visualizer_metric.py +++ b/anomalib/utils/callbacks/visualizer/visualizer_metric.py @@ -29,7 +29,7 @@ def on_test_end(self, trainer: pl.Trainer, pl_module: AnomalyModule) -> None: """Log images of the metrics contained in pl_module. In order to also plot custom metrics, they need to have implemented a `generate_figure` function that returns - Tuple[matplotlib.figure.Figure, str]. + tuple[matplotlib.figure.Figure, str]. Args: trainer (pl.Trainer): pytorch lightning trainer. diff --git a/anomalib/utils/cli/cli.py b/anomalib/utils/cli/cli.py index 14a0bc8470..37edc7e8b4 100644 --- a/anomalib/utils/cli/cli.py +++ b/anomalib/utils/cli/cli.py @@ -3,13 +3,15 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import logging import os import warnings from datetime import datetime from importlib import import_module from pathlib import Path -from typing import Any, Callable, Dict, Optional, Type, Union +from typing import Any, Callable from omegaconf.omegaconf import OmegaConf from pytorch_lightning import LightningDataModule, LightningModule, Trainer @@ -47,19 +49,19 @@ class AnomalibCLI(LightningCLI): def __init__( # pylint: disable=too-many-function-args self, - model_class: Optional[Union[Type[LightningModule], Callable[..., LightningModule]]] = None, - datamodule_class: Optional[Union[Type[LightningDataModule], Callable[..., LightningDataModule]]] = None, - save_config_callback: Optional[Type[SaveConfigCallback]] = SaveConfigCallback, + model_class: type[LightningModule] | Callable[..., LightningModule] | None = None, + datamodule_class: type[LightningDataModule] | Callable[..., LightningDataModule] | None = None, + save_config_callback: type[SaveConfigCallback] | None = SaveConfigCallback, save_config_filename: str = "config.yaml", save_config_overwrite: bool = False, save_config_multifile: bool = False, - trainer_class: Union[Type[Trainer], Callable[..., Trainer]] = Trainer, - trainer_defaults: Optional[Dict[str, Any]] = None, - seed_everything_default: Optional[int] = None, + trainer_class: type[Trainer] | Callable[..., Trainer] = Trainer, + trainer_defaults: dict[str, Any] | None = None, + seed_everything_default: int | None = None, description: str = "Anomalib trainer command line tool", env_prefix: str = "Anomalib", env_parse: bool = False, - parser_kwargs: Optional[Union[Dict[str, Any], Dict[str, Dict[str, Any]]]] = None, + parser_kwargs: dict[str, Any] | dict[str, dict[str, Any]] | None = None, subclass_mode_model: bool = False, subclass_mode_data: bool = False, run: bool = True, diff --git a/anomalib/utils/hpo/config.py b/anomalib/utils/hpo/config.py index cda7b8699a..859ac17e1a 100644 --- a/anomalib/utils/hpo/config.py +++ b/anomalib/utils/hpo/config.py @@ -3,7 +3,8 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -from typing import List + +from __future__ import annotations from omegaconf import DictConfig @@ -18,7 +19,7 @@ def flatten_hpo_params(params_dict: DictConfig) -> DictConfig: flattened version of the parameter dictionary. """ - def process_params(nested_params: DictConfig, keys: List[str], flattened_params: DictConfig) -> None: + def process_params(nested_params: DictConfig, keys: list[str], flattened_params: DictConfig) -> None: """Flatten nested dictionary till the time it reaches the hpo params. Recursive helper function that traverses the nested config object and stores the leaf nodes in a flattened @@ -26,7 +27,7 @@ def process_params(nested_params: DictConfig, keys: List[str], flattened_params: Args: nested_params: DictConfig: config object containing the original parameters. - keys: List[str]: list of keys leading to the current location in the config. + keys: list[str]: list of keys leading to the current location in the config. flattened_params: DictConfig: Dictionary in which the flattened parameters are stored. """ if len({"values", "min", "max"}.intersection(nested_params.keys())) > 0: diff --git a/anomalib/utils/hpo/runners.py b/anomalib/utils/hpo/runners.py index c19a555b87..3760b2180f 100644 --- a/anomalib/utils/hpo/runners.py +++ b/anomalib/utils/hpo/runners.py @@ -3,7 +3,7 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -from typing import Optional, Union +from __future__ import annotations import pytorch_lightning as pl from comet_ml import Optimizer @@ -34,9 +34,9 @@ class WandbSweep: def __init__( self, - config: Union[DictConfig, ListConfig], - sweep_config: Union[DictConfig, ListConfig], - entity: Optional[str] = None, + config: DictConfig | ListConfig, + sweep_config: DictConfig | ListConfig, + entity: str | None = None, ) -> None: self.config = config self.sweep_config = sweep_config @@ -89,9 +89,9 @@ class CometSweep: def __init__( self, - config: Union[DictConfig, ListConfig], - sweep_config: Union[DictConfig, ListConfig], - entity: Optional[str] = None, + config: DictConfig | ListConfig, + sweep_config: DictConfig | ListConfig, + entity: str | None = None, ) -> None: self.config = config self.sweep_config = sweep_config diff --git a/anomalib/utils/loggers/__init__.py b/anomalib/utils/loggers/__init__.py index 3d53534548..fa28f64ada 100644 --- a/anomalib/utils/loggers/__init__.py +++ b/anomalib/utils/loggers/__init__.py @@ -3,10 +3,12 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import logging import os import warnings -from typing import Iterable, List, Union +from typing import Iterable from omegaconf.dictconfig import DictConfig from omegaconf.listconfig import ListConfig @@ -35,11 +37,11 @@ class UnknownLogger(Exception): """This is raised when the logger option in `config.yaml` file is set incorrectly.""" -def configure_logger(level: Union[int, str] = logging.INFO) -> None: +def configure_logger(level: int | str = logging.INFO) -> None: """Get console logger by name. Args: - level (Union[int, str], optional): Logger Level. Defaults to logging.INFO. + level (int | str, optional): Logger Level. Defaults to logging.INFO. Returns: Logger: The expected logger. @@ -58,8 +60,8 @@ def configure_logger(level: Union[int, str] = logging.INFO) -> None: def get_experiment_logger( - config: Union[DictConfig, ListConfig] -) -> Union[LightningLoggerBase, Iterable[LightningLoggerBase], bool]: + config: DictConfig | ListConfig, +) -> LightningLoggerBase | Iterable[LightningLoggerBase] | bool: """Return a logger based on the choice of logger in the config file. Args: @@ -69,7 +71,7 @@ def get_experiment_logger( ValueError: for any logger types apart from false and tensorboard Returns: - Union[LightningLoggerBase, Iterable[LightningLoggerBase], bool]: Logger + LightningLoggerBase | Iterable[LightningLoggerBase] | bool]: Logger """ logger.info("Loading the experiment logger(s)") @@ -88,7 +90,7 @@ def get_experiment_logger( if config.logging.logger in [None, False]: return False - logger_list: List[LightningLoggerBase] = [] + logger_list: list[LightningLoggerBase] = [] if isinstance(config.logging.logger, str): config.logging.logger = [config.logging.logger] diff --git a/anomalib/utils/loggers/base.py b/anomalib/utils/loggers/base.py index 7bbc756de0..91f3607a89 100644 --- a/anomalib/utils/loggers/base.py +++ b/anomalib/utils/loggers/base.py @@ -3,8 +3,10 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + from abc import abstractmethod -from typing import Any, Optional, Union +from typing import Any import numpy as np from matplotlib.figure import Figure @@ -14,6 +16,6 @@ class ImageLoggerBase: """Adds a common interface for logging the images.""" @abstractmethod - def add_image(self, image: Union[np.ndarray, Figure], name: Optional[str] = None, **kwargs: Any) -> None: + def add_image(self, image: np.ndarray | Figure, name: str | None = None, **kwargs: Any) -> None: """Interface to log images in the respective loggers.""" raise NotImplementedError() diff --git a/anomalib/utils/loggers/comet.py b/anomalib/utils/loggers/comet.py index 3fa406dd0f..8daf40cd6b 100644 --- a/anomalib/utils/loggers/comet.py +++ b/anomalib/utils/loggers/comet.py @@ -3,7 +3,9 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -from typing import Any, Optional, Union +from __future__ import annotations + +from typing import Any import numpy as np from matplotlib.figure import Figure @@ -69,15 +71,15 @@ class AnomalibCometLogger(ImageLoggerBase, CometLogger): def __init__( self, - api_key: Optional[str] = None, - save_dir: Optional[str] = None, - project_name: Optional[str] = None, - rest_api_key: Optional[str] = None, - experiment_name: Optional[str] = None, - experiment_key: Optional[str] = None, + api_key: str | None = None, + save_dir: str | None = None, + project_name: str | None = None, + rest_api_key: str | None = None, + experiment_name: str | None = None, + experiment_key: str | None = None, offline: bool = False, prefix: str = "", - **kwargs + **kwargs, ) -> None: super().__init__( api_key=api_key, @@ -88,17 +90,17 @@ def __init__( experiment_key=experiment_key, offline=offline, prefix=prefix, - **kwargs + **kwargs, ) self.experiment.log_other("Created from", "Anomalib") @rank_zero_only - def add_image(self, image: Union[np.ndarray, Figure], name: Optional[str] = None, **kwargs: Any) -> None: + def add_image(self, image: np.ndarray | Figure, name: str | None = None, **kwargs: Any) -> None: """Interface to add image to comet logger. Args: - image (Union[np.ndarray, Figure]): Image to log - name (Optional[str]): The tag of the image + image (np.ndarray | Figure): Image to log + name (str | None): The tag of the image kwargs: Accepts only `global_step` (int). The step at which to log the image. """ if "global_step" not in kwargs: diff --git a/anomalib/utils/loggers/tensorboard.py b/anomalib/utils/loggers/tensorboard.py index bdf620b3fc..74137e19bb 100644 --- a/anomalib/utils/loggers/tensorboard.py +++ b/anomalib/utils/loggers/tensorboard.py @@ -3,7 +3,9 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -from typing import Any, Optional, Union +from __future__ import annotations + +from typing import Any import numpy as np from matplotlib.figure import Figure @@ -52,12 +54,12 @@ class AnomalibTensorBoardLogger(ImageLoggerBase, TensorBoardLogger): def __init__( self, save_dir: str, - name: Optional[str] = "default", - version: Optional[Union[int, str]] = None, + name: str | None = "default", + version: int | str | None = None, log_graph: bool = False, default_hp_metric: bool = True, prefix: str = "", - **kwargs + **kwargs, ): super().__init__( save_dir, @@ -66,16 +68,16 @@ def __init__( log_graph=log_graph, default_hp_metric=default_hp_metric, prefix=prefix, - **kwargs + **kwargs, ) @rank_zero_only - def add_image(self, image: Union[np.ndarray, Figure], name: Optional[str] = None, **kwargs: Any): + def add_image(self, image: np.ndarray | Figure, name: str | None = None, **kwargs: Any): """Interface to add image to tensorboard logger. Args: - image (Union[np.ndarray, Figure]): Image to log - name (Optional[str]): The tag of the image + image (np.ndarray | Figure): Image to log + name (str | None): The tag of the image kwargs: Accepts only `global_step` (int). The step at which to log the image. """ if "global_step" not in kwargs: diff --git a/anomalib/utils/loggers/wandb.py b/anomalib/utils/loggers/wandb.py index 416a3a5c15..6260337adb 100644 --- a/anomalib/utils/loggers/wandb.py +++ b/anomalib/utils/loggers/wandb.py @@ -3,7 +3,9 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -from typing import Any, List, Optional, Union +from __future__ import annotations + +from typing import Any import numpy as np from matplotlib.figure import Figure @@ -68,17 +70,17 @@ class AnomalibWandbLogger(ImageLoggerBase, WandbLogger): def __init__( self, - name: Optional[str] = None, - save_dir: Optional[str] = None, - offline: Optional[bool] = False, - id: Optional[str] = None, # kept to match wandb init pylint: disable=redefined-builtin - anonymous: Optional[bool] = None, - version: Optional[str] = None, - project: Optional[str] = None, - log_model: Union[str, bool] = False, + name: str | None = None, + save_dir: str | None = None, + offline: bool | None = False, + id: str | None = None, # kept to match wandb init pylint: disable=redefined-builtin + anonymous: bool | None = None, + version: str | None = None, + project: str | None = None, + log_model: str | bool = False, experiment=None, - prefix: Optional[str] = "", - **kwargs + prefix: str | None = "", + **kwargs, ) -> None: super().__init__( name=name, @@ -91,17 +93,17 @@ def __init__( log_model=log_model, experiment=experiment, prefix=prefix, - **kwargs + **kwargs, ) - self.image_list: List[wandb.Image] = [] # Cache images + self.image_list: list[wandb.Image] = [] # Cache images @rank_zero_only - def add_image(self, image: Union[np.ndarray, Figure], name: Optional[str] = None, **kwargs: Any): + def add_image(self, image: np.ndarray | Figure, name: str | None = None, **kwargs: Any): """Interface to add image to wandb logger. Args: - image (Union[np.ndarray, Figure]): Image to log - name (Optional[str]): The tag of the image + image (np.ndarray | Figure): Image to log + name (str | None): The tag of the image """ image = wandb.Image(image, caption=name) self.image_list.append(image) diff --git a/anomalib/utils/metrics/__init__.py b/anomalib/utils/metrics/__init__.py index e1fd7541fb..c8b7e84c3a 100644 --- a/anomalib/utils/metrics/__init__.py +++ b/anomalib/utils/metrics/__init__.py @@ -3,9 +3,11 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import importlib import warnings -from typing import Any, Dict, List, Optional, Union +from typing import Any import torchmetrics from omegaconf import DictConfig, ListConfig @@ -23,15 +25,15 @@ __all__ = ["AUROC", "AUPR", "AUPRO", "OptimalF1", "AnomalyScoreThreshold", "AnomalyScoreDistribution", "MinMax", "PRO"] -def metric_collection_from_names(metric_names: List[str], prefix: Optional[str]) -> AnomalibMetricCollection: +def metric_collection_from_names(metric_names: list[str], prefix: str | None) -> AnomalibMetricCollection: """Create a metric collection from a list of metric names. The function will first try to retrieve the metric from the metrics defined in Anomalib metrics module, then in TorchMetrics package. Args: - metric_names (List[str]): List of metric names to be included in the collection. - prefix (Optional[str]): prefix to assign to the metrics in the collection. + metric_names (list[str]): List of metric names to be included in the collection. + prefix (str | None): prefix to assign to the metrics in the collection. Returns: AnomalibMetricCollection: Collection of metrics. @@ -53,7 +55,7 @@ def metric_collection_from_names(metric_names: List[str], prefix: Optional[str]) return metrics -def _validate_metrics_dict(metrics: Dict[str, Dict[str, Any]]) -> None: +def _validate_metrics_dict(metrics: dict[str, dict[str, Any]]) -> None: """Check the assumptions about metrics config dict. - Keys are metric names @@ -90,7 +92,7 @@ def _get_class_from_path(class_path: str) -> Any: return cls -def metric_collection_from_dicts(metrics: Dict[str, Dict[str, Any]], prefix: Optional[str]) -> AnomalibMetricCollection: +def metric_collection_from_dicts(metrics: dict[str, dict[str, Any]], prefix: str | None) -> AnomalibMetricCollection: """Create a metric collection from a dict of "metric name" -> "metric specifications". Example: @@ -123,11 +125,11 @@ def metric_collection_from_dicts(metrics: Dict[str, Dict[str, Any]], prefix: Opt ``` Args: - metrics (Dict[str, Dict[str, Any]]): keys are metric names, values are dictionaries. - Internal Dict[str, Any] keys are "class_path" (value is string) and "init_args" (value is dict), + metrics (dict[str, dict[str, Any]]): keys are metric names, values are dictionaries. + Internal dict[str, Any] keys are "class_path" (value is string) and "init_args" (value is dict), following the convention in Pytorch Lightning CLI. - prefix (Optional[str]): prefix to assign to the metrics in the collection. + prefix (str | None): prefix to assign to the metrics in the collection. Returns: AnomalibMetricCollection: Collection of metrics. @@ -143,21 +145,21 @@ def metric_collection_from_dicts(metrics: Dict[str, Dict[str, Any]], prefix: Opt def create_metric_collection( - metrics: Union[List[str], Dict[str, Dict[str, Any]]], prefix: Optional[str] + metrics: list[str] | dict[str, dict[str, Any]], prefix: str | None ) -> AnomalibMetricCollection: """Create a metric collection from a list of metric names or dictionaries. This function will dispatch the actual creation to the appropriate function depending on the input type: - - if List[str] (names of metrics): see `metric_collection_from_names` - - if Dict[str, Dict[str, Any]] (path and init args of a class): see `metric_collection_from_dicts` + - if list[str] (names of metrics): see `metric_collection_from_names` + - if dict[str, dict[str, Any]] (path and init args of a class): see `metric_collection_from_dicts` The function will first try to retrieve the metric from the metrics defined in Anomalib metrics module, then in TorchMetrics package. Args: - metrics (Union[List[str], Dict[str, Dict[str, Any]]]). - prefix (Optional[str]): prefix to assign to the metrics in the collection. + metrics (list[str] | dict[str, dict[str, Any]]). + prefix (str | None): prefix to assign to the metrics in the collection. Returns: AnomalibMetricCollection: Collection of metrics. diff --git a/anomalib/utils/metrics/anomaly_score_distribution.py b/anomalib/utils/metrics/anomaly_score_distribution.py index e0b6ebfc8b..4aed1e5bf7 100644 --- a/anomalib/utils/metrics/anomaly_score_distribution.py +++ b/anomalib/utils/metrics/anomaly_score_distribution.py @@ -3,7 +3,7 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -from typing import List, Optional, Tuple +from __future__ import annotations import torch from torch import Tensor @@ -15,8 +15,8 @@ class AnomalyScoreDistribution(Metric): def __init__(self, **kwargs) -> None: super().__init__(**kwargs) - self.anomaly_maps: List[Tensor] = [] - self.anomaly_scores: List[Tensor] = [] + self.anomaly_maps: list[Tensor] = [] + self.anomaly_scores: list[Tensor] = [] self.add_state("image_mean", torch.empty(0), persistent=True) self.add_state("image_std", torch.empty(0), persistent=True) @@ -28,9 +28,7 @@ def __init__(self, **kwargs) -> None: self.pixel_mean = torch.empty(0) self.pixel_std = torch.empty(0) - def update( - self, *args, anomaly_scores: Optional[Tensor] = None, anomaly_maps: Optional[Tensor] = None, **kwargs - ) -> None: + def update(self, *args, anomaly_scores: Tensor | None = None, anomaly_maps: Tensor | None = None, **kwargs) -> None: """Update the precision-recall curve metric.""" del args, kwargs # These variables are not used. @@ -39,7 +37,7 @@ def update( if anomaly_scores is not None: self.anomaly_scores.append(anomaly_scores) - def compute(self) -> Tuple[Tensor, Tensor, Tensor, Tensor]: + def compute(self) -> tuple[Tensor, Tensor, Tensor, Tensor]: """Compute stats.""" anomaly_scores = torch.hstack(self.anomaly_scores) anomaly_scores = torch.log(anomaly_scores) diff --git a/anomalib/utils/metrics/anomaly_score_threshold.py b/anomalib/utils/metrics/anomaly_score_threshold.py index 4d4a26fb4f..118ace3711 100644 --- a/anomalib/utils/metrics/anomaly_score_threshold.py +++ b/anomalib/utils/metrics/anomaly_score_threshold.py @@ -3,6 +3,8 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import warnings import torch diff --git a/anomalib/utils/metrics/aupr.py b/anomalib/utils/metrics/aupr.py index f567444b10..813aab6115 100644 --- a/anomalib/utils/metrics/aupr.py +++ b/anomalib/utils/metrics/aupr.py @@ -3,7 +3,8 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -from typing import Tuple + +from __future__ import annotations import torch from matplotlib.figure import Figure @@ -44,7 +45,7 @@ def update(self, preds: Tensor, target: Tensor) -> None: # type: ignore """ super().update(preds.flatten(), target.flatten()) - def _compute(self) -> Tuple[Tensor, Tensor]: + def _compute(self) -> tuple[Tensor, Tensor]: """Compute prec/rec value pairs. Returns: @@ -55,11 +56,11 @@ def _compute(self) -> Tuple[Tensor, Tensor]: prec, rec, _ = super().compute() return (prec, rec) - def generate_figure(self) -> Tuple[Figure, str]: + def generate_figure(self) -> tuple[Figure, str]: """Generate a figure containing the PR curve as well as the random baseline and the AUC. Returns: - Tuple[Figure, str]: Tuple containing both the PR curve and the figure title to be used for logging + tuple[Figure, str]: Tuple containing both the PR curve and the figure title to be used for logging """ prec, rec = self._compute() aupr = self.compute() diff --git a/anomalib/utils/metrics/aupro.py b/anomalib/utils/metrics/aupro.py index 670be1f93d..7b7fa17d04 100644 --- a/anomalib/utils/metrics/aupro.py +++ b/anomalib/utils/metrics/aupro.py @@ -3,7 +3,9 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -from typing import Any, Callable, List, Optional, Tuple +from __future__ import annotations + +from typing import Any, Callable import torch from matplotlib.figure import Figure @@ -24,16 +26,16 @@ class AUPRO(Metric): """Area under per region overlap (AUPRO) Metric.""" is_differentiable: bool = False - higher_is_better: Optional[bool] = None + higher_is_better: bool | None = None full_state_update: bool = False - preds: List[Tensor] - target: List[Tensor] + preds: list[Tensor] + target: list[Tensor] def __init__( self, compute_on_step: bool = True, dist_sync_on_step: bool = False, - process_group: Optional[Any] = None, + process_group: Any | None = None, dist_sync_fn: Callable = None, fpr_limit: float = 0.3, ) -> None: @@ -58,7 +60,7 @@ def update(self, preds: Tensor, target: Tensor) -> None: # type: ignore self.target.append(target) self.preds.append(preds) - def _compute(self) -> Tuple[Tensor, Tensor]: + def _compute(self) -> tuple[Tensor, Tensor]: """Compute the pro/fpr value-pairs until the fpr specified by self.fpr_limit. It leverages the fact that the overlap corresponds to the tpr, and thus computes the overall @@ -69,7 +71,7 @@ def _compute(self) -> Tuple[Tensor, Tensor]: connected component analysis. Returns: - Tuple[Tensor, Tensor]: tuple containing final fpr and tpr values. + tuple[Tensor, Tensor]: tuple containing final fpr and tpr values. """ target = dim_zero_cat(self.target) preds = dim_zero_cat(self.preds) @@ -165,11 +167,11 @@ def compute(self) -> Tensor: return aupro - def generate_figure(self) -> Tuple[Figure, str]: + def generate_figure(self) -> tuple[Figure, str]: """Generate a figure containing the PRO curve and the AUPRO. Returns: - Tuple[Figure, str]: Tuple containing both the figure and the figure title to be used for logging + tuple[Figure, str]: Tuple containing both the figure and the figure title to be used for logging """ fpr, tpr = self._compute() aupro = self.compute() diff --git a/anomalib/utils/metrics/auroc.py b/anomalib/utils/metrics/auroc.py index 60889d6be9..7cbc62976a 100644 --- a/anomalib/utils/metrics/auroc.py +++ b/anomalib/utils/metrics/auroc.py @@ -3,7 +3,7 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -from typing import Tuple +from __future__ import annotations import torch from matplotlib.figure import Figure @@ -43,7 +43,7 @@ def update(self, preds: Tensor, target: Tensor) -> None: # type: ignore """ super().update(preds.flatten(), target.flatten()) - def _compute(self) -> Tuple[Tensor, Tensor]: + def _compute(self) -> tuple[Tensor, Tensor]: """Compute fpr/tpr value pairs. Returns: @@ -54,11 +54,11 @@ def _compute(self) -> Tuple[Tensor, Tensor]: fpr, tpr, _thresholds = super().compute() return (fpr, tpr) - def generate_figure(self) -> Tuple[Figure, str]: + def generate_figure(self) -> tuple[Figure, str]: """Generate a figure containing the ROC curve, the baseline and the AUROC. Returns: - Tuple[Figure, str]: Tuple containing both the figure and the figure title to be used for logging + tuple[Figure, str]: Tuple containing both the figure and the figure title to be used for logging """ fpr, tpr = self._compute() auroc = self.compute() diff --git a/anomalib/utils/metrics/min_max.py b/anomalib/utils/metrics/min_max.py index a256395be1..ce0b98e1df 100644 --- a/anomalib/utils/metrics/min_max.py +++ b/anomalib/utils/metrics/min_max.py @@ -3,7 +3,7 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -from typing import Tuple +from __future__ import annotations import torch from torch import Tensor @@ -30,6 +30,6 @@ def update(self, predictions: Tensor, *args, **kwargs) -> None: self.max = torch.max(self.max, torch.max(predictions)) self.min = torch.min(self.min, torch.min(predictions)) - def compute(self) -> Tuple[Tensor, Tensor]: + def compute(self) -> tuple[Tensor, Tensor]: """Return min and max values.""" return self.min, self.max diff --git a/anomalib/utils/metrics/plotting_utils.py b/anomalib/utils/metrics/plotting_utils.py index 7162c55808..f6ec537ecc 100644 --- a/anomalib/utils/metrics/plotting_utils.py +++ b/anomalib/utils/metrics/plotting_utils.py @@ -3,7 +3,7 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -from typing import Tuple +from __future__ import annotations import torch from matplotlib import pyplot as plt @@ -16,14 +16,14 @@ def plot_figure( x_vals: Tensor, y_vals: Tensor, auc: Tensor, - xlim: Tuple[float, float], - ylim: Tuple[float, float], + xlim: tuple[float, float], + ylim: tuple[float, float], xlabel: str, ylabel: str, loc: str, title: str, sample_points: int = 1000, -) -> Tuple[Figure, Axis]: +) -> tuple[Figure, Axis]: """Generate a simple, ROC-style plot, where x_vals is plotted against y_vals. Note that a subsampling is applied if > sample_points are present in x/y, as matplotlib plotting draws @@ -33,8 +33,8 @@ def plot_figure( x_vals (Tensor): x values to plot y_vals (Tensor): y values to plot auc (Tensor): normalized area under the curve spanned by x_vals, y_vals - xlim (Tuple[float, float]): displayed range for x-axis - ylim (Tuple[float, float]): displayed range for y-axis + xlim (tuple[float, float]): displayed range for x-axis + ylim (tuple[float, float]): displayed range for y-axis xlabel (str): label of x axis ylabel (str): label of y axis loc (str): string-based legend location, for details see @@ -43,7 +43,7 @@ def plot_figure( sample_points (int): number of sampling points to subsample x_vals/y_vals with Returns: - Tuple[Figure, Axis]: Figure and the contained Axis + tuple[Figure, Axis]: Figure and the contained Axis """ fig, axis = plt.subplots() diff --git a/anomalib/utils/metrics/pro.py b/anomalib/utils/metrics/pro.py index a3f8922c4f..18c9069414 100644 --- a/anomalib/utils/metrics/pro.py +++ b/anomalib/utils/metrics/pro.py @@ -3,7 +3,7 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -from typing import List +from __future__ import annotations import torch from torch import Tensor @@ -17,8 +17,8 @@ class PRO(Metric): """Per-Region Overlap (PRO) Score.""" - target: List[Tensor] - preds: List[Tensor] + target: list[Tensor] + preds: list[Tensor] def __init__(self, threshold: float = 0.5, **kwargs) -> None: super().__init__(**kwargs) diff --git a/anomalib/utils/sweep/config.py b/anomalib/utils/sweep/config.py index ed97d99d41..d8a0fdf7ae 100644 --- a/anomalib/utils/sweep/config.py +++ b/anomalib/utils/sweep/config.py @@ -3,16 +3,18 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import itertools import operator from collections.abc import Iterable, ValuesView from functools import reduce -from typing import Any, Generator, List, Tuple +from typing import Any, Generator from omegaconf import DictConfig -def convert_to_tuple(values: ValuesView) -> List[Tuple]: +def convert_to_tuple(values: ValuesView) -> list[tuple]: """Converts a ValuesView object to a list of tuples. This is useful to get list of possible values for each parameter in the config and a tuple for values that are @@ -36,7 +38,7 @@ def convert_to_tuple(values: ValuesView) -> List[Tuple]: values: ValuesView: ValuesView object to be converted to a list of tuples. Returns: - List[Tuple]: List of tuples. + list[Tuple]: List of tuples. """ return_list = [] for value in values: @@ -63,7 +65,7 @@ def flatten_sweep_params(params_dict: DictConfig) -> DictConfig: flattened version of the parameter dictionary. """ - def flatten_nested_dict(nested_params: DictConfig, keys: List[str], flattened_params: DictConfig) -> None: + def flatten_nested_dict(nested_params: DictConfig, keys: list[str], flattened_params: DictConfig) -> None: """Flatten nested dictionary. Recursive helper function that traverses the nested config object and stores the leaf nodes in a flattened @@ -71,7 +73,7 @@ def flatten_nested_dict(nested_params: DictConfig, keys: List[str], flattened_pa Args: nested_params: DictConfig: config object containing the original parameters. - keys: List[str]: list of keys leading to the current location in the config. + keys: list[str]: list of keys leading to the current location in the config. flattened_params: DictConfig: Dictionary in which the flattened parameters are stored. """ for name, cfg in nested_params.items(): @@ -123,22 +125,22 @@ def get_run_config(params_dict: DictConfig) -> Generator[DictConfig, None, None] yield run_config -def get_from_nested_config(config: DictConfig, keymap: List) -> Any: +def get_from_nested_config(config: DictConfig, keymap: list) -> Any: """Retrieves an item from a nested config object using a list of keys. Args: config: DictConfig: nested DictConfig object - keymap: List[str]: list of keys corresponding to item that should be retrieved. + keymap: list[str]: list of keys corresponding to item that should be retrieved. """ return reduce(operator.getitem, keymap, config) -def set_in_nested_config(config: DictConfig, keymap: List, value: Any) -> None: +def set_in_nested_config(config: DictConfig, keymap: list, value: Any) -> None: """Set an item in a nested config object using a list of keys. Args: config: DictConfig: nested DictConfig object - keymap: List[str]: list of keys corresponding to item that should be set. + keymap: list[str]: list of keys corresponding to item that should be set. value: Any: Value that should be assigned to the dictionary item at the specified location. Example: diff --git a/anomalib/utils/sweep/helpers/callbacks.py b/anomalib/utils/sweep/helpers/callbacks.py index 62dacda2d1..fd26d7fb6e 100644 --- a/anomalib/utils/sweep/helpers/callbacks.py +++ b/anomalib/utils/sweep/helpers/callbacks.py @@ -4,7 +4,7 @@ # SPDX-License-Identifier: Apache-2.0 -from typing import List, Union +from __future__ import annotations from omegaconf import DictConfig, ListConfig from pytorch_lightning import Callback @@ -16,16 +16,16 @@ from anomalib.utils.callbacks.timer import TimerCallback -def get_sweep_callbacks(config: Union[ListConfig, DictConfig]) -> List[Callback]: +def get_sweep_callbacks(config: DictConfig | ListConfig) -> list[Callback]: """Gets callbacks relevant to sweep. Args: - config (Union[DictConfig, ListConfig]): Model config loaded from anomalib + config (DictConfig | ListConfig): Model config loaded from anomalib Returns: - List[Callback]: List of callbacks + list[Callback]: List of callbacks """ - callbacks: List[Callback] = [TimerCallback()] + callbacks: list[Callback] = [TimerCallback()] # Add metric configuration to the model via MetricsConfigurationCallback # TODO: Remove this once the old CLI is deprecated. diff --git a/anomalib/utils/sweep/helpers/inference.py b/anomalib/utils/sweep/helpers/inference.py index 78906a8880..b53eb35906 100644 --- a/anomalib/utils/sweep/helpers/inference.py +++ b/anomalib/utils/sweep/helpers/inference.py @@ -3,9 +3,10 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import time from pathlib import Path -from typing import Union import torch from omegaconf import DictConfig, ListConfig @@ -15,13 +16,11 @@ from anomalib.models.components import AnomalyModule -def get_torch_throughput( - config: Union[DictConfig, ListConfig], model: AnomalyModule, test_dataset: DataLoader -) -> float: +def get_torch_throughput(config: DictConfig | ListConfig, model: AnomalyModule, test_dataset: DataLoader) -> float: """Tests the model on dummy data. Images are passed sequentially to make the comparision with OpenVINO model fair. Args: - config (Union[DictConfig, ListConfig]): Model config. + config (DictConfig | ListConfig): Model config. model (Path): Model on which inference is called. test_dataset (DataLoader): The test dataset used as a reference for the mock dataset. @@ -48,11 +47,11 @@ def get_torch_throughput( return throughput -def get_openvino_throughput(config: Union[DictConfig, ListConfig], model_path: Path, test_dataset: DataLoader) -> float: +def get_openvino_throughput(config: DictConfig | ListConfig, model_path: Path, test_dataset: DataLoader) -> float: """Runs the generated OpenVINO model on a dummy dataset to get throughput. Args: - config (Union[DictConfig, ListConfig]): Model config. + config (DictConfig | ListConfig): Model config. model_path (Path): Path to folder containing the OpenVINO models. It then searches `model.xml` in the folder. test_dataset (DataLoader): The test dataset used as a reference for the mock dataset. diff --git a/notebooks/000_getting_started/001_getting_started.ipynb b/notebooks/000_getting_started/001_getting_started.ipynb index f30fddff2d..3a8f817cd6 100644 --- a/notebooks/000_getting_started/001_getting_started.ipynb +++ b/notebooks/000_getting_started/001_getting_started.ipynb @@ -106,8 +106,10 @@ "metadata": {}, "outputs": [], "source": [ + "from __future__ import annotations\n", + "\n", "from pathlib import Path\n", - "from typing import Any, Dict\n", + "from typing import Any\n", "\n", "import numpy as np\n", "from IPython.display import display\n", @@ -241,7 +243,7 @@ "metadata": {}, "outputs": [], "source": [ - "def show_image_and_mask(sample: Dict[str, Any], index: int) -> Image:\n", + "def show_image_and_mask(sample: dict[str, Any], index: int) -> Image:\n", " img = ToPILImage()(Denormalize()(sample[\"image\"][index].clone()))\n", " msk = ToPILImage()(sample[\"mask\"][index]).convert(\"RGB\")\n", "\n", @@ -365,7 +367,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.13" + "version": "3.8.13 (default, Oct 21 2022, 23:50:54) \n[GCC 11.2.0]" }, "orig_nbformat": 4, "vscode": { diff --git a/tools/benchmarking/benchmark.py b/tools/benchmarking/benchmark.py index e236f3445e..20dc24c2fc 100644 --- a/tools/benchmarking/benchmark.py +++ b/tools/benchmarking/benchmark.py @@ -3,6 +3,8 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import functools import io import logging @@ -16,7 +18,7 @@ from datetime import datetime from pathlib import Path from tempfile import TemporaryDirectory -from typing import Dict, List, Optional, Union, cast +from typing import cast import torch from omegaconf import DictConfig, ListConfig, OmegaConf @@ -74,7 +76,7 @@ def wrapper(*args, **kwargs): @hide_output -def get_single_model_metrics(model_config: Union[DictConfig, ListConfig], openvino_metrics: bool = False) -> Dict: +def get_single_model_metrics(model_config: DictConfig | ListConfig, openvino_metrics: bool = False) -> dict: """Collects metrics for `model_name` and returns a dict of results. Args: @@ -82,7 +84,7 @@ def get_single_model_metrics(model_config: Union[DictConfig, ListConfig], openvi openvino_metrics (bool): If True, converts the model to OpenVINO format and gathers inference metrics. Returns: - Dict: Collection of all the metrics such as time taken, throughput and performance scores. + dict: Collection of all the metrics such as time taken, throughput and performance scores. """ with TemporaryDirectory() as project_path: @@ -135,7 +137,7 @@ def get_single_model_metrics(model_config: Union[DictConfig, ListConfig], openvi return data -def compute_on_cpu(sweep_config: Union[DictConfig, ListConfig], folder: Optional[str] = None): +def compute_on_cpu(sweep_config: DictConfig | ListConfig, folder: str | None = None): """Compute all run configurations over a sigle CPU.""" for run_config in get_run_config(sweep_config.grid_search): model_metrics = sweep(run_config, 0, sweep_config.seed, False) @@ -143,20 +145,20 @@ def compute_on_cpu(sweep_config: Union[DictConfig, ListConfig], folder: Optional def compute_on_gpu( - run_configs: List[DictConfig], + run_configs: list[DictConfig], device: int, seed: int, - writers: List[str], - folder: Optional[str] = None, + writers: list[str], + folder: str | None = None, compute_openvino: bool = False, ): """Go over each run config and collect the result. Args: - run_configs (Union[DictConfig, ListConfig]): List of run configurations. + run_configs (DictConfig | ListConfig): List of run configurations. device (int): The GPU id used for running the sweep. seed (int): Fix a seed. - writers (List[str]): Destinations to write to. + writers (list[str]): Destinations to write to. folder (optional, str): Sub-directory to which runs are written to. Defaults to None. If none writes to root. compute_openvino (bool, optional): Compute OpenVINO throughput. Defaults to False. """ @@ -170,7 +172,7 @@ def compute_on_gpu( ) -def distribute_over_gpus(sweep_config: Union[DictConfig, ListConfig], folder: Optional[str] = None): +def distribute_over_gpus(sweep_config: DictConfig | ListConfig, folder: str | None = None): """Distribute metric collection over all available GPUs. This is done by splitting the list of configurations.""" with ProcessPoolExecutor( max_workers=torch.cuda.device_count(), mp_context=multiprocessing.get_context("spawn") @@ -198,11 +200,11 @@ def distribute_over_gpus(sweep_config: Union[DictConfig, ListConfig], folder: Op raise Exception(f"Error occurred while computing benchmark on GPU {job}") from exc -def distribute(config: Union[DictConfig, ListConfig]): +def distribute(config: DictConfig | ListConfig): """Run all cpu experiments on a single process. Distribute gpu experiments over all available gpus. Args: - config: (Union[DictConfig, ListConfig]): Sweep configuration. + config: (DictConfig | ListConfig): Sweep configuration. """ runs_folder = datetime.strftime(datetime.now(), "%Y_%m_%d-%H_%M_%S") @@ -232,17 +234,17 @@ def distribute(config: Union[DictConfig, ListConfig]): def sweep( - run_config: Union[DictConfig, ListConfig], device: int = 0, seed: int = 42, convert_openvino: bool = False -) -> Dict[str, Union[float, str]]: + run_config: DictConfig | ListConfig, device: int = 0, seed: int = 42, convert_openvino: bool = False +) -> dict[str, str | float]: """Go over all the values mentioned in `grid_search` parameter of the benchmarking config. Args: - run_config: (Union[DictConfig, ListConfig], optional): Configuration for current run. + run_config: (DictConfig | ListConfig, optional): Configuration for current run. device (int, optional): Name of the device on which the model is trained. Defaults to 0 "cpu". convert_openvino (bool, optional): Whether to convert the model to openvino format. Defaults to False. Returns: - Dict[str, Union[float, str]]: Dictionary containing the metrics gathered from the sweep. + dict[str, str | float]: Dictionary containing the metrics gathered from the sweep. """ seed_everything(seed, workers=True) # This assumes that `model_name` is always present in the sweep config. diff --git a/tools/benchmarking/utils/metrics.py b/tools/benchmarking/utils/metrics.py index 04bc3dff1f..8b34e93082 100644 --- a/tools/benchmarking/utils/metrics.py +++ b/tools/benchmarking/utils/metrics.py @@ -3,11 +3,12 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import random import string from glob import glob from pathlib import Path -from typing import Dict, List, Optional, Union import pandas as pd from comet_ml import Experiment @@ -17,15 +18,15 @@ def write_metrics( - model_metrics: Dict[str, Union[str, float]], - writers: List[str], - folder: Optional[str] = None, + model_metrics: dict[str, str | float], + writers: list[str], + folder: str | None = None, ): """Writes metrics to destination provided in the sweep config. Args: - model_metrics (Dict): Dictionary to be written - writers (List[str]): List of destinations. + model_metrics (dict): Dictionary to be written + writers (list[str]): List of destinations. folder (optional, str): Sub-directory to which runs are written to. Defaults to None. If none writes to root. """ # Write to file as each run is computed @@ -47,15 +48,15 @@ def write_metrics( def write_to_tensorboard( - model_metrics: Dict[str, Union[str, float]], + model_metrics: dict[str, str | float], ): """Write model_metrics to tensorboard. Args: - model_metrics (Dict[str, Union[str, float]]): Dictionary containing collected results. + model_metrics (dict[str, str | float]): Dictionary containing collected results. """ scalar_metrics = {} - scalar_prefixes: List[str] = [] + scalar_prefixes: list[str] = [] string_metrics = {} for key, metric in model_metrics.items(): if isinstance(metric, (int, float, bool)): @@ -92,7 +93,7 @@ def get_unique_key(str_len: int) -> str: def upload_to_wandb( team: str = "anomalib", - folder: Optional[str] = None, + folder: str | None = None, ): """Upload the data in csv files to wandb. @@ -120,7 +121,7 @@ def upload_to_wandb( def upload_to_comet( - folder: Optional[str] = None, + folder: str | None = None, ): """Upload the data in csv files to comet. diff --git a/tools/hpo/sweep.py b/tools/hpo/sweep.py index d14d43c1d3..6f2a2f7abf 100644 --- a/tools/hpo/sweep.py +++ b/tools/hpo/sweep.py @@ -3,9 +3,10 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + from argparse import ArgumentParser from pathlib import Path -from typing import Union from omegaconf import OmegaConf from pytorch_lightning import seed_everything @@ -39,7 +40,7 @@ def get_args(): seed_everything(model_config.project.seed) # check hpo config structure to see whether it adheres to comet or wandb format - sweep: Union[CometSweep, WandbSweep] + sweep: CometSweep | WandbSweep if "spec" in hpo_config.keys(): sweep = CometSweep(model_config, hpo_config, entity=args.entity) else: diff --git a/tools/inference/gradio_inference.py b/tools/inference/gradio_inference.py index 3fd670bce1..deb0d7f494 100644 --- a/tools/inference/gradio_inference.py +++ b/tools/inference/gradio_inference.py @@ -6,10 +6,11 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + from argparse import ArgumentParser, Namespace from importlib import import_module from pathlib import Path -from typing import Optional, Tuple import gradio as gr import gradio.inputs @@ -41,13 +42,13 @@ def get_args() -> Namespace: return parser.parse_args() -def get_inferencer(config_path: Path, weight_path: Path, meta_data_path: Optional[Path] = None) -> Inferencer: +def get_inferencer(config_path: Path, weight_path: Path, meta_data_path: Path | None = None) -> Inferencer: """Parse args and open inferencer. Args: config_path (Path): Path to model configuration file or the name of the model. weight_path (Path): Path to model weights. - meta_data_path (Optional[Path], optional): Metadata is required for OpenVINO models. Defaults to None. + meta_data_path (Path | None, optional): Metadata is required for OpenVINO models. Defaults to None. Raises: ValueError: If unsupported model weight is passed. @@ -78,7 +79,7 @@ def get_inferencer(config_path: Path, weight_path: Path, meta_data_path: Optiona return inferencer -def infer(image: np.ndarray, inferencer: Inferencer) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: +def infer(image: np.ndarray, inferencer: Inferencer) -> tuple[np.ndarray, np.ndarray, np.ndarray]: """Inference function, return anomaly map, score, heat map, prediction mask ans visualisation. Args: @@ -86,7 +87,7 @@ def infer(image: np.ndarray, inferencer: Inferencer) -> Tuple[np.ndarray, np.nda inferencer (Inferencer): model inferencer Returns: - Tuple[np.ndarray, float, np.ndarray, np.ndarray, np.ndarray]: + tuple[np.ndarray, float, np.ndarray, np.ndarray, np.ndarray]: heat_map, pred_mask, segmentation result. """ # Perform inference for the given image. From 1d48952407ae8c9f2a1ccc5e4644eee953879ad0 Mon Sep 17 00:00:00 2001 From: Samet Date: Tue, 17 Jan 2023 10:14:39 -0700 Subject: [PATCH 77/85] Change the method signature in csflow --- anomalib/models/csflow/lightning_model.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/anomalib/models/csflow/lightning_model.py b/anomalib/models/csflow/lightning_model.py index 19e4a9a8f6..c64c4eb69a 100644 --- a/anomalib/models/csflow/lightning_model.py +++ b/anomalib/models/csflow/lightning_model.py @@ -57,11 +57,11 @@ def __init__( ) self.loss = CsFlowLoss() - def training_step(self, batch, _) -> dict[str, Tensor]: + def training_step(self, batch: dict[str, str | Tensor], *args, **kwargs) -> STEP_OUTPUT: """Training Step of CS-Flow. Args: - batch (Tensor): Input batch + batch (dict[str, str | Tensor]): Input batch _: Index of the batch. Returns: From b89da662f93f223243abefcf596c7a3fe2a54349 Mon Sep 17 00:00:00 2001 From: Samet Date: Tue, 17 Jan 2023 13:54:35 -0700 Subject: [PATCH 78/85] Fix pre-commit --- tests/helpers/detection.py | 2 +- .../utils/callbacks/export_callback/dummy_lightning_model.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/helpers/detection.py b/tests/helpers/detection.py index 10de219e11..7f16b38d7b 100644 --- a/tests/helpers/detection.py +++ b/tests/helpers/detection.py @@ -1,6 +1,6 @@ """Helpers for detection tests.""" import os -import xml.etree.cElementTree as ET # nosec +import xml.etree.ElementTree as ET # nosec from glob import glob from typing import List, Tuple diff --git a/tests/pre_merge/utils/callbacks/export_callback/dummy_lightning_model.py b/tests/pre_merge/utils/callbacks/export_callback/dummy_lightning_model.py index fe7a338565..075a4327b5 100644 --- a/tests/pre_merge/utils/callbacks/export_callback/dummy_lightning_model.py +++ b/tests/pre_merge/utils/callbacks/export_callback/dummy_lightning_model.py @@ -18,7 +18,7 @@ class FakeDataModule(pl.LightningDataModule): def __init__(self, batch_size: int = 32): - super(FakeDataModule, self).__init__() + super().__init__() self.batch_size = batch_size self.pre_process = transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,))]) From 16ece78f02fdd317cfe565d9a975b99b61b72d9c Mon Sep 17 00:00:00 2001 From: Samet Date: Wed, 18 Jan 2023 03:05:07 -0700 Subject: [PATCH 79/85] Fix ganomaly errors --- anomalib/models/ganomaly/lightning_model.py | 22 +++++++++++++-------- anomalib/models/ganomaly/loss.py | 10 ++++++---- anomalib/models/ganomaly/torch_model.py | 8 ++++---- 3 files changed, 24 insertions(+), 16 deletions(-) diff --git a/anomalib/models/ganomaly/lightning_model.py b/anomalib/models/ganomaly/lightning_model.py index 675b5bf767..0034e8ee5e 100644 --- a/anomalib/models/ganomaly/lightning_model.py +++ b/anomalib/models/ganomaly/lightning_model.py @@ -12,9 +12,9 @@ import torch from omegaconf import DictConfig, ListConfig -from pytorch_lightning.callbacks import EarlyStopping +from pytorch_lightning.callbacks import Callback, EarlyStopping from pytorch_lightning.utilities.cli import MODEL_REGISTRY -from pytorch_lightning.utilities.types import STEP_OUTPUT +from pytorch_lightning.utilities.types import EPOCH_OUTPUT, STEP_OUTPUT from torch import Tensor, optim from anomalib.models.components import AnomalyModule @@ -31,7 +31,7 @@ class Ganomaly(AnomalyModule): Args: batch_size (int): Batch size. - input_size (tuple[int,int]): Input dimension. + input_size (tuple[int, int]): Input dimension. n_features (int): Number of features layers in the CNNs. latent_vec_size (int): Size of autoencoder latent vector. extra_layers (int, optional): Number of extra layers for encoder/decoder. Defaults to 0. @@ -111,15 +111,21 @@ def configure_optimizers(self) -> list[optim.Optimizer]: ) return [optimizer_d, optimizer_g] - def training_step(self, batch: dict[str, str | Tensor], optimizer_idx: int, *args, **kwargs) -> STEP_OUTPUT: + def training_step( + self, batch: dict[str, str | Tensor], batch_idx: int, optimizer_idx: int + ) -> STEP_OUTPUT: # pylint: disable=arguments-differ """Training step. Args: batch (dict[str, str | Tensor]): Input batch containing images. + batch_idx (int): Batch index. + optimizer_idx (int): Optimizer which is being called for current training step. Returns: STEP_OUTPUT: Loss """ + del batch_idx # `batch_idx` variables is not used. + # forward pass padded, fake, latent_i, latent_o = self.model(batch["image"]) pred_real, _ = self.model.discriminator(padded) @@ -146,14 +152,14 @@ def validation_step(self, batch: dict[str, str | Tensor], *args, **kwargs) -> ST batch (dict[str, str | Tensor]): Predicted difference between z and z_hat. Returns: - dict[str, Tensor]: batch + (STEP_OUTPUT): Output predictions. """ batch["pred_scores"] = self.model(batch["image"]) self.max_scores = max(self.max_scores, torch.max(batch["pred_scores"])) self.min_scores = min(self.min_scores, torch.min(batch["pred_scores"])) return batch - def validation_epoch_end(self, outputs): + def validation_epoch_end(self, outputs: EPOCH_OUTPUT) -> EPOCH_OUTPUT: """Normalize outputs based on min/max values.""" logger.info("Normalizing validation outputs based on min/max values.") for prediction in outputs: @@ -173,7 +179,7 @@ def test_step(self, batch: dict[str, str | Tensor], batch_idx: int, *args, **kwa self.min_scores = min(self.min_scores, torch.min(batch["pred_scores"])) return batch - def test_epoch_end(self, outputs): + def test_epoch_end(self, outputs: EPOCH_OUTPUT) -> EPOCH_OUTPUT: """Normalize outputs based on min/max values.""" logger.info("Normalizing test outputs based on min/max values.") for prediction in outputs: @@ -222,7 +228,7 @@ def __init__(self, hparams: DictConfig | ListConfig) -> None: self.hparams: DictConfig | ListConfig # type: ignore self.save_hyperparameters(hparams) - def configure_callbacks(self) -> list[EarlyStopping]: + def configure_callbacks(self) -> list[Callback]: """Configure model-specific callbacks. Note: diff --git a/anomalib/models/ganomaly/loss.py b/anomalib/models/ganomaly/loss.py index 77a6e19fba..f432c62aa6 100644 --- a/anomalib/models/ganomaly/loss.py +++ b/anomalib/models/ganomaly/loss.py @@ -3,6 +3,8 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import torch from torch import Tensor, nn @@ -16,7 +18,7 @@ class GeneratorLoss(nn.Module): wenc (int, optional): Latent vector encoder weight. Defaults to 1. """ - def __init__(self, wadv=1, wcon=50, wenc=1): + def __init__(self, wadv=1, wcon=50, wenc=1) -> None: super().__init__() self.loss_enc = nn.SmoothL1Loss() @@ -54,13 +56,13 @@ def forward( class DiscriminatorLoss(nn.Module): """Discriminator loss for the GANomaly model.""" - def __init__(self): + def __init__(self) -> None: super().__init__() self.loss_bce = nn.BCELoss() - def forward(self, pred_real, pred_fake): - """Compye the loss for a predicted batch. + def forward(self, pred_real: Tensor, pred_fake: Tensor) -> Tensor: + """Compute the loss for a predicted batch. Args: pred_real (Tensor): Discriminator predictions for the real image. diff --git a/anomalib/models/ganomaly/torch_model.py b/anomalib/models/ganomaly/torch_model.py index bb96293ebc..bc707e9ab7 100644 --- a/anomalib/models/ganomaly/torch_model.py +++ b/anomalib/models/ganomaly/torch_model.py @@ -207,7 +207,7 @@ class Discriminator(nn.Module): Made of only one encoder layer which takes x and x_hat to produce a score. Args: - input_size (tuple[int,int]): Input image size. + input_size (tuple[int, int]): Input image size. num_input_channels (int): Number of image channels. n_features (int): Number of feature maps in each convolution layer. extra_layers (int, optional): Add extra intermediate layers. Defaults to 0. @@ -243,7 +243,7 @@ class Generator(nn.Module): Made of an encoder-decoder-encoder architecture. Args: - input_size (tuple[int,int]): Size of input data. + input_size (tuple[int, int]): Size of input data. latent_vec_size (int): Dimension of latent vector produced between the first encoder-decoder. num_input_channels (int): Number of channels in input image. n_features (int): Number of feature maps in each convolution layer. @@ -281,7 +281,7 @@ class GanomalyModel(nn.Module): """Ganomaly Model. Args: - input_size (tuple[int,int]): Input dimension. + input_size (tuple[int, int]): Input dimension. num_input_channels (int): Number of input channels. n_features (int): Number of features layers in the CNNs. latent_vec_size (int): Size of autoencoder latent vector. @@ -330,7 +330,7 @@ def weights_init(module: nn.Module) -> None: nn.init.normal_(module.weight.data, 1.0, 0.02) nn.init.constant_(module.bias.data, 0) - def forward(self, batch: Tensor) -> Tensor | tuple[Tensor, Tensor, Tensor, Tensor]: + def forward(self, batch: Tensor) -> tuple[Tensor, Tensor, Tensor, Tensor] | Tensor: """Get scores for batch. Args: From b238fad76459b11049c8285471100957c0b78ac2 Mon Sep 17 00:00:00 2001 From: Samet Akcay Date: Fri, 20 Jan 2023 16:09:45 +0000 Subject: [PATCH 80/85] Update anomalib/models/reverse_distillation/components/bottleneck.py Co-authored-by: Dick Ameln --- anomalib/models/reverse_distillation/components/bottleneck.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/anomalib/models/reverse_distillation/components/bottleneck.py b/anomalib/models/reverse_distillation/components/bottleneck.py index 7b29c9363f..a04f6a8174 100644 --- a/anomalib/models/reverse_distillation/components/bottleneck.py +++ b/anomalib/models/reverse_distillation/components/bottleneck.py @@ -52,7 +52,7 @@ class OCBE(nn.Module): def __init__( self, - block: type[Bottleneck | BasicBlock], + block: Bottleneck | BasicBlock, layers: int, groups: int = 1, width_per_group: int = 64, From fcd21762a8bd3c75ef550d71a5904da4f77aa536 Mon Sep 17 00:00:00 2001 From: Samet Date: Mon, 23 Jan 2023 04:58:15 -0700 Subject: [PATCH 81/85] Address the PR comments. --- anomalib/data/btech.py | 49 ++++++++++++------- .../components/feature_extractors/timm.py | 2 +- anomalib/models/csflow/lightning_model.py | 4 +- 3 files changed, 33 insertions(+), 22 deletions(-) diff --git a/anomalib/data/btech.py b/anomalib/data/btech.py index ae42df03e9..37ec148c70 100644 --- a/anomalib/data/btech.py +++ b/anomalib/data/btech.py @@ -180,25 +180,36 @@ class BTech(AnomalibDataModule): """BTech Lightning Data Module. Args: - root: Path to the BTech dataset - category: Name of the BTech category. - image_size: Variable to which image is resized. - center_crop (int | tuple[int, int] | None, optional): When provided, the images will be center-cropped - to the provided dimensions. - normalize (bool): When True, the images will be normalized to the ImageNet statistics. - train_batch_size: Training batch size. - test_batch_size: Testing batch size. - num_workers: Number of workers. - task: ``classification``, ``detection`` or ``segmentation`` - transform_config_train: Config for pre-processing during training. - transform_config_val: Config for pre-processing during validation. - create_validation_set: Create a validation subset in addition to the train and test subsets - seed (int | None, optional): Seed used during random subset splitting. - test_split_mode (TestSplitMode): Setting that determines how the testing subset is obtained. - test_split_ratio (float): Fraction of images from the train set that will be reserved for testing. - val_split_mode (ValSplitMode): Setting that determines how the validation subset is obtained. - val_split_ratio (float): Fraction of train or test images that will be reserved for validation. - seed (int | None, optional): Seed which may be set to a fixed value for reproducibility. + + root (str): Path to the BTech dataset. + category (str): Name of the BTech category. + image_size (int | tuple[int, int] | None, optional): Variable to which image is resized. Defaults to None. + center_crop (int | tuple[int, int] | None, optional): When provided, the images will be center-cropped to the + provided dimensions. + Defaults to None. + normalization (str | InputNormalizationMethod, optional): When True, the images will be normalized to the + ImageNet statistics. + Defaults to InputNormalizationMethod.IMAGENET. + train_batch_size (int, optional): Training batch size. + Defaults to 32. + eval_batch_size (int, optional): Eval batch size. + Defaults to 32. + num_workers (int, optional): Number of workers. Defaults to 8. + task (TaskType, optional): Task type. + Defaults to TaskType.SEGMENTATION. + transform_config_train (str | A.Compose | None, optional): Config for pre-processing during training. + Defaults to None. + transform_config_eval (str | A.Compose | None, optional): Config for pre-processing during validation. + Defaults to None. + test_split_mode (TestSplitMode, optional): Setting that determines how the testing subset is obtained. + Defaults to TestSplitMode.FROM_DIR. + test_split_ratio (float, optional): Fraction of images from the train set that will be reserved for testing. + Defaults to 0.2. + val_split_mode (ValSplitMode, optional): Setting that determines how the validation subset is obtained. + Defaults to ValSplitMode.SAME_AS_TEST. + val_split_ratio (float, optional): Fraction of train or test images that will be reserved for validation. + Defaults to 0.5. + seed (int | None, optional): Seed which may be set to a fixed value for reproducibility. Defaults to None. Examples: >>> from anomalib.data import BTech diff --git a/anomalib/models/components/feature_extractors/timm.py b/anomalib/models/components/feature_extractors/timm.py index df79a5600c..363cde0320 100644 --- a/anomalib/models/components/feature_extractors/timm.py +++ b/anomalib/models/components/feature_extractors/timm.py @@ -23,7 +23,7 @@ class TimmFeatureExtractor(nn.Module): Args: backbone (nn.Module): The backbone to which the feature extraction hooks are attached. - layers (Iterable[str]): list of layer names of the backbone to which the hooks are attached. + layers (Iterable[str]): List of layer names of the backbone to which the hooks are attached. pre_trained (bool): Whether to use a pre-trained backbone. Defaults to True. requires_grad (bool): Whether to require gradients for the backbone. Defaults to False. Models like ``stfpm`` use the feature extractor model as a trainable network. In such cases gradient diff --git a/anomalib/models/csflow/lightning_model.py b/anomalib/models/csflow/lightning_model.py index c64c4eb69a..ec99fe198b 100644 --- a/anomalib/models/csflow/lightning_model.py +++ b/anomalib/models/csflow/lightning_model.py @@ -12,7 +12,7 @@ import torch from omegaconf import DictConfig, ListConfig -from pytorch_lightning.callbacks import EarlyStopping +from pytorch_lightning.callbacks import Callback, EarlyStopping from pytorch_lightning.utilities.cli import MODEL_REGISTRY from pytorch_lightning.utilities.types import STEP_OUTPUT from torch import Tensor @@ -106,7 +106,7 @@ def __init__(self, hparams: DictConfig | ListConfig) -> None: self.hparams: DictConfig | ListConfig # type: ignore self.save_hyperparameters(hparams) - def configure_callbacks(self) -> list[EarlyStopping]: + def configure_callbacks(self) -> list[Callback]: """Configure model-specific callbacks. Note: From abb990092fa32e459a699f07c4d043926b37661a Mon Sep 17 00:00:00 2001 From: Samet Date: Tue, 24 Jan 2023 07:35:12 -0700 Subject: [PATCH 82/85] Address refurb comments --- anomalib/config/config.py | 2 +- anomalib/data/btech.py | 2 +- anomalib/data/folder.py | 2 +- anomalib/data/mvtec.py | 2 +- anomalib/data/ucsd_ped.py | 2 +- anomalib/data/utils/boxes.py | 4 ++-- anomalib/data/utils/download.py | 4 ++-- anomalib/data/utils/image.py | 2 +- anomalib/data/utils/split.py | 4 +--- anomalib/data/visa.py | 2 +- anomalib/deploy/export.py | 2 +- anomalib/deploy/inferencers/openvino_inferencer.py | 4 ++-- anomalib/models/cflow/torch_model.py | 2 +- anomalib/models/dfm/torch_model.py | 2 +- anomalib/models/fastflow/torch_model.py | 4 ++-- anomalib/models/reverse_distillation/torch_model.py | 2 +- anomalib/models/stfpm/torch_model.py | 2 +- anomalib/post_processing/visualizer.py | 8 ++++---- anomalib/pre_processing/pre_process.py | 2 +- anomalib/pre_processing/tiler.py | 4 ++-- anomalib/utils/callbacks/__init__.py | 10 +++------- anomalib/utils/callbacks/export.py | 4 ++-- anomalib/utils/callbacks/nncf/callback.py | 3 ++- .../utils/callbacks/post_processing_configuration.py | 4 ++-- anomalib/utils/callbacks/visualizer/visualizer_base.py | 4 ++-- anomalib/utils/cli/cli.py | 2 +- anomalib/utils/loggers/__init__.py | 7 ++++--- 27 files changed, 44 insertions(+), 48 deletions(-) diff --git a/anomalib/config/config.py b/anomalib/config/config.py index 2b1ca131fb..1d88a0d5b4 100644 --- a/anomalib/config/config.py +++ b/anomalib/config/config.py @@ -219,7 +219,7 @@ def get_configurable_parameters( Returns: DictConfig | ListConfig: Configurable parameters in DictConfig object. """ - if model_name is None and config_path is None: + if model_name is None is config_path: raise ValueError( "Both model_name and model config path cannot be None! " "Please provide a model name or path to a config file!" diff --git a/anomalib/data/btech.py b/anomalib/data/btech.py index 37ec148c70..87bbcc5a50 100644 --- a/anomalib/data/btech.py +++ b/anomalib/data/btech.py @@ -81,7 +81,7 @@ def make_btech_dataset(path: Path, split: str | Split | None = None) -> DataFram samples_list = [ (str(path),) + filename.parts[-3:] for filename in path.glob("**/*") if filename.suffix in (".bmp", ".png") ] - if len(samples_list) == 0: + if not samples_list: raise RuntimeError(f"Found 0 images in {path}") samples = pd.DataFrame(samples_list, columns=["path", "split", "label", "image_path"]) diff --git a/anomalib/data/folder.py b/anomalib/data/folder.py index bf43d9a1ee..d0a7892abc 100644 --- a/anomalib/data/folder.py +++ b/anomalib/data/folder.py @@ -61,7 +61,7 @@ def _prepare_files_labels( extensions = (extensions,) filenames = [f for f in path.glob(r"**/*") if f.suffix in extensions and not f.is_dir()] - if len(filenames) == 0: + if not filenames: raise RuntimeError(f"Found 0 {path_type} images in {path}") labels = [path_type] * len(filenames) diff --git a/anomalib/data/mvtec.py b/anomalib/data/mvtec.py index f3ec3f8152..963a2c25bd 100644 --- a/anomalib/data/mvtec.py +++ b/anomalib/data/mvtec.py @@ -110,7 +110,7 @@ def make_mvtec_dataset( root = Path(root) samples_list = [(str(root),) + f.parts[-3:] for f in root.glob(r"**/*") if f.suffix in extensions] - if len(samples_list) == 0: + if not samples_list: raise RuntimeError(f"Found 0 images in {root}") samples = DataFrame(samples_list, columns=["path", "split", "label", "image_path"]) diff --git a/anomalib/data/ucsd_ped.py b/anomalib/data/ucsd_ped.py index e775627921..429644a951 100644 --- a/anomalib/data/ucsd_ped.py +++ b/anomalib/data/ucsd_ped.py @@ -70,7 +70,7 @@ def make_ucsd_dataset(path: Path, split: str | Split | None = None) -> DataFrame DataFrame: an output dataframe containing samples for the requested split (ie., train or test) """ folders = [filename for filename in sorted(path.glob("*/*")) if filename.is_dir()] - folders = [folder for folder in folders if len(list(folder.glob("*.tif"))) > 0] + folders = [folder for folder in folders if list(folder.glob("*.tif"))] samples_list = [(str(path),) + folder.parts[-2:] for folder in folders] samples = DataFrame(samples_list, columns=["root", "folder", "image_path"]) diff --git a/anomalib/data/utils/boxes.py b/anomalib/data/utils/boxes.py index f75a5b6a9c..4e5da46c18 100644 --- a/anomalib/data/utils/boxes.py +++ b/anomalib/data/utils/boxes.py @@ -47,8 +47,8 @@ def masks_to_boxes(masks: Tensor, anomaly_maps: Tensor | None = None) -> tuple[l im_boxes.append(Tensor([torch.min(x_loc), torch.min(y_loc), torch.max(x_loc), torch.max(y_loc)])) if anomaly_maps is not None: im_scores.append(torch.max(anomaly_maps[im_idx, y_loc, x_loc])) - batch_boxes.append(torch.stack(im_boxes) if len(im_boxes) > 0 else torch.empty((0, 4))) - batch_scores.append(torch.stack(im_scores) if len(im_scores) > 0 else torch.empty(0)) + batch_boxes.append(torch.stack(im_boxes) if im_boxes else torch.empty((0, 4))) + batch_scores.append(torch.stack(im_scores) if im_scores else torch.empty(0)) return batch_boxes, batch_scores diff --git a/anomalib/data/utils/download.py b/anomalib/data/utils/download.py index 0cfd7ce21b..a70c7c4bf5 100644 --- a/anomalib/data/utils/download.py +++ b/anomalib/data/utils/download.py @@ -209,7 +209,7 @@ def hash_check(file_path: Path, expected_hash: str) -> None: file_path (Path): Path to file. expected_hash (str): Expected hash of the file. """ - with open(file_path, "rb") as hash_file: + with file_path.open("rb") as hash_file: assert ( hashlib.md5(hash_file.read()).hexdigest() == expected_hash ), f"Downloaded file {file_path} does not match the required hash." @@ -243,7 +243,7 @@ def download_and_extract(root: Path, info: DownloadInfo) -> None: if downloaded_file_path.suffix == ".zip": with ZipFile(downloaded_file_path, "r") as zip_file: zip_file.extractall(root) - elif downloaded_file_path.suffix in [".tar", ".gz", ".xz"]: + elif downloaded_file_path.suffix in (".tar", ".gz", ".xz"): with tarfile.open(downloaded_file_path) as tar_file: tar_file.extractall(root) else: diff --git a/anomalib/data/utils/image.py b/anomalib/data/utils/image.py index b3e10d343a..cb3ee898ca 100644 --- a/anomalib/data/utils/image.py +++ b/anomalib/data/utils/image.py @@ -37,7 +37,7 @@ def get_image_filenames(path: str | Path) -> list[Path]: if path.is_dir(): image_filenames = [p for p in path.glob("**/*") if p.suffix in IMG_EXTENSIONS] - if len(image_filenames) == 0: + if image_filenames: raise ValueError(f"Found 0 images in {path}") return image_filenames diff --git a/anomalib/data/utils/split.py b/anomalib/data/utils/split.py index 33e95a863d..e78d49ed5f 100644 --- a/anomalib/data/utils/split.py +++ b/anomalib/data/utils/split.py @@ -102,9 +102,7 @@ def random_split( # split each (label-aware) subset of source data for label_dataset in per_label_datasets: # get subset lengths - subset_lengths = [] - for ratio in split_ratio: - subset_lengths.append(int(math.floor(len(label_dataset.samples) * ratio))) + subset_lengths = [math.floor(len(label_dataset.samples) * ratio) for ratio in split_ratio] for i in range(len(label_dataset.samples) - sum(subset_lengths)): subset_idx = i % sum(subset_lengths) subset_lengths[subset_idx] += 1 diff --git a/anomalib/data/visa.py b/anomalib/data/visa.py index d18ff4fdd9..1935aaa629 100644 --- a/anomalib/data/visa.py +++ b/anomalib/data/visa.py @@ -219,7 +219,7 @@ def apply_cls1_split(self) -> None: test_img_bad_folder.mkdir(parents=True, exist_ok=True) test_mask_bad_folder.mkdir(parents=True, exist_ok=True) - with open(split_file, encoding="utf-8") as file: + with split_file.open(encoding="utf-8") as file: csvreader = csv.reader(file) next(csvreader) for row in csvreader: diff --git a/anomalib/deploy/export.py b/anomalib/deploy/export.py index 558dcb1477..9d2b1da2b5 100644 --- a/anomalib/deploy/export.py +++ b/anomalib/deploy/export.py @@ -69,7 +69,7 @@ def export( # Write metadata to json file. The file is written in the same directory as the target model. export_path: Path = Path(str(export_root)) / export_mode.value export_path.mkdir(parents=True, exist_ok=True) - with open(Path(export_path) / "meta_data.json", "w", encoding="utf-8") as metadata_file: + with (Path(export_path) / "meta_data.json").open("w", encoding="utf-8") as metadata_file: meta_data = get_model_metadata(model) # Convert metadata from torch for key, value in meta_data.items(): diff --git a/anomalib/deploy/inferencers/openvino_inferencer.py b/anomalib/deploy/inferencers/openvino_inferencer.py index b027c15609..5f6dc0f957 100644 --- a/anomalib/deploy/inferencers/openvino_inferencer.py +++ b/anomalib/deploy/inferencers/openvino_inferencer.py @@ -171,7 +171,7 @@ def post_process(self, predictions: np.ndarray, meta_data: dict | DictConfig | N if task == TaskType.CLASSIFICATION: _, pred_score = self._normalize(pred_scores=pred_score, meta_data=meta_data) - elif task in [TaskType.SEGMENTATION, TaskType.DETECTION]: + elif task in (TaskType.SEGMENTATION, TaskType.DETECTION): if "pixel_threshold" in meta_data: pred_mask = (anomaly_map >= meta_data["pixel_threshold"]).astype(np.uint8) @@ -224,5 +224,5 @@ def _get_boxes(mask: np.ndarray) -> np.ndarray: for label in labels[labels != 0]: y_loc, x_loc = np.where(comps == label) boxes.append([np.min(x_loc), np.min(y_loc), np.max(x_loc), np.max(y_loc)]) - boxes = np.stack(boxes) if len(boxes) > 0 else np.empty((0, 4)) + boxes = np.stack(boxes) if boxes else np.empty((0, 4)) return boxes diff --git a/anomalib/models/cflow/torch_model.py b/anomalib/models/cflow/torch_model.py index 1e7b3333c7..2c518475d1 100644 --- a/anomalib/models/cflow/torch_model.py +++ b/anomalib/models/cflow/torch_model.py @@ -59,7 +59,7 @@ def __init__( for parameters in self.encoder.parameters(): parameters.requires_grad = False - self.anomaly_map_generator = AnomalyMapGenerator(image_size=tuple(input_size), pool_layers=self.pool_layers) + self.anomaly_map_generator = AnomalyMapGenerator(image_size=input_size, pool_layers=self.pool_layers) def forward(self, images) -> Any: """Forward-pass images into the network to extract encoder features and compute probability. diff --git a/anomalib/models/dfm/torch_model.py b/anomalib/models/dfm/torch_model.py index b8e719abb8..834464be0d 100644 --- a/anomalib/models/dfm/torch_model.py +++ b/anomalib/models/dfm/torch_model.py @@ -103,7 +103,7 @@ def __init__( self.gaussian_model = SingleClassGaussian() self.score_type = score_type self.layer = layer - self.input_size = tuple(input_size) + self.input_size = input_size self.feature_extractor = FeatureExtractor( backbone=self.backbone, pre_trained=pre_trained, layers=[layer] ).eval() diff --git a/anomalib/models/fastflow/torch_model.py b/anomalib/models/fastflow/torch_model.py index ca8a3c78a7..3720194686 100644 --- a/anomalib/models/fastflow/torch_model.py +++ b/anomalib/models/fastflow/torch_model.py @@ -117,11 +117,11 @@ def __init__( self.input_size = input_size - if backbone in ["cait_m48_448", "deit_base_distilled_patch16_384"]: + if backbone in ("cait_m48_448", "deit_base_distilled_patch16_384"): self.feature_extractor = timm.create_model(backbone, pretrained=pre_trained) channels = [768] scales = [16] - elif backbone in ["resnet18", "wide_resnet50_2"]: + elif backbone in ("resnet18", "wide_resnet50_2"): self.feature_extractor = timm.create_model( backbone, pretrained=pre_trained, diff --git a/anomalib/models/reverse_distillation/torch_model.py b/anomalib/models/reverse_distillation/torch_model.py index 086120390a..576bf56a86 100644 --- a/anomalib/models/reverse_distillation/torch_model.py +++ b/anomalib/models/reverse_distillation/torch_model.py @@ -48,7 +48,7 @@ def __init__( else: image_size = input_size - self.anomaly_map_generator = AnomalyMapGenerator(image_size=tuple(image_size), mode=anomaly_map_mode) + self.anomaly_map_generator = AnomalyMapGenerator(image_size=image_size, mode=anomaly_map_mode) def forward(self, images: Tensor) -> Tensor | list[Tensor] | tuple[list[Tensor]]: """Forward-pass images to the network. diff --git a/anomalib/models/stfpm/torch_model.py b/anomalib/models/stfpm/torch_model.py index dfe6cc15d2..a730c1fe50 100644 --- a/anomalib/models/stfpm/torch_model.py +++ b/anomalib/models/stfpm/torch_model.py @@ -46,7 +46,7 @@ def __init__( image_size = (self.tiler.tile_size_h, self.tiler.tile_size_w) else: image_size = input_size - self.anomaly_map_generator = AnomalyMapGenerator(image_size=tuple(image_size)) + self.anomaly_map_generator = AnomalyMapGenerator(image_size=image_size) def forward(self, images: Tensor) -> Tensor | dict[str, Tensor] | tuple[dict[str, Tensor]]: """Forward-pass images into the network. diff --git a/anomalib/post_processing/visualizer.py b/anomalib/post_processing/visualizer.py index 8ebe4a95de..4086ddbdfe 100644 --- a/anomalib/post_processing/visualizer.py +++ b/anomalib/post_processing/visualizer.py @@ -70,10 +70,10 @@ class Visualizer: """ def __init__(self, mode: str, task: TaskType) -> None: - if mode not in ["full", "simple"]: + if mode not in ("full", "simple"): raise ValueError(f"Unknown visualization mode: {mode}. Please choose one of ['full', 'simple']") self.mode = mode - if task not in [TaskType.CLASSIFICATION, TaskType.DETECTION, TaskType.SEGMENTATION]: + if task not in (TaskType.CLASSIFICATION, TaskType.DETECTION, TaskType.SEGMENTATION): raise ValueError( f"Unknown task type: {mode}. Please choose one of ['classification', 'detection', 'segmentation']" ) @@ -90,9 +90,9 @@ def visualize_batch(self, batch: dict) -> Iterator[np.ndarray]: """ batch_size, _num_channels, height, width = batch["image"].size() for i in range(batch_size): - if "image_path" in batch.keys(): + if "image_path" in batch: image = read_image(path=batch["image_path"][i], image_size=(height, width)) - elif "video_path" in batch.keys(): + elif "video_path" in batch: image = batch["original_image"][i].squeeze().numpy() image = cv2.resize(image, dsize=(width, height), interpolation=cv2.INTER_AREA) else: diff --git a/anomalib/pre_processing/pre_process.py b/anomalib/pre_processing/pre_process.py index 5a3cb56a9d..fac77a427f 100644 --- a/anomalib/pre_processing/pre_process.py +++ b/anomalib/pre_processing/pre_process.py @@ -121,7 +121,7 @@ def get_transforms( ) ) - if config is None and image_size is None: + if config is None is image_size: raise ValueError( "Both config and image_size cannot be `None`. " "Provide either config file to de-serialize transforms " diff --git a/anomalib/pre_processing/tiler.py b/anomalib/pre_processing/tiler.py index ceb69f2f9c..43b148d048 100644 --- a/anomalib/pre_processing/tiler.py +++ b/anomalib/pre_processing/tiler.py @@ -161,7 +161,7 @@ def __init__( if stride is not None: self.stride_h, self.stride_w = self.__validate_size_type(stride) - self.remove_border_count = int(remove_border_count) + self.remove_border_count = remove_border_count self.overlapping = not (self.stride_h == self.tile_size_h and self.stride_w == self.tile_size_w) self.mode = mode @@ -171,7 +171,7 @@ def __init__( "Please ensure stride size is less than or equal than tiling size." ) - if self.mode not in ["padding", "interpolation"]: + if self.mode not in ("padding", "interpolation"): raise ValueError(f"Unknown tiling mode {self.mode}. Available modes are padding and interpolation") self.batch_size: int diff --git a/anomalib/utils/callbacks/__init__.py b/anomalib/utils/callbacks/__init__.py index 735a0240fa..5ee1bfdefc 100644 --- a/anomalib/utils/callbacks/__init__.py +++ b/anomalib/utils/callbacks/__init__.py @@ -98,7 +98,7 @@ def get_callbacks(config: DictConfig | ListConfig) -> list[Callback]: if "normalization_method" in config.model.keys() and not config.model.normalization_method == "none": if config.model.normalization_method == "cdf": - if config.model.name in ["padim", "stfpm"]: + if config.model.name in ("padim", "stfpm"): if "nncf" in config.optimization and config.optimization.nncf.apply: raise NotImplementedError("CDF Score Normalization is currently not compatible with NNCF.") callbacks.append(CdfNormalizationCallback()) @@ -142,7 +142,7 @@ def get_callbacks(config: DictConfig | ListConfig) -> list[Callback]: warnings.warn(f"Export option: {config.optimization.export_mode} not found. Defaulting to no model export") # Add callback to log graph to loggers - if config.logging.log_graph not in [None, False]: + if config.logging.log_graph not in (None, False): callbacks.append(GraphLogger()) return callbacks @@ -184,11 +184,7 @@ def add_visualizer_callback(callbacks: list[Callback], config: DictConfig | List config.visualization.inputs_are_normalized = not config.post_processing.normalization_method == "none" if config.visualization.log_images or config.visualization.save_images or config.visualization.show_images: - image_save_path = ( - config.visualization.image_save_path - if config.visualization.image_save_path - else config.project.path + "/images" - ) + image_save_path = config.visualization.image_save_path or config.project.path + "/images" for callback in (ImageVisualizerCallback, MetricVisualizerCallback): callbacks.append( callback( diff --git a/anomalib/utils/callbacks/export.py b/anomalib/utils/callbacks/export.py index ffb42c8ddd..ced5f25671 100644 --- a/anomalib/utils/callbacks/export.py +++ b/anomalib/utils/callbacks/export.py @@ -6,7 +6,7 @@ from __future__ import annotations import logging -import os +from pathlib import Path import pytorch_lightning as pl from pytorch_lightning import Callback @@ -45,7 +45,7 @@ def on_train_end(self, trainer: pl.Trainer, pl_module: AnomalyModule) -> None: del trainer # `trainer` variable is not used. logger.info("Exporting the model") - os.makedirs(self.dirpath, exist_ok=True) + Path(self.dirpath).mkdir(parents=True) export( model=pl_module, input_size=self.input_size, diff --git a/anomalib/utils/callbacks/nncf/callback.py b/anomalib/utils/callbacks/nncf/callback.py index 7d4754a729..bdc3dbbe08 100644 --- a/anomalib/utils/callbacks/nncf/callback.py +++ b/anomalib/utils/callbacks/nncf/callback.py @@ -6,6 +6,7 @@ from __future__ import annotations import os +from pathlib import Path from typing import Any import pytorch_lightning as pl @@ -88,7 +89,7 @@ def on_train_end(self, trainer: pl.Trainer, pl_module: pl.LightningModule) -> No if self.export_dir is None or self.nncf_ctrl is None: return - os.makedirs(self.export_dir, exist_ok=True) + Path(self.export_dir).mkdir(parents=True) onnx_path = os.path.join(self.export_dir, "model_nncf.onnx") self.nncf_ctrl.export_model(onnx_path) optimize_command = "mo --input_model " + onnx_path + " --output_dir " + self.export_dir diff --git a/anomalib/utils/callbacks/post_processing_configuration.py b/anomalib/utils/callbacks/post_processing_configuration.py index 2adbe144a4..d097277687 100644 --- a/anomalib/utils/callbacks/post_processing_configuration.py +++ b/anomalib/utils/callbacks/post_processing_configuration.py @@ -42,7 +42,7 @@ def __init__( self.normalization_method = normalization_method if threshold_method == ThresholdMethod.ADAPTIVE and all( - i is not None for i in [manual_image_threshold, manual_pixel_threshold] + 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` " @@ -50,7 +50,7 @@ def __init__( ) if threshold_method == ThresholdMethod.MANUAL and all( - i is None for i in [manual_image_threshold, manual_pixel_threshold] + 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` " diff --git a/anomalib/utils/callbacks/visualizer/visualizer_base.py b/anomalib/utils/callbacks/visualizer/visualizer_base.py index cfcf3d3c30..aae8329851 100644 --- a/anomalib/utils/callbacks/visualizer/visualizer_base.py +++ b/anomalib/utils/callbacks/visualizer/visualizer_base.py @@ -37,10 +37,10 @@ def __init__( save_images: bool = True, ) -> None: """Visualizer callback.""" - if mode not in ["full", "simple"]: + if mode not in ("full", "simple"): raise ValueError(f"Unknown visualization mode: {mode}. Please choose one of ['full', 'simple']") self.mode = mode - if task not in [TaskType.CLASSIFICATION, TaskType.DETECTION, TaskType.SEGMENTATION]: + if task not in (TaskType.CLASSIFICATION, TaskType.DETECTION, TaskType.SEGMENTATION): raise ValueError( f"Unknown task type: {mode}. Please choose one of ['classification', 'detection', 'segmentation']" ) diff --git a/anomalib/utils/cli/cli.py b/anomalib/utils/cli/cli.py index 37edc7e8b4..86bc21f4cd 100644 --- a/anomalib/utils/cli/cli.py +++ b/anomalib/utils/cli/cli.py @@ -149,7 +149,7 @@ def __set_default_root_dir(self) -> None: # If `resume_from_checkpoint` is not specified, it means that the project has not been created before. # Therefore, we need to create the project directory first. if config.trainer.resume_from_checkpoint is None: - root_dir = config.trainer.default_root_dir if config.trainer.default_root_dir else "./results" + root_dir = config.trainer.default_root_dir or "./results" model_name = config.model.class_path.split(".")[-1].lower() data_name = config.data.class_path.split(".")[-1].lower() category = config.data.init_args.category if "category" in config.data.init_args else "" diff --git a/anomalib/utils/loggers/__init__.py b/anomalib/utils/loggers/__init__.py index fa28f64ada..2e493f639b 100644 --- a/anomalib/utils/loggers/__init__.py +++ b/anomalib/utils/loggers/__init__.py @@ -8,6 +8,7 @@ import logging import os import warnings +from pathlib import Path from typing import Iterable from omegaconf.dictconfig import DictConfig @@ -87,7 +88,7 @@ def get_experiment_logger( else: config.logging.logger = config.project.logger - if config.logging.logger in [None, False]: + if config.logging.logger in (None, False): return False logger_list: list[LightningLoggerBase] = [] @@ -105,7 +106,7 @@ def get_experiment_logger( ) elif experiment_logger == "wandb": wandb_logdir = os.path.join(config.project.path, "logs") - os.makedirs(wandb_logdir, exist_ok=True) + Path(wandb_logdir).mkdir(parents=True) name = ( config.model.name if "category" not in config.dataset.keys() @@ -120,7 +121,7 @@ def get_experiment_logger( ) elif experiment_logger == "comet": comet_logdir = os.path.join(config.project.path, "logs") - os.makedirs(comet_logdir, exist_ok=True) + Path(comet_logdir).mkdir(parents=True) run_name = ( config.model.name if "category" not in config.dataset.keys() From d47e149c3cd360a15b7c248e9b803db7c6a405af Mon Sep 17 00:00:00 2001 From: Samet Date: Tue, 24 Jan 2023 09:35:06 -0700 Subject: [PATCH 83/85] add exits_ok=True to address failed tests --- anomalib/utils/callbacks/export.py | 2 +- anomalib/utils/callbacks/nncf/callback.py | 2 +- anomalib/utils/loggers/__init__.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/anomalib/utils/callbacks/export.py b/anomalib/utils/callbacks/export.py index ced5f25671..65ae1ff53a 100644 --- a/anomalib/utils/callbacks/export.py +++ b/anomalib/utils/callbacks/export.py @@ -45,7 +45,7 @@ def on_train_end(self, trainer: pl.Trainer, pl_module: AnomalyModule) -> None: del trainer # `trainer` variable is not used. logger.info("Exporting the model") - Path(self.dirpath).mkdir(parents=True) + Path(self.dirpath).mkdir(parents=True, exist_ok=True) export( model=pl_module, input_size=self.input_size, diff --git a/anomalib/utils/callbacks/nncf/callback.py b/anomalib/utils/callbacks/nncf/callback.py index bdc3dbbe08..51ca1de386 100644 --- a/anomalib/utils/callbacks/nncf/callback.py +++ b/anomalib/utils/callbacks/nncf/callback.py @@ -89,7 +89,7 @@ def on_train_end(self, trainer: pl.Trainer, pl_module: pl.LightningModule) -> No if self.export_dir is None or self.nncf_ctrl is None: return - Path(self.export_dir).mkdir(parents=True) + Path(self.export_dir).mkdir(parents=True, exist_ok=True) onnx_path = os.path.join(self.export_dir, "model_nncf.onnx") self.nncf_ctrl.export_model(onnx_path) optimize_command = "mo --input_model " + onnx_path + " --output_dir " + self.export_dir diff --git a/anomalib/utils/loggers/__init__.py b/anomalib/utils/loggers/__init__.py index 2e493f639b..98a4caa0af 100644 --- a/anomalib/utils/loggers/__init__.py +++ b/anomalib/utils/loggers/__init__.py @@ -106,7 +106,7 @@ def get_experiment_logger( ) elif experiment_logger == "wandb": wandb_logdir = os.path.join(config.project.path, "logs") - Path(wandb_logdir).mkdir(parents=True) + Path(wandb_logdir).mkdir(parents=True, exist_ok=True) name = ( config.model.name if "category" not in config.dataset.keys() @@ -121,7 +121,7 @@ def get_experiment_logger( ) elif experiment_logger == "comet": comet_logdir = os.path.join(config.project.path, "logs") - Path(comet_logdir).mkdir(parents=True) + Path(comet_logdir).mkdir(parents=True, exist_ok=True) run_name = ( config.model.name if "category" not in config.dataset.keys() From 7be9eb56d33609b84e6cd93a429799e908038509 Mon Sep 17 00:00:00 2001 From: Samet Date: Tue, 24 Jan 2023 14:08:32 -0700 Subject: [PATCH 84/85] Fix tests --- anomalib/models/csflow/torch_model.py | 57 +++++++++++++++------------ anomalib/models/dfm/torch_model.py | 2 +- 2 files changed, 33 insertions(+), 26 deletions(-) diff --git a/anomalib/models/csflow/torch_model.py b/anomalib/models/csflow/torch_model.py index aa785ce12f..153672f73e 100644 --- a/anomalib/models/csflow/torch_model.py +++ b/anomalib/models/csflow/torch_model.py @@ -401,11 +401,14 @@ def __init__( self.graph = self._create_graph() def _create_graph(self) -> GraphINN: - nodes = [] + nodes: list[Node] = [] # 304 is the number of features extracted from EfficientNet-B5 feature extractor - nodes.append(InputNode(304, (self.input_dims[1] // 32), (self.input_dims[2] // 32), name="input")) - nodes.append(InputNode(304, (self.input_dims[1] // 64), (self.input_dims[2] // 64), name="input2")) - nodes.append(InputNode(304, (self.input_dims[1] // 128), (self.input_dims[2] // 128), name="input3")) + input_nodes = [ + InputNode(304, (self.input_dims[1] // 32), (self.input_dims[2] // 32), name="input"), + InputNode(304, (self.input_dims[1] // 64), (self.input_dims[2] // 64), name="input2"), + InputNode(304, (self.input_dims[1] // 128), (self.input_dims[2] // 128), name="input3"), + ] + nodes.extend(input_nodes) for coupling_block in range(self.n_coupling_blocks): if coupling_block == 0: @@ -413,27 +416,33 @@ def _create_graph(self) -> GraphINN: else: node_to_permute = [nodes[-1].out0, nodes[-1].out1, nodes[-1].out2] - nodes.append( - Node(node_to_permute, ParallelPermute, {"seed": coupling_block}, name=f"permute_{coupling_block}") + permute_node = Node( + inputs=node_to_permute, + module_type=ParallelPermute, + module_args={"seed": coupling_block}, + name=f"permute_{coupling_block}", ) - nodes.append( - Node( - [nodes[-1].out0, nodes[-1].out1, nodes[-1].out2], - ParallelGlowCouplingLayer, - { - "clamp": self.clamp, - "subnet_args": { - "channels_hidden": self.cross_conv_hidden_channels, - "kernel_size": self.kernel_sizes[coupling_block], - }, + nodes.extend([permute_node]) + coupling_layer_node = Node( + inputs=[nodes[-1].out0, nodes[-1].out1, nodes[-1].out2], + module_type=ParallelGlowCouplingLayer, + module_args={ + "clamp": self.clamp, + "subnet_args": { + "channels_hidden": self.cross_conv_hidden_channels, + "kernel_size": self.kernel_sizes[coupling_block], }, - name=f"fc1_{coupling_block}", - ) + }, + name=f"fc1_{coupling_block}", ) - - nodes.append(OutputNode([nodes[-1].out0], name="output_end0")) - nodes.append(OutputNode([nodes[-2].out1], name="output_end1")) - nodes.append(OutputNode([nodes[-3].out2], name="output_end2")) + nodes.extend([coupling_layer_node]) + + output_nodes = [ + OutputNode([nodes[-1].out0], name="output_end0"), + OutputNode([nodes[-1].out1], name="output_end1"), + OutputNode([nodes[-1].out2], name="output_end2"), + ] + nodes.extend(output_nodes) return GraphINN(nodes) def forward(self, inputs: Tensor) -> tuple[Tensor, Tensor]: @@ -554,9 +563,7 @@ def _compute_anomaly_scores(self, z_dists: Tensor) -> Tensor: Tensor: Anomaly scores. """ # z_dist is a 3 length list of tensors with shape b x 304 x fx x fy - flat_maps: list[Tensor] = [] - for z_dist in z_dists: - flat_maps.append(z_dist.reshape(z_dist.shape[0], -1)) + flat_maps = [z_dist.reshape(z_dist.shape[0], -1) for z_dist in z_dists] flat_maps_tensor = torch.cat(flat_maps, dim=1) anomaly_scores = torch.mean(flat_maps_tensor**2 / 2, dim=1) return anomaly_scores diff --git a/anomalib/models/dfm/torch_model.py b/anomalib/models/dfm/torch_model.py index 834464be0d..05a61c37c8 100644 --- a/anomalib/models/dfm/torch_model.py +++ b/anomalib/models/dfm/torch_model.py @@ -103,7 +103,7 @@ def __init__( self.gaussian_model = SingleClassGaussian() self.score_type = score_type self.layer = layer - self.input_size = input_size + self.input_size = input_size if isinstance(input_size, tuple) else tuple(input_size) self.feature_extractor = FeatureExtractor( backbone=self.backbone, pre_trained=pre_trained, layers=[layer] ).eval() From 936e0ca9bec02985a04b6a85a20f7c3279c09964 Mon Sep 17 00:00:00 2001 From: Samet Akcay Date: Thu, 26 Jan 2023 16:14:23 +0000 Subject: [PATCH 85/85] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0b4902538..2df1029957 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Added +- Add `pyupgrade` to `pre-commit` configs, and refactor based on `pyupgrade` and `refurb` () - Add [CFA](https://arxiv.org/abs/2206.04325) model implementation () - Add RKDE model implementation () - Add Visual Anomaly (VisA) dataset adapter ()