From 434a7addeea8adc8f39e5f1182b1dd1ba6ad9468 Mon Sep 17 00:00:00 2001 From: jpcbertoldo <24547377+jpcbertoldo@users.noreply.github.com> Date: Thu, 1 Sep 2022 21:14:53 +0200 Subject: [PATCH 01/38] copy mvtec and add some config conts in comments --- anomalib/data/mvtec_loco.py | 556 ++++++++++++++++++++++++++++++++++++ 1 file changed, 556 insertions(+) create mode 100644 anomalib/data/mvtec_loco.py diff --git a/anomalib/data/mvtec_loco.py b/anomalib/data/mvtec_loco.py new file mode 100644 index 0000000000..940f2b4927 --- /dev/null +++ b/anomalib/data/mvtec_loco.py @@ -0,0 +1,556 @@ +""" + +distinguishes structural and logical anomalies +n_image: 3644 +splits: +no overlap and fixed +train +normal-only +n_image_train: 1772 +validation +normal-only +n_image_validation: 304 +test +normal + anomalous (structural and logical) +n_image_test: 1568 +n_category: 5 +breakfast_box +juice_bottle +pushpins +screw_bag +splicing_connectors +n_defect_type: 89 + +################################################## + +configs +https://docs.google.com/spreadsheets/d/1qHbyTsU2At1fusQsV8KH3_qdp3SYHHLy7QtpohKJIk0/edit?usp=sharing + +stats overview +https://docs.google.com/spreadsheets/d/11GSf1SVsHFYDSwMAULEd7g5QK7P3Y21YMB10D_g0-Gk/edit?usp=sharing + +################################################## + +assumptions +objects are in a fixed position (mechanical alignment) +illumination is well suited +the access to images with real anomalies is limited (“impossible”) +images only show a single object or logically ensemble set of objects (i.e. one-class setting although a “class” here is a composed object) +no training annotations -- although it is assumed that the images in training are indeed from the target class (i.e. no noise) +problem 1 (image-wise anomaly detection): “is there an anomaly in the image?” +problem 2 (pixel-wise anomaly detection or anomaly segmentation): “which pixels belong to the anomaly?” +pixel-wise metric: Saturated Per-Region Overlap (sPRO) +structural anomaly pixel annotation policy +defects are confined to local regions +each pixel that introduces a visual structure that is not present in the anomaly-free images is anomalous +logical anomaly pixel annotation policy +the union of all areas of the image that could be the cause for the anomaly is anomalous +a method is not necessarily required to predict the whole ground truth area as anomalous + +################################################## + +breakfast box +n_anomaly_type (n_structural, n_logical): 22 (5, 17) +logical constraints +contains 2 tangerines +contains 1 nectarine +the tangerines and the nectarine on the left +cereals (C) and a mix of banana chips and almonds (B&A) on the right +the ratio between C and B&A is fixed +the relative position of C and B&A is fixed +examples of logical defects +too many banana chips and almonds + +################################################## + +juice bottle +n_anomaly_type (n_structural, n_logical): 18 (7, 11) +logical constraints +there is 1 bottle +the bottle is filled with a liquid and the fill level is always the same +the liquid is of 1 out of 3 colors (red, yellow, white-ish) +the bottle carries 2 labels +the first label is attached to the center of the bottle +the first label displays an icon that determines the type of liquid (cherry, orange, banana) +cherry: red +orange: yellow +banana: white-ish +the second label is attached to the lower part of the bottle +the second label contains the text “100% Juice” +examples of logical defects +(left) the icon does not match the type of juice +(middle) the icon is slightly misplaced +(right) the fill level is too high + +################################################## + +pushpins +n_anomaly_type (n_structural, n_logical): 8 (4, 4) +logical constraints +each compartment contains 1 pushpin +examples of logical defects +1 compartment has a missing pin + +################################################## + +screw bag +n_anomaly_type (n_structural, n_logical): 20 (4, 16) +logical constraints +the bag contains +2 washers +2 nuts +1 long screw +2 short screw +examples of logical defects +two long screws and lacks a short one + +################################################## + +splicing connectors +n_anomaly_type (n_structural, n_logical): 21 (8, 13) +logical constraints +there are 2 splicing connectors +they have the same number of cable clamps +they are linked by 1 cable +the number of clamps has a one-to-one correspondence to the color of the cable +2: yellow +3: blue +5: red +the cable has to terminate in the same relative position on its two ends such that the whole construction exhibits a mirror symmetry +examples of logical defects +(left) the two splicing connectors do not have the same number of clamps +(center) the color of the cable does not match the number of clamps +(right) the cable terminates in different positions + +################################################## + +missing objects +the area in which the object could occur +the saturation threshold is chosen to be equal to the area of the missing object +the saturation threshold for an object is chosen from the lower end of the distribution of its (manually annotated) area +example (image): pushpin +the missing pushpin can occur anywhere inside its compartment, therefore its entire area is annotated +the saturation threshold is set to the size of a pushpin + +################################################## + +additional objects +too many instances of an object: all instances of the object are annotated +the saturation threshold is set to the area of the extraneous objects +example (image): splicing connectors +an additional cable is present between the two splicing connectors +it is not clear which of the two cables represents the anomaly, therefore both are annotated +the saturation threshold is set to the area of one cable (i.e., half of the annotated region) +properties +a method can obtain a perfect score even if it only marks one of the two cables as an anomaly +a method that marks both is neither penalized nor (extra-)rewarded + +################################################## + +other logical constraints +example (image, left): juice bottle +the bottle is filled with orange juice but carries the label of the cherry juice +both the orange juice and the label with the cherry are present in the training set, but the logical anomaly arises due to the erroneous combination of the two in the same image +either the area filled with juice or the cherry as could be considered anomalous, therefore the union of the two regions is annotated +the saturation threshold is set to the area of the cherry because the segmentation of the cherry is sufficient to solve the anomaly localization + +""" + + +""" +category anomaly type gt_value saturation_definition saturation_parameter +breakfast_box missing_almonds logical 255 relative_to_anomaly 1.0000000 +breakfast_box missing_bananas logical 254 relative_to_anomaly 1.0000000 +breakfast_box missing_toppings logical 253 relative_to_anomaly 1.0000000 +breakfast_box missing_cereals logical 252 relative_to_anomaly 1.0000000 +breakfast_box missing_cereals_and_toppings logical 251 relative_to_anomaly 1.0000000 +breakfast_box nectarines_2_tangerine_1 logical 250 relative_to_image 0.0488770 +breakfast_box nectarine_1_tangerine_1 logical 249 relative_to_image 0.0411621 +breakfast_box nectarines_0_tangerines_2 logical 248 relative_to_image 0.0488770 +breakfast_box nectarines_0_tangerines_3 logical 247 relative_to_image 0.0488770 +breakfast_box nectarines_3_tangerines_0 logical 246 relative_to_image 0.0977539 +breakfast_box nectarines_0_tangerine_1 logical 245 relative_to_image 0.0900391 +breakfast_box nectarines_0_tangerines_0 logical 244 relative_to_image 0.1312012 +breakfast_box nectarines_0_tangerines_4 logical 243 relative_to_image 0.0823242 +breakfast_box compartments_swapped logical 242 relative_to_anomaly 1.0000000 +breakfast_box overflow logical 241 relative_to_anomaly 1.0000000 +breakfast_box underflow logical 240 relative_to_anomaly 1.0000000 +breakfast_box wrong_ratio logical 239 relative_to_anomaly 1.0000000 +breakfast_box mixed_cereals structural 238 relative_to_anomaly 1.0000000 +breakfast_box fruit_damaged structural 237 relative_to_anomaly 1.0000000 +breakfast_box box_damaged structural 236 relative_to_anomaly 1.0000000 +breakfast_box toppings_crushed structural 235 relative_to_anomaly 1.0000000 +breakfast_box contamination structural 234 relative_to_anomaly 1.0000000 +juice_box missing_top_label logical 255 relative_to_image 0.0550000 +juice_box missing_bottom_label logical 254 relative_to_image 0.0255469 +juice_box swapped_labels logical 253 relative_to_image 0.1100000 +juice_box damaged_label structural 252 relative_to_anomaly 1.0000000 +juice_box rotated_label structural 251 relative_to_anomaly 1.0000000 +juice_box misplaced_label_top logical 250 relative_to_image 0.0550000 +juice_box misplaced_label_bottom logical 249 relative_to_image 0.0255469 +juice_box label_text_incomplete structural 248 relative_to_anomaly 1.0000000 +juice_box empty_bottle logical 247 relative_to_anomaly 1.0000000 +juice_box wrong_fill_level_too_much logical 246 relative_to_anomaly 1.0000000 +juice_box wrong_fill_level_not_enough logical 245 relative_to_anomaly 1.0000000 +juice_box misplaced_fruit_icon logical 244 relative_to_anomaly 1.0000000 +juice_box missing_fruit_icon logical 243 relative_to_anomaly 1.0000000 +juice_box unknown_fruit_icon structural 242 relative_to_anomaly 1.0000000 +juice_box incomplete_fruit_icon structural 241 relative_to_anomaly 1.0000000 +juice_box wrong_juice_type logical 240 relative_to_image 0.0035156 +juice_box juice_color structural 239 relative_to_anomaly 1.0000000 +juice_box contamination structural 238 relative_to_anomaly 1.0000000 +pushpins additional_1_pushpin logical 255 relative_to_image 0.0037059 +pushpins additional_2_pushpins logical 254 relative_to_image 0.0074118 +pushpins missing_pushpin logical 253 relative_to_image 0.0037059 +pushpins missing_separator logical 252 relative_to_anomaly 1.0000000 +pushpins front_bent structural 251 relative_to_anomaly 1.0000000 +pushpins broken structural 250 relative_to_anomaly 1.0000000 +pushpins color structural 249 relative_to_anomaly 1.0000000 +pushpins contamination structural 248 relative_to_anomaly 1.0000000 +screw_bag screw_too_long logical 255 relative_to_image 0.0051136 +screw_bag screw_too_shor logical 254 relative_to_image 0.0051136 +screw_bag screws_1_very_short logical 253 relative_to_anomaly 1.0000000 +screw_bag screws_2_very_short logical 252 relative_to_image 0.0102273 +screw_bag additional_1_long_screw logical 251 relative_to_image 0.0168182 +screw_bag additional_1_short_screw logical 250 relative_to_image 0.0117045 +screw_bag additional_1_nut_ logical 249 relative_to_image 0.0042614 +screw_bag additional_2_nuts_ logical 248 relative_to_image 0.0085227 +screw_bag additional_1_washer_ logical 247 relative_to_image 0.0031250 +screw_bag additional_2_washers_ logical 246 relative_to_image 0.0062500 +screw_bag missing_1_long_screw logical 245 relative_to_image 0.0168182 +screw_bag missing_1_short_screw logical 244 relative_to_image 0.0117045 +screw_bag missing_1_nut logical 243 relative_to_image 0.0042614 +screw_bag missing_2_nuts logical 242 relative_to_image 0.0085227 +screw_bag missing_1_washer logical 241 relative_to_image 0.0031250 +screw_bag missing_2_washers logical 240 relative_to_image 0.0062500 +screw_bag bag_broken structural 239 relative_to_anomaly 1.0000000 +screw_bag color structural 238 relative_to_anomaly 1.0000000 +screw_bag contamination structural 237 relative_to_anomaly 1.0000000 +screw_bag part_broken structural 236 relative_to_anomaly 1.0000000 +splicing_connectors wrong_connector_type_5_2 logical 255 relative_to_image 0.0464360 +splicing_connectors wrong_connector_type_5_3 logical 254 relative_to_image 0.0306574 +splicing_connectors wrong_connector_type_3_2 logical 253 relative_to_image 0.0152941 +splicing_connectors cable_too_short_t2 logical 252 relative_to_image 0.0368858 +splicing_connectors cable_too_short_t3 logical 251 relative_to_image 0.0526644 +splicing_connectors cable_too_short_t5 logical 250 relative_to_image 0.0830450 +splicing_connectors missing_connector logical 249 relative_to_anomaly 1.0000000 +splicing_connectors missing_connector_and_cable logical 248 relative_to_image 0.0716955 +splicing_connectors missing_cable logical 247 relative_to_image 0.0124567 +splicing_connectors extra_cable logical 246 relative_to_anomaly 0.5000000 +splicing_connectors cable_color logical 245 relative_to_image 0.0124567 +splicing_connectors broken_cable structural 244 relative_to_anomaly 1.0000000 +splicing_connectors cable_cut logical 243 relative_to_anomaly 1.0000000 +splicing_connectors cable_not_plugged structural 242 relative_to_anomaly 1.0000000 +splicing_connectors unknown_cable_color structural 241 relative_to_anomaly 1.0000000 +splicing_connectors wrong_cable_location logical 240 relative_to_image 0.0124567 +splicing_connectors flipped_connector structural 239 relative_to_anomaly 1.0000000 +splicing_connectors broken_connector structural 238 relative_to_anomaly 1.0000000 +splicing_connectors open_lever structural 237 relative_to_anomaly 1.0000000 +splicing_connectors color structural 236 relative_to_anomaly 1.0000000 +splicing_connectors contamination structural 235 relative_to_anomaly 1.0000000 +""" + +import logging +import tarfile +import warnings +from pathlib import Path +from typing import Dict, Optional, Tuple, Union +from urllib.request import urlretrieve + +import albumentations as A +import cv2 +import numpy as np +import pandas as pd +from pandas.core.frame import DataFrame +from pytorch_lightning.core.datamodule import LightningDataModule +from pytorch_lightning.utilities.cli import DATAMODULE_REGISTRY +from pytorch_lightning.utilities.types import EVAL_DATALOADERS, TRAIN_DATALOADERS +from torch import Tensor +from torch.utils.data import DataLoader +from torch.utils.data.dataset import Dataset +from torchvision.datasets.folder import VisionDataset + +from anomalib.data.inference import InferenceDataset +from anomalib.data.utils import DownloadProgressBar, hash_check, read_image +from anomalib.data.utils.split import ( + create_validation_set_from_test_set, + split_normal_images_in_train_set, +) +from anomalib.pre_processing import PreProcessor + +logger = logging.getLogger(__name__) + + +def make_mvtec_loco_dataset( + path: Path, + split: Optional[str] = None, + split_ratio: float = 0.1, + seed: Optional[int] = None, + create_validation_set: bool = False, +) -> DataFrame: + samples_list = [(str(path),) + filename.parts[-3:] for filename in path.glob("**/*.png")] + if len(samples_list) == 0: + raise RuntimeError(f"Found 0 images in {path}") + + samples = pd.DataFrame(samples_list, columns=["path", "split", "label", "image_path"]) + samples = samples[samples.split != "ground_truth"] + + # Create mask_path column + samples["mask_path"] = ( + samples.path + + "/ground_truth/" + + samples.label + + "/" + + samples.image_path.str.rstrip("png").str.rstrip(".") + + "_mask.png" + ) + + # Modify image_path column by converting to absolute path + samples["image_path"] = samples.path + "/" + samples.split + "/" + samples.label + "/" + samples.image_path + + # Split the normal images in training set if test set doesn't + # contain any normal images. This is needed because AUC score + # cannot be computed based on 1-class + if sum((samples.split == "test") & (samples.label == "good")) == 0: + samples = split_normal_images_in_train_set(samples, split_ratio, seed) + + # Good images don't have mask + samples.loc[(samples.split == "test") & (samples.label == "good"), "mask_path"] = "" + + # Create label index for normal (0) and anomalous (1) images. + samples.loc[(samples.label == "good"), "label_index"] = 0 + samples.loc[(samples.label != "good"), "label_index"] = 1 + samples.label_index = samples.label_index.astype(int) + + if create_validation_set: + samples = create_validation_set_from_test_set(samples, seed=seed) + + # Get the data frame for the split. + if split is not None and split in ["train", "val", "test"]: + samples = samples[samples.split == split] + samples = samples.reset_index(drop=True) + + return samples + + +class MVTecLOCODataset(VisionDataset): + """MVTec LOCO AD PyTorch Dataset.""" + + def __init__( + self, + root: Union[Path, str], + category: str, + pre_process: PreProcessor, + split: str, + task: str = "segmentation", + seed: Optional[int] = None, + create_validation_set: bool = False, + ) -> None: + super().__init__(root) + + if seed is None: + warnings.warn( + "seed is None." + " When seed is not set, images from the normal directory are split between training and test dir." + " This will lead to inconsistency between runs." + ) + + self.root = Path(root) if isinstance(root, str) else root + self.category: str = category + self.split = split + self.task = task + + self.pre_process = pre_process + + self.samples = make_mvtec_loco_dataset( + path=self.root / category, + split=self.split, + seed=seed, + create_validation_set=create_validation_set, + ) + + def __len__(self) -> int: + """Get length of the dataset.""" + return len(self.samples) + + def __getitem__(self, index: int) -> Dict[str, Union[str, Tensor]]: + """Get dataset item for the index ``index``. + + Args: + index (int): Index to get the item. + + Returns: + Union[Dict[str, Tensor], Dict[str, Union[str, Tensor]]]: Dict of image tensor during training. + Otherwise, Dict containing image path, target path, image tensor, label and transformed bounding box. + """ + item: Dict[str, Union[str, Tensor]] = {} + + image_path = self.samples.image_path[index] + image = read_image(image_path) + + pre_processed = self.pre_process(image=image) + item = {"image": pre_processed["image"]} + + if self.split in ["val", "test"]: + label_index = self.samples.label_index[index] + + item["image_path"] = image_path + item["label"] = label_index + + if self.task == "segmentation": + mask_path = self.samples.mask_path[index] + + # Only Anomalous (1) images has masks in MVTec AD dataset. + # Therefore, create empty mask for Normal (0) images. + if label_index == 0: + mask = np.zeros(shape=image.shape[:2]) + else: + mask = cv2.imread(mask_path, flags=0) / 255.0 + + pre_processed = self.pre_process(image=image, mask=mask) + + item["mask_path"] = mask_path + item["image"] = pre_processed["image"] + item["mask"] = pre_processed["mask"] + + return item + + +@DATAMODULE_REGISTRY +class MVTecLOCO(LightningDataModule): + """MVTec LOCO AD Lightning Data Module.""" + + def __init__( + self, + root: str, + category: str, + # TODO: Remove default values. IAAALD-211 + image_size: Optional[Union[int, Tuple[int, int]]] = None, + train_batch_size: int = 32, + test_batch_size: int = 32, + num_workers: int = 8, + task: str = "segmentation", + transform_config_train: Optional[Union[str, A.Compose]] = None, + transform_config_val: Optional[Union[str, A.Compose]] = None, + seed: Optional[int] = None, + create_validation_set: bool = False, + ) -> None: + super().__init__() + + self.root = root if isinstance(root, Path) else Path(root) + self.category = category + self.dataset_path = self.root / self.category + self.transform_config_train = transform_config_train + self.transform_config_val = transform_config_val + self.image_size = image_size + + if self.transform_config_train is not None and self.transform_config_val is None: + self.transform_config_val = self.transform_config_train + + self.pre_process_train = PreProcessor(config=self.transform_config_train, image_size=self.image_size) + self.pre_process_val = PreProcessor(config=self.transform_config_val, image_size=self.image_size) + + self.train_batch_size = train_batch_size + self.test_batch_size = test_batch_size + self.num_workers = num_workers + + self.create_validation_set = create_validation_set + self.task = task + self.seed = seed + + self.train_data: Dataset + self.test_data: Dataset + if create_validation_set: + self.val_data: Dataset + self.inference_data: Dataset + + def prepare_data(self) -> None: + """Download the dataset if not available.""" + if (self.root / self.category).is_dir(): + logger.info("Found the dataset.") + else: + self.root.mkdir(parents=True, exist_ok=True) + + logger.info("Downloading the Mvtec AD dataset.") + url = "https://www.mydrive.ch/shares/38536/3830184030e49fe74747669442f0f282/download/420938113-1629952094" + dataset_name = "mvtec_anomaly_detection.tar.xz" + zip_filename = self.root / dataset_name + with DownloadProgressBar(unit="B", unit_scale=True, miniters=1, desc="MVTec AD") as progress_bar: + urlretrieve( + url=f"{url}/{dataset_name}", + filename=zip_filename, + reporthook=progress_bar.update_to, + ) + logger.info("Checking hash") + hash_check(zip_filename, "eefca59f2cede9c3fc5b6befbfec275e") + + logger.info("Extracting the dataset.") + with tarfile.open(zip_filename) as tar_file: + tar_file.extractall(self.root) + + logger.info("Cleaning the tar file") + (zip_filename).unlink() + + def setup(self, stage: Optional[str] = None) -> None: + """Setup train, validation and test data. + + Args: + stage: Optional[str]: Train/Val/Test stages. (Default value = None) + + """ + logger.info("Setting up train, validation, test and prediction datasets.") + if stage in (None, "fit"): + self.train_data = MVTecDataset( + root=self.root, + category=self.category, + pre_process=self.pre_process_train, + split="train", + task=self.task, + seed=self.seed, + create_validation_set=self.create_validation_set, + ) + + if self.create_validation_set: + self.val_data = MVTecDataset( + root=self.root, + category=self.category, + pre_process=self.pre_process_val, + split="val", + task=self.task, + seed=self.seed, + create_validation_set=self.create_validation_set, + ) + + self.test_data = MVTecLOCODataset( + root=self.root, + category=self.category, + pre_process=self.pre_process_val, + split="test", + task=self.task, + seed=self.seed, + create_validation_set=self.create_validation_set, + ) + + if stage == "predict": + self.inference_data = InferenceDataset( + path=self.root, image_size=self.image_size, transform_config=self.transform_config_val + ) + + def train_dataloader(self) -> TRAIN_DATALOADERS: + """Get train dataloader.""" + return DataLoader(self.train_data, shuffle=True, batch_size=self.train_batch_size, num_workers=self.num_workers) + + def val_dataloader(self) -> EVAL_DATALOADERS: + """Get validation dataloader.""" + dataset = self.val_data if self.create_validation_set else self.test_data + return DataLoader(dataset=dataset, shuffle=False, batch_size=self.test_batch_size, num_workers=self.num_workers) + + def test_dataloader(self) -> EVAL_DATALOADERS: + """Get test dataloader.""" + return DataLoader(self.test_data, shuffle=False, batch_size=self.test_batch_size, num_workers=self.num_workers) + + def predict_dataloader(self) -> EVAL_DATALOADERS: + """Get predict dataloader.""" + return DataLoader( + self.inference_data, shuffle=False, batch_size=self.test_batch_size, num_workers=self.num_workers + ) From 92ad7e24f6b11b6a7298e27dfc78f6cf32e1b233 Mon Sep 17 00:00:00 2001 From: jpcbertoldo <24547377+jpcbertoldo@users.noreply.github.com> Date: Sat, 3 Sep 2022 22:02:29 +0200 Subject: [PATCH 02/38] first version building the dataset --- anomalib/data/mvtec_loco.py | 857 +++++++++++++++++++++++--------- anomalib/data/utils/__init__.py | 8 +- anomalib/data/utils/download.py | 13 + anomalib/data/utils/image.py | 5 + 4 files changed, 639 insertions(+), 244 deletions(-) diff --git a/anomalib/data/mvtec_loco.py b/anomalib/data/mvtec_loco.py index 940f2b4927..639ddc2f64 100644 --- a/anomalib/data/mvtec_loco.py +++ b/anomalib/data/mvtec_loco.py @@ -39,7 +39,7 @@ no training annotations -- although it is assumed that the images in training are indeed from the target class (i.e. no noise) problem 1 (image-wise anomaly detection): “is there an anomaly in the image?” problem 2 (pixel-wise anomaly detection or anomaly segmentation): “which pixels belong to the anomaly?” -pixel-wise metric: Saturated Per-Region Overlap (sPRO) +pixel-wise metric: Saturated Per-Region Overlap (sPRO) structural anomaly pixel annotation policy defects are confined to local regions each pixel that introduces a visual structure that is not present in the anomaly-free images is anomalous @@ -53,11 +53,11 @@ n_anomaly_type (n_structural, n_logical): 22 (5, 17) logical constraints contains 2 tangerines -contains 1 nectarine -the tangerines and the nectarine on the left +contains 1 nectarine +the tangerines and the nectarine on the left cereals (C) and a mix of banana chips and almonds (B&A) on the right -the ratio between C and B&A is fixed -the relative position of C and B&A is fixed +the ratio between C and B&A is fixed +the relative position of C and B&A is fixed examples of logical defects too many banana chips and almonds @@ -66,7 +66,7 @@ juice bottle n_anomaly_type (n_structural, n_logical): 18 (7, 11) logical constraints -there is 1 bottle +there is 1 bottle the bottle is filled with a liquid and the fill level is always the same the liquid is of 1 out of 3 colors (red, yellow, white-ish) the bottle carries 2 labels @@ -74,8 +74,8 @@ the first label displays an icon that determines the type of liquid (cherry, orange, banana) cherry: red orange: yellow -banana: white-ish -the second label is attached to the lower part of the bottle +banana: white-ish +the second label is attached to the lower part of the bottle the second label contains the text “100% Juice” examples of logical defects (left) the icon does not match the type of juice @@ -87,7 +87,7 @@ pushpins n_anomaly_type (n_structural, n_logical): 8 (4, 4) logical constraints -each compartment contains 1 pushpin +each compartment contains 1 pushpin examples of logical defects 1 compartment has a missing pin @@ -96,7 +96,7 @@ screw bag n_anomaly_type (n_structural, n_logical): 20 (4, 16) logical constraints -the bag contains +the bag contains 2 washers 2 nuts 1 long screw @@ -120,7 +120,7 @@ examples of logical defects (left) the two splicing connectors do not have the same number of clamps (center) the color of the cable does not match the number of clamps -(right) the cable terminates in different positions +(right) the cable terminates in different positions ################################################## @@ -128,9 +128,9 @@ the area in which the object could occur the saturation threshold is chosen to be equal to the area of the missing object the saturation threshold for an object is chosen from the lower end of the distribution of its (manually annotated) area -example (image): pushpin +example (image): pushpin the missing pushpin can occur anywhere inside its compartment, therefore its entire area is annotated -the saturation threshold is set to the size of a pushpin +the saturation threshold is set to the size of a pushpin ################################################## @@ -142,127 +142,38 @@ it is not clear which of the two cables represents the anomaly, therefore both are annotated the saturation threshold is set to the area of one cable (i.e., half of the annotated region) properties -a method can obtain a perfect score even if it only marks one of the two cables as an anomaly -a method that marks both is neither penalized nor (extra-)rewarded +a method can obtain a perfect score even if it only marks one of the two cables as an anomaly +a method that marks both is neither penalized nor (extra-)rewarded ################################################## -other logical constraints +other logical constraints example (image, left): juice bottle the bottle is filled with orange juice but carries the label of the cherry juice both the orange juice and the label with the cherry are present in the training set, but the logical anomaly arises due to the erroneous combination of the two in the same image either the area filled with juice or the cherry as could be considered anomalous, therefore the union of the two regions is annotated the saturation threshold is set to the area of the cherry because the segmentation of the cherry is sufficient to solve the anomaly localization - """ - -""" -category anomaly type gt_value saturation_definition saturation_parameter -breakfast_box missing_almonds logical 255 relative_to_anomaly 1.0000000 -breakfast_box missing_bananas logical 254 relative_to_anomaly 1.0000000 -breakfast_box missing_toppings logical 253 relative_to_anomaly 1.0000000 -breakfast_box missing_cereals logical 252 relative_to_anomaly 1.0000000 -breakfast_box missing_cereals_and_toppings logical 251 relative_to_anomaly 1.0000000 -breakfast_box nectarines_2_tangerine_1 logical 250 relative_to_image 0.0488770 -breakfast_box nectarine_1_tangerine_1 logical 249 relative_to_image 0.0411621 -breakfast_box nectarines_0_tangerines_2 logical 248 relative_to_image 0.0488770 -breakfast_box nectarines_0_tangerines_3 logical 247 relative_to_image 0.0488770 -breakfast_box nectarines_3_tangerines_0 logical 246 relative_to_image 0.0977539 -breakfast_box nectarines_0_tangerine_1 logical 245 relative_to_image 0.0900391 -breakfast_box nectarines_0_tangerines_0 logical 244 relative_to_image 0.1312012 -breakfast_box nectarines_0_tangerines_4 logical 243 relative_to_image 0.0823242 -breakfast_box compartments_swapped logical 242 relative_to_anomaly 1.0000000 -breakfast_box overflow logical 241 relative_to_anomaly 1.0000000 -breakfast_box underflow logical 240 relative_to_anomaly 1.0000000 -breakfast_box wrong_ratio logical 239 relative_to_anomaly 1.0000000 -breakfast_box mixed_cereals structural 238 relative_to_anomaly 1.0000000 -breakfast_box fruit_damaged structural 237 relative_to_anomaly 1.0000000 -breakfast_box box_damaged structural 236 relative_to_anomaly 1.0000000 -breakfast_box toppings_crushed structural 235 relative_to_anomaly 1.0000000 -breakfast_box contamination structural 234 relative_to_anomaly 1.0000000 -juice_box missing_top_label logical 255 relative_to_image 0.0550000 -juice_box missing_bottom_label logical 254 relative_to_image 0.0255469 -juice_box swapped_labels logical 253 relative_to_image 0.1100000 -juice_box damaged_label structural 252 relative_to_anomaly 1.0000000 -juice_box rotated_label structural 251 relative_to_anomaly 1.0000000 -juice_box misplaced_label_top logical 250 relative_to_image 0.0550000 -juice_box misplaced_label_bottom logical 249 relative_to_image 0.0255469 -juice_box label_text_incomplete structural 248 relative_to_anomaly 1.0000000 -juice_box empty_bottle logical 247 relative_to_anomaly 1.0000000 -juice_box wrong_fill_level_too_much logical 246 relative_to_anomaly 1.0000000 -juice_box wrong_fill_level_not_enough logical 245 relative_to_anomaly 1.0000000 -juice_box misplaced_fruit_icon logical 244 relative_to_anomaly 1.0000000 -juice_box missing_fruit_icon logical 243 relative_to_anomaly 1.0000000 -juice_box unknown_fruit_icon structural 242 relative_to_anomaly 1.0000000 -juice_box incomplete_fruit_icon structural 241 relative_to_anomaly 1.0000000 -juice_box wrong_juice_type logical 240 relative_to_image 0.0035156 -juice_box juice_color structural 239 relative_to_anomaly 1.0000000 -juice_box contamination structural 238 relative_to_anomaly 1.0000000 -pushpins additional_1_pushpin logical 255 relative_to_image 0.0037059 -pushpins additional_2_pushpins logical 254 relative_to_image 0.0074118 -pushpins missing_pushpin logical 253 relative_to_image 0.0037059 -pushpins missing_separator logical 252 relative_to_anomaly 1.0000000 -pushpins front_bent structural 251 relative_to_anomaly 1.0000000 -pushpins broken structural 250 relative_to_anomaly 1.0000000 -pushpins color structural 249 relative_to_anomaly 1.0000000 -pushpins contamination structural 248 relative_to_anomaly 1.0000000 -screw_bag screw_too_long logical 255 relative_to_image 0.0051136 -screw_bag screw_too_shor logical 254 relative_to_image 0.0051136 -screw_bag screws_1_very_short logical 253 relative_to_anomaly 1.0000000 -screw_bag screws_2_very_short logical 252 relative_to_image 0.0102273 -screw_bag additional_1_long_screw logical 251 relative_to_image 0.0168182 -screw_bag additional_1_short_screw logical 250 relative_to_image 0.0117045 -screw_bag additional_1_nut_ logical 249 relative_to_image 0.0042614 -screw_bag additional_2_nuts_ logical 248 relative_to_image 0.0085227 -screw_bag additional_1_washer_ logical 247 relative_to_image 0.0031250 -screw_bag additional_2_washers_ logical 246 relative_to_image 0.0062500 -screw_bag missing_1_long_screw logical 245 relative_to_image 0.0168182 -screw_bag missing_1_short_screw logical 244 relative_to_image 0.0117045 -screw_bag missing_1_nut logical 243 relative_to_image 0.0042614 -screw_bag missing_2_nuts logical 242 relative_to_image 0.0085227 -screw_bag missing_1_washer logical 241 relative_to_image 0.0031250 -screw_bag missing_2_washers logical 240 relative_to_image 0.0062500 -screw_bag bag_broken structural 239 relative_to_anomaly 1.0000000 -screw_bag color structural 238 relative_to_anomaly 1.0000000 -screw_bag contamination structural 237 relative_to_anomaly 1.0000000 -screw_bag part_broken structural 236 relative_to_anomaly 1.0000000 -splicing_connectors wrong_connector_type_5_2 logical 255 relative_to_image 0.0464360 -splicing_connectors wrong_connector_type_5_3 logical 254 relative_to_image 0.0306574 -splicing_connectors wrong_connector_type_3_2 logical 253 relative_to_image 0.0152941 -splicing_connectors cable_too_short_t2 logical 252 relative_to_image 0.0368858 -splicing_connectors cable_too_short_t3 logical 251 relative_to_image 0.0526644 -splicing_connectors cable_too_short_t5 logical 250 relative_to_image 0.0830450 -splicing_connectors missing_connector logical 249 relative_to_anomaly 1.0000000 -splicing_connectors missing_connector_and_cable logical 248 relative_to_image 0.0716955 -splicing_connectors missing_cable logical 247 relative_to_image 0.0124567 -splicing_connectors extra_cable logical 246 relative_to_anomaly 0.5000000 -splicing_connectors cable_color logical 245 relative_to_image 0.0124567 -splicing_connectors broken_cable structural 244 relative_to_anomaly 1.0000000 -splicing_connectors cable_cut logical 243 relative_to_anomaly 1.0000000 -splicing_connectors cable_not_plugged structural 242 relative_to_anomaly 1.0000000 -splicing_connectors unknown_cable_color structural 241 relative_to_anomaly 1.0000000 -splicing_connectors wrong_cable_location logical 240 relative_to_image 0.0124567 -splicing_connectors flipped_connector structural 239 relative_to_anomaly 1.0000000 -splicing_connectors broken_connector structural 238 relative_to_anomaly 1.0000000 -splicing_connectors open_lever structural 237 relative_to_anomaly 1.0000000 -splicing_connectors color structural 236 relative_to_anomaly 1.0000000 -splicing_connectors contamination structural 235 relative_to_anomaly 1.0000000 -""" +# TODO: clear module docstring import logging import tarfile import warnings from pathlib import Path -from typing import Dict, Optional, Tuple, Union +from posixpath import split +from typing import Dict, List, Optional, Tuple, Union +from unicodedata import category from urllib.request import urlretrieve import albumentations as A import cv2 import numpy as np import pandas as pd +from numpy import ndarray from pandas.core.frame import DataFrame from pytorch_lightning.core.datamodule import LightningDataModule +from pytorch_lightning.trainer.states import TrainerFn from pytorch_lightning.utilities.cli import DATAMODULE_REGISTRY from pytorch_lightning.utilities.types import EVAL_DATALOADERS, TRAIN_DATALOADERS from torch import Tensor @@ -271,64 +182,425 @@ from torchvision.datasets.folder import VisionDataset from anomalib.data.inference import InferenceDataset -from anomalib.data.utils import DownloadProgressBar, hash_check, read_image -from anomalib.data.utils.split import ( - create_validation_set_from_test_set, - split_normal_images_in_train_set, -) +from anomalib.data.utils import DownloadProgressBar, hash_check, read_image, read_mask +from anomalib.data.utils.download import tar_extract_all from anomalib.pre_processing import PreProcessor +# TODO: open discussion about keeping pre-resized tensors in the dataset folder +# TODO: create an issue in mvtec so the dataset will retain the information abou the anomaly type (label) so one can do per-label evaluation +# TODO: document the notion of label and superlabel + logger = logging.getLogger(__name__) -def make_mvtec_loco_dataset( +TASK_SEGMENTATION = "segmentation" +TASKS = TASK_SEGMENTATION + +SPLIT_TRAIN = "train" +# "validation" instead of "val" is an explicit choice because this will match the name of the folder +SPLIT_VALIDATION = "validation" +SPLIT_TEST = "test" +SPLITS = (SPLIT_TRAIN, SPLIT_VALIDATION, SPLIT_TEST) + +# each image is read upon demand +IMREAD_STRATEGY_ONTHEFLY = "onthefly" +# all images are pre-loaded into memory at initialization +IMREAD_STRATEGY_PRELOAD = "preload" +IMREAD_STRATEGIES = (IMREAD_STRATEGY_ONTHEFLY, IMREAD_STRATEGY_PRELOAD) +# TODO make a strategy preload_tensor + +CATEGORY_BREAKFAST_BOX = "breakfast_box" +CATEGORY_JUICE_BOTTLE = "juice_bottle" +CATEGORY_PUSHPINS = "pushpins" +CATEGORY_SCREW_BAG = "screw_bag" +CATEGORY_SPLICING_CONNECTORS = "splicing_connectors" +CATEGORIES: Tuple[str, ...] = ( + CATEGORY_BREAKFAST_BOX, + CATEGORY_JUICE_BOTTLE, + CATEGORY_PUSHPINS, + CATEGORY_SCREW_BAG, + CATEGORY_SPLICING_CONNECTORS, +) + +LABEL_NORMAL = 0 +LABEL_ANOMALOUS = 1 + +SUPER_ANOTYPE_GOOD = "good" +SUPER_ANOTYPE_LOGICAL = "logical_anomalies" +SUPER_ANOTYPE_STRUCTURAL = "structural_anomalies" +SUPER_ANOTYPES: Tuple[str, ...] = (SUPER_ANOTYPE_GOOD, SUPER_ANOTYPE_LOGICAL, SUPER_ANOTYPE_STRUCTURAL) + +ANOTYPE_GOOD = "good" + +ANOTYPES_BREAKFAST_BOX: Tuple[str, ...] = ( + ANOTYPE_BB_GOOD := "good", + ANOTYPE_BB_COMPARTMENTS_SWAPPED := "compartments_swapped", + ANOTYPE_BB_MISSING_ALMONDS := "missing_almonds", + ANOTYPE_BB_MISSING_BANANAS := "missing_bananas", + ANOTYPE_BB_MISSING_CEREALS := "missing_cereals", + ANOTYPE_BB_MISSING_CEREALS_AND_TOPPINGS := "missing_cereals_and_toppings", + ANOTYPE_BB_MISSING_TOPPINGS := "missing_toppings", + ANOTYPE_BB_NECTARINE_1_TANGERINE_1 := "nectarine_1_tangerine_1", + ANOTYPE_BB_NECTARINES_0_TANGERINE_1 := "nectarines_0_tangerine_1", + ANOTYPE_BB_NECTARINES_0_TANGERINES_0 := "nectarines_0_tangerines_0", + ANOTYPE_BB_NECTARINES_0_TANGERINES_2 := "nectarines_0_tangerines_2", + ANOTYPE_BB_NECTARINES_0_TANGERINES_3 := "nectarines_0_tangerines_3", + ANOTYPE_BB_NECTARINES_0_TANGERINES_4 := "nectarines_0_tangerines_4", + ANOTYPE_BB_NECTARINES_2_TANGERINE_1 := "nectarines_2_tangerine_1", + ANOTYPE_BB_NECTARINES_3_TANGERINES_0 := "nectarines_3_tangerines_0", + ANOTYPE_BB_OVERFLOW := "overflow", + ANOTYPE_BB_UNDERFLOW := "underflow", + ANOTYPE_BB_WRONG_RATIO := "wrong_ratio", + ANOTYPE_BB_BOX_DAMAGED := "box_damaged", + ANOTYPE_BB_CONTAMINATION := "contamination", + ANOTYPE_BB_FRUIT_DAMAGED := "fruit_damaged", + ANOTYPE_BB_MIXED_CEREALS := "mixed_cereals", + ANOTYPE_BB_TOPPINGS_CRUSHED := "toppings_crushed", +) + +ANOTYPES_JUICE_BOTTLE: Tuple[str, ...] = ( + ANOTYPE_JB_GOOD := "good", + ANOTYPE_JB_EMPTY_BOTTLE := "empty_bottle", + ANOTYPE_JB_MISPLACED_FRUIT_ICON := "misplaced_fruit_icon", + ANOTYPE_JB_MISPLACED_LABEL_BOTTOM := "misplaced_label_bottom", + ANOTYPE_JB_MISPLACED_LABEL_TOP := "misplaced_label_top", + ANOTYPE_JB_MISSING_BOTTOM_LABEL := "missing_bottom_label", + ANOTYPE_JB_MISSING_FRUIT_ICON := "missing_fruit_icon", + ANOTYPE_JB_MISSING_TOP_LABEL := "missing_top_label", + ANOTYPE_JB_SWAPPED_LABELS := "swapped_labels", + ANOTYPE_JB_WRONG_FILL_LEVEL_NOT_ENOUGH := "wrong_fill_level_not_enough", + ANOTYPE_JB_WRONG_FILL_LEVEL_TOO_MUCH := "wrong_fill_level_too_much", + ANOTYPE_JB_WRONG_JUICE_TYPE := "wrong_juice_type", + ANOTYPE_JB_CONTAMINATION := "contamination", + ANOTYPE_JB_DAMAGED_LABEL := "damaged_label", + ANOTYPE_JB_INCOMPLETE_FRUIT_ICON := "incomplete_fruit_icon", + ANOTYPE_JB_JUICE_COLOR := "juice_color", + ANOTYPE_JB_LABEL_TEXT_INCOMPLETE := "label_text_incomplete", + ANOTYPE_JB_ROTATED_LABEL := "rotated_label", + ANOTYPE_JB_UNKNOWN_FRUIT_ICON := "unknown_fruit_icon", +) + +ANOTYPES_PUSHPINS: Tuple[str, ...] = ( + ANOTYPE_P_GOOD := "good", + ANOTYPE_P_ADDITIONAL_1_PUSHPIN := "additional_1_pushpin", + ANOTYPE_P_ADDITIONAL_2_PUSHPINS := "additional_2_pushpins", + ANOTYPE_P_MISSING_PUSHPIN := "missing_pushpin", + ANOTYPE_P_MISSING_SEPARATOR := "missing_separator", + ANOTYPE_P_BROKEN := "broken", + ANOTYPE_P_COLOR := "color", + ANOTYPE_P_CONTAMINATION := "contamination", + ANOTYPE_P_FRONT_BENT := "front_bent", +) + +ANOTYPES_SCREW_BAG: Tuple[str, ...] = ( + ANOTYPE_SB_GOOD := "good", + ANOTYPE_SB_ADDITIONAL_1_LONG_SCREW := "additional_1_long_screw", + ANOTYPE_SB_ADDITIONAL_1_NUT_ := "additional_1_nut_", + ANOTYPE_SB_ADDITIONAL_1_SHORT_SCREW := "additional_1_short_screw", + ANOTYPE_SB_ADDITIONAL_1_WASHER_ := "additional_1_washer_", + ANOTYPE_SB_ADDITIONAL_2_NUTS_ := "additional_2_nuts_", + ANOTYPE_SB_ADDITIONAL_2_WASHERS_ := "additional_2_washers_", + ANOTYPE_SB_MISSING_1_LONG_SCREW := "missing_1_long_screw", + ANOTYPE_SB_MISSING_1_NUT := "missing_1_nut", + ANOTYPE_SB_MISSING_1_SHORT_SCREW := "missing_1_short_screw", + ANOTYPE_SB_MISSING_1_WASHER := "missing_1_washer", + ANOTYPE_SB_MISSING_2_NUTS := "missing_2_nuts", + ANOTYPE_SB_MISSING_2_WASHERS := "missing_2_washers", + ANOTYPE_SB_SCREW_TOO_LONG := "screw_too_long", + ANOTYPE_SB_SCREW_TOO_SHOR := "screw_too_shor", + ANOTYPE_SB_SCREWS_1_VERY_SHORT := "screws_1_very_short", + ANOTYPE_SB_SCREWS_2_VERY_SHORT := "screws_2_very_short", + ANOTYPE_SB_BAG_BROKEN := "bag_broken", + ANOTYPE_SB_COLOR := "color", + ANOTYPE_SB_CONTAMINATION := "contamination", + ANOTYPE_SB_PART_BROKEN := "part_broken", +) + +ANOTYPES_SPLICING_CONNECTORS: Tuple[str, ...] = ( + ANOTYPE_SC_GOOD := "good", + ANOTYPE_SC_CABLE_COLOR := "cable_color", + ANOTYPE_SC_CABLE_CUT := "cable_cut", + ANOTYPE_SC_CABLE_TOO_SHORT_T2 := "cable_too_short_t2", + ANOTYPE_SC_CABLE_TOO_SHORT_T3 := "cable_too_short_t3", + ANOTYPE_SC_CABLE_TOO_SHORT_T5 := "cable_too_short_t5", + ANOTYPE_SC_EXTRA_CABLE := "extra_cable", + ANOTYPE_SC_MISSING_CABLE := "missing_cable", + ANOTYPE_SC_MISSING_CONNECTOR := "missing_connector", + ANOTYPE_SC_MISSING_CONNECTOR_AND_CABLE := "missing_connector_and_cable", + ANOTYPE_SC_WRONG_CABLE_LOCATION := "wrong_cable_location", + ANOTYPE_SC_WRONG_CONNECTOR_TYPE_3_2 := "wrong_connector_type_3_2", + ANOTYPE_SC_WRONG_CONNECTOR_TYPE_5_2 := "wrong_connector_type_5_2", + ANOTYPE_SC_WRONG_CONNECTOR_TYPE_5_3 := "wrong_connector_type_5_3", + ANOTYPE_SC_BROKEN_CABLE := "broken_cable", + ANOTYPE_SC_BROKEN_CONNECTOR := "broken_connector", + ANOTYPE_SC_CABLE_NOT_PLUGGED := "cable_not_plugged", + ANOTYPE_SC_COLOR := "color", + ANOTYPE_SC_CONTAMINATION := "contamination", + ANOTYPE_SC_FLIPPED_CONNECTOR := "flipped_connector", + ANOTYPE_SC_OPEN_LEVER := "open_lever", + ANOTYPE_SC_UNKNOWN_CABLE_COLOR := "unknown_cable_color", +) + +# this is given at the paper, each anomaly type (label) has a different gtvalue in the mask +# source: Beyond Dents and Scratches: Logical Constraints in Unsupervised Anomaly Detection and Localization (Bergmann, P. et al, 2022). +_MAP_ANOTYPE_2_GTVALUE: Dict[Tuple[str, str, str], int] = { + (CATEGORY_BREAKFAST_BOX, SUPER_ANOTYPE_LOGICAL, ANOTYPE_BB_COMPARTMENTS_SWAPPED): 242, + (CATEGORY_BREAKFAST_BOX, SUPER_ANOTYPE_LOGICAL, ANOTYPE_BB_MISSING_ALMONDS): 255, + (CATEGORY_BREAKFAST_BOX, SUPER_ANOTYPE_LOGICAL, ANOTYPE_BB_MISSING_BANANAS): 254, + (CATEGORY_BREAKFAST_BOX, SUPER_ANOTYPE_LOGICAL, ANOTYPE_BB_MISSING_CEREALS): 252, + (CATEGORY_BREAKFAST_BOX, SUPER_ANOTYPE_LOGICAL, ANOTYPE_BB_MISSING_CEREALS_AND_TOPPINGS): 251, + (CATEGORY_BREAKFAST_BOX, SUPER_ANOTYPE_LOGICAL, ANOTYPE_BB_MISSING_TOPPINGS): 253, + (CATEGORY_BREAKFAST_BOX, SUPER_ANOTYPE_LOGICAL, ANOTYPE_BB_NECTARINE_1_TANGERINE_1): 249, + (CATEGORY_BREAKFAST_BOX, SUPER_ANOTYPE_LOGICAL, ANOTYPE_BB_NECTARINES_0_TANGERINE_1): 245, + (CATEGORY_BREAKFAST_BOX, SUPER_ANOTYPE_LOGICAL, ANOTYPE_BB_NECTARINES_0_TANGERINES_0): 244, + (CATEGORY_BREAKFAST_BOX, SUPER_ANOTYPE_LOGICAL, ANOTYPE_BB_NECTARINES_0_TANGERINES_2): 248, + (CATEGORY_BREAKFAST_BOX, SUPER_ANOTYPE_LOGICAL, ANOTYPE_BB_NECTARINES_0_TANGERINES_3): 247, + (CATEGORY_BREAKFAST_BOX, SUPER_ANOTYPE_LOGICAL, ANOTYPE_BB_NECTARINES_0_TANGERINES_4): 243, + (CATEGORY_BREAKFAST_BOX, SUPER_ANOTYPE_LOGICAL, ANOTYPE_BB_NECTARINES_2_TANGERINE_1): 250, + (CATEGORY_BREAKFAST_BOX, SUPER_ANOTYPE_LOGICAL, ANOTYPE_BB_NECTARINES_3_TANGERINES_0): 246, + (CATEGORY_BREAKFAST_BOX, SUPER_ANOTYPE_LOGICAL, ANOTYPE_BB_OVERFLOW): 241, + (CATEGORY_BREAKFAST_BOX, SUPER_ANOTYPE_LOGICAL, ANOTYPE_BB_UNDERFLOW): 240, + (CATEGORY_BREAKFAST_BOX, SUPER_ANOTYPE_LOGICAL, ANOTYPE_BB_WRONG_RATIO): 239, + (CATEGORY_BREAKFAST_BOX, SUPER_ANOTYPE_STRUCTURAL, ANOTYPE_BB_BOX_DAMAGED): 236, + (CATEGORY_BREAKFAST_BOX, SUPER_ANOTYPE_STRUCTURAL, ANOTYPE_BB_CONTAMINATION): 234, + (CATEGORY_BREAKFAST_BOX, SUPER_ANOTYPE_STRUCTURAL, ANOTYPE_BB_FRUIT_DAMAGED): 237, + (CATEGORY_BREAKFAST_BOX, SUPER_ANOTYPE_STRUCTURAL, ANOTYPE_BB_MIXED_CEREALS): 238, + (CATEGORY_BREAKFAST_BOX, SUPER_ANOTYPE_STRUCTURAL, ANOTYPE_BB_TOPPINGS_CRUSHED): 235, + (CATEGORY_JUICE_BOTTLE, SUPER_ANOTYPE_LOGICAL, ANOTYPE_JB_EMPTY_BOTTLE): 247, + (CATEGORY_JUICE_BOTTLE, SUPER_ANOTYPE_LOGICAL, ANOTYPE_JB_MISPLACED_FRUIT_ICON): 244, + (CATEGORY_JUICE_BOTTLE, SUPER_ANOTYPE_LOGICAL, ANOTYPE_JB_MISPLACED_LABEL_BOTTOM): 249, + (CATEGORY_JUICE_BOTTLE, SUPER_ANOTYPE_LOGICAL, ANOTYPE_JB_MISPLACED_LABEL_TOP): 250, + (CATEGORY_JUICE_BOTTLE, SUPER_ANOTYPE_LOGICAL, ANOTYPE_JB_MISSING_BOTTOM_LABEL): 254, + (CATEGORY_JUICE_BOTTLE, SUPER_ANOTYPE_LOGICAL, ANOTYPE_JB_MISSING_FRUIT_ICON): 243, + (CATEGORY_JUICE_BOTTLE, SUPER_ANOTYPE_LOGICAL, ANOTYPE_JB_MISSING_TOP_LABEL): 255, + (CATEGORY_JUICE_BOTTLE, SUPER_ANOTYPE_LOGICAL, ANOTYPE_JB_SWAPPED_LABELS): 253, + (CATEGORY_JUICE_BOTTLE, SUPER_ANOTYPE_LOGICAL, ANOTYPE_JB_WRONG_FILL_LEVEL_NOT_ENOUGH): 245, + (CATEGORY_JUICE_BOTTLE, SUPER_ANOTYPE_LOGICAL, ANOTYPE_JB_WRONG_FILL_LEVEL_TOO_MUCH): 246, + (CATEGORY_JUICE_BOTTLE, SUPER_ANOTYPE_LOGICAL, ANOTYPE_JB_WRONG_JUICE_TYPE): 240, + (CATEGORY_JUICE_BOTTLE, SUPER_ANOTYPE_STRUCTURAL, ANOTYPE_JB_CONTAMINATION): 238, + (CATEGORY_JUICE_BOTTLE, SUPER_ANOTYPE_STRUCTURAL, ANOTYPE_JB_DAMAGED_LABEL): 252, + (CATEGORY_JUICE_BOTTLE, SUPER_ANOTYPE_STRUCTURAL, ANOTYPE_JB_INCOMPLETE_FRUIT_ICON): 241, + (CATEGORY_JUICE_BOTTLE, SUPER_ANOTYPE_STRUCTURAL, ANOTYPE_JB_JUICE_COLOR): 239, + (CATEGORY_JUICE_BOTTLE, SUPER_ANOTYPE_STRUCTURAL, ANOTYPE_JB_LABEL_TEXT_INCOMPLETE): 248, + (CATEGORY_JUICE_BOTTLE, SUPER_ANOTYPE_STRUCTURAL, ANOTYPE_JB_ROTATED_LABEL): 251, + (CATEGORY_JUICE_BOTTLE, SUPER_ANOTYPE_STRUCTURAL, ANOTYPE_JB_UNKNOWN_FRUIT_ICON): 242, + (CATEGORY_PUSHPINS, SUPER_ANOTYPE_LOGICAL, ANOTYPE_P_ADDITIONAL_1_PUSHPIN): 255, + (CATEGORY_PUSHPINS, SUPER_ANOTYPE_LOGICAL, ANOTYPE_P_ADDITIONAL_2_PUSHPINS): 254, + (CATEGORY_PUSHPINS, SUPER_ANOTYPE_LOGICAL, ANOTYPE_P_MISSING_PUSHPIN): 253, + (CATEGORY_PUSHPINS, SUPER_ANOTYPE_LOGICAL, ANOTYPE_P_MISSING_SEPARATOR): 252, + (CATEGORY_PUSHPINS, SUPER_ANOTYPE_STRUCTURAL, ANOTYPE_P_BROKEN): 250, + (CATEGORY_PUSHPINS, SUPER_ANOTYPE_STRUCTURAL, ANOTYPE_P_COLOR): 249, + (CATEGORY_PUSHPINS, SUPER_ANOTYPE_STRUCTURAL, ANOTYPE_P_CONTAMINATION): 248, + (CATEGORY_PUSHPINS, SUPER_ANOTYPE_STRUCTURAL, ANOTYPE_P_FRONT_BENT): 251, + (CATEGORY_SCREW_BAG, SUPER_ANOTYPE_LOGICAL, ANOTYPE_SB_ADDITIONAL_1_LONG_SCREW): 251, + (CATEGORY_SCREW_BAG, SUPER_ANOTYPE_LOGICAL, ANOTYPE_SB_ADDITIONAL_1_NUT_): 249, + (CATEGORY_SCREW_BAG, SUPER_ANOTYPE_LOGICAL, ANOTYPE_SB_ADDITIONAL_1_SHORT_SCREW): 250, + (CATEGORY_SCREW_BAG, SUPER_ANOTYPE_LOGICAL, ANOTYPE_SB_ADDITIONAL_1_WASHER_): 247, + (CATEGORY_SCREW_BAG, SUPER_ANOTYPE_LOGICAL, ANOTYPE_SB_ADDITIONAL_2_NUTS_): 248, + (CATEGORY_SCREW_BAG, SUPER_ANOTYPE_LOGICAL, ANOTYPE_SB_ADDITIONAL_2_WASHERS_): 246, + (CATEGORY_SCREW_BAG, SUPER_ANOTYPE_LOGICAL, ANOTYPE_SB_MISSING_1_LONG_SCREW): 245, + (CATEGORY_SCREW_BAG, SUPER_ANOTYPE_LOGICAL, ANOTYPE_SB_MISSING_1_NUT): 243, + (CATEGORY_SCREW_BAG, SUPER_ANOTYPE_LOGICAL, ANOTYPE_SB_MISSING_1_SHORT_SCREW): 244, + (CATEGORY_SCREW_BAG, SUPER_ANOTYPE_LOGICAL, ANOTYPE_SB_MISSING_1_WASHER): 241, + (CATEGORY_SCREW_BAG, SUPER_ANOTYPE_LOGICAL, ANOTYPE_SB_MISSING_2_NUTS): 242, + (CATEGORY_SCREW_BAG, SUPER_ANOTYPE_LOGICAL, ANOTYPE_SB_MISSING_2_WASHERS): 240, + (CATEGORY_SCREW_BAG, SUPER_ANOTYPE_LOGICAL, ANOTYPE_SB_SCREW_TOO_LONG): 255, + (CATEGORY_SCREW_BAG, SUPER_ANOTYPE_LOGICAL, ANOTYPE_SB_SCREW_TOO_SHOR): 254, + (CATEGORY_SCREW_BAG, SUPER_ANOTYPE_LOGICAL, ANOTYPE_SB_SCREWS_1_VERY_SHORT): 253, + (CATEGORY_SCREW_BAG, SUPER_ANOTYPE_LOGICAL, ANOTYPE_SB_SCREWS_2_VERY_SHORT): 252, + (CATEGORY_SCREW_BAG, SUPER_ANOTYPE_STRUCTURAL, ANOTYPE_SB_BAG_BROKEN): 239, + (CATEGORY_SCREW_BAG, SUPER_ANOTYPE_STRUCTURAL, ANOTYPE_SB_COLOR): 238, + (CATEGORY_SCREW_BAG, SUPER_ANOTYPE_STRUCTURAL, ANOTYPE_SB_CONTAMINATION): 237, + (CATEGORY_SCREW_BAG, SUPER_ANOTYPE_STRUCTURAL, ANOTYPE_SB_PART_BROKEN): 236, + (CATEGORY_SPLICING_CONNECTORS, SUPER_ANOTYPE_LOGICAL, ANOTYPE_SC_CABLE_COLOR): 245, + (CATEGORY_SPLICING_CONNECTORS, SUPER_ANOTYPE_LOGICAL, ANOTYPE_SC_CABLE_CUT): 243, + (CATEGORY_SPLICING_CONNECTORS, SUPER_ANOTYPE_LOGICAL, ANOTYPE_SC_CABLE_TOO_SHORT_T2): 252, + (CATEGORY_SPLICING_CONNECTORS, SUPER_ANOTYPE_LOGICAL, ANOTYPE_SC_CABLE_TOO_SHORT_T3): 251, + (CATEGORY_SPLICING_CONNECTORS, SUPER_ANOTYPE_LOGICAL, ANOTYPE_SC_CABLE_TOO_SHORT_T5): 250, + (CATEGORY_SPLICING_CONNECTORS, SUPER_ANOTYPE_LOGICAL, ANOTYPE_SC_EXTRA_CABLE): 246, + (CATEGORY_SPLICING_CONNECTORS, SUPER_ANOTYPE_LOGICAL, ANOTYPE_SC_MISSING_CABLE): 247, + (CATEGORY_SPLICING_CONNECTORS, SUPER_ANOTYPE_LOGICAL, ANOTYPE_SC_MISSING_CONNECTOR): 249, + (CATEGORY_SPLICING_CONNECTORS, SUPER_ANOTYPE_LOGICAL, ANOTYPE_SC_MISSING_CONNECTOR_AND_CABLE): 248, + (CATEGORY_SPLICING_CONNECTORS, SUPER_ANOTYPE_LOGICAL, ANOTYPE_SC_WRONG_CABLE_LOCATION): 240, + (CATEGORY_SPLICING_CONNECTORS, SUPER_ANOTYPE_LOGICAL, ANOTYPE_SC_WRONG_CONNECTOR_TYPE_3_2): 253, + (CATEGORY_SPLICING_CONNECTORS, SUPER_ANOTYPE_LOGICAL, ANOTYPE_SC_WRONG_CONNECTOR_TYPE_5_2): 255, + (CATEGORY_SPLICING_CONNECTORS, SUPER_ANOTYPE_LOGICAL, ANOTYPE_SC_WRONG_CONNECTOR_TYPE_5_3): 254, + (CATEGORY_SPLICING_CONNECTORS, SUPER_ANOTYPE_STRUCTURAL, ANOTYPE_SC_BROKEN_CABLE): 244, + (CATEGORY_SPLICING_CONNECTORS, SUPER_ANOTYPE_STRUCTURAL, ANOTYPE_SC_BROKEN_CONNECTOR): 238, + (CATEGORY_SPLICING_CONNECTORS, SUPER_ANOTYPE_STRUCTURAL, ANOTYPE_SC_CABLE_NOT_PLUGGED): 242, + (CATEGORY_SPLICING_CONNECTORS, SUPER_ANOTYPE_STRUCTURAL, ANOTYPE_SC_COLOR): 236, + (CATEGORY_SPLICING_CONNECTORS, SUPER_ANOTYPE_STRUCTURAL, ANOTYPE_SC_CONTAMINATION): 235, + (CATEGORY_SPLICING_CONNECTORS, SUPER_ANOTYPE_STRUCTURAL, ANOTYPE_SC_FLIPPED_CONNECTOR): 239, + (CATEGORY_SPLICING_CONNECTORS, SUPER_ANOTYPE_STRUCTURAL, ANOTYPE_SC_OPEN_LEVER): 237, + (CATEGORY_SPLICING_CONNECTORS, SUPER_ANOTYPE_STRUCTURAL, ANOTYPE_SC_UNKNOWN_CABLE_COLOR): 241, +} + +# map: (category, gtvalue) -> (superlabel, label) +_MAP_GTVALUE_2_ANOTYPE: Dict[Tuple[str, int], Tuple[str, str]] = { + (category, gtvalue): (super_anotype, anotype) + for (category, super_anotype, anotype), gtvalue in _MAP_ANOTYPE_2_GTVALUE.items() +} + +# expected number of images in each category split so that we can check if the dataset is complete +# source: Beyond Dents and Scratches: Logical Constraints in Unsupervised Anomaly Detection and Localization (Bergmann, P. et al, 2022). +_EXPECTED_NSAMPLES: Dict[Tuple[str, str], int] = { + (CATEGORY_BREAKFAST_BOX, SPLIT_TRAIN): 351, + (CATEGORY_BREAKFAST_BOX, SPLIT_VALIDATION): 62, + (CATEGORY_BREAKFAST_BOX, SPLIT_TEST): 275, + (CATEGORY_JUICE_BOTTLE, SPLIT_TRAIN): 335, + (CATEGORY_JUICE_BOTTLE, SPLIT_VALIDATION): 54, + (CATEGORY_JUICE_BOTTLE, SPLIT_TEST): 330, + (CATEGORY_PUSHPINS, SPLIT_TRAIN): 372, + (CATEGORY_PUSHPINS, SPLIT_VALIDATION): 69, + (CATEGORY_PUSHPINS, SPLIT_TEST): 310, + (CATEGORY_SCREW_BAG, SPLIT_TRAIN): 360, + (CATEGORY_SCREW_BAG, SPLIT_VALIDATION): 60, + (CATEGORY_SCREW_BAG, SPLIT_TEST): 341, + # these two below were wrong in the paper + # TODO send a correction to the authors + # (CATEGORY_SPLICING_CONNECTORS, SPLIT_TRAIN): 354, + # (CATEGORY_SPLICING_CONNECTORS, SPLIT_VALIDATION): 59, + (CATEGORY_SPLICING_CONNECTORS, SPLIT_TRAIN): 360, + (CATEGORY_SPLICING_CONNECTORS, SPLIT_VALIDATION): 60, + (CATEGORY_SPLICING_CONNECTORS, SPLIT_TEST): 312, +} + + +def _binarize_mask_float(mask: np.ndarray) -> np.ndarray: + """ + the masks use different gtvalue values for the different anomaly types so the > 0 is making it binary + this operation is very simple but it is in a function to make sure its standard because it is used in different places + e.g. preloading while building the dataset and on the fly while training + """ + return (mask > 0).astype(float) + + +def _make_dataset( path: Path, split: Optional[str] = None, - split_ratio: float = 0.1, - seed: Optional[int] = None, - create_validation_set: bool = False, + imread_strategy: str = IMREAD_STRATEGY_PRELOAD, ) -> DataFrame: - samples_list = [(str(path),) + filename.parts[-3:] for filename in path.glob("**/*.png")] - if len(samples_list) == 0: - raise RuntimeError(f"Found 0 images in {path}") - - samples = pd.DataFrame(samples_list, columns=["path", "split", "label", "image_path"]) - samples = samples[samples.split != "ground_truth"] - - # Create mask_path column - samples["mask_path"] = ( - samples.path - + "/ground_truth/" - + samples.label - + "/" - + samples.image_path.str.rstrip("png").str.rstrip(".") - + "_mask.png" - ) - - # Modify image_path column by converting to absolute path - samples["image_path"] = samples.path + "/" + samples.split + "/" + samples.label + "/" + samples.image_path - - # Split the normal images in training set if test set doesn't - # contain any normal images. This is needed because AUC score - # cannot be computed based on 1-class - if sum((samples.split == "test") & (samples.label == "good")) == 0: - samples = split_normal_images_in_train_set(samples, split_ratio, seed) - - # Good images don't have mask - samples.loc[(samples.split == "test") & (samples.label == "good"), "mask_path"] = "" - - # Create label index for normal (0) and anomalous (1) images. - samples.loc[(samples.label == "good"), "label_index"] = 0 - samples.loc[(samples.label != "good"), "label_index"] = 1 - samples.label_index = samples.label_index.astype(int) - - if create_validation_set: - samples = create_validation_set_from_test_set(samples, seed=seed) - - # Get the data frame for the split. - if split is not None and split in ["train", "val", "test"]: - samples = samples[samples.split == split] - samples = samples.reset_index(drop=True) + # todo create optional to get a subset of anomlies in the test + + assert split is None or split in SPLITS, f"Invalid split: {split}" + assert imread_strategy in IMREAD_STRATEGIES, f"Invalid imread strategy: {imread_strategy}" + + category = path.resolve().name + assert category in CATEGORIES, f"Invalid path '{path}'. The directory ('{category}') must be one of {CATEGORIES}" + + if split is None: + return pd.concat([_make_dataset(path, split_, imread_strategy) for split_ in SPLITS], axis=0) + + logger.info(f"Creating MVTec LOCO AD dataset for category '{category}' split '{split}'") + + """ + structure of the files in the dataset ("/" is 'path') + + images: /{split}/{super_anotype}/{image_index}.png + + /train/good/000.png + /train/good/... + /train/good/350.png + + /validation/good/... + + /test/good/... + /test/logical_anomalies/... + /test/structural_anomalies/... + + masks: /ground_truth/{super_anotype}/{image_index}/000.png + + /ground_truth/logical_anomalies/000/000.png + /ground_truth/logical_anomalies/.../000.png + /ground_truth/logical_anomalies/079/000.png + + /ground_truth/structural_anomalies/.../000.png + ... + """ + + # these values look like "(train|validation|test)/(good|logical_anomalies|structural_anomalies)/(000|...|n).png" + # where (a|b) means either a or b + samples_paths = sorted(path.glob(f"{split}/**/*.png")) + expected_nsamples = _EXPECTED_NSAMPLES[(category, split)] + + if len(samples_paths) != expected_nsamples: + warnings.warn( + f"Expected {expected_nsamples} samples for split '{split}' in category '{category}' but found {len(samples_paths)}." + "Is the dataset corrupted?" + ) + + def build_record(sample_path: Path): + + ret: Dict[str, Union[Path, None, str, int]] = { + "image_path": sample_path, + **dict(zip(("split", "super_anotype", "image_filename"), sample_path.parts[-3:])), + } + + super_anotype: str = ret["super_anotype"] # type: ignore + + if super_anotype == SUPER_ANOTYPE_GOOD: + ret.update( + { + "mask_path": None, + "label": LABEL_NORMAL, + "super_anotype": SUPER_ANOTYPE_GOOD, + "anotype": ANOTYPE_GOOD, + } + ) + return ret + + elif super_anotype in (SUPER_ANOTYPE_LOGICAL, SUPER_ANOTYPE_STRUCTURAL): + + mask_path: Path = path / "ground_truth" / super_anotype / sample_path.stem / "000.png" + + assert mask_path.exists(), f"Mask file '{mask_path}' does not exist. Is the dataset corrupted?" + + # mask images are supposed to have only two values: 0 and GTVALUE_ANOMALY + # GTVALUE_ANOMALY \in {234, ..., 255} and is given in the paper, encoded in the mapping below + # TODO create an issue to cache this info so the mask is not read here + gtvalue = read_mask(mask_path).astype(int).max() + _, anotype = _MAP_GTVALUE_2_ANOTYPE[(category, gtvalue)] + + ret.update( + { + "mask_path": mask_path, + "gtvalue": gtvalue, + "label": LABEL_ANOMALOUS, + "super_anotype": super_anotype, + "anotype": anotype, + } + ) + + return ret + + else: + # there should only be the folders "good", "logical_anomalies" and "structural_anomalies" + raise RuntimeError( + f"Something wrong in the dataset folder. Unknown folder {super_anotype}, path={sample_path}" + ) + + samples = pd.DataFrame.from_records([build_record(sp) for sp in samples_paths]) + + if imread_strategy == IMREAD_STRATEGY_PRELOAD: + + # warnings.warn( + # "Preloading images into memory. " + # "If your dataset is too large, consider using another imread_strategy instead.", + # stacklevel=3 + # ) + + logger.debug(f"Preloading images into memory") + samples["image"] = samples.image_path.map(read_image) + + logger.debug(f"Preloading masks into memory") + + # this is used to select the rows in the dataframe + has_mask = ~samples.mask_path.isnull() + + samples.loc[has_mask, "mask"] = samples.loc[has_mask, "mask_path"].map( + lambda x: _binarize_mask_float(read_mask(x)) + ) + samples.loc[~has_mask, "mask"] = None return samples @@ -342,40 +614,71 @@ def __init__( category: str, pre_process: PreProcessor, split: str, - task: str = "segmentation", - seed: Optional[int] = None, - create_validation_set: bool = False, + task: str = TASK_SEGMENTATION, + # create_validation_set: bool = False, + imread_strategy: str = IMREAD_STRATEGY_PRELOAD, ) -> None: super().__init__(root) - if seed is None: - warnings.warn( - "seed is None." - " When seed is not set, images from the normal directory are split between training and test dir." - " This will lead to inconsistency between runs." - ) + assert split in SPLITS, f"Split '{split}' is not supported. Supported splits are {SPLITS}" + assert task in TASKS, f"Task '{task}' is not supported. Supported tasks are {TASKS}" + assert ( + imread_strategy in IMREAD_STRATEGIES + ), f"Imread strategy '{imread_strategy}' is not supported. Supported imread strategies are {IMREAD_STRATEGIES}" self.root = Path(root) if isinstance(root, str) else root self.category: str = category self.split = split self.task = task - self.pre_process = pre_process + self.imread_strategy = imread_strategy - self.samples = make_mvtec_loco_dataset( - path=self.root / category, + self.samples = _make_dataset( + path=self.dataset_path, split=self.split, - seed=seed, - create_validation_set=create_validation_set, + imread_strategy=self.imread_strategy, ) + @property + def dataset_path(self) -> Path: + """Path to the dataset folder.""" + return self.root / self.category + def __len__(self) -> int: """Get length of the dataset.""" return len(self.samples) + def _get_image(self, index: int) -> ndarray: + """Get image at index.""" + + if self.imread_strategy == IMREAD_STRATEGY_PRELOAD: + return self.samples.image[index] + + elif self.imread_strategy == IMREAD_STRATEGY_ONTHEFLY: + return read_image(self.samples.image_path[index]) + + else: + raise NotImplementedError(f"Imread strategy '{self.imread_strategy}' is not supported.") + + def _get_mask(self, index: int) -> ndarray: + """Get mask at index.""" + + if self.imread_strategy == IMREAD_STRATEGY_PRELOAD: + return self.samples.mask[index] + + elif self.imread_strategy == IMREAD_STRATEGY_ONTHEFLY: + return _binarize_mask_float(read_mask(self.samples.mask_path[index])) + + else: + raise NotImplementedError(f"Imread strategy '{self.imread_strategy}' is not supported.") + def __getitem__(self, index: int) -> Dict[str, Union[str, Tensor]]: """Get dataset item for the index ``index``. + TODO: include the label string + TODO: include the label anomaly type (strutural, logical) + TODO: here? probably better to separate it... return the sPRO saturation value + Args: index (int): Index to get the item. @@ -385,33 +688,41 @@ def __getitem__(self, index: int) -> Dict[str, Union[str, Tensor]]: """ item: Dict[str, Union[str, Tensor]] = {} - image_path = self.samples.image_path[index] - image = read_image(image_path) - + image = self._get_image(index) pre_processed = self.pre_process(image=image) - item = {"image": pre_processed["image"]} - - if self.split in ["val", "test"]: - label_index = self.samples.label_index[index] - - item["image_path"] = image_path - item["label"] = label_index - - if self.task == "segmentation": - mask_path = self.samples.mask_path[index] + item = { + "image": pre_processed["image"], + } + + if self.split not in (SPLIT_VALIDATION, SPLIT_TEST): + return item + + item.update( + { + "label": self.samples.label[index], + "image_path": self.samples.image_path[index], + } + ) - # Only Anomalous (1) images has masks in MVTec AD dataset. - # Therefore, create empty mask for Normal (0) images. - if label_index == 0: - mask = np.zeros(shape=image.shape[:2]) - else: - mask = cv2.imread(mask_path, flags=0) / 255.0 + if self.task != TASK_SEGMENTATION: + return item - pre_processed = self.pre_process(image=image, mask=mask) + # Only Anomalous (1) images has masks in MVTec LOCO AD dataset. + # Therefore, create empty mask for Normal (0) images. + if self.samples.label[index] == LABEL_NORMAL: + mask = np.zeros(shape=image.shape[:2]) # shape: (H, W, C) - item["mask_path"] = mask_path - item["image"] = pre_processed["image"] - item["mask"] = pre_processed["mask"] + else: + mask = self._get_mask(index) + + # TODO: ask how this works, does the transform re-apply the last call when mask is not None? + pre_processed = self.pre_process(image=image, mask=mask) + item.update( + { + "mask_path": self.samples.mask_path[index], + "mask": pre_processed["mask"], + } + ) return item @@ -420,33 +731,37 @@ def __getitem__(self, index: int) -> Dict[str, Union[str, Tensor]]: class MVTecLOCO(LightningDataModule): """MVTec LOCO AD Lightning Data Module.""" + # todo correct inconsistency: `transform_config_*val*` used for val and test set, but `*test*_batch_size` used for val and set + def __init__( self, root: str, category: str, - # TODO: Remove default values. IAAALD-211 + # TODO: add a parameter to specify the anomaly types and (more specifically) the anomaly classes -- "label" image_size: Optional[Union[int, Tuple[int, int]]] = None, train_batch_size: int = 32, test_batch_size: int = 32, num_workers: int = 8, - task: str = "segmentation", + task: str = TASK_SEGMENTATION, transform_config_train: Optional[Union[str, A.Compose]] = None, transform_config_val: Optional[Union[str, A.Compose]] = None, seed: Optional[int] = None, - create_validation_set: bool = False, + imread_strategy: str = IMREAD_STRATEGY_PRELOAD, ) -> None: super().__init__() + # todo? add input assertions/validations + assert task in TASKS, f"Task '{task}' is not supported. Supported tasks are {TASKS}" + assert ( + imread_strategy in IMREAD_STRATEGIES + ), f"Imread strategy '{imread_strategy}' is not supported. Supported imread strategies are {IMREAD_STRATEGIES}" + self.root = root if isinstance(root, Path) else Path(root) self.category = category - self.dataset_path = self.root / self.category self.transform_config_train = transform_config_train self.transform_config_val = transform_config_val self.image_size = image_size - if self.transform_config_train is not None and self.transform_config_val is None: - self.transform_config_val = self.transform_config_train - self.pre_process_train = PreProcessor(config=self.transform_config_train, image_size=self.image_size) self.pre_process_val = PreProcessor(config=self.transform_config_val, image_size=self.image_size) @@ -454,84 +769,90 @@ def __init__( self.test_batch_size = test_batch_size self.num_workers = num_workers - self.create_validation_set = create_validation_set self.task = task self.seed = seed + self.imread_strategy = imread_strategy self.train_data: Dataset self.test_data: Dataset - if create_validation_set: - self.val_data: Dataset + self.val_data: Dataset self.inference_data: Dataset + @property + def dataset_path(self) -> Path: + return self.root / self.category + def prepare_data(self) -> None: """Download the dataset if not available.""" - if (self.root / self.category).is_dir(): + + if self.dataset_path.is_dir(): logger.info("Found the dataset.") + else: self.root.mkdir(parents=True, exist_ok=True) - logger.info("Downloading the Mvtec AD dataset.") - url = "https://www.mydrive.ch/shares/38536/3830184030e49fe74747669442f0f282/download/420938113-1629952094" - dataset_name = "mvtec_anomaly_detection.tar.xz" + logger.info("Downloading the Mvtec LOCO AD dataset.") + URL_MVTEC_LOCO_TARGZ = "https://www.mydrive.ch/shares/48237/1b9106ccdfbb09a0c414bd49fe44a14a/download/430647091-1646842701/mvtec_loco_anomaly_detection.tar.xz" + dataset_name = "mvtec_loco_anomaly_detection.tar.xz" zip_filename = self.root / dataset_name - with DownloadProgressBar(unit="B", unit_scale=True, miniters=1, desc="MVTec AD") as progress_bar: + with DownloadProgressBar(unit="B", unit_scale=True, miniters=1, desc="MVTec LOCO download") as progress_bar: urlretrieve( - url=f"{url}/{dataset_name}", + url=URL_MVTEC_LOCO_TARGZ, filename=zip_filename, reporthook=progress_bar.update_to, ) + logger.info("Checking hash") - hash_check(zip_filename, "eefca59f2cede9c3fc5b6befbfec275e") + MD5HASH_MVTEC_LOCO = "d40f092ac6f88433f609583c4a05f56f" + hash_check(zip_filename, MD5HASH_MVTEC_LOCO) - logger.info("Extracting the dataset.") - with tarfile.open(zip_filename) as tar_file: - tar_file.extractall(self.root) + logger.info(f"Extracting the dataset.") + logger.debug(f"Extracting to {self.root}") + tar_extract_all(zip_filename, self.root) logger.info("Cleaning the tar file") - (zip_filename).unlink() + zip_filename.unlink() def setup(self, stage: Optional[str] = None) -> None: """Setup train, validation and test data. Args: - stage: Optional[str]: Train/Val/Test stages. (Default value = None) + stage: Optional[str]: fit/validate/test/predict stages. (Default value = None = fit) """ - logger.info("Setting up train, validation, test and prediction datasets.") - if stage in (None, "fit"): - self.train_data = MVTecDataset( + logger.info("Setting up {} dataset." % str(stage or TrainerFn.FITTING)) + + if stage in (None, TrainerFn.FITTING): + self.train_data = MVTecLOCODataset( root=self.root, category=self.category, + split=SPLIT_TRAIN, pre_process=self.pre_process_train, - split="train", task=self.task, - seed=self.seed, - create_validation_set=self.create_validation_set, + imread_strategy=self.imread_strategy, ) - if self.create_validation_set: - self.val_data = MVTecDataset( + if stage == TrainerFn.VALIDATING: + self.val_data = MVTecLOCODataset( root=self.root, category=self.category, pre_process=self.pre_process_val, - split="val", + split=SPLIT_VALIDATION, task=self.task, - seed=self.seed, - create_validation_set=self.create_validation_set, + imread_strategy=self.imread_strategy, ) - self.test_data = MVTecLOCODataset( - root=self.root, - category=self.category, - pre_process=self.pre_process_val, - split="test", - task=self.task, - seed=self.seed, - create_validation_set=self.create_validation_set, - ) + if stage == TrainerFn.TESTING: + self.test_data = MVTecLOCODataset( + root=self.root, + category=self.category, + pre_process=self.pre_process_val, + split=SPLIT_TEST, + task=self.task, + imread_strategy=self.imread_strategy, + ) - if stage == "predict": + if stage == TrainerFn.PREDICTING: self.inference_data = InferenceDataset( path=self.root, image_size=self.image_size, transform_config=self.transform_config_val ) @@ -542,8 +863,7 @@ def train_dataloader(self) -> TRAIN_DATALOADERS: def val_dataloader(self) -> EVAL_DATALOADERS: """Get validation dataloader.""" - dataset = self.val_data if self.create_validation_set else self.test_data - return DataLoader(dataset=dataset, shuffle=False, batch_size=self.test_batch_size, num_workers=self.num_workers) + return DataLoader(self.val_data, shuffle=False, batch_size=self.test_batch_size, num_workers=self.num_workers) def test_dataloader(self) -> EVAL_DATALOADERS: """Get test dataloader.""" @@ -554,3 +874,54 @@ def predict_dataloader(self) -> EVAL_DATALOADERS: return DataLoader( self.inference_data, shuffle=False, batch_size=self.test_batch_size, num_workers=self.num_workers ) + + +# TODO remove me +# debug _make_dataset in main +if __name__ == "__main__": + import itertools + + for cat in CATEGORIES: + _make_dataset(Path(f"/home/jcasagrandebertoldo/Downloads/loco/{cat}")) + + for cat, split_ in itertools.product(CATEGORIES, SPLITS): + pass + # _make_dataset(Path(f"/home/jcasagrandebertoldo/Downloads/loco/{cat}"), split_) + # next + # next + # next + # next + # next + # next + # next + # next + # test instantiate of the two classes + # then test with script + # next + # next + # next + # next + # next + # next + # next + # next + # next + # next + # next + # next + # next + # next + # next + # next + # next + # next + # next + # next + # next + # next + # next + # next + # next + # next + # next + # next diff --git a/anomalib/data/utils/__init__.py b/anomalib/data/utils/__init__.py index 53cf04e4fd..e8ce1cfe28 100644 --- a/anomalib/data/utils/__init__.py +++ b/anomalib/data/utils/__init__.py @@ -5,7 +5,12 @@ from .download import DownloadProgressBar, hash_check from .generators import random_2d_perlin -from .image import generate_output_image_filename, get_image_filenames, read_image +from .image import ( + generate_output_image_filename, + get_image_filenames, + read_image, + read_mask, +) __all__ = [ "generate_output_image_filename", @@ -13,5 +18,6 @@ "hash_check", "random_2d_perlin", "read_image", + "read_mask", "DownloadProgressBar", ] diff --git a/anomalib/data/utils/download.py b/anomalib/data/utils/download.py index eefe001f44..b16a56834a 100644 --- a/anomalib/data/utils/download.py +++ b/anomalib/data/utils/download.py @@ -5,6 +5,7 @@ import hashlib import io +import tarfile from pathlib import Path from typing import Dict, Iterable, Optional, Union @@ -195,3 +196,15 @@ def hash_check(file_path: Path, expected_hash: str): assert ( hashlib.md5(hash_file.read()).hexdigest() == expected_hash ), f"Downloaded file {file_path} does not match the required hash." + + +def tar_extract_all(file_path: Path, output_dir: Path): + """Extract all files from a targz archive. + + Args: + file_path (Path): Path to archive. + output_dir (Path): Output directory. + """ + with tarfile.open(name=file_path) as tar_file: + for member in tqdm(iterable=tar_file.getmembers(), total=len(tar_file.getmembers())): + tar_file.extract(member=member, path=output_dir) diff --git a/anomalib/data/utils/image.py b/anomalib/data/utils/image.py index c8c5039882..cb4e48723b 100644 --- a/anomalib/data/utils/image.py +++ b/anomalib/data/utils/image.py @@ -160,6 +160,11 @@ def read_image(path: Union[str, Path]) -> np.ndarray: return image +def read_mask(mask_path: Union[str, Path]) -> np.ndarray: + """Read a mask image from disk and keep the original values.""" + return cv2.imread(str(mask_path), flags=cv2.IMREAD_GRAYSCALE) + + def pad_nextpow2(batch: Tensor) -> Tensor: """Compute required padding from input size and return padded images. From 7a80914dc99d17f88c9bb0eca3ac6ace6d3c0d86 Mon Sep 17 00:00:00 2001 From: jpcbertoldo <24547377+jpcbertoldo@users.noreply.github.com> Date: Sat, 3 Sep 2022 23:13:58 +0200 Subject: [PATCH 03/38] pass pre-commit hooks --- anomalib/data/mvtec_loco.py | 154 ++++++++++++++++++++++-------------- 1 file changed, 93 insertions(+), 61 deletions(-) diff --git a/anomalib/data/mvtec_loco.py b/anomalib/data/mvtec_loco.py index 639ddc2f64..25d173992b 100644 --- a/anomalib/data/mvtec_loco.py +++ b/anomalib/data/mvtec_loco.py @@ -1,4 +1,26 @@ -""" +"""MVTec LOCO AD Dataset (CC BY-NC-SA 4.0). + +Description: + This script contains PyTorch Dataset, Dataloader and PyTorch + Lightning DataModule for the MVTec LOCO AD dataset. + + If the dataset is not on the file system, the script downloads and extracts the dataset. + +License: + MVTec LOCO AD dataset is released under the Creative Commons + Attribution-NonCommercial-ShareAlike 4.0 International License + (CC BY-NC-SA 4.0)(https://creativecommons.org/licenses/by-nc-sa/4.0/). + +Reference: + + - Paul Bergmann, Kilian Batzner, Michael Fauser, David Sattlegger, Carsten Steger: + Beyond Dents and Scratches: Logical Constraints in Unsupervised Anomaly Detection + and Localization; in: International Journal of Computer Vision, 2022, + DOI: 10.1007/s11263-022-01578-9. + + - https://www.mvtec.com/company/research/datasets/mvtec-loco + +################################################## distinguishes structural and logical anomalies n_image: 3644 @@ -35,8 +57,10 @@ objects are in a fixed position (mechanical alignment) illumination is well suited the access to images with real anomalies is limited (“impossible”) -images only show a single object or logically ensemble set of objects (i.e. one-class setting although a “class” here is a composed object) -no training annotations -- although it is assumed that the images in training are indeed from the target class (i.e. no noise) +images only show a single object or logically ensemble set of objects + (i.e. one-class setting although a “class” here is a composed object) +no training annotations -- although it is assumed that the images in training + are indeed from the target class (i.e. no noise) problem 1 (image-wise anomaly detection): “is there an anomaly in the image?” problem 2 (pixel-wise anomaly detection or anomaly segmentation): “which pixels belong to the anomaly?” pixel-wise metric: Saturated Per-Region Overlap (sPRO) @@ -116,7 +140,8 @@ 2: yellow 3: blue 5: red -the cable has to terminate in the same relative position on its two ends such that the whole construction exhibits a mirror symmetry +the cable has to terminate in the same relative position on its two ends such + that the whole construction exhibits a mirror symmetry examples of logical defects (left) the two splicing connectors do not have the same number of clamps (center) the color of the cable does not match the number of clamps @@ -127,7 +152,8 @@ missing objects the area in which the object could occur the saturation threshold is chosen to be equal to the area of the missing object -the saturation threshold for an object is chosen from the lower end of the distribution of its (manually annotated) area +the saturation threshold for an object is chosen from the lower end of + the distribution of its (manually annotated) area example (image): pushpin the missing pushpin can occur anywhere inside its compartment, therefore its entire area is annotated the saturation threshold is set to the size of a pushpin @@ -150,24 +176,23 @@ other logical constraints example (image, left): juice bottle the bottle is filled with orange juice but carries the label of the cherry juice -both the orange juice and the label with the cherry are present in the training set, but the logical anomaly arises due to the erroneous combination of the two in the same image -either the area filled with juice or the cherry as could be considered anomalous, therefore the union of the two regions is annotated -the saturation threshold is set to the area of the cherry because the segmentation of the cherry is sufficient to solve the anomaly localization +both the orange juice and the label with the cherry are present in the training set, + but the logical anomaly arises due to the erroneous combination of the two in the same image +either the area filled with juice or the cherry as could be considered anomalous, + therefore the union of the two regions is annotated +the saturation threshold is set to the area of the cherry because the + segmentation of the cherry is sufficient to solve the anomaly localization """ # TODO: clear module docstring import logging -import tarfile import warnings from pathlib import Path -from posixpath import split -from typing import Dict, List, Optional, Tuple, Union -from unicodedata import category +from typing import Dict, Optional, Tuple, Union from urllib.request import urlretrieve import albumentations as A -import cv2 import numpy as np import pandas as pd from numpy import ndarray @@ -187,7 +212,9 @@ from anomalib.pre_processing import PreProcessor # TODO: open discussion about keeping pre-resized tensors in the dataset folder -# TODO: create an issue in mvtec so the dataset will retain the information abou the anomaly type (label) so one can do per-label evaluation +# obs: mvtecad's doc says "...and create PyTorch data objects." but it does not!!! +# TODO: create an issue in mvtec so the dataset will retain the information abou +# the anomaly type (label) so one can do per-label evaluation # TODO: document the notion of label and superlabel logger = logging.getLogger(__name__) @@ -342,7 +369,8 @@ ) # this is given at the paper, each anomaly type (label) has a different gtvalue in the mask -# source: Beyond Dents and Scratches: Logical Constraints in Unsupervised Anomaly Detection and Localization (Bergmann, P. et al, 2022). +# source: Beyond Dents and Scratches: Logical Constraints in +# Unsupervised Anomaly Detection and Localization (Bergmann, P. et al, 2022). _MAP_ANOTYPE_2_GTVALUE: Dict[Tuple[str, str, str], int] = { (CATEGORY_BREAKFAST_BOX, SUPER_ANOTYPE_LOGICAL, ANOTYPE_BB_COMPARTMENTS_SWAPPED): 242, (CATEGORY_BREAKFAST_BOX, SUPER_ANOTYPE_LOGICAL, ANOTYPE_BB_MISSING_ALMONDS): 255, @@ -442,7 +470,8 @@ } # expected number of images in each category split so that we can check if the dataset is complete -# source: Beyond Dents and Scratches: Logical Constraints in Unsupervised Anomaly Detection and Localization (Bergmann, P. et al, 2022). +# source: Beyond Dents and Scratches: Logical Constraints +# in Unsupervised Anomaly Detection and Localization (Bergmann, P. et al, 2022). _EXPECTED_NSAMPLES: Dict[Tuple[str, str], int] = { (CATEGORY_BREAKFAST_BOX, SPLIT_TRAIN): 351, (CATEGORY_BREAKFAST_BOX, SPLIT_VALIDATION): 62, @@ -466,10 +495,11 @@ } -def _binarize_mask_float(mask: np.ndarray) -> np.ndarray: +def _binarize_mask_float(mask: np.ndarray) -> np.ndarray: # noqa """ the masks use different gtvalue values for the different anomaly types so the > 0 is making it binary - this operation is very simple but it is in a function to make sure its standard because it is used in different places + this operation is very simple but it is in a function to make + sure its standard because it is used in different places e.g. preloading while building the dataset and on the fly while training """ return (mask > 0).astype(float) @@ -479,22 +509,11 @@ def _make_dataset( path: Path, split: Optional[str] = None, imread_strategy: str = IMREAD_STRATEGY_PRELOAD, -) -> DataFrame: - # todo create optional to get a subset of anomlies in the test - - assert split is None or split in SPLITS, f"Invalid split: {split}" - assert imread_strategy in IMREAD_STRATEGIES, f"Invalid imread strategy: {imread_strategy}" - - category = path.resolve().name - assert category in CATEGORIES, f"Invalid path '{path}'. The directory ('{category}') must be one of {CATEGORIES}" - - if split is None: - return pd.concat([_make_dataset(path, split_, imread_strategy) for split_ in SPLITS], axis=0) - - logger.info(f"Creating MVTec LOCO AD dataset for category '{category}' split '{split}'") - +) -> DataFrame: # noqa D212 """ - structure of the files in the dataset ("/" is 'path') + Find the images in the given path and create a DataFrame with all the information from each sample. + + Expected structure of the files in the dataset ("/" is 'path') images: /{split}/{super_anotype}/{image_index}.png @@ -517,6 +536,18 @@ def _make_dataset( /ground_truth/structural_anomalies/.../000.png ... """ + # todo create optional to get a subset of anomlies in the test + + assert split is None or split in SPLITS, f"Invalid split: {split}" + assert imread_strategy in IMREAD_STRATEGIES, f"Invalid imread strategy: {imread_strategy}" + + category = path.resolve().name + assert category in CATEGORIES, f"Invalid path '{path}'. The directory ('{category}') must be one of {CATEGORIES}" + + if split is None: + return pd.concat([_make_dataset(path, split_, imread_strategy) for split_ in SPLITS], axis=0) + + logger.info("Creating MVTec LOCO AD dataset for category '%s' split '%s'", category, split) # these values look like "(train|validation|test)/(good|logical_anomalies|structural_anomalies)/(000|...|n).png" # where (a|b) means either a or b @@ -525,7 +556,8 @@ def _make_dataset( if len(samples_paths) != expected_nsamples: warnings.warn( - f"Expected {expected_nsamples} samples for split '{split}' in category '{category}' but found {len(samples_paths)}." + f"Expected {expected_nsamples} samples for split '{split}' " + "in category '{category}' but found {len(samples_paths)}." "Is the dataset corrupted?" ) @@ -549,7 +581,7 @@ def build_record(sample_path: Path): ) return ret - elif super_anotype in (SUPER_ANOTYPE_LOGICAL, SUPER_ANOTYPE_STRUCTURAL): + if super_anotype in (SUPER_ANOTYPE_LOGICAL, SUPER_ANOTYPE_STRUCTURAL): mask_path: Path = path / "ground_truth" / super_anotype / sample_path.stem / "000.png" @@ -573,11 +605,8 @@ def build_record(sample_path: Path): return ret - else: - # there should only be the folders "good", "logical_anomalies" and "structural_anomalies" - raise RuntimeError( - f"Something wrong in the dataset folder. Unknown folder {super_anotype}, path={sample_path}" - ) + # there should only be the folders "good", "logical_anomalies" and "structural_anomalies" + raise RuntimeError(f"Something wrong in the dataset folder. Unknown folder {super_anotype}, path={sample_path}") samples = pd.DataFrame.from_records([build_record(sp) for sp in samples_paths]) @@ -589,10 +618,10 @@ def build_record(sample_path: Path): # stacklevel=3 # ) - logger.debug(f"Preloading images into memory") + logger.debug("Preloading images into memory") samples["image"] = samples.image_path.map(read_image) - logger.debug(f"Preloading masks into memory") + logger.debug("Preloading masks into memory") # this is used to select the rows in the dataframe has_mask = ~samples.mask_path.isnull() @@ -634,14 +663,14 @@ def __init__( self.imread_strategy = imread_strategy self.samples = _make_dataset( - path=self.dataset_path, + path=self.category_dataset_path, split=self.split, imread_strategy=self.imread_strategy, ) @property - def dataset_path(self) -> Path: - """Path to the dataset folder.""" + def category_dataset_path(self) -> Path: + """Path to the category dataset (root/category) folder.""" return self.root / self.category def __len__(self) -> int: @@ -654,11 +683,10 @@ def _get_image(self, index: int) -> ndarray: if self.imread_strategy == IMREAD_STRATEGY_PRELOAD: return self.samples.image[index] - elif self.imread_strategy == IMREAD_STRATEGY_ONTHEFLY: + if self.imread_strategy == IMREAD_STRATEGY_ONTHEFLY: return read_image(self.samples.image_path[index]) - else: - raise NotImplementedError(f"Imread strategy '{self.imread_strategy}' is not supported.") + raise NotImplementedError(f"Imread strategy '{self.imread_strategy}' is not supported.") def _get_mask(self, index: int) -> ndarray: """Get mask at index.""" @@ -666,11 +694,10 @@ def _get_mask(self, index: int) -> ndarray: if self.imread_strategy == IMREAD_STRATEGY_PRELOAD: return self.samples.mask[index] - elif self.imread_strategy == IMREAD_STRATEGY_ONTHEFLY: + if self.imread_strategy == IMREAD_STRATEGY_ONTHEFLY: return _binarize_mask_float(read_mask(self.samples.mask_path[index])) - else: - raise NotImplementedError(f"Imread strategy '{self.imread_strategy}' is not supported.") + raise NotImplementedError(f"Imread strategy '{self.imread_strategy}' is not supported.") def __getitem__(self, index: int) -> Dict[str, Union[str, Tensor]]: """Get dataset item for the index ``index``. @@ -731,7 +758,8 @@ def __getitem__(self, index: int) -> Dict[str, Union[str, Tensor]]: class MVTecLOCO(LightningDataModule): """MVTec LOCO AD Lightning Data Module.""" - # todo correct inconsistency: `transform_config_*val*` used for val and test set, but `*test*_batch_size` used for val and set + # todo correct inconsistency: `transform_config_*val*` used for val and + # test set, but `*test*_batch_size` used for val and set def __init__( self, @@ -779,35 +807,38 @@ def __init__( self.inference_data: Dataset @property - def dataset_path(self) -> Path: + def category_dataset_path(self) -> Path: + """Path to the category dataset (root/category) folder.""" return self.root / self.category def prepare_data(self) -> None: """Download the dataset if not available.""" - if self.dataset_path.is_dir(): + if self.category_dataset_path.is_dir(): logger.info("Found the dataset.") else: self.root.mkdir(parents=True, exist_ok=True) logger.info("Downloading the Mvtec LOCO AD dataset.") - URL_MVTEC_LOCO_TARGZ = "https://www.mydrive.ch/shares/48237/1b9106ccdfbb09a0c414bd49fe44a14a/download/430647091-1646842701/mvtec_loco_anomaly_detection.tar.xz" + # flake8: noqa: E501 + # pylint: disable=line-too-long + url_mvtec_loco_targz = "https://www.mydrive.ch/shares/48237/1b9106ccdfbb09a0c414bd49fe44a14a/download/430647091-1646842701/mvtec_loco_anomaly_detection.tar.xz" dataset_name = "mvtec_loco_anomaly_detection.tar.xz" zip_filename = self.root / dataset_name with DownloadProgressBar(unit="B", unit_scale=True, miniters=1, desc="MVTec LOCO download") as progress_bar: urlretrieve( - url=URL_MVTEC_LOCO_TARGZ, + url=url_mvtec_loco_targz, filename=zip_filename, reporthook=progress_bar.update_to, ) logger.info("Checking hash") - MD5HASH_MVTEC_LOCO = "d40f092ac6f88433f609583c4a05f56f" - hash_check(zip_filename, MD5HASH_MVTEC_LOCO) + md5hash_mvtec_loco = "d40f092ac6f88433f609583c4a05f56f" + hash_check(zip_filename, md5hash_mvtec_loco) - logger.info(f"Extracting the dataset.") - logger.debug(f"Extracting to {self.root}") + logger.info("Extracting the dataset.") + logger.debug("Extracting to %s", self.root) tar_extract_all(zip_filename, self.root) logger.info("Cleaning the tar file") @@ -820,7 +851,8 @@ def setup(self, stage: Optional[str] = None) -> None: stage: Optional[str]: fit/validate/test/predict stages. (Default value = None = fit) """ - logger.info("Setting up {} dataset." % str(stage or TrainerFn.FITTING)) + # pylint: disable=consider-using-f-string + logger.info("Setting up %s dataset." % stage or TrainerFn.FITTING) if stage in (None, TrainerFn.FITTING): self.train_data = MVTecLOCODataset( From 375ac656749dc998e3547959c0999ec4a84e0229 Mon Sep 17 00:00:00 2001 From: jpcbertoldo <24547377+jpcbertoldo@users.noreply.github.com> Date: Sat, 3 Sep 2022 23:49:48 +0200 Subject: [PATCH 04/38] remove todos and correct a const --- anomalib/data/mvtec_loco.py | 185 +----------------------------------- 1 file changed, 5 insertions(+), 180 deletions(-) diff --git a/anomalib/data/mvtec_loco.py b/anomalib/data/mvtec_loco.py index 25d173992b..98e13ef8e0 100644 --- a/anomalib/data/mvtec_loco.py +++ b/anomalib/data/mvtec_loco.py @@ -19,173 +19,8 @@ DOI: 10.1007/s11263-022-01578-9. - https://www.mvtec.com/company/research/datasets/mvtec-loco - -################################################## - -distinguishes structural and logical anomalies -n_image: 3644 -splits: -no overlap and fixed -train -normal-only -n_image_train: 1772 -validation -normal-only -n_image_validation: 304 -test -normal + anomalous (structural and logical) -n_image_test: 1568 -n_category: 5 -breakfast_box -juice_bottle -pushpins -screw_bag -splicing_connectors -n_defect_type: 89 - -################################################## - -configs -https://docs.google.com/spreadsheets/d/1qHbyTsU2At1fusQsV8KH3_qdp3SYHHLy7QtpohKJIk0/edit?usp=sharing - -stats overview -https://docs.google.com/spreadsheets/d/11GSf1SVsHFYDSwMAULEd7g5QK7P3Y21YMB10D_g0-Gk/edit?usp=sharing - -################################################## - -assumptions -objects are in a fixed position (mechanical alignment) -illumination is well suited -the access to images with real anomalies is limited (“impossible”) -images only show a single object or logically ensemble set of objects - (i.e. one-class setting although a “class” here is a composed object) -no training annotations -- although it is assumed that the images in training - are indeed from the target class (i.e. no noise) -problem 1 (image-wise anomaly detection): “is there an anomaly in the image?” -problem 2 (pixel-wise anomaly detection or anomaly segmentation): “which pixels belong to the anomaly?” -pixel-wise metric: Saturated Per-Region Overlap (sPRO) -structural anomaly pixel annotation policy -defects are confined to local regions -each pixel that introduces a visual structure that is not present in the anomaly-free images is anomalous -logical anomaly pixel annotation policy -the union of all areas of the image that could be the cause for the anomaly is anomalous -a method is not necessarily required to predict the whole ground truth area as anomalous - -################################################## - -breakfast box -n_anomaly_type (n_structural, n_logical): 22 (5, 17) -logical constraints -contains 2 tangerines -contains 1 nectarine -the tangerines and the nectarine on the left -cereals (C) and a mix of banana chips and almonds (B&A) on the right -the ratio between C and B&A is fixed -the relative position of C and B&A is fixed -examples of logical defects -too many banana chips and almonds - -################################################## - -juice bottle -n_anomaly_type (n_structural, n_logical): 18 (7, 11) -logical constraints -there is 1 bottle -the bottle is filled with a liquid and the fill level is always the same -the liquid is of 1 out of 3 colors (red, yellow, white-ish) -the bottle carries 2 labels -the first label is attached to the center of the bottle -the first label displays an icon that determines the type of liquid (cherry, orange, banana) -cherry: red -orange: yellow -banana: white-ish -the second label is attached to the lower part of the bottle -the second label contains the text “100% Juice” -examples of logical defects -(left) the icon does not match the type of juice -(middle) the icon is slightly misplaced -(right) the fill level is too high - -################################################## - -pushpins -n_anomaly_type (n_structural, n_logical): 8 (4, 4) -logical constraints -each compartment contains 1 pushpin -examples of logical defects -1 compartment has a missing pin - -################################################## - -screw bag -n_anomaly_type (n_structural, n_logical): 20 (4, 16) -logical constraints -the bag contains -2 washers -2 nuts -1 long screw -2 short screw -examples of logical defects -two long screws and lacks a short one - -################################################## - -splicing connectors -n_anomaly_type (n_structural, n_logical): 21 (8, 13) -logical constraints -there are 2 splicing connectors -they have the same number of cable clamps -they are linked by 1 cable -the number of clamps has a one-to-one correspondence to the color of the cable -2: yellow -3: blue -5: red -the cable has to terminate in the same relative position on its two ends such - that the whole construction exhibits a mirror symmetry -examples of logical defects -(left) the two splicing connectors do not have the same number of clamps -(center) the color of the cable does not match the number of clamps -(right) the cable terminates in different positions - -################################################## - -missing objects -the area in which the object could occur -the saturation threshold is chosen to be equal to the area of the missing object -the saturation threshold for an object is chosen from the lower end of - the distribution of its (manually annotated) area -example (image): pushpin -the missing pushpin can occur anywhere inside its compartment, therefore its entire area is annotated -the saturation threshold is set to the size of a pushpin - -################################################## - -additional objects -too many instances of an object: all instances of the object are annotated -the saturation threshold is set to the area of the extraneous objects -example (image): splicing connectors -an additional cable is present between the two splicing connectors -it is not clear which of the two cables represents the anomaly, therefore both are annotated -the saturation threshold is set to the area of one cable (i.e., half of the annotated region) -properties -a method can obtain a perfect score even if it only marks one of the two cables as an anomaly -a method that marks both is neither penalized nor (extra-)rewarded - -################################################## - -other logical constraints -example (image, left): juice bottle -the bottle is filled with orange juice but carries the label of the cherry juice -both the orange juice and the label with the cherry are present in the training set, - but the logical anomaly arises due to the erroneous combination of the two in the same image -either the area filled with juice or the cherry as could be considered anomalous, - therefore the union of the two regions is annotated -the saturation threshold is set to the area of the cherry because the - segmentation of the cherry is sufficient to solve the anomaly localization """ -# TODO: clear module docstring - import logging import warnings from pathlib import Path @@ -211,17 +46,12 @@ from anomalib.data.utils.download import tar_extract_all from anomalib.pre_processing import PreProcessor -# TODO: open discussion about keeping pre-resized tensors in the dataset folder -# obs: mvtecad's doc says "...and create PyTorch data objects." but it does not!!! -# TODO: create an issue in mvtec so the dataset will retain the information abou -# the anomaly type (label) so one can do per-label evaluation -# TODO: document the notion of label and superlabel - logger = logging.getLogger(__name__) +TASK_CLASSIFICATION = "classification" TASK_SEGMENTATION = "segmentation" -TASKS = TASK_SEGMENTATION +TASKS = (TASK_CLASSIFICATION, TASK_SEGMENTATION) SPLIT_TRAIN = "train" # "validation" instead of "val" is an explicit choice because this will match the name of the folder @@ -234,7 +64,6 @@ # all images are pre-loaded into memory at initialization IMREAD_STRATEGY_PRELOAD = "preload" IMREAD_STRATEGIES = (IMREAD_STRATEGY_ONTHEFLY, IMREAD_STRATEGY_PRELOAD) -# TODO make a strategy preload_tensor CATEGORY_BREAKFAST_BOX = "breakfast_box" CATEGORY_JUICE_BOTTLE = "juice_bottle" @@ -486,7 +315,6 @@ (CATEGORY_SCREW_BAG, SPLIT_VALIDATION): 60, (CATEGORY_SCREW_BAG, SPLIT_TEST): 341, # these two below were wrong in the paper - # TODO send a correction to the authors # (CATEGORY_SPLICING_CONNECTORS, SPLIT_TRAIN): 354, # (CATEGORY_SPLICING_CONNECTORS, SPLIT_VALIDATION): 59, (CATEGORY_SPLICING_CONNECTORS, SPLIT_TRAIN): 360, @@ -702,10 +530,6 @@ def _get_mask(self, index: int) -> ndarray: def __getitem__(self, index: int) -> Dict[str, Union[str, Tensor]]: """Get dataset item for the index ``index``. - TODO: include the label string - TODO: include the label anomaly type (strutural, logical) - TODO: here? probably better to separate it... return the sPRO saturation value - Args: index (int): Index to get the item. @@ -728,6 +552,8 @@ def __getitem__(self, index: int) -> Dict[str, Union[str, Tensor]]: { "label": self.samples.label[index], "image_path": self.samples.image_path[index], + "anotype": self.samples.anotype[index], + "super_anotype": self.samples.super_anotype[index], } ) @@ -742,7 +568,6 @@ def __getitem__(self, index: int) -> Dict[str, Union[str, Tensor]]: else: mask = self._get_mask(index) - # TODO: ask how this works, does the transform re-apply the last call when mask is not None? pre_processed = self.pre_process(image=image, mask=mask) item.update( { @@ -765,7 +590,7 @@ def __init__( self, root: str, category: str, - # TODO: add a parameter to specify the anomaly types and (more specifically) the anomaly classes -- "label" + # TODO: add a parameter to specify the anomaly types and (more specifically) image_size: Optional[Union[int, Tuple[int, int]]] = None, train_batch_size: int = 32, test_batch_size: int = 32, From f63d5a95eff9ef893dec689787992e69fc7e889f Mon Sep 17 00:00:00 2001 From: jpcbertoldo <24547377+jpcbertoldo@users.noreply.github.com> Date: Sat, 3 Sep 2022 23:51:33 +0200 Subject: [PATCH 05/38] remove todos and correct a const --- anomalib/data/mvtec_loco.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/anomalib/data/mvtec_loco.py b/anomalib/data/mvtec_loco.py index 98e13ef8e0..63a9fd9c89 100644 --- a/anomalib/data/mvtec_loco.py +++ b/anomalib/data/mvtec_loco.py @@ -440,11 +440,11 @@ def build_record(sample_path: Path): if imread_strategy == IMREAD_STRATEGY_PRELOAD: - # warnings.warn( - # "Preloading images into memory. " - # "If your dataset is too large, consider using another imread_strategy instead.", - # stacklevel=3 - # ) + warnings.warn( + "Preloading images into memory. " + "If your dataset is too large, consider using another imread_strategy instead.", + stacklevel=3, + ) logger.debug("Preloading images into memory") samples["image"] = samples.image_path.map(read_image) From fd2d9171a1e26959b5f24220293162e4eb355e29 Mon Sep 17 00:00:00 2001 From: jpcbertoldo <24547377+jpcbertoldo@users.noreply.github.com> Date: Sun, 4 Sep 2022 14:48:15 +0200 Subject: [PATCH 06/38] create notebook and make small corrections --- anomalib/data/__init__.py | 15 + anomalib/data/mvtec_loco.py | 287 ++++++++----- .../100_datamodules/104_mvtec_loco.ipynb | 394 ++++++++++++++++++ 3 files changed, 587 insertions(+), 109 deletions(-) create mode 100644 notebooks/100_datamodules/104_mvtec_loco.ipynb diff --git a/anomalib/data/__init__.py b/anomalib/data/__init__.py index 8c295a1061..fa0a9036b4 100644 --- a/anomalib/data/__init__.py +++ b/anomalib/data/__init__.py @@ -13,6 +13,7 @@ from .folder import Folder from .inference import InferenceDataset from .mvtec import MVTec +from .mvtec_loco import MVTecLOCO logger = logging.getLogger(__name__) @@ -79,6 +80,19 @@ def get_datamodule(config: Union[DictConfig, ListConfig]) -> LightningDataModule transform_config_val=config.dataset.transform_config.val, create_validation_set=config.dataset.create_validation_set, ) + elif config.dataset.format.lower() == "mvtec_loco": + datamodule = MVTecLOCO( + root=config.dataset.path, + category=config.dataset.category, + image_size=(config.dataset.image_size[0], config.dataset.image_size[1]), + train_batch_size=config.dataset.train_batch_size, + test_batch_size=config.dataset.test_batch_size, + num_workers=config.dataset.num_workers, + task=config.dataset.task, + transform_config_train=config.dataset.transform_config.train, + transform_config_val=config.dataset.transform_config.val, + imread_strategy=config.dataset.imread_strategy, + ) else: raise ValueError( "Unknown dataset! \n" @@ -95,4 +109,5 @@ def get_datamodule(config: Union[DictConfig, ListConfig]) -> LightningDataModule "Folder", "InferenceDataset", "MVTec", + "MVTecLOCO", ] diff --git a/anomalib/data/mvtec_loco.py b/anomalib/data/mvtec_loco.py index 63a9fd9c89..5280cf470e 100644 --- a/anomalib/data/mvtec_loco.py +++ b/anomalib/data/mvtec_loco.py @@ -59,11 +59,14 @@ SPLIT_TEST = "test" SPLITS = (SPLIT_TRAIN, SPLIT_VALIDATION, SPLIT_TEST) -# each image is read upon demand IMREAD_STRATEGY_ONTHEFLY = "onthefly" -# all images are pre-loaded into memory at initialization +"""Images are read into memory upon demand (no cache).""" + IMREAD_STRATEGY_PRELOAD = "preload" +"""All images are read into memory at initialization.""" + IMREAD_STRATEGIES = (IMREAD_STRATEGY_ONTHEFLY, IMREAD_STRATEGY_PRELOAD) +"""Options of strategies to read images into memory.""" CATEGORY_BREAKFAST_BOX = "breakfast_box" CATEGORY_JUICE_BOTTLE = "juice_bottle" @@ -333,9 +336,46 @@ def _binarize_mask_float(mask: np.ndarray) -> np.ndarray: # noqa return (mask > 0).astype(float) +def download_and_extract_mvtec_loco(root: Union[str, Path]) -> None: + """Download and extract the MVTec LOCO dataset to the given root directory. + + Args: + root (Union[str, Path]): directory where the dataset will be stored. + + """ + root = Path(root) + root.mkdir(parents=True, exist_ok=True) + + logger.info("Downloading the Mvtec LOCO AD dataset.") + + # flake8: noqa: E501 + # pylint: disable=line-too-long + url_mvtec_loco_targz = "https://www.mydrive.ch/shares/48237/1b9106ccdfbb09a0c414bd49fe44a14a/download/430647091-1646842701/mvtec_loco_anomaly_detection.tar.xz" + + dataset_name = "mvtec_loco_anomaly_detection.tar.xz" + zip_filename = root / dataset_name + with DownloadProgressBar(unit="B", unit_scale=True, miniters=1, desc="MVTec LOCO download") as progress_bar: + urlretrieve( + url=url_mvtec_loco_targz, + filename=zip_filename, + reporthook=progress_bar.update_to, + ) + + logger.info("Checking hash") + md5hash_mvtec_loco = "d40f092ac6f88433f609583c4a05f56f" + hash_check(zip_filename, md5hash_mvtec_loco) + + logger.info("Extracting the dataset.") + logger.debug("Extracting to %s", root) + tar_extract_all(zip_filename, root) + + logger.info("Cleaning the tar file") + zip_filename.unlink() + + def _make_dataset( path: Path, - split: Optional[str] = None, + split: str, imread_strategy: str = IMREAD_STRATEGY_PRELOAD, ) -> DataFrame: # noqa D212 """ @@ -364,16 +404,12 @@ def _make_dataset( /ground_truth/structural_anomalies/.../000.png ... """ - # todo create optional to get a subset of anomlies in the test - assert split is None or split in SPLITS, f"Invalid split: {split}" + assert split in SPLITS, f"Invalid split: {split}" assert imread_strategy in IMREAD_STRATEGIES, f"Invalid imread strategy: {imread_strategy}" category = path.resolve().name - assert category in CATEGORIES, f"Invalid path '{path}'. The directory ('{category}') must be one of {CATEGORIES}" - - if split is None: - return pd.concat([_make_dataset(path, split_, imread_strategy) for split_ in SPLITS], axis=0) + assert category in CATEGORIES, f"Invalid path '{path}'. The category '{category}' must be one of {CATEGORIES}" logger.info("Creating MVTec LOCO AD dataset for category '%s' split '%s'", category, split) @@ -382,10 +418,12 @@ def _make_dataset( samples_paths = sorted(path.glob(f"{split}/**/*.png")) expected_nsamples = _EXPECTED_NSAMPLES[(category, split)] + assert len(samples_paths) > 0, f"No samples found in {path}" + if len(samples_paths) != expected_nsamples: warnings.warn( f"Expected {expected_nsamples} samples for split '{split}' " - "in category '{category}' but found {len(samples_paths)}." + f"in category '{category}' but found {len(samples_paths)}." "Is the dataset corrupted?" ) @@ -447,12 +485,12 @@ def build_record(sample_path: Path): ) logger.debug("Preloading images into memory") - samples["image"] = samples.image_path.map(read_image) + samples["image"] = samples["image_path"].map(read_image) logger.debug("Preloading masks into memory") # this is used to select the rows in the dataframe - has_mask = ~samples.mask_path.isnull() + has_mask = ~samples["mask_path"].isnull() samples.loc[has_mask, "mask"] = samples.loc[has_mask, "mask_path"].map( lambda x: _binarize_mask_float(read_mask(x)) @@ -469,12 +507,31 @@ def __init__( self, root: Union[Path, str], category: str, - pre_process: PreProcessor, split: str, + pre_process: PreProcessor, task: str = TASK_SEGMENTATION, - # create_validation_set: bool = False, imread_strategy: str = IMREAD_STRATEGY_PRELOAD, ) -> None: + """Mvtec LOCO AD Dataset class. + + Args: + root: Path to the MVTec LOCO AD dataset root folder. + category: Name of the MVTec LOCO AD category (there are 5). + See ``anomalib.data.mvtec_loco.CATEGORIES``. + split: 'train', 'validation' or 'test' + See anomalib.data.mvtec_loco.SPLITS. + pre_process: List of pre_processing object containing albumentation compose or config. + task: ``classification`` or ``segmentation`` + Default: ``segmentation`` + ``anomalib.data.mvtec_loco.TASKS``. + imread_strategy: When should images be read into memory? + Default: ``preload`` + See ``anomalib.data.mvtec_loco.IMREAD_STRATEGIES``. + + TODO add link + See examples in the repository ``anomalib/notebooks/100_datamodules/104_mvtec_loco.ipynb``. + """ + super().__init__(root) assert split in SPLITS, f"Split '{split}' is not supported. Supported splits are {SPLITS}" @@ -509,10 +566,10 @@ def _get_image(self, index: int) -> ndarray: """Get image at index.""" if self.imread_strategy == IMREAD_STRATEGY_PRELOAD: - return self.samples.image[index] + return self.samples.iloc[index]["image"] if self.imread_strategy == IMREAD_STRATEGY_ONTHEFLY: - return read_image(self.samples.image_path[index]) + return read_image(self.samples.iloc[index]["image_path"]) raise NotImplementedError(f"Imread strategy '{self.imread_strategy}' is not supported.") @@ -520,14 +577,14 @@ def _get_mask(self, index: int) -> ndarray: """Get mask at index.""" if self.imread_strategy == IMREAD_STRATEGY_PRELOAD: - return self.samples.mask[index] + return self.samples.iloc[index]["mask"] if self.imread_strategy == IMREAD_STRATEGY_ONTHEFLY: - return _binarize_mask_float(read_mask(self.samples.mask_path[index])) + return _binarize_mask_float(read_mask(self.samples.iloc[index]["mask_path"])) raise NotImplementedError(f"Imread strategy '{self.imread_strategy}' is not supported.") - def __getitem__(self, index: int) -> Dict[str, Union[str, Tensor]]: + def __getitem__(self, index: int) -> Union[Dict[str, Tensor], Dict[str, Union[str, Tensor, int]]]: """Get dataset item for the index ``index``. Args: @@ -535,9 +592,10 @@ def __getitem__(self, index: int) -> Dict[str, Union[str, Tensor]]: Returns: Union[Dict[str, Tensor], Dict[str, Union[str, Tensor]]]: Dict of image tensor during training. - Otherwise, Dict containing image path, target path, image tensor, label and transformed bounding box. + Otherwise, Dict containing image path, image tensor, label, anomaly type and, + if it is segmentation task, mask path and mask tensor. """ - item: Dict[str, Union[str, Tensor]] = {} + item: Dict[str, Union[str, Tensor, int]] = {} image = self._get_image(index) pre_processed = self.pre_process(image=image) @@ -550,10 +608,10 @@ def __getitem__(self, index: int) -> Dict[str, Union[str, Tensor]]: item.update( { - "label": self.samples.label[index], - "image_path": self.samples.image_path[index], - "anotype": self.samples.anotype[index], - "super_anotype": self.samples.super_anotype[index], + "label": self.samples.iloc[index]["label"], + "image_path": str(self.samples.iloc[index]["image_path"]), + "anotype": self.samples.iloc[index]["anotype"], + "super_anotype": self.samples.iloc[index]["super_anotype"], } ) @@ -562,7 +620,7 @@ def __getitem__(self, index: int) -> Dict[str, Union[str, Tensor]]: # Only Anomalous (1) images has masks in MVTec LOCO AD dataset. # Therefore, create empty mask for Normal (0) images. - if self.samples.label[index] == LABEL_NORMAL: + if self.samples.iloc[index]["label"] == LABEL_NORMAL: mask = np.zeros(shape=image.shape[:2]) # shape: (H, W, C) else: @@ -571,7 +629,7 @@ def __getitem__(self, index: int) -> Dict[str, Union[str, Tensor]]: pre_processed = self.pre_process(image=image, mask=mask) item.update( { - "mask_path": self.samples.mask_path[index], + "mask_path": str(self.samples.iloc[index]["mask_path"]), "mask": pre_processed["mask"], } ) @@ -590,20 +648,47 @@ def __init__( self, root: str, category: str, - # TODO: add a parameter to specify the anomaly types and (more specifically) + task: str = TASK_SEGMENTATION, + imread_strategy: str = IMREAD_STRATEGY_PRELOAD, image_size: Optional[Union[int, Tuple[int, int]]] = None, - train_batch_size: int = 32, - test_batch_size: int = 32, num_workers: int = 8, - task: str = TASK_SEGMENTATION, + train_batch_size: int = 32, transform_config_train: Optional[Union[str, A.Compose]] = None, + test_batch_size: int = 32, transform_config_val: Optional[Union[str, A.Compose]] = None, - seed: Optional[int] = None, - imread_strategy: str = IMREAD_STRATEGY_PRELOAD, + # TODO: add a parameter to specify the anomaly types and (more specifically) ) -> None: + """Mvtec LOCO AD Lightning Data Module. + + Args: + root: Path to the MVTec LOCO AD dataset root folder. + category: Name of the MVTec LOCO AD category (there are 5). + See ``anomalib.data.mvtec_loco.CATEGORIES``. + task: ``classification`` or ``segmentation`` + Default: ``segmentation`` + See ``anomalib.data.mvtec_loco.TASKS``. + imread_strategy: When should images be read into memory? + Default: ``preload`` + See ``anomalib.data.mvtec_loco.IMREAD_STRATEGIES``. + image_size: Images are resized to `image_size` (HEIGHT, WIDTH), or (SIZE, SIZE) if a single value is given. + num_workers: Number of workers. + train_batch_size: Training batch size. + transform_config_train: List of pre_processing object containing albumentation compose or + config applied during training. + test_batch_size: Testing batch size. + transform_config_val: List of pre_processing object containing albumentation compose or + config applied during validation. + + TODO add link + See examples in the repository ``anomalib/notebooks/100_datamodules/104_mvtec_loco.ipynb``. + """ super().__init__() - # todo? add input assertions/validations + # TODO create option to get a subset of anomalies in the test + # TODO the images are not squared here, maybe we should add warn the user if + # the ration from image_size is too different from the original image when + # the image size is given as an int + assert task in TASKS, f"Task '{task}' is not supported. Supported tasks are {TASKS}" assert ( imread_strategy in IMREAD_STRATEGIES @@ -623,7 +708,6 @@ def __init__( self.num_workers = num_workers self.task = task - self.seed = seed self.imread_strategy = imread_strategy self.train_data: Dataset @@ -643,31 +727,7 @@ def prepare_data(self) -> None: logger.info("Found the dataset.") else: - self.root.mkdir(parents=True, exist_ok=True) - - logger.info("Downloading the Mvtec LOCO AD dataset.") - # flake8: noqa: E501 - # pylint: disable=line-too-long - url_mvtec_loco_targz = "https://www.mydrive.ch/shares/48237/1b9106ccdfbb09a0c414bd49fe44a14a/download/430647091-1646842701/mvtec_loco_anomaly_detection.tar.xz" - dataset_name = "mvtec_loco_anomaly_detection.tar.xz" - zip_filename = self.root / dataset_name - with DownloadProgressBar(unit="B", unit_scale=True, miniters=1, desc="MVTec LOCO download") as progress_bar: - urlretrieve( - url=url_mvtec_loco_targz, - filename=zip_filename, - reporthook=progress_bar.update_to, - ) - - logger.info("Checking hash") - md5hash_mvtec_loco = "d40f092ac6f88433f609583c4a05f56f" - hash_check(zip_filename, md5hash_mvtec_loco) - - logger.info("Extracting the dataset.") - logger.debug("Extracting to %s", self.root) - tar_extract_all(zip_filename, self.root) - - logger.info("Cleaning the tar file") - zip_filename.unlink() + download_and_extract_mvtec_loco(self.root) def setup(self, stage: Optional[str] = None) -> None: """Setup train, validation and test data. @@ -680,6 +740,11 @@ def setup(self, stage: Optional[str] = None) -> None: logger.info("Setting up %s dataset." % stage or TrainerFn.FITTING) if stage in (None, TrainerFn.FITTING): + + if hasattr(self, "train_data"): + logger.debug("Train data already exists. Skipping setup.") + return + self.train_data = MVTecLOCODataset( root=self.root, category=self.category, @@ -690,6 +755,11 @@ def setup(self, stage: Optional[str] = None) -> None: ) if stage == TrainerFn.VALIDATING: + + if hasattr(self, "val_data"): + logger.debug("Validation data already exists. Skipping setup.") + return + self.val_data = MVTecLOCODataset( root=self.root, category=self.category, @@ -700,6 +770,11 @@ def setup(self, stage: Optional[str] = None) -> None: ) if stage == TrainerFn.TESTING: + + if hasattr(self, "test_data"): + logger.debug("Test data already exists. Skipping setup.") + return + self.test_data = MVTecLOCODataset( root=self.root, category=self.category, @@ -710,75 +785,69 @@ def setup(self, stage: Optional[str] = None) -> None: ) if stage == TrainerFn.PREDICTING: + + if hasattr(self, "inference_data"): + logger.debug("Inference data already exists. Skipping setup.") + return + self.inference_data = InferenceDataset( path=self.root, image_size=self.image_size, transform_config=self.transform_config_val ) def train_dataloader(self) -> TRAIN_DATALOADERS: """Get train dataloader.""" + if not hasattr(self, "train_data"): + raise RuntimeError("Train data not setup. Did you run `datamodule.setup('fit')`?") return DataLoader(self.train_data, shuffle=True, batch_size=self.train_batch_size, num_workers=self.num_workers) def val_dataloader(self) -> EVAL_DATALOADERS: """Get validation dataloader.""" + if not hasattr(self, "val_data"): + raise RuntimeError("Validation data not setup. Did you run `datamodule.setup('validate')`?") return DataLoader(self.val_data, shuffle=False, batch_size=self.test_batch_size, num_workers=self.num_workers) def test_dataloader(self) -> EVAL_DATALOADERS: """Get test dataloader.""" + if not hasattr(self, "test_data"): + raise RuntimeError("Test data not setup. Did you run `datamodule.setup('test')`?") return DataLoader(self.test_data, shuffle=False, batch_size=self.test_batch_size, num_workers=self.num_workers) def predict_dataloader(self) -> EVAL_DATALOADERS: """Get predict dataloader.""" + if not hasattr(self, "inference_data"): + raise RuntimeError("Inference data not setup. Did you run `datamodule.setup('predict')`?") return DataLoader( self.inference_data, shuffle=False, batch_size=self.test_batch_size, num_workers=self.num_workers ) -# TODO remove me -# debug _make_dataset in main -if __name__ == "__main__": - import itertools - - for cat in CATEGORIES: - _make_dataset(Path(f"/home/jcasagrandebertoldo/Downloads/loco/{cat}")) - - for cat, split_ in itertools.product(CATEGORIES, SPLITS): - pass - # _make_dataset(Path(f"/home/jcasagrandebertoldo/Downloads/loco/{cat}"), split_) - # next - # next - # next - # next - # next - # next - # next - # next - # test instantiate of the two classes - # then test with script - # next - # next - # next - # next - # next - # next - # next - # next - # next - # next - # next - # next - # next - # next - # next - # next - # next - # next - # next - # next - # next - # next - # next - # next - # next - # next - # next - # next +# next +# correct the multi-image ground truth +# then show it in the notebook +# then create unit tests +# next +# next +# next +# next +# next +# next +# next +# next +# next +# next +# next +# next +# next +# next +# next +# next +# next +# next +# next +# next +# next +# next +# next +# next +# next +# next diff --git a/notebooks/100_datamodules/104_mvtec_loco.ipynb b/notebooks/100_datamodules/104_mvtec_loco.ipynb new file mode 100644 index 0000000000..0f5fe9585c --- /dev/null +++ b/notebooks/100_datamodules/104_mvtec_loco.ipynb @@ -0,0 +1,394 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## MVTec LOCO AD" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from pathlib import Path\n", + "\n", + "import numpy as np\n", + "from IPython.core.interactiveshell import InteractiveShell\n", + "from PIL import Image\n", + "from torchvision.transforms import ToPILImage\n", + "\n", + "from anomalib.data.mvtec_loco import (\n", + " MVTecLOCO,\n", + " MVTecLOCODataset,\n", + " download_and_extract_mvtec_loco,\n", + ")\n", + "from anomalib.pre_processing import PreProcessor\n", + "from anomalib.pre_processing.transforms import Denormalize\n", + "\n", + "# make a cell print all the outputs instead of just the last one\n", + "InteractiveShell.ast_node_interactivity = \"all\"\n", + "\n", + "# pylint: disable=locally-disabled, pointless-statement\n", + "# the ``pointless-statement`` warning is disabled because we use them to print stuff" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Download and extract the dataset" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "root = Path(\"../../datasets/MVTecLOCO\")\n", + "if not root.exists():\n", + " download_and_extract_mvtec_loco(root)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Torch Dataset" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "MVTecLOCODataset??" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To create `MVTecDataset` we need to import `pre_process` that applies transforms to the input image." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "PreProcessor??" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pre_process = PreProcessor(image_size=(100, 170), to_tensor=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Classification Task" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# MVTec LOCO Classification Train Set\n", + "mvtec_loco_dataset_classification_train = MVTecLOCODataset(\n", + " root=\"../../datasets/MVTecLOCO\",\n", + " category=\"pushpins\",\n", + " split=\"train\",\n", + " pre_process=pre_process,\n", + " task=\"classification\",\n", + ")\n", + "mvtec_loco_dataset_classification_train.samples.head()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "sample = mvtec_loco_dataset_classification_train[0]\n", + "sample.keys()\n", + "sample[\"image\"].shape" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As can be seen above, when we choose `classification` task and `train` split, the dataset only returns `image`. This is mainly because training only requires normal images and no labels. Now let's try `test` split for the `classification` task" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# MVTec Classification Test Set\n", + "mvtec_loco_dataset_classification_test = MVTecLOCODataset(\n", + " root=\"../../datasets/MVTecLOCO\",\n", + " category=\"pushpins\",\n", + " split=\"test\",\n", + " pre_process=pre_process,\n", + " task=\"classification\",\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "sample = mvtec_loco_dataset_classification_test[0]\n", + "sample.keys()\n", + "sample[\"image\"].shape\n", + "sample[\"image_path\"]\n", + "sample[\"label\"]\n", + "sample[\"super_anotype\"], sample[\"anotype\"]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Negative indices are also enabled." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "sample = mvtec_loco_dataset_classification_test[-1]\n", + "sample.keys()\n", + "sample[\"image\"].shape\n", + "sample[\"image_path\"]\n", + "sample[\"label\"]\n", + "sample[\"super_anotype\"], sample[\"anotype\"]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Segmentation Task\n", + "\n", + "It is also possible to configure the MVTec LOCO dataset for the segmentation task, where the dataset object returns image and ground-truth mask." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# MVTec LOCO Segmentation Train Set\n", + "mvtec_loco_dataset_segmentation_train = MVTecLOCODataset(\n", + " root=\"../../datasets/MVTecLOCO\",\n", + " category=\"pushpins\",\n", + " pre_process=pre_process,\n", + " split=\"train\",\n", + " task=\"segmentation\",\n", + ")\n", + "mvtec_loco_dataset_segmentation_train.samples.head()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# MVTec LOCO Segmentation Test Set\n", + "mvtec_loco_dataset_segmentation_test = MVTecLOCODataset(\n", + " root=\"../../datasets/MVTecLOCO\",\n", + " category=\"pushpins\",\n", + " pre_process=pre_process,\n", + " split=\"test\",\n", + " task=\"segmentation\",\n", + ")\n", + "sample = mvtec_loco_dataset_segmentation_test[20]\n", + "sample.keys()\n", + "sample[\"image\"].shape\n", + "sample[\"mask\"].shape" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's visualize the image and the mask..." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "img = ToPILImage()(Denormalize()(sample[\"image\"].clone()))\n", + "msk = ToPILImage()(sample[\"mask\"]).convert(\"RGB\")\n", + "\n", + "Image.fromarray(np.vstack((np.array(img), np.array(msk))))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### DataModule\n", + "\n", + "So far, we have shown the Torch Dateset implementation of MVTec LOCO AD dataset. This is quite useful to get a sample, but we do need more than this when we train models in an end-to-end fashion.\n", + " \n", + "The [PyTorch Lightning DataModule](https://pytorch-lightning.readthedocs.io/en/latest/data/datamodule.html) for MVTec LOCO AD (shown below) is handles the the dataset download, and train/val/test/inference dataloaders instantiation." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "MVTecLOCO??" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "mvtec_datamodule = MVTecLOCO(\n", + " root=\"../../datasets/MVTecLOCO\",\n", + " category=\"pushpins\",\n", + " image_size=(200, 340), # (height, width) 5x smaller than original\n", + " train_batch_size=32,\n", + " test_batch_size=32,\n", + " num_workers=8,\n", + " task=\"segmentation\",\n", + ")\n", + "\n", + "# verify if the dataset is available and download it if not\n", + "mvtec_datamodule.prepare_data()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Train images\n", + "\n", + "# instantiate the Torch Dataset(s), loading the (meta-)data into memory\n", + "mvtec_datamodule.setup(\"fit\")\n", + "\n", + "i, data = next(enumerate(mvtec_datamodule.train_dataloader()))\n", + "data.keys()\n", + "data[\"image\"].shape" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Validation images\n", + "mvtec_datamodule.setup(\"validate\")\n", + "i, data = next(enumerate(mvtec_datamodule.val_dataloader()))\n", + "data.keys()\n", + "data[\"image\"].shape\n", + "data[\"mask\"].shape\n", + "data[\"super_anotype\"][0], data[\"anotype\"][0]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Test images\n", + "mvtec_datamodule.setup(\"test\")\n", + "# iterate a few times so we can find a sample with an anomaly\n", + "for i, data in enumerate(mvtec_datamodule.test_dataloader()):\n", + " if i == 5:\n", + " break\n", + "data.keys()\n", + "data[\"image\"].shape\n", + "data[\"mask\"].shape\n", + "data[\"super_anotype\"][0], data[\"anotype\"][0]\n", + "\n", + "img = ToPILImage()(Denormalize()(data[\"image\"][0].clone()))\n", + "msk = ToPILImage()(data[\"mask\"][0]).convert(\"RGB\")\n", + "\n", + "Image.fromarray(np.vstack((np.array(img), np.array(msk))))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "TODO: show that the ground truth is divided in multiple images\n", + "\n", + "TODO: create issue to correct docs in mvtec, e.g. it should not give example in the docstring but send the user\n", + " to the notebooks (more maintainable)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As can be seen above, creating the dataloaders are pretty straghtforward, which could be directly used for training/testing/inference." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3.8.13 ('anomalib-dev')", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.13" + }, + "orig_nbformat": 4, + "vscode": { + "interpreter": { + "hash": "8787c31053eaf11dad02e12159779e58bcbd87fee611b470525fee7090bb4db2" + } + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} From 43f154e6f3d7ddd6ed9cda7bd7e538c9e9678ced Mon Sep 17 00:00:00 2001 From: jpcbertoldo <24547377+jpcbertoldo@users.noreply.github.com> Date: Fri, 9 Sep 2022 23:35:58 +0200 Subject: [PATCH 07/38] manage multiple masks --- anomalib/data/mvtec_loco.py | 132 +- .../100_datamodules/104_mvtec_loco.ipynb | 1526 ++++++++++++++++- 2 files changed, 1564 insertions(+), 94 deletions(-) diff --git a/anomalib/data/mvtec_loco.py b/anomalib/data/mvtec_loco.py index 5280cf470e..d5274ddab9 100644 --- a/anomalib/data/mvtec_loco.py +++ b/anomalib/data/mvtec_loco.py @@ -376,12 +376,12 @@ def download_and_extract_mvtec_loco(root: Union[str, Path]) -> None: def _make_dataset( path: Path, split: str, - imread_strategy: str = IMREAD_STRATEGY_PRELOAD, ) -> DataFrame: # noqa D212 """ Find the images in the given path and create a DataFrame with all the information from each sample. Expected structure of the files in the dataset ("/" is 'path') + Important: notice that the groud truth masks can be in multiple files! images: /{split}/{super_anotype}/{image_index}.png @@ -402,11 +402,11 @@ def _make_dataset( /ground_truth/logical_anomalies/079/000.png /ground_truth/structural_anomalies/.../000.png + /ground_truth/structural_anomalies/.../001.png ... """ assert split in SPLITS, f"Invalid split: {split}" - assert imread_strategy in IMREAD_STRATEGIES, f"Invalid imread strategy: {imread_strategy}" category = path.resolve().name assert category in CATEGORIES, f"Invalid path '{path}'. The category '{category}' must be one of {CATEGORIES}" @@ -429,7 +429,7 @@ def _make_dataset( def build_record(sample_path: Path): - ret: Dict[str, Union[Path, None, str, int]] = { + ret: Dict[str, Union[Path, Tuple[Path, ...], None, str, int]] = { "image_path": sample_path, **dict(zip(("split", "super_anotype", "image_filename"), sample_path.parts[-3:])), } @@ -439,7 +439,7 @@ def build_record(sample_path: Path): if super_anotype == SUPER_ANOTYPE_GOOD: ret.update( { - "mask_path": None, + "mask_paths": None, "label": LABEL_NORMAL, "super_anotype": SUPER_ANOTYPE_GOOD, "anotype": ANOTYPE_GOOD, @@ -449,23 +449,27 @@ def build_record(sample_path: Path): if super_anotype in (SUPER_ANOTYPE_LOGICAL, SUPER_ANOTYPE_STRUCTURAL): - mask_path: Path = path / "ground_truth" / super_anotype / sample_path.stem / "000.png" + mask_paths: Tuple[Path, ...] = tuple( + sorted((path / "ground_truth" / super_anotype / sample_path.stem).glob("*.png")) + ) - assert mask_path.exists(), f"Mask file '{mask_path}' does not exist. Is the dataset corrupted?" + assert len(mask_paths) > 0, f"No masks found for sample '{sample_path}'. Is the dataset corrupted?" # mask images are supposed to have only two values: 0 and GTVALUE_ANOMALY # GTVALUE_ANOMALY \in {234, ..., 255} and is given in the paper, encoded in the mapping below # TODO create an issue to cache this info so the mask is not read here - gtvalue = read_mask(mask_path).astype(int).max() + first_mask_path = mask_paths[0] + gtvalue = read_mask(first_mask_path).astype(int).max() _, anotype = _MAP_GTVALUE_2_ANOTYPE[(category, gtvalue)] ret.update( { - "mask_path": mask_path, + "mask_paths": mask_paths, "gtvalue": gtvalue, "label": LABEL_ANOMALOUS, "super_anotype": super_anotype, "anotype": anotype, + "is_multimask": len(mask_paths) > 1, } ) @@ -476,27 +480,6 @@ def build_record(sample_path: Path): samples = pd.DataFrame.from_records([build_record(sp) for sp in samples_paths]) - if imread_strategy == IMREAD_STRATEGY_PRELOAD: - - warnings.warn( - "Preloading images into memory. " - "If your dataset is too large, consider using another imread_strategy instead.", - stacklevel=3, - ) - - logger.debug("Preloading images into memory") - samples["image"] = samples["image_path"].map(read_image) - - logger.debug("Preloading masks into memory") - - # this is used to select the rows in the dataframe - has_mask = ~samples["mask_path"].isnull() - - samples.loc[has_mask, "mask"] = samples.loc[has_mask, "mask_path"].map( - lambda x: _binarize_mask_float(read_mask(x)) - ) - samples.loc[~has_mask, "mask"] = None - return samples @@ -528,7 +511,6 @@ def __init__( Default: ``preload`` See ``anomalib.data.mvtec_loco.IMREAD_STRATEGIES``. - TODO add link See examples in the repository ``anomalib/notebooks/100_datamodules/104_mvtec_loco.ipynb``. """ @@ -550,9 +532,50 @@ def __init__( self.samples = _make_dataset( path=self.category_dataset_path, split=self.split, - imread_strategy=self.imread_strategy, ) + if self.imread_strategy == IMREAD_STRATEGY_PRELOAD: + + warnings.warn( + "Preloading images into memory. " + "If your dataset is too large, consider using another imread_strategy instead.", + stacklevel=2, + ) + + logger.debug("Preloading images into memory") + self.samples["image"] = self.samples["image_path"].map(read_image) + + logger.debug("Preloading masks into memory") + # this is used to select the rows in the dataframe + has_mask = ~self.samples["mask_paths"].isnull() + + # iterate the mask paths and read the masks returning a tuple of masks + self.samples.loc[has_mask, "masks"] = self.samples.loc[has_mask, "mask_paths"].map( + lambda tupe_of_paths: tuple(_binarize_mask_float(read_mask(mask_path)) for mask_path in tupe_of_paths) + ) + + # combine the multiple masks into a single (binary) mask + self.samples.loc[has_mask, "mask"] = self.samples.loc[has_mask, "masks"].map( + lambda masks: np.stack(masks, axis=0).sum(axis=0).clip(0, 1) + ) + + # replace the tuple of masks by a single array where each anomaly has + # a different value encoding an individual anomaly region + self.samples.loc[has_mask, "masks"] = self.samples.loc[has_mask, "masks"].map(self._sum_masks) + + self.samples.loc[~has_mask, "masks"] = None + self.samples.loc[~has_mask, "mask"] = None + + @staticmethod + def _sum_masks(tupe_of_masks: Tuple[np.ndarray, ...]) -> np.ndarray: + """Combines multiple masks into a single mask by encoding each mask with a different value and summing them.""" + n_masks = len(tupe_of_masks) + # +1 is to compensate the open interval on the right + # expand_dims is to add the W and H dimensions, to make sure they are broadcasted + gtvalues = np.expand_dims(np.arange(1, n_masks + 1), (1, 2)) + stacked_masks = np.stack(tupe_of_masks, axis=0) + return (gtvalues * stacked_masks).sum(axis=0) + @property def category_dataset_path(self) -> Path: """Path to the category dataset (root/category) folder.""" @@ -573,14 +596,36 @@ def _get_image(self, index: int) -> ndarray: raise NotImplementedError(f"Imread strategy '{self.imread_strategy}' is not supported.") - def _get_mask(self, index: int) -> ndarray: + def _get_masks(self, index: int) -> Dict[str, ndarray]: """Get mask at index.""" if self.imread_strategy == IMREAD_STRATEGY_PRELOAD: - return self.samples.iloc[index]["mask"] + return { + "masks": self.samples.iloc[index]["masks"], + "mask": self.samples.iloc[index]["mask"], + } if self.imread_strategy == IMREAD_STRATEGY_ONTHEFLY: - return _binarize_mask_float(read_mask(self.samples.iloc[index]["mask_path"])) + + mask_paths = self.samples.iloc[index]["mask_paths"] + if mask_paths is None: + return { + "masks": None, + "mask": None, + } + + # iterate the mask paths and read the masks returning a tuple of masks + masks: Tuple[np.ndarray, ...] = tuple( + _binarize_mask_float(read_mask(mask_path)) for mask_path in mask_paths + ) + + return { + # replace the tuple of masks by a single array where each anomaly has + # a different value encoding an individual anomaly region + "masks": self._sum_masks(masks), + # combine the multiple masks into a single (binary) mask + "mask": np.stack(masks, axis=0).sum(axis=0).clip(0, 1), + } raise NotImplementedError(f"Imread strategy '{self.imread_strategy}' is not supported.") @@ -618,19 +663,23 @@ def __getitem__(self, index: int) -> Union[Dict[str, Tensor], Dict[str, Union[st if self.task != TASK_SEGMENTATION: return item + mask_dict: Dict[str, ndarray] + # Only Anomalous (1) images has masks in MVTec LOCO AD dataset. # Therefore, create empty mask for Normal (0) images. if self.samples.iloc[index]["label"] == LABEL_NORMAL: mask = np.zeros(shape=image.shape[:2]) # shape: (H, W, C) + mask_dict = {"mask": mask, "masks": mask} else: - mask = self._get_mask(index) + mask_dict = self._get_masks(index) - pre_processed = self.pre_process(image=image, mask=mask) item.update( { - "mask_path": str(self.samples.iloc[index]["mask_path"]), - "mask": pre_processed["mask"], + "mask_paths": str(self.samples.iloc[index]["mask_paths"]), + # TODO CHECK IF THE DOUBLE CALL TO PREPROCESS WILL WORK WITH ALBUMENTATIONS + "masks": self.pre_process(image=image, mask=mask_dict["masks"])["mask"], + "mask": self.pre_process(image=image, mask=mask_dict["mask"])["mask"], } ) @@ -656,7 +705,6 @@ def __init__( transform_config_train: Optional[Union[str, A.Compose]] = None, test_batch_size: int = 32, transform_config_val: Optional[Union[str, A.Compose]] = None, - # TODO: add a parameter to specify the anomaly types and (more specifically) ) -> None: """Mvtec LOCO AD Lightning Data Module. @@ -679,16 +727,10 @@ def __init__( transform_config_val: List of pre_processing object containing albumentation compose or config applied during validation. - TODO add link See examples in the repository ``anomalib/notebooks/100_datamodules/104_mvtec_loco.ipynb``. """ super().__init__() - # TODO create option to get a subset of anomalies in the test - # TODO the images are not squared here, maybe we should add warn the user if - # the ration from image_size is too different from the original image when - # the image size is given as an int - assert task in TASKS, f"Task '{task}' is not supported. Supported tasks are {TASKS}" assert ( imread_strategy in IMREAD_STRATEGIES diff --git a/notebooks/100_datamodules/104_mvtec_loco.ipynb b/notebooks/100_datamodules/104_mvtec_loco.ipynb index 0f5fe9585c..9dce64e7da 100644 --- a/notebooks/100_datamodules/104_mvtec_loco.ipynb +++ b/notebooks/100_datamodules/104_mvtec_loco.ipynb @@ -9,7 +9,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -44,7 +44,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ @@ -62,9 +62,221 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[0;31mInit signature:\u001b[0m \u001b[0mMVTecLOCODataset\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwds\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mSource:\u001b[0m \n", + "\u001b[0;32mclass\u001b[0m \u001b[0mMVTecLOCODataset\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mVisionDataset\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"\"\"MVTec LOCO AD PyTorch Dataset.\"\"\"\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m__init__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mroot\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mUnion\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mPath\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mstr\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mcategory\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mstr\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0msplit\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mstr\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mpre_process\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mPreProcessor\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mtask\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mstr\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mTASK_SEGMENTATION\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mimread_strategy\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mstr\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mIMREAD_STRATEGY_PRELOAD\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"\"\"Mvtec LOCO AD Dataset class.\u001b[0m\n", + "\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m Args:\u001b[0m\n", + "\u001b[0;34m root: Path to the MVTec LOCO AD dataset root folder.\u001b[0m\n", + "\u001b[0;34m category: Name of the MVTec LOCO AD category (there are 5).\u001b[0m\n", + "\u001b[0;34m See ``anomalib.data.mvtec_loco.CATEGORIES``.\u001b[0m\n", + "\u001b[0;34m split: 'train', 'validation' or 'test'\u001b[0m\n", + "\u001b[0;34m See anomalib.data.mvtec_loco.SPLITS.\u001b[0m\n", + "\u001b[0;34m pre_process: List of pre_processing object containing albumentation compose or config.\u001b[0m\n", + "\u001b[0;34m task: ``classification`` or ``segmentation``\u001b[0m\n", + "\u001b[0;34m Default: ``segmentation``\u001b[0m\n", + "\u001b[0;34m ``anomalib.data.mvtec_loco.TASKS``.\u001b[0m\n", + "\u001b[0;34m imread_strategy: When should images be read into memory?\u001b[0m\n", + "\u001b[0;34m Default: ``preload``\u001b[0m\n", + "\u001b[0;34m See ``anomalib.data.mvtec_loco.IMREAD_STRATEGIES``.\u001b[0m\n", + "\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m TODO add link\u001b[0m\n", + "\u001b[0;34m See examples in the repository ``anomalib/notebooks/100_datamodules/104_mvtec_loco.ipynb``.\u001b[0m\n", + "\u001b[0;34m \"\"\"\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0msuper\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__init__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mroot\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32massert\u001b[0m \u001b[0msplit\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mSPLITS\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34mf\"Split '{split}' is not supported. Supported splits are {SPLITS}\"\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32massert\u001b[0m \u001b[0mtask\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mTASKS\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34mf\"Task '{task}' is not supported. Supported tasks are {TASKS}\"\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32massert\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mimread_strategy\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mIMREAD_STRATEGIES\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34mf\"Imread strategy '{imread_strategy}' is not supported. Supported imread strategies are {IMREAD_STRATEGIES}\"\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mroot\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mPath\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mroot\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0misinstance\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mroot\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mstr\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32melse\u001b[0m \u001b[0mroot\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcategory\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mstr\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mcategory\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msplit\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0msplit\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtask\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mtask\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpre_process\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mpre_process\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mimread_strategy\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mimread_strategy\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msamples\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0m_make_dataset\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mpath\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcategory_dataset_path\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0msplit\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msplit\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mimread_strategy\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0mIMREAD_STRATEGY_PRELOAD\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mwarnings\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mwarn\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"Preloading images into memory. \"\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"If your dataset is too large, consider using another imread_strategy instead.\"\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mstacklevel\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m2\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mlogger\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdebug\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Preloading images into memory\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msamples\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"image\"\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msamples\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"image_path\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmap\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mread_image\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mlogger\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdebug\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Preloading masks into memory\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;31m# this is used to select the rows in the dataframe\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mhas_mask\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m~\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msamples\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"mask_paths\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0misnull\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;31m# iterate the mask paths and read the masks returning a tuple of masks\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msamples\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mloc\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mhas_mask\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"masks\"\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msamples\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mloc\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mhas_mask\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"mask_paths\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmap\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mlambda\u001b[0m \u001b[0mtupe_of_paths\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mtuple\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0m_binarize_mask_float\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mread_mask\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmask_path\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mmask_path\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mtupe_of_paths\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;31m# combine the multiple masks into a single (binary) mask\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msamples\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mloc\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mhas_mask\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"mask\"\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msamples\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mloc\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mhas_mask\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"masks\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmap\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mlambda\u001b[0m \u001b[0mmasks\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mstack\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmasks\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0maxis\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msum\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0maxis\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mclip\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;31m# replace the tuple of masks by a single array where each anomaly has\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;31m# a different value encoding an individual anomaly region\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msamples\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mloc\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mhas_mask\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"masks\"\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msamples\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mloc\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mhas_mask\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"masks\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmap\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_sum_masks\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msamples\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mloc\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m~\u001b[0m\u001b[0mhas_mask\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"masks\"\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msamples\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mloc\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m~\u001b[0m\u001b[0mhas_mask\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"mask\"\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m@\u001b[0m\u001b[0mstaticmethod\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m_sum_masks\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mtupe_of_masks\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mTuple\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mndarray\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mndarray\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"\"\"combines multiple masks into a single mask by encoding each mask with a different value and summing them.\"\"\"\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mn_masks\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mlen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mtupe_of_masks\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;31m# +1 is to compensate the open interval on the right\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;31m# expand_dims is to add the W and H dimensions, to make sure they are broadcasted\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mgtvalues\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mexpand_dims\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0marange\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn_masks\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m2\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mstacked_masks\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mstack\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mtupe_of_masks\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0maxis\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0mgtvalues\u001b[0m \u001b[0;34m*\u001b[0m \u001b[0mstacked_masks\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msum\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0maxis\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m@\u001b[0m\u001b[0mproperty\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mcategory_dataset_path\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0mPath\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"\"\"Path to the category dataset (root/category) folder.\"\"\"\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mroot\u001b[0m \u001b[0;34m/\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcategory\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m__len__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0mint\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"\"\"Get length of the dataset.\"\"\"\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mlen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msamples\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m_get_image\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mindex\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mint\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0mndarray\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"\"\"Get image at index.\"\"\"\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mimread_strategy\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0mIMREAD_STRATEGY_PRELOAD\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msamples\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0miloc\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mindex\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"image\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mimread_strategy\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0mIMREAD_STRATEGY_ONTHEFLY\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mread_image\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msamples\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0miloc\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mindex\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"image_path\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mraise\u001b[0m \u001b[0mNotImplementedError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34mf\"Imread strategy '{self.imread_strategy}' is not supported.\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m_get_masks\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mindex\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mint\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0mDict\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mstr\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mndarray\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"\"\"Get mask at index.\"\"\"\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mimread_strategy\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0mIMREAD_STRATEGY_PRELOAD\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0;34m{\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"masks\"\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msamples\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0miloc\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mindex\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"masks\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"mask\"\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msamples\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0miloc\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mindex\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"mask\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m}\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mimread_strategy\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0mIMREAD_STRATEGY_ONTHEFLY\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mmask_paths\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msamples\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0miloc\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mindex\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"mask_paths\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mmask_paths\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0;34m{\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"masks\"\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"mask\"\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m}\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;31m# iterate the mask paths and read the masks returning a tuple of masks\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mmasks\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mtuple\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0m_binarize_mask_float\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mread_mask\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmask_path\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mmask_path\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mmask_paths\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0;34m{\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;31m# replace the tuple of masks by a single array where each anomaly has\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;31m# a different value encoding an individual anomaly region \u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"masks\"\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_sum_masks\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmasks\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;31m# combine the multiple masks into a single (binary) mask\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"mask\"\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mstack\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmasks\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0maxis\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msum\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0maxis\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mclip\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m}\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mraise\u001b[0m \u001b[0mNotImplementedError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34mf\"Imread strategy '{self.imread_strategy}' is not supported.\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m__getitem__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mindex\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mint\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0mUnion\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mDict\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mstr\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mTensor\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mDict\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mstr\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mUnion\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mstr\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mTensor\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mint\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"\"\"Get dataset item for the index ``index``.\u001b[0m\n", + "\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m Args:\u001b[0m\n", + "\u001b[0;34m index (int): Index to get the item.\u001b[0m\n", + "\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m Returns:\u001b[0m\n", + "\u001b[0;34m Union[Dict[str, Tensor], Dict[str, Union[str, Tensor]]]: Dict of image tensor during training.\u001b[0m\n", + "\u001b[0;34m Otherwise, Dict containing image path, image tensor, label, anomaly type and,\u001b[0m\n", + "\u001b[0;34m if it is segmentation task, mask path and mask tensor.\u001b[0m\n", + "\u001b[0;34m \"\"\"\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mitem\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mDict\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mstr\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mUnion\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mstr\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mTensor\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mint\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m{\u001b[0m\u001b[0;34m}\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mimage\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_get_image\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mindex\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mpre_processed\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpre_process\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mimage\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mimage\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mitem\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m{\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"image\"\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mpre_processed\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"image\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m}\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msplit\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0;32min\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0mSPLIT_VALIDATION\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mSPLIT_TEST\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mitem\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mitem\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mupdate\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m{\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"label\"\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msamples\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0miloc\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mindex\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"label\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"image_path\"\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mstr\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msamples\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0miloc\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mindex\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"image_path\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"anotype\"\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msamples\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0miloc\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mindex\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"anotype\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"super_anotype\"\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msamples\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0miloc\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mindex\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"super_anotype\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m}\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtask\u001b[0m \u001b[0;34m!=\u001b[0m \u001b[0mTASK_SEGMENTATION\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mitem\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mmask_dict\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mDict\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mstr\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mndarray\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;31m# Only Anomalous (1) images has masks in MVTec LOCO AD dataset.\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;31m# Therefore, create empty mask for Normal (0) images.\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msamples\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0miloc\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mindex\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"label\"\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0mLABEL_NORMAL\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mmask\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mzeros\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mshape\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mimage\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mshape\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;36m2\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;31m# shape: (H, W, C)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mmask_dict\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m{\u001b[0m\u001b[0;34m\"mask\"\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mmask\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"masks\"\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mmask\u001b[0m\u001b[0;34m}\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mmask_dict\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_get_masks\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mindex\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mitem\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mupdate\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m{\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"mask_paths\"\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mstr\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msamples\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0miloc\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mindex\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"mask_paths\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;31m# TODO CHECK IF THE DOUBLE CALL TO PREPROCESS WILL WORK WITH ALBUMENTATIONS\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"masks\"\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpre_process\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mimage\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mimage\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mmask\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mmask_dict\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"masks\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"mask\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"mask\"\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpre_process\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mimage\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mimage\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mmask\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mmask_dict\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"mask\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"mask\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m}\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mitem\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mFile:\u001b[0m ~/repos/anomalib/anomalib/data/mvtec_loco.py\n", + "\u001b[0;31mType:\u001b[0m type\n", + "\u001b[0;31mSubclasses:\u001b[0m \n" + ] + } + ], "source": [ "MVTecLOCODataset??" ] @@ -78,16 +290,154 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[0;31mInit signature:\u001b[0m\n", + "\u001b[0mPreProcessor\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mconfig\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mUnion\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mstr\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0malbumentations\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcore\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcomposition\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mCompose\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mNoneType\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mimage_size\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mUnion\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mint\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mTuple\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mNoneType\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mto_tensor\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mbool\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mTrue\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mSource:\u001b[0m \n", + "\u001b[0;32mclass\u001b[0m \u001b[0mPreProcessor\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"\"\"Applies pre-processing and data augmentations to the input and returns the transformed output.\u001b[0m\n", + "\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m Output could be either numpy ndarray or torch tensor.\u001b[0m\n", + "\u001b[0;34m When `PreProcessor` class is used for training, the output would be `torch.Tensor`.\u001b[0m\n", + "\u001b[0;34m For the inference it returns a numpy array.\u001b[0m\n", + "\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m Args:\u001b[0m\n", + "\u001b[0;34m config (Optional[Union[str, A.Compose]], optional): Transformation configurations.\u001b[0m\n", + "\u001b[0;34m When it is ``None``, ``PreProcessor`` only applies resizing. When it is ``str``\u001b[0m\n", + "\u001b[0;34m it loads the config via ``albumentations`` deserialisation methos . Defaults to None.\u001b[0m\n", + "\u001b[0;34m image_size (Optional[Union[int, Tuple[int, int]]], optional): When there is no config,\u001b[0m\n", + "\u001b[0;34m ``image_size`` resizes the image. Defaults to None.\u001b[0m\n", + "\u001b[0;34m to_tensor (bool, optional): Boolean to check whether the augmented image is transformed\u001b[0m\n", + "\u001b[0;34m into a tensor or not. Defaults to True.\u001b[0m\n", + "\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m Examples:\u001b[0m\n", + "\u001b[0;34m >>> import skimage\u001b[0m\n", + "\u001b[0;34m >>> image = skimage.data.astronaut()\u001b[0m\n", + "\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m >>> pre_processor = PreProcessor(image_size=256, to_tensor=False)\u001b[0m\n", + "\u001b[0;34m >>> output = pre_processor(image=image)\u001b[0m\n", + "\u001b[0;34m >>> output[\"image\"].shape\u001b[0m\n", + "\u001b[0;34m (256, 256, 3)\u001b[0m\n", + "\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m >>> pre_processor = PreProcessor(image_size=256, to_tensor=True)\u001b[0m\n", + "\u001b[0;34m >>> output = pre_processor(image=image)\u001b[0m\n", + "\u001b[0;34m >>> output[\"image\"].shape\u001b[0m\n", + "\u001b[0;34m torch.Size([3, 256, 256])\u001b[0m\n", + "\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m Transforms could be read from albumentations Compose object.\u001b[0m\n", + "\u001b[0;34m >>> import albumentations as A\u001b[0m\n", + "\u001b[0;34m >>> from albumentations.pytorch import ToTensorV2\u001b[0m\n", + "\u001b[0;34m >>> config = A.Compose([A.Resize(512, 512), ToTensorV2()])\u001b[0m\n", + "\u001b[0;34m >>> pre_processor = PreProcessor(config=config, to_tensor=False)\u001b[0m\n", + "\u001b[0;34m >>> output = pre_processor(image=image)\u001b[0m\n", + "\u001b[0;34m >>> output[\"image\"].shape\u001b[0m\n", + "\u001b[0;34m (512, 512, 3)\u001b[0m\n", + "\u001b[0;34m >>> type(output[\"image\"])\u001b[0m\n", + "\u001b[0;34m numpy.ndarray\u001b[0m\n", + "\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m Transforms could be deserialized from a yaml file.\u001b[0m\n", + "\u001b[0;34m >>> transforms = A.Compose([A.Resize(1024, 1024), ToTensorV2()])\u001b[0m\n", + "\u001b[0;34m >>> A.save(transforms, \"/tmp/transforms.yaml\", data_format=\"yaml\")\u001b[0m\n", + "\u001b[0;34m >>> pre_processor = PreProcessor(config=\"/tmp/transforms.yaml\")\u001b[0m\n", + "\u001b[0;34m >>> output = pre_processor(image=image)\u001b[0m\n", + "\u001b[0;34m >>> output[\"image\"].shape\u001b[0m\n", + "\u001b[0;34m torch.Size([3, 1024, 1024])\u001b[0m\n", + "\u001b[0;34m \"\"\"\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m__init__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mconfig\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mOptional\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mUnion\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mstr\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mA\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mCompose\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mimage_size\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mOptional\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mUnion\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mint\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mTuple\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mto_tensor\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mbool\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mTrue\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mconfig\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mconfig\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mimage_size\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mimage_size\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mto_tensor\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mto_tensor\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtransforms\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget_transforms\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mget_transforms\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0mA\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mCompose\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"\"\"Get transforms from config or image size.\u001b[0m\n", + "\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m Returns:\u001b[0m\n", + "\u001b[0;34m A.Compose: List of albumentation transformations to apply to the\u001b[0m\n", + "\u001b[0;34m input image.\u001b[0m\n", + "\u001b[0;34m \"\"\"\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mconfig\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mNone\u001b[0m \u001b[0;32mand\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mimage_size\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mraise\u001b[0m \u001b[0mValueError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"Both config and image_size cannot be `None`. \"\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"Provide either config file to de-serialize transforms \"\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"or image_size to get the default transformations\"\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mtransforms\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mA\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mCompose\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mconfig\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mNone\u001b[0m \u001b[0;32mand\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mimage_size\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mheight\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mwidth\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_get_height_and_width\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mtransforms\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mA\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mCompose\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mA\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mResize\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mheight\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mheight\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mwidth\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mwidth\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0malways_apply\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mTrue\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mA\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mNormalize\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmean\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m0.485\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m0.456\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m0.406\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mstd\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m0.229\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m0.224\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m0.225\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mToTensorV2\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mconfig\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0misinstance\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mconfig\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mstr\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mtransforms\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mA\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mload\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfilepath\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mconfig\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mdata_format\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m\"yaml\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32melif\u001b[0m \u001b[0misinstance\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mconfig\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mA\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mCompose\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mtransforms\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mconfig\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mraise\u001b[0m \u001b[0mValueError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"config could be either ``str`` or ``A.Compose``\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mto_tensor\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0misinstance\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mtransforms\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m-\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mToTensorV2\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mtransforms\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mA\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mCompose\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mtransforms\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m-\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;31m# always resize to specified image size\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0many\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0misinstance\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mtransform\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mA\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mResize\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mtransform\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mtransforms\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mand\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mimage_size\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mheight\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mwidth\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_get_height_and_width\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mtransforms\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mA\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mCompose\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mA\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mResize\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mheight\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mheight\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mwidth\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mwidth\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0malways_apply\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mTrue\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtransforms\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mtransforms\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m__call__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"\"\"Return transformed arguments.\"\"\"\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtransforms\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m_get_height_and_width\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0mTuple\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mOptional\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mint\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mOptional\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mint\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"\"\"Extract height and width from image size attribute.\"\"\"\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0misinstance\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mimage_size\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mint\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mimage_size\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mimage_size\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0misinstance\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mimage_size\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtuple\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mimage_size\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mimage_size\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mimage_size\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mraise\u001b[0m \u001b[0mValueError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"``image_size`` could be either int or Tuple[int, int]\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mFile:\u001b[0m ~/repos/anomalib/anomalib/pre_processing/pre_process.py\n", + "\u001b[0;31mType:\u001b[0m type\n", + "\u001b[0;31mSubclasses:\u001b[0m \n" + ] + } + ], "source": [ "PreProcessor??" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "metadata": {}, "outputs": [], "source": [ @@ -103,9 +453,148 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/tmp/ipykernel_115879/3968494898.py:2: UserWarning: Preloading images into memory. If your dataset is too large, consider using another imread_strategy instead.\n", + " mvtec_loco_dataset_classification_train = MVTecLOCODataset(\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
image_pathsplitsuper_anotypeimage_filenamemask_pathslabelanotypeimagemasksmask
0../../datasets/MVTecLOCO/pushpins/train/good/0...traingood000.pngNone0good[[[11, 11, 12], [11, 11, 12], [11, 11, 13], [1...NoneNone
1../../datasets/MVTecLOCO/pushpins/train/good/0...traingood001.pngNone0good[[[12, 11, 11], [12, 10, 11], [12, 10, 12], [1...NoneNone
2../../datasets/MVTecLOCO/pushpins/train/good/0...traingood002.pngNone0good[[[14, 12, 12], [13, 12, 13], [12, 12, 13], [1...NoneNone
3../../datasets/MVTecLOCO/pushpins/train/good/0...traingood003.pngNone0good[[[12, 11, 12], [12, 11, 12], [11, 11, 12], [1...NoneNone
4../../datasets/MVTecLOCO/pushpins/train/good/0...traingood004.pngNone0good[[[12, 12, 13], [12, 11, 14], [11, 11, 14], [1...NoneNone
\n", + "
" + ], + "text/plain": [ + " image_path split super_anotype \\\n", + "0 ../../datasets/MVTecLOCO/pushpins/train/good/0... train good \n", + "1 ../../datasets/MVTecLOCO/pushpins/train/good/0... train good \n", + "2 ../../datasets/MVTecLOCO/pushpins/train/good/0... train good \n", + "3 ../../datasets/MVTecLOCO/pushpins/train/good/0... train good \n", + "4 ../../datasets/MVTecLOCO/pushpins/train/good/0... train good \n", + "\n", + " image_filename mask_paths label anotype \\\n", + "0 000.png None 0 good \n", + "1 001.png None 0 good \n", + "2 002.png None 0 good \n", + "3 003.png None 0 good \n", + "4 004.png None 0 good \n", + "\n", + " image masks mask \n", + "0 [[[11, 11, 12], [11, 11, 12], [11, 11, 13], [1... None None \n", + "1 [[[12, 11, 11], [12, 10, 11], [12, 10, 12], [1... None None \n", + "2 [[[14, 12, 12], [13, 12, 13], [12, 12, 13], [1... None None \n", + "3 [[[12, 11, 12], [12, 11, 12], [11, 11, 12], [1... None None \n", + "4 [[[12, 12, 13], [12, 11, 14], [11, 11, 14], [1... None None " + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# MVTec LOCO Classification Train Set\n", "mvtec_loco_dataset_classification_train = MVTecLOCODataset(\n", @@ -120,9 +609,30 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "dict_keys(['image'])" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "torch.Size([3, 100, 170])" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "sample = mvtec_loco_dataset_classification_train[0]\n", "sample.keys()\n", @@ -138,9 +648,18 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/tmp/ipykernel_115879/3604180834.py:2: UserWarning: Preloading images into memory. If your dataset is too large, consider using another imread_strategy instead.\n", + " mvtec_loco_dataset_classification_test = MVTecLOCODataset(\n" + ] + } + ], "source": [ "# MVTec Classification Test Set\n", "mvtec_loco_dataset_classification_test = MVTecLOCODataset(\n", @@ -154,9 +673,60 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "dict_keys(['image', 'label', 'image_path', 'anotype', 'super_anotype'])" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "torch.Size([3, 100, 170])" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "'../../datasets/MVTecLOCO/pushpins/test/good/000.png'" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "0" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "('good', 'good')" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "sample = mvtec_loco_dataset_classification_test[0]\n", "sample.keys()\n", @@ -175,9 +745,60 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 10, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "dict_keys(['image', 'label', 'image_path', 'anotype', 'super_anotype'])" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "torch.Size([3, 100, 170])" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "'../../datasets/MVTecLOCO/pushpins/test/structural_anomalies/080.png'" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "1" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "('structural_anomalies', 'front_bent')" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "sample = mvtec_loco_dataset_classification_test[-1]\n", "sample.keys()\n", @@ -198,9 +819,148 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 11, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/tmp/ipykernel_115879/3202540915.py:2: UserWarning: Preloading images into memory. If your dataset is too large, consider using another imread_strategy instead.\n", + " mvtec_loco_dataset_segmentation_train = MVTecLOCODataset(\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
image_pathsplitsuper_anotypeimage_filenamemask_pathslabelanotypeimagemasksmask
0../../datasets/MVTecLOCO/pushpins/train/good/0...traingood000.pngNone0good[[[11, 11, 12], [11, 11, 12], [11, 11, 13], [1...NoneNone
1../../datasets/MVTecLOCO/pushpins/train/good/0...traingood001.pngNone0good[[[12, 11, 11], [12, 10, 11], [12, 10, 12], [1...NoneNone
2../../datasets/MVTecLOCO/pushpins/train/good/0...traingood002.pngNone0good[[[14, 12, 12], [13, 12, 13], [12, 12, 13], [1...NoneNone
3../../datasets/MVTecLOCO/pushpins/train/good/0...traingood003.pngNone0good[[[12, 11, 12], [12, 11, 12], [11, 11, 12], [1...NoneNone
4../../datasets/MVTecLOCO/pushpins/train/good/0...traingood004.pngNone0good[[[12, 12, 13], [12, 11, 14], [11, 11, 14], [1...NoneNone
\n", + "
" + ], + "text/plain": [ + " image_path split super_anotype \\\n", + "0 ../../datasets/MVTecLOCO/pushpins/train/good/0... train good \n", + "1 ../../datasets/MVTecLOCO/pushpins/train/good/0... train good \n", + "2 ../../datasets/MVTecLOCO/pushpins/train/good/0... train good \n", + "3 ../../datasets/MVTecLOCO/pushpins/train/good/0... train good \n", + "4 ../../datasets/MVTecLOCO/pushpins/train/good/0... train good \n", + "\n", + " image_filename mask_paths label anotype \\\n", + "0 000.png None 0 good \n", + "1 001.png None 0 good \n", + "2 002.png None 0 good \n", + "3 003.png None 0 good \n", + "4 004.png None 0 good \n", + "\n", + " image masks mask \n", + "0 [[[11, 11, 12], [11, 11, 12], [11, 11, 13], [1... None None \n", + "1 [[[12, 11, 11], [12, 10, 11], [12, 10, 12], [1... None None \n", + "2 [[[14, 12, 12], [13, 12, 13], [12, 12, 13], [1... None None \n", + "3 [[[12, 11, 12], [12, 11, 12], [11, 11, 12], [1... None None \n", + "4 [[[12, 12, 13], [12, 11, 14], [11, 11, 14], [1... None None " + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# MVTec LOCO Segmentation Train Set\n", "mvtec_loco_dataset_segmentation_train = MVTecLOCODataset(\n", @@ -215,9 +975,48 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 12, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/tmp/ipykernel_115879/3616612057.py:2: UserWarning: Preloading images into memory. If your dataset is too large, consider using another imread_strategy instead.\n", + " mvtec_loco_dataset_segmentation_test = MVTecLOCODataset(\n" + ] + }, + { + "data": { + "text/plain": [ + "dict_keys(['image', 'label', 'image_path', 'anotype', 'super_anotype', 'mask_paths', 'masks', 'mask'])" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "torch.Size([3, 100, 170])" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "torch.Size([100, 170])" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# MVTec LOCO Segmentation Test Set\n", "mvtec_loco_dataset_segmentation_test = MVTecLOCODataset(\n", @@ -242,16 +1041,290 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 13, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "img = ToPILImage()(Denormalize()(sample[\"image\"].clone()))\n", + "msk = ToPILImage()(sample[\"mask\"]).convert(\"RGB\")\n", + "\n", + "Image.fromarray(np.vstack((np.array(img), np.array(msk))))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "An example of structural anomaly" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "dict_keys(['image', 'label', 'image_path', 'anotype', 'super_anotype', 'mask_paths', 'masks', 'mask'])" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "'structural_anomalies'" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "'broken'" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "sample = mvtec_loco_dataset_segmentation_test[250]\n", + "sample.keys()\n", + "sample[\"super_anotype\"]\n", + "sample[\"anotype\"]\n", + "img = ToPILImage()(Denormalize()(sample[\"image\"].clone()))\n", + "msk = ToPILImage()(sample[\"mask\"]).convert(\"RGB\")\n", + "\n", + "Image.fromarray(np.vstack((np.array(img), np.array(msk))))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "An example of logical anomaly" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "dict_keys(['image', 'label', 'image_path', 'anotype', 'super_anotype', 'mask_paths', 'masks', 'mask'])" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "'logical_anomalies'" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "'missing_separator'" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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y/YX+3WoNQJBekDJdOceCNaDGjeY4b5y0tCyIvCA9to7ojMbBjexo5eeWGLywpd+/F+97V0v+vhP/q2/+ge6ehIdYY3HMdJxp9YNPjApK4auwZ6xqPz2X7nwM99wGCaYzLonl3d/b3neq3eWg19yaGa9Tjc7bJbVggYg21tbQGNmKOyVr/iKez965wfuvhuip06dUekIMWUEiNZgg2zMggLJZ6ErCqQh11mTUXRFO8ik01kv231/5Mjxz/7cT13WOptuvPc977vv4dNOEStQpRR201R8sL5XCCHaqdoMClH2pahiop1NrFeZFu0F08K+n5ihA2ZbW9NONyd9sS7Cd4dh98J5hEA9gqoS0RUdqhNSgApYRKggTCVCihroESq9AK2mBlO6lGKw6WxSzDY606J1MR98ubdXBbWii4hlZEHanQALYwlAXKsiIiChDIVWCIJKhBZiEDc1VHGlWpHNftbPShAmvrNfT156xQP33AXV+WIeQUuEmxC4QSopcEAqqc4ANFhRVQvqslf26mfmvjvUvaEiuCGc9JPtzY0XPfum533RN6ioe4iiNGB1JI5NijGWIBY+AFAND3jkXPaApXiXo2l8BDwhrpwWFQqPUDAwtG6HTGv25cELL3zx+Z39xdYR/8u3vuehRx9LojCJaKUOGFAbuJ87vavYmP4lfVuSDMTUwBCMYmKmitf96L9fLvah+OGf/dUH7r8PK6GQJpCRbLwQM9KzetRWqQgabSH3EM85IglzQdRw05Of/i1f/jLUxWxz899824+iYc5NggWtljN+3/jYSTfLQInIXqRG2gfRAjDT4ycuedYN137mp71kPh/K1F79Ez8L02E5ECBVJKLJpCjdiehKN1RPmoiKmoSIFUVn2lsppqWbWCkbW9su3Nreuur4sSuv2WI3Od5XoHF6ARQiRLSRt9SUEWaAhlcQHtlZuOKSgJJzIVqojRF7yvUCVIweFxn2I/daQezvPXbf/btx/wOnz59N1nDWg1sO0sicbEmAgEwdNBISPqB1ArkA9NZkagpIzh792u/88V/76e/+9p/59Qfuvw8AII2UGsgmQwm4Au6mCLikrlAiZmwJeDLpahaZPBFogPbhD7z/9353+IIv/LSv/vYfybarkcobDVzLEAgN7iYgMQQzjxxULBKAFCeR/SRIyKny0Ycfe8vZM09/0rVHTxz97h94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", + "text/plain": [ + "" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ + "sample = mvtec_loco_dataset_segmentation_test[200]\n", + "sample.keys()\n", + "sample[\"super_anotype\"]\n", + "sample[\"anotype\"]\n", "img = ToPILImage()(Denormalize()(sample[\"image\"].clone()))\n", "msk = ToPILImage()(sample[\"mask\"]).convert(\"RGB\")\n", "\n", "Image.fromarray(np.vstack((np.array(img), np.array(msk))))" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "An example of logical anomaly with multiple anomalous regions\n", + "\n", + "**Important**: the ground truth can have multiple masks (one for each logical anomalous region).\n", + "\n", + "The **union** of the (multiple) masks is conveniently returned in the field `\"mask\"`, but the individual `\"masks\"` (with **s**!) should be considered for correct evaluation!" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "dict_keys(['image', 'label', 'image_path', 'anotype', 'super_anotype', 'mask_paths', 'masks', 'mask'])" + ] + }, + "execution_count": 38, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "'logical_anomalies'" + ] + }, + "execution_count": 38, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "'additional_1_pushpin'" + ] + }, + "execution_count": 38, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "tensor([0., 1.], dtype=torch.float64)" + ] + }, + "execution_count": 38, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "array([ 0, 255], dtype=uint8)" + ] + }, + "execution_count": 38, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "tensor([0., 1., 2.], dtype=torch.float64)" + ] + }, + "execution_count": 38, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "array([ 0, 56, 156], dtype=uint8)" + ] + }, + "execution_count": 38, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "" + ] + }, + "execution_count": 38, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "" + ] + }, + "execution_count": 38, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "sample = mvtec_loco_dataset_segmentation_test[150]\n", + "sample.keys()\n", + "sample[\"super_anotype\"]\n", + "sample[\"anotype\"]\n", + "img = np.array(ToPILImage()(Denormalize()(sample[\"image\"].clone())))\n", + "msk = np.array(ToPILImage()(sample[\"mask\"]).convert(\"RGB\"))\n", + "\n", + "# !!!!!\n", + "# \"100 * \" is artifically increasing the gtvalue of the mask to make it more visible\n", + "msks = np.array(ToPILImage()(100 * sample[\"masks\"]).convert(\"RGB\"))\n", + "# !!!!!\n", + "\n", + "sample[\"mask\"].unique()\n", + "np.unique(msk)\n", + "\n", + "sample[\"masks\"].unique()\n", + "np.unique(msks)\n", + "\n", + "Image.fromarray(np.vstack((img, msk)))\n", + "Image.fromarray(np.vstack((img, msks)))" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -265,16 +1338,221 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 39, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[0;31mInit signature:\u001b[0m\n", + "\u001b[0mMVTecLOCO\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mroot\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mstr\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mcategory\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mstr\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mtask\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mstr\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m'segmentation'\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mimread_strategy\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mstr\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m'preload'\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mimage_size\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mUnion\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mint\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mTuple\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mint\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mint\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mNoneType\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mnum_workers\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mint\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;36m8\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mtrain_batch_size\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mint\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;36m32\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mtransform_config_train\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mUnion\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mstr\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0malbumentations\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcore\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcomposition\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mCompose\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mNoneType\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mtest_batch_size\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mint\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;36m32\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mtransform_config_val\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mUnion\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mstr\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0malbumentations\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcore\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcomposition\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mCompose\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mNoneType\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mSource:\u001b[0m \n", + "\u001b[0;32mclass\u001b[0m \u001b[0mMVTecLOCO\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mLightningDataModule\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"\"\"MVTec LOCO AD Lightning Data Module.\"\"\"\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;31m# todo correct inconsistency: `transform_config_*val*` used for val and\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;31m# test set, but `*test*_batch_size` used for val and set\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m__init__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mroot\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mstr\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mcategory\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mstr\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mtask\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mstr\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mTASK_SEGMENTATION\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mimread_strategy\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mstr\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mIMREAD_STRATEGY_PRELOAD\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mimage_size\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mOptional\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mUnion\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mint\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mTuple\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mint\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mint\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mnum_workers\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mint\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;36m8\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mtrain_batch_size\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mint\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;36m32\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mtransform_config_train\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mOptional\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mUnion\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mstr\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mA\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mCompose\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mtest_batch_size\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mint\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;36m32\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mtransform_config_val\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mOptional\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mUnion\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mstr\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mA\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mCompose\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;31m# TODO: add a parameter to specify the anomaly types and (more specifically)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"\"\"Mvtec LOCO AD Lightning Data Module.\u001b[0m\n", + "\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m Args:\u001b[0m\n", + "\u001b[0;34m root: Path to the MVTec LOCO AD dataset root folder.\u001b[0m\n", + "\u001b[0;34m category: Name of the MVTec LOCO AD category (there are 5).\u001b[0m\n", + "\u001b[0;34m See ``anomalib.data.mvtec_loco.CATEGORIES``.\u001b[0m\n", + "\u001b[0;34m task: ``classification`` or ``segmentation``\u001b[0m\n", + "\u001b[0;34m Default: ``segmentation``\u001b[0m\n", + "\u001b[0;34m See ``anomalib.data.mvtec_loco.TASKS``.\u001b[0m\n", + "\u001b[0;34m imread_strategy: When should images be read into memory?\u001b[0m\n", + "\u001b[0;34m Default: ``preload``\u001b[0m\n", + "\u001b[0;34m See ``anomalib.data.mvtec_loco.IMREAD_STRATEGIES``.\u001b[0m\n", + "\u001b[0;34m image_size: Images are resized to `image_size` (HEIGHT, WIDTH), or (SIZE, SIZE) if a single value is given.\u001b[0m\n", + "\u001b[0;34m num_workers: Number of workers.\u001b[0m\n", + "\u001b[0;34m train_batch_size: Training batch size.\u001b[0m\n", + "\u001b[0;34m transform_config_train: List of pre_processing object containing albumentation compose or\u001b[0m\n", + "\u001b[0;34m config applied during training.\u001b[0m\n", + "\u001b[0;34m test_batch_size: Testing batch size.\u001b[0m\n", + "\u001b[0;34m transform_config_val: List of pre_processing object containing albumentation compose or\u001b[0m\n", + "\u001b[0;34m config applied during validation.\u001b[0m\n", + "\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m TODO add link\u001b[0m\n", + "\u001b[0;34m See examples in the repository ``anomalib/notebooks/100_datamodules/104_mvtec_loco.ipynb``.\u001b[0m\n", + "\u001b[0;34m \"\"\"\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0msuper\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__init__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;31m# TODO create option to get a subset of anomalies in the test\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;31m# TODO the images are not squared here, maybe we should add warn the user if\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;31m# the ration from image_size is too different from the original image when\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;31m# the image size is given as an int\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32massert\u001b[0m \u001b[0mtask\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mTASKS\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34mf\"Task '{task}' is not supported. Supported tasks are {TASKS}\"\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32massert\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mimread_strategy\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mIMREAD_STRATEGIES\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34mf\"Imread strategy '{imread_strategy}' is not supported. Supported imread strategies are {IMREAD_STRATEGIES}\"\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mroot\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mroot\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0misinstance\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mroot\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mPath\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32melse\u001b[0m \u001b[0mPath\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mroot\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcategory\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mcategory\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtransform_config_train\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mtransform_config_train\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtransform_config_val\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mtransform_config_val\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mimage_size\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mimage_size\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpre_process_train\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mPreProcessor\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mconfig\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtransform_config_train\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mimage_size\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mimage_size\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpre_process_val\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mPreProcessor\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mconfig\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtransform_config_val\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mimage_size\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mimage_size\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtrain_batch_size\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mtrain_batch_size\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtest_batch_size\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mtest_batch_size\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mnum_workers\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mnum_workers\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtask\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mtask\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mimread_strategy\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mimread_strategy\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtrain_data\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mDataset\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtest_data\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mDataset\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mval_data\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mDataset\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0minference_data\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mDataset\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m@\u001b[0m\u001b[0mproperty\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mcategory_dataset_path\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0mPath\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"\"\"Path to the category dataset (root/category) folder.\"\"\"\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mroot\u001b[0m \u001b[0;34m/\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcategory\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mprepare_data\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"\"\"Download the dataset if not available.\"\"\"\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcategory_dataset_path\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mis_dir\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mlogger\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0minfo\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Found the dataset.\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mdownload_and_extract_mvtec_loco\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mroot\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0msetup\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mstage\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mOptional\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mstr\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"\"\"Setup train, validation and test data.\u001b[0m\n", + "\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m Args:\u001b[0m\n", + "\u001b[0;34m stage: Optional[str]: fit/validate/test/predict stages. (Default value = None = fit)\u001b[0m\n", + "\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m \"\"\"\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;31m# pylint: disable=consider-using-f-string\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mlogger\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0minfo\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Setting up %s dataset.\"\u001b[0m \u001b[0;34m%\u001b[0m \u001b[0mstage\u001b[0m \u001b[0;32mor\u001b[0m \u001b[0mTrainerFn\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mFITTING\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mstage\u001b[0m \u001b[0;32min\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mTrainerFn\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mFITTING\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mhasattr\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"train_data\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mlogger\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdebug\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Train data already exists. Skipping setup.\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mreturn\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtrain_data\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mMVTecLOCODataset\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mroot\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mroot\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mcategory\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcategory\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0msplit\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mSPLIT_TRAIN\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mpre_process\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpre_process_train\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mtask\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtask\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mimread_strategy\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mimread_strategy\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mstage\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0mTrainerFn\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mVALIDATING\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mhasattr\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"val_data\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mlogger\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdebug\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Validation data already exists. Skipping setup.\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mreturn\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mval_data\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mMVTecLOCODataset\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mroot\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mroot\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mcategory\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcategory\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mpre_process\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpre_process_val\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0msplit\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mSPLIT_VALIDATION\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mtask\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtask\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mimread_strategy\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mimread_strategy\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mstage\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0mTrainerFn\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mTESTING\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mhasattr\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"test_data\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mlogger\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdebug\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Test data already exists. Skipping setup.\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mreturn\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtest_data\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mMVTecLOCODataset\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mroot\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mroot\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mcategory\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcategory\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mpre_process\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpre_process_val\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0msplit\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mSPLIT_TEST\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mtask\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtask\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mimread_strategy\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mimread_strategy\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mstage\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0mTrainerFn\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mPREDICTING\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mhasattr\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"inference_data\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mlogger\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdebug\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Inference data already exists. Skipping setup.\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mreturn\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0minference_data\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mInferenceDataset\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mpath\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mroot\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mimage_size\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mimage_size\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtransform_config\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtransform_config_val\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mtrain_dataloader\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0mTRAIN_DATALOADERS\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"\"\"Get train dataloader.\"\"\"\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0mhasattr\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"train_data\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mraise\u001b[0m \u001b[0mRuntimeError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Train data not setup. Did you run `datamodule.setup('fit')`?\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mDataLoader\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtrain_data\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mshuffle\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mTrue\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mbatch_size\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtrain_batch_size\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mnum_workers\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mnum_workers\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mval_dataloader\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0mEVAL_DATALOADERS\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"\"\"Get validation dataloader.\"\"\"\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0mhasattr\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"val_data\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mraise\u001b[0m \u001b[0mRuntimeError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Validation data not setup. Did you run `datamodule.setup('validate')`?\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mDataLoader\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mval_data\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mshuffle\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mFalse\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mbatch_size\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtest_batch_size\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mnum_workers\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mnum_workers\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mtest_dataloader\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0mEVAL_DATALOADERS\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"\"\"Get test dataloader.\"\"\"\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0mhasattr\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"test_data\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mraise\u001b[0m \u001b[0mRuntimeError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Test data not setup. Did you run `datamodule.setup('test')`?\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mDataLoader\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtest_data\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mshuffle\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mFalse\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mbatch_size\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtest_batch_size\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mnum_workers\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mnum_workers\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mpredict_dataloader\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0mEVAL_DATALOADERS\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"\"\"Get predict dataloader.\"\"\"\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0mhasattr\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"inference_data\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mraise\u001b[0m \u001b[0mRuntimeError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Inference data not setup. Did you run `datamodule.setup('predict')`?\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mDataLoader\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0minference_data\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mshuffle\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mFalse\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mbatch_size\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtest_batch_size\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mnum_workers\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mnum_workers\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mFile:\u001b[0m ~/repos/anomalib/anomalib/data/mvtec_loco.py\n", + "\u001b[0;31mType:\u001b[0m type\n", + "\u001b[0;31mSubclasses:\u001b[0m \n" + ] + } + ], "source": [ "MVTecLOCO??" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 40, "metadata": {}, "outputs": [], "source": [ @@ -294,9 +1572,38 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 41, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/jcasagrandebertoldo/repos/anomalib/anomalib/data/mvtec_loco.py:794: UserWarning: Preloading images into memory. If your dataset is too large, consider using another imread_strategy instead.\n", + " self.train_data = MVTecLOCODataset(\n" + ] + }, + { + "data": { + "text/plain": [ + "dict_keys(['image'])" + ] + }, + "execution_count": 41, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "torch.Size([32, 3, 200, 340])" + ] + }, + "execution_count": 41, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# Train images\n", "\n", @@ -310,9 +1617,60 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 44, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "dict_keys(['image', 'label', 'image_path', 'anotype', 'super_anotype', 'mask_paths', 'masks', 'mask'])" + ] + }, + "execution_count": 44, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "torch.Size([32, 3, 200, 340])" + ] + }, + "execution_count": 44, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "torch.Size([32, 200, 340])" + ] + }, + "execution_count": 44, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "torch.Size([32, 200, 340])" + ] + }, + "execution_count": 44, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "('good', 'good')" + ] + }, + "execution_count": 44, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# Validation images\n", "mvtec_datamodule.setup(\"validate\")\n", @@ -320,14 +1678,98 @@ "data.keys()\n", "data[\"image\"].shape\n", "data[\"mask\"].shape\n", + "data[\"masks\"].shape\n", "data[\"super_anotype\"][0], data[\"anotype\"][0]" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 46, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "dict_keys(['image', 'label', 'image_path', 'anotype', 'super_anotype', 'mask_paths', 'masks', 'mask'])" + ] + }, + "execution_count": 46, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "torch.Size([32, 3, 200, 340])" + ] + }, + "execution_count": 46, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "torch.Size([32, 200, 340])" + ] + }, + "execution_count": 46, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "tensor([0., 1.], dtype=torch.float64)" + ] + }, + "execution_count": 46, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "torch.Size([32, 200, 340])" + ] + }, + "execution_count": 46, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "tensor([ 0., 1., 2., 3., 4., 5., 6., 7., 8., 9., 10., 11., 12., 13.,\n", + " 14., 15.], dtype=torch.float64)" + ] + }, + "execution_count": 46, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "('logical_anomalies', 'additional_1_pushpin')" + ] + }, + "execution_count": 46, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "" + ] + }, + "execution_count": 46, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# Test images\n", "mvtec_datamodule.setup(\"test\")\n", @@ -338,6 +1780,9 @@ "data.keys()\n", "data[\"image\"].shape\n", "data[\"mask\"].shape\n", + "data[\"mask\"].unique()\n", + "data[\"masks\"].shape\n", + "data[\"masks\"].unique()\n", "data[\"super_anotype\"][0], data[\"anotype\"][0]\n", "\n", "img = ToPILImage()(Denormalize()(data[\"image\"][0].clone()))\n", @@ -345,23 +1790,6 @@ "\n", "Image.fromarray(np.vstack((np.array(img), np.array(msk))))" ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "TODO: show that the ground truth is divided in multiple images\n", - "\n", - "TODO: create issue to correct docs in mvtec, e.g. it should not give example in the docstring but send the user\n", - " to the notebooks (more maintainable)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "As can be seen above, creating the dataloaders are pretty straghtforward, which could be directly used for training/testing/inference." - ] } ], "metadata": { From 49a5a5c11b66f9ffed73f04dfc5dc9c31a74a910 Mon Sep 17 00:00:00 2001 From: jpcbertoldo <24547377+jpcbertoldo@users.noreply.github.com> Date: Sun, 11 Sep 2022 14:22:13 +0200 Subject: [PATCH 08/38] add unit tests for mvtec loco --- anomalib/data/mvtec_loco.py | 32 ---------- tests/pre_merge/datasets/test_dataset.py | 77 +++++++++++++++++++++++- 2 files changed, 75 insertions(+), 34 deletions(-) diff --git a/anomalib/data/mvtec_loco.py b/anomalib/data/mvtec_loco.py index d5274ddab9..1dd8f6cf3c 100644 --- a/anomalib/data/mvtec_loco.py +++ b/anomalib/data/mvtec_loco.py @@ -861,35 +861,3 @@ def predict_dataloader(self) -> EVAL_DATALOADERS: return DataLoader( self.inference_data, shuffle=False, batch_size=self.test_batch_size, num_workers=self.num_workers ) - - -# next -# correct the multi-image ground truth -# then show it in the notebook -# then create unit tests -# next -# next -# next -# next -# next -# next -# next -# next -# next -# next -# next -# next -# next -# next -# next -# next -# next -# next -# next -# next -# next -# next -# next -# next -# next -# next diff --git a/tests/pre_merge/datasets/test_dataset.py b/tests/pre_merge/datasets/test_dataset.py index 06d9629b45..3b93d190fd 100644 --- a/tests/pre_merge/datasets/test_dataset.py +++ b/tests/pre_merge/datasets/test_dataset.py @@ -6,12 +6,44 @@ import pytest from anomalib.config import update_input_size_config -from anomalib.data import BTech, Folder, MVTec, get_datamodule +from anomalib.data import BTech, Folder, MVTec, MVTecLOCO, get_datamodule, mvtec_loco from anomalib.pre_processing.transforms import Denormalize, ToNumpy from tests.helpers.config import get_test_configurable_parameters from tests.helpers.dataset import TestDataset, get_dataset_path +@pytest.fixture +def mvtec_loco_data_module(request): + datamodule = MVTecLOCO( + root=get_dataset_path(dataset="MVTecLOCO"), + category="pushpins", + task="segmentation", + image_size=(100, 170), # 10x smaller than original + train_batch_size=1, + test_batch_size=1, + num_workers=0, + imread_strategy=request.param.get("imread_strategy", mvtec_loco.IMREAD_STRATEGY_ONTHEFLY), + transform_config_train=None, + transform_config_val=None, + ) + datamodule.prepare_data() + datamodule.setup("fit") + datamodule.setup("validate") + datamodule.setup("test") + + yield datamodule + + +MVTEC_LOCO_PARAMS_ALL_IMREAD_STRATEGIES = [ + {"imread_strategy": mvtec_loco.IMREAD_STRATEGY_ONTHEFLY}, + {"imread_strategy": mvtec_loco.IMREAD_STRATEGY_PRELOAD}, +] + +MVTEC_LOCO_PARAMS_ALL_IMREAD_ONTHEFLY_ONLY = [ + {"imread_strategy": mvtec_loco.IMREAD_STRATEGY_ONTHEFLY}, +] + + @pytest.fixture(autouse=True) def mvtec_data_module(): datamodule = MVTec( @@ -45,7 +77,7 @@ def btech_data_module(): return datamodule -@pytest.fixture(autouse=True) +@pytest.fixture(autouse=False) def folder_data_module(): """Create Folder Data Module.""" root = get_dataset_path(dataset="bottle") @@ -74,6 +106,47 @@ def data_sample(mvtec_data_module): return data +class TestMVTecLOCODataModule: + """Test MVTec LOCO Data Module.""" + + @pytest.mark.parametrize("mvtec_loco_data_module", MVTEC_LOCO_PARAMS_ALL_IMREAD_STRATEGIES, indirect=True) + def test_sizes(self, mvtec_loco_data_module): + """test_mvtec_datamodule [summary]""" + + _, train_data_sample = next(enumerate(mvtec_loco_data_module.train_dataloader())) + _, val_data_sample = next(enumerate(mvtec_loco_data_module.val_dataloader())) + _, test_data_sample = next(enumerate(mvtec_loco_data_module.test_dataloader())) + + for split, data_sample in zip(["train", "val", "test"], [train_data_sample, val_data_sample, test_data_sample]): + image = data_sample["image"] + assert image.shape == (1, 3, 100, 170), f"Image shape is wrong for {split} split" + + @pytest.mark.parametrize("mvtec_loco_data_module", MVTEC_LOCO_PARAMS_ALL_IMREAD_STRATEGIES, indirect=True) + def test_val_and_test_dataloaders_has_mask_and_gt(self, mvtec_loco_data_module): + """Test Validation and Test dataloaders should return more things than just the image.""" + _, val_data = next(enumerate(mvtec_loco_data_module.val_dataloader())) + _, test_data = next(enumerate(mvtec_loco_data_module.test_dataloader())) + expected_keys = sorted( + ["image", "image_path", "mask", "masks", "mask_paths", "label", "super_anotype", "anotype"] + ) + assert expected_keys == sorted(val_data.keys()), "Validation dataloader keys are wrong" + assert expected_keys == sorted(test_data.keys()), "Test dataloader keys are wrong" + + @pytest.mark.parametrize("mvtec_loco_data_module", MVTEC_LOCO_PARAMS_ALL_IMREAD_ONTHEFLY_ONLY, indirect=True) + def test_non_overlapping_splits(self, mvtec_loco_data_module): + """This test ensures that the train and test splits generated are non-overlapping.""" + + train_paths = set(mvtec_loco_data_module.train_data.samples["image_path"].values) + val_paths = set(mvtec_loco_data_module.val_data.samples["image_path"].values) + test_paths = set(mvtec_loco_data_module.test_data.samples["image_path"].values) + + assert len(set.intersection(train_paths, val_paths)) == 0, "Found train and val split contamination" + + assert len(set.intersection(train_paths, test_paths)) == 0, "Found train and test split contamination" + + assert len(set.intersection(test_paths, val_paths)) == 0, "Found val and test split contamination" + + class TestMVTecDataModule: """Test MVTec AD Data Module.""" From e9809c47c815e8d7568c5ac3b4a5507594709635 Mon Sep 17 00:00:00 2001 From: Sid Mehta Date: Wed, 14 Sep 2022 07:07:09 -0700 Subject: [PATCH 09/38] Benchmarking tool with Comet (#545) * comet benchmarking enabled * updated BM docs * tweaked comment * commen changet * fixed end of file Co-authored-by: Samet Akcay --- docs/source/guides/benchmarking.rst | 3 ++- tools/benchmarking/benchmark.py | 4 +++- tools/benchmarking/utils/__init__.py | 4 ++-- tools/benchmarking/utils/metrics.py | 27 +++++++++++++++++++++++++++ 4 files changed, 34 insertions(+), 4 deletions(-) diff --git a/docs/source/guides/benchmarking.rst b/docs/source/guides/benchmarking.rst index a1b04ef74d..cd844537e4 100644 --- a/docs/source/guides/benchmarking.rst +++ b/docs/source/guides/benchmarking.rst @@ -3,7 +3,7 @@ Benchmarking ============= -To add to the suit of experiment tracking and optimization, anomalib also includes a benchmarking script for gathering results across different combinations of models, their parameters, and dataset categories. The model performance and throughputs are logged into a csv file that can also serve as a means to track model drift. Optionally, these same results can be logged to Weights and Biases and TensorBoard. A sample configuration file is shown below. +To add to the suit of experiment tracking and optimization, anomalib also includes a benchmarking script for gathering results across different combinations of models, their parameters, and dataset categories. The model performance and throughputs are logged into a csv file that can also serve as a means to track model drift. Optionally, these same results can be logged to Comet, Weights and Biases and TensorBoard. A sample configuration file is shown below. .. code-block:: yaml @@ -13,6 +13,7 @@ To add to the suit of experiment tracking and optimization, anomalib also includ - cpu - gpu writer: + - comet - wandb - tensorboard grid_search: diff --git a/tools/benchmarking/benchmark.py b/tools/benchmarking/benchmark.py index 67a1ccaac5..3de36e464d 100644 --- a/tools/benchmarking/benchmark.py +++ b/tools/benchmarking/benchmark.py @@ -21,7 +21,7 @@ import torch from omegaconf import DictConfig, ListConfig, OmegaConf from pytorch_lightning import Trainer, seed_everything -from utils import convert_to_openvino, upload_to_wandb, write_metrics +from utils import convert_to_openvino, upload_to_comet, upload_to_wandb, write_metrics from anomalib.config import get_configurable_parameters, update_input_size_config from anomalib.data import get_datamodule @@ -225,6 +225,8 @@ def distribute(config: Union[DictConfig, ListConfig]): distribute_over_gpus(config, folder=runs_folder) if "wandb" in config.writer: upload_to_wandb(team="anomalib", folder=runs_folder) + if "comet" in config.writer: + upload_to_comet(folder=runs_folder) def sweep( diff --git a/tools/benchmarking/utils/__init__.py b/tools/benchmarking/utils/__init__.py index 32cdd3840a..3c5124abaa 100644 --- a/tools/benchmarking/utils/__init__.py +++ b/tools/benchmarking/utils/__init__.py @@ -4,6 +4,6 @@ # SPDX-License-Identifier: Apache-2.0 from .convert import convert_to_openvino -from .metrics import upload_to_wandb, write_metrics +from .metrics import upload_to_comet, upload_to_wandb, write_metrics -__all__ = ["convert_to_openvino", "write_metrics", "upload_to_wandb"] +__all__ = ["convert_to_openvino", "write_metrics", "upload_to_comet", "upload_to_wandb"] diff --git a/tools/benchmarking/utils/metrics.py b/tools/benchmarking/utils/metrics.py index f61ecc1635..04bc3dff1f 100644 --- a/tools/benchmarking/utils/metrics.py +++ b/tools/benchmarking/utils/metrics.py @@ -10,6 +10,7 @@ from typing import Dict, List, Optional, Union import pandas as pd +from comet_ml import Experiment from torch.utils.tensorboard.writer import SummaryWriter import wandb @@ -116,3 +117,29 @@ def upload_to_wandb( ) wandb.log(row) wandb.finish() + + +def upload_to_comet( + folder: Optional[str] = None, +): + """Upload the data in csv files to comet. + + Creates a project named benchmarking_[two random characters]. This is so that the project names are unique. + One issue is that it does not check for collision + + Args: + folder (optional, str): Sub-directory from which runs are picked up. Defaults to None. If none picks from runs. + """ + project = f"benchmarking_{get_unique_key(2)}" + tag_list = ["dataset.category", "model_name", "dataset.image_size", "model.backbone", "device"] + search_path = "runs/*.csv" if folder is None else f"runs/{folder}/*.csv" + for csv_file in glob(search_path): + table = pd.read_csv(csv_file) + for index, row in table.iterrows(): + row = dict(row[1:]) # remove index column + tags = [str(row[column]) for column in tag_list if column in row.keys()] + experiment = Experiment(project_name=project) + experiment.set_name(f"{row['model_name']}_{row['dataset.category']}_{index}") + experiment.log_metrics(row, step=1, epoch=1) # populates auto-generated charts on panel view + experiment.add_tags(tags) + experiment.log_table(filename=csv_file) From 7305246c00746b9acb94a0532be20aa01f3af26e Mon Sep 17 00:00:00 2001 From: Samet Akcay Date: Fri, 16 Sep 2022 15:46:09 +0100 Subject: [PATCH 10/38] =?UTF-8?q?=F0=9F=90=9E=20Fix:=20Add=20map=5Flocatio?= =?UTF-8?q?n=20when=20loading=20the=20weights=20(#562)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- anomalib/utils/callbacks/model_loader.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/anomalib/utils/callbacks/model_loader.py b/anomalib/utils/callbacks/model_loader.py index a89b5ecd68..ad1e7ebc71 100644 --- a/anomalib/utils/callbacks/model_loader.py +++ b/anomalib/utils/callbacks/model_loader.py @@ -27,7 +27,7 @@ def on_test_start(self, _trainer, pl_module: AnomalyModule) -> None: # pylint: Loads the model weights from ``weights_path`` into the PyTorch module. """ logger.info("Loading the model from %s", self.weights_path) - pl_module.load_state_dict(torch.load(self.weights_path)["state_dict"]) + pl_module.load_state_dict(torch.load(self.weights_path, map_location=pl_module.device)["state_dict"]) def on_predict_start(self, _trainer, pl_module: AnomalyModule) -> None: """Call when inference begins. @@ -35,4 +35,4 @@ def on_predict_start(self, _trainer, pl_module: AnomalyModule) -> None: Loads the model weights from ``weights_path`` into the PyTorch module. """ logger.info("Loading the model from %s", self.weights_path) - pl_module.load_state_dict(torch.load(self.weights_path)["state_dict"]) + pl_module.load_state_dict(torch.load(self.weights_path, map_location=pl_module.device)["state_dict"]) From a055416495cd51280a7ab917fbf8ea6e16066e58 Mon Sep 17 00:00:00 2001 From: Ashwin Vaidya Date: Tue, 20 Sep 2022 09:48:27 +0200 Subject: [PATCH 11/38] Add patchcore to openvino export test + upgrade lightning (#565) --- requirements/base.txt | 4 ++-- tests/pre_merge/deploy/test_inferencer.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements/base.txt b/requirements/base.txt index 4b6ac9dab6..37dc678cc3 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -12,6 +12,6 @@ pandas>=1.1.0 pytorch-lightning>=1.6.0,<1.7.0 timm==0.5.4 torchmetrics>=0.9.1,<=0.9.3 -torchvision>=0.9.1,<=0.12.0 -torchtext>=0.9.1,<=0.12.0 +torchvision>=0.9.1,<=0.13.0 +torchtext>=0.9.1,<=0.13.0 wandb==0.12.17 diff --git a/tests/pre_merge/deploy/test_inferencer.py b/tests/pre_merge/deploy/test_inferencer.py index fbde1211c3..2a86d20482 100644 --- a/tests/pre_merge/deploy/test_inferencer.py +++ b/tests/pre_merge/deploy/test_inferencer.py @@ -80,7 +80,7 @@ def test_torch_inference(self, model_name: str, category: str = "shapes", path: @pytest.mark.parametrize( "model_name", - ["dfm", "draem", "ganomaly", "padim", "stfpm"], + ["dfm", "draem", "ganomaly", "padim", "patchcore", "stfpm"], ) @TestDataset(num_train=20, num_test=1, path=get_dataset_path(), use_mvtec=False) def test_openvino_inference(self, model_name: str, category: str = "shapes", path: str = "./datasets/MVTec"): From 4860abc78b0976faf2c423713d5b90cfaa55ed22 Mon Sep 17 00:00:00 2001 From: Samet Akcay Date: Tue, 20 Sep 2022 11:01:56 +0100 Subject: [PATCH 12/38] =?UTF-8?q?=F0=9F=90=9E=20Fix=20category=20check=20f?= =?UTF-8?q?or=20folder=20dataset=20in=20anomalib=20CLI=20(#567)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix category check * Fix config file --- anomalib/utils/cli/cli.py | 2 +- configs/model/fastflow.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/anomalib/utils/cli/cli.py b/anomalib/utils/cli/cli.py index 826e98cd46..48c1769163 100644 --- a/anomalib/utils/cli/cli.py +++ b/anomalib/utils/cli/cli.py @@ -144,7 +144,7 @@ def __set_default_root_dir(self) -> None: root_dir = config.trainer.default_root_dir if config.trainer.default_root_dir else "./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 config.data.init_args.keys() else "" + category = config.data.init_args.category if "category" in config.data.init_args else "" time_stamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") default_root_dir = os.path.join(root_dir, model_name, data_name, category, time_stamp) diff --git a/configs/model/fastflow.yaml b/configs/model/fastflow.yaml index c63491f4b5..1a2217ab6f 100644 --- a/configs/model/fastflow.yaml +++ b/configs/model/fastflow.yaml @@ -30,7 +30,7 @@ model: hidden_ratio: 1.0 # options: [1.0, 1.0, 0.16, 0.16] - for each supported backbone optimizer: - class_path: torch.optim._multi_tensor.Adam + class_path: torch.optim._multi_tensor.adam.Adam init_args: lr: 0.001 weight_decay: 0.00001 From 5b3fc2b117de0011d39759128602d9b50c0ddb8c Mon Sep 17 00:00:00 2001 From: Samet Akcay Date: Fri, 23 Sep 2022 07:21:48 +0100 Subject: [PATCH 13/38] =?UTF-8?q?=F0=9F=9A=9C=20Refactor=20`PreProcessor`?= =?UTF-8?q?=20and=20fix=20`Visualizer`=20denormalization=20issue.=20(#570)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * move sample generation to datamodule instead of dataset * move sample generation from init to setup * remove inference stage and add base classes * replace dataset classes with AnomalibDataset * move setup to base class, create samples as class method * update docstrings * refactor btech to new format * allow training with no anomalous data * remove MVTec name from comment * raise NotImplementedError in base class * allow both png and bmp images for btech * use label_index to check if dataset contains anomalous images * refactor getitem in dataset class * use iloc for indexing * move dataloader getters to base class * refactor to add validate stage in setup * Add warning message when there is no config file passed * Extract get_transforms and get_height_and_width functions * refactor pre-processor and fix visualizer normalization issue * Revert thenew data refactor * rename variable * Revert the changes not merged yet * Fix tests * Fix tests * Address codacy concerns Co-authored-by: Dick Ameln --- anomalib/data/utils/__init__.py | 8 +- anomalib/data/utils/image.py | 52 +++++- anomalib/post_processing/visualizer.py | 7 +- anomalib/pre_processing/pre_process.py | 157 +++++++++++------- .../dummy_lightning_model.py | 3 +- .../visualizer_callback/test_visualizer.py | 4 +- 6 files changed, 166 insertions(+), 65 deletions(-) diff --git a/anomalib/data/utils/__init__.py b/anomalib/data/utils/__init__.py index 53cf04e4fd..5059b51c06 100644 --- a/anomalib/data/utils/__init__.py +++ b/anomalib/data/utils/__init__.py @@ -5,11 +5,17 @@ from .download import DownloadProgressBar, hash_check from .generators import random_2d_perlin -from .image import generate_output_image_filename, get_image_filenames, read_image +from .image import ( + generate_output_image_filename, + get_image_filenames, + get_image_height_and_width, + read_image, +) __all__ = [ "generate_output_image_filename", "get_image_filenames", + "get_image_height_and_width", "hash_check", "random_2d_perlin", "read_image", diff --git a/anomalib/data/utils/image.py b/anomalib/data/utils/image.py index c8c5039882..758124f42e 100644 --- a/anomalib/data/utils/image.py +++ b/anomalib/data/utils/image.py @@ -6,7 +6,7 @@ import math import warnings from pathlib import Path -from typing import List, Union +from typing import List, Optional, Tuple, Union import cv2 import numpy as np @@ -141,7 +141,48 @@ def generate_output_image_filename(input_path: Union[str, Path], output_path: Un return file_path -def read_image(path: Union[str, Path]) -> np.ndarray: +def get_image_height_and_width(image_size: Optional[Union[int, Tuple]] = None) -> Tuple[Optional[int], Optional[int]]: + """Get image height and width from ``image_size`` variable. + + Args: + image_size (Optional[Union[int, Tuple[int, int]]], optional): Input image size. + + Raises: + ValueError: Image size not None, int or tuple. + + Examples: + >>> get_image_height_and_width(image_size=256) + (256, 256) + + >>> get_image_height_and_width(image_size=(256, 256)) + (256, 256) + + >>> get_image_height_and_width(image_size=(256, 256, 3)) + (256, 256) + + >>> get_image_height_and_width(image_size=256.) + 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] + + Returns: + Tuple[Optional[int], Optional[int]]: A tuple containing image height and width values. + """ + height_and_width: Tuple[Optional[int], Optional[int]] + 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]) + elif image_size is None: + height_and_width = (None, None) + else: + 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]] = None) -> np.ndarray: """Read image from disk in RGB format. Args: @@ -157,6 +198,13 @@ def read_image(path: Union[str, Path]) -> np.ndarray: image = cv2.imread(path) image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) + if image_size: + # This part is optional, where the user wants to quickly resize the image + # with a one-liner code. This would particularly be useful especially when + # prototyping new ideas. + height, width = get_image_height_and_width(image_size) + image = cv2.resize(image, dsize=(width, height), interpolation=cv2.INTER_AREA) + return image diff --git a/anomalib/post_processing/visualizer.py b/anomalib/post_processing/visualizer.py index 409a52b4a2..19b65887a7 100644 --- a/anomalib/post_processing/visualizer.py +++ b/anomalib/post_processing/visualizer.py @@ -13,12 +13,12 @@ import numpy as np from skimage.segmentation import mark_boundaries +from anomalib.data.utils import read_image from anomalib.post_processing.post_process import ( add_anomalous_label, add_normal_label, superimpose_anomaly_map, ) -from anomalib.pre_processing.transforms import Denormalize @dataclass @@ -73,9 +73,10 @@ def visualize_batch(self, batch: Dict) -> Iterator[np.ndarray]: Returns: Generator that yields a display-ready visualization for each image. """ - for i in range(batch["image"].size(0)): + batch_size, _num_channels, height, width = batch["image"].size() + for i in range(batch_size): image_result = ImageResult( - image=Denormalize()(batch["image"][i].cpu()), + image=read_image(path=batch["image_path"][i], image_size=(height, width)), pred_score=batch["pred_scores"][i].cpu().numpy().item(), pred_label=batch["pred_labels"][i].cpu().numpy().item(), anomaly_map=batch["anomaly_maps"][i].cpu().numpy() if "anomaly_maps" in batch else None, diff --git a/anomalib/pre_processing/pre_process.py b/anomalib/pre_processing/pre_process.py index 28740496eb..44cbc294e4 100644 --- a/anomalib/pre_processing/pre_process.py +++ b/anomalib/pre_processing/pre_process.py @@ -7,11 +7,111 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +import logging from typing import Optional, Tuple, Union import albumentations as A from albumentations.pytorch import ToTensorV2 +from anomalib.data.utils import get_image_height_and_width + +logger = logging.getLogger(__name__) + + +def get_transforms( + config: Optional[Union[str, A.Compose]] = None, + image_size: Optional[Union[int, Tuple]] = None, + to_tensor: bool = True, +) -> A.Compose: + """Get transforms from config or image size. + + Args: + config (Optional[Union[str, A.Compose]], 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. + to_tensor (bool, optional): Boolean to convert the final transforms into Torch tensor. Defaults to True. + + Raises: + ValueError: When both ``config`` and ``image_size`` is ``None``. + ValueError: When ``config`` is not a ``str`` or `A.Compose`` object. + + Returns: + A.Compose: Albumentation ``Compose`` object containing the image transforms. + + Examples: + >>> import skimage + >>> image = skimage.data.astronaut() + + >>> transforms = get_transforms(image_size=256, to_tensor=False) + >>> output = transforms(image=image) + >>> output["image"].shape + (256, 256, 3) + + >>> transforms = get_transforms(image_size=256, to_tensor=True) + >>> output = transforms(image=image) + >>> output["image"].shape + torch.Size([3, 256, 256]) + + + Transforms could be read from albumentations Compose object. + >>> import albumentations as A + >>> from albumentations.pytorch import ToTensorV2 + >>> config = A.Compose([A.Resize(512, 512), ToTensorV2()]) + >>> transforms = get_transforms(config=config, to_tensor=False) + >>> output = transforms(image=image) + >>> output["image"].shape + (512, 512, 3) + >>> type(output["image"]) + numpy.ndarray + + Transforms could be deserialized from a yaml file. + >>> transforms = A.Compose([A.Resize(1024, 1024), ToTensorV2()]) + >>> A.save(transforms, "/tmp/transforms.yaml", data_format="yaml") + >>> transforms = get_transforms(config="/tmp/transforms.yaml") + >>> output = transforms(image=image) + >>> output["image"].shape + torch.Size([3, 1024, 1024]) + """ + if config is None and image_size is None: + raise ValueError( + "Both config and image_size cannot be `None`. " + "Provide either config file to de-serialize transforms " + "or image_size to get the default transformations" + ) + + transforms: A.Compose + + if config is None and image_size is not None: + logger.warning("Transform configs has not been provided. Images will be normalized using ImageNet statistics.") + + height, width = get_image_height_and_width(image_size) + transforms = A.Compose( + [ + A.Resize(height=height, width=width, always_apply=True), + A.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)), + ToTensorV2(), + ] + ) + + if config is not None: + if isinstance(config, str): + transforms = A.load(filepath=config, data_format="yaml") + elif isinstance(config, A.Compose): + transforms = config + else: + raise ValueError("config could be either ``str`` or ``A.Compose``") + + if not to_tensor: + if isinstance(transforms[-1], ToTensorV2): + transforms = A.Compose(transforms[:-1]) + + # always resize to specified image size + if not any(isinstance(transform, A.Resize) for transform in transforms) and image_size is not None: + height, width = get_image_height_and_width(image_size) + transforms = A.Compose([A.Resize(height=height, width=width, always_apply=True), transforms]) + + return transforms + class PreProcessor: """Applies pre-processing and data augmentations to the input and returns the transformed output. @@ -74,63 +174,8 @@ def __init__( self.image_size = image_size self.to_tensor = to_tensor - self.transforms = self.get_transforms() - - def get_transforms(self) -> A.Compose: - """Get transforms from config or image size. - - Returns: - A.Compose: List of albumentation transformations to apply to the - input image. - """ - if self.config is None and self.image_size is None: - raise ValueError( - "Both config and image_size cannot be `None`. " - "Provide either config file to de-serialize transforms " - "or image_size to get the default transformations" - ) - - transforms: A.Compose - - if self.config is None and self.image_size is not None: - height, width = self._get_height_and_width() - transforms = A.Compose( - [ - A.Resize(height=height, width=width, always_apply=True), - A.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)), - ToTensorV2(), - ] - ) - - if self.config is not None: - if isinstance(self.config, str): - transforms = A.load(filepath=self.config, data_format="yaml") - elif isinstance(self.config, A.Compose): - transforms = self.config - else: - raise ValueError("config could be either ``str`` or ``A.Compose``") - - if not self.to_tensor: - if isinstance(transforms[-1], ToTensorV2): - transforms = A.Compose(transforms[:-1]) - - # always resize to specified image size - if not any(isinstance(transform, A.Resize) for transform in transforms) and self.image_size is not None: - height, width = self._get_height_and_width() - transforms = A.Compose([A.Resize(height=height, width=width, always_apply=True), transforms]) - - return transforms + self.transforms = get_transforms(config, image_size, to_tensor) def __call__(self, *args, **kwargs): """Return transformed arguments.""" return self.transforms(*args, **kwargs) - - def _get_height_and_width(self) -> Tuple[Optional[int], Optional[int]]: - """Extract height and width from image size attribute.""" - if isinstance(self.image_size, int): - return self.image_size, self.image_size - if isinstance(self.image_size, tuple): - return int(self.image_size[0]), int(self.image_size[1]) - if self.image_size is None: - return None, None - raise ValueError("``image_size`` could be either int or Tuple[int, int]") diff --git a/tests/pre_merge/utils/callbacks/visualizer_callback/dummy_lightning_model.py b/tests/pre_merge/utils/callbacks/visualizer_callback/dummy_lightning_model.py index 6072852fbe..8644871661 100644 --- a/tests/pre_merge/utils/callbacks/visualizer_callback/dummy_lightning_model.py +++ b/tests/pre_merge/utils/callbacks/visualizer_callback/dummy_lightning_model.py @@ -11,6 +11,7 @@ from anomalib.models.components import AnomalyModule from anomalib.utils.callbacks import ImageVisualizerCallback from anomalib.utils.metrics import get_metrics +from tests.helpers.dataset import get_dataset_path class DummyDataset(Dataset): @@ -68,7 +69,7 @@ def test_step(self, batch, _): """Only used to trigger on_test_epoch_end.""" self.log(name="loss", value=0.0, prog_bar=True) outputs = dict( - image_path=[Path("test1.jpg")], + image_path=[Path(get_dataset_path("bottle")) / "broken_large/000.png"], image=torch.rand((1, 3, 100, 100)), mask=torch.zeros((1, 100, 100)), anomaly_maps=torch.ones((1, 100, 100)), diff --git a/tests/pre_merge/utils/callbacks/visualizer_callback/test_visualizer.py b/tests/pre_merge/utils/callbacks/visualizer_callback/test_visualizer.py index 6ecd92c6f0..35fdb2e129 100644 --- a/tests/pre_merge/utils/callbacks/visualizer_callback/test_visualizer.py +++ b/tests/pre_merge/utils/callbacks/visualizer_callback/test_visualizer.py @@ -1,7 +1,7 @@ import glob import os import tempfile -from unittest import mock +from pathlib import Path import pytest import pytorch_lightning as pl @@ -42,7 +42,7 @@ def test_add_images(dataset): ) trainer.test(model=model, datamodule=DummyDataModule()) # test if images are logged - if len(glob.glob(os.path.join(dir_loc, "images", "*.jpg"))) != 1: + if len(list(Path(dir_loc).glob("**/*.png"))) != 1: raise Exception("Failed to save to local path") # test if tensorboard logs are created From de1bea206735aef71405a5f55a77ed78638b53c9 Mon Sep 17 00:00:00 2001 From: Ashwin Vaidya Date: Fri, 23 Sep 2022 08:22:57 +0200 Subject: [PATCH 14/38] =?UTF-8?q?=F0=9F=94=A8=20Check=20for=20successful?= =?UTF-8?q?=20openvino=20conversion=20(#571)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Check for successful openvino conversion --- anomalib/deploy/optimize.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/anomalib/deploy/optimize.py b/anomalib/deploy/optimize.py index a91fcc08ca..93b433da5b 100644 --- a/anomalib/deploy/optimize.py +++ b/anomalib/deploy/optimize.py @@ -68,7 +68,7 @@ def export_convert( if export_mode == "openvino": export_path = os.path.join(str(export_path), "openvino") optimize_command = "mo --input_model " + str(onnx_path) + " --output_dir " + str(export_path) - os.system(optimize_command) + assert os.system(optimize_command) == 0, "OpenVINO conversion failed" with open(Path(export_path) / "meta_data.json", "w", encoding="utf-8") as metadata_file: meta_data = get_model_metadata(model) # Convert metadata from torch From 353d981e8a6500034bd44ae8d9ed07a93baf3e84 Mon Sep 17 00:00:00 2001 From: Sid Mehta Date: Mon, 26 Sep 2022 00:54:58 -0700 Subject: [PATCH 15/38] =?UTF-8?q?=F0=9F=93=8A=20Comet=20HPO=20=20(#563)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * added hpo * lint fixed * Update hyperparameter_optimization.rst * fixed file lint * fixed documentation images * added sweep doc image * updated hpo docs to include images * fixed linting errors * added config folder to store sample sweeps * fixed docs for new location of config files * not needed. moved to config directory * not needed moved to config directory * renamed to configs * changed to "configs" * fixed grammar --- .../guides/hyperparameter_optimization.rst | 41 +++++++++++-- docs/source/guides/logging.rst | 4 +- docs/source/images/logging/comet_sweep.png | Bin 0 -> 350955 bytes tools/hpo/configs/comet_sweep.yaml | 15 +++++ .../{sweep.yaml => configs/wandb_sweep.yaml} | 0 tools/hpo/sweep.py | 57 +++++++++++++++++- 6 files changed, 106 insertions(+), 11 deletions(-) create mode 100644 docs/source/images/logging/comet_sweep.png create mode 100644 tools/hpo/configs/comet_sweep.yaml rename tools/hpo/{sweep.yaml => configs/wandb_sweep.yaml} (100%) diff --git a/docs/source/guides/hyperparameter_optimization.rst b/docs/source/guides/hyperparameter_optimization.rst index a275ffb4c4..92aa5da882 100644 --- a/docs/source/guides/hyperparameter_optimization.rst +++ b/docs/source/guides/hyperparameter_optimization.rst @@ -3,12 +3,34 @@ Hyperparameter Optimization =========================== -The default configuration for the models will not always work on a new dataset. Additionally, to increase performance, learning rate, optimizers, activation functions, etc. need to be tuned/selected. To make it easier to run such broad experiments that isolate the right combination of hyperparameters, Anomalib supports hyperparameter optimization using weights and biases. +The default configuration for the models will not always work on a new dataset. Additionally, to increase performance, learning rate, optimizers, activation functions, etc. need to be tuned/selected. To make it easier to run such broad experiments that isolate the right combination of hyperparameters, Anomalib supports hyperparameter optimization using Comet or weights and biases. YAML file ********** -A sample configuration file for hyperparameter optimization is provided at ``tools/hpo/sweep.yaml`` and is reproduced below: +A Sample configuration files for hyperparameter optimization with Comet is provided at ``tools/hpo/config/comet_sweep.yaml`` and reproduced below: + +.. code-block:: yaml + + algorithm: "bayes" + spec: + maxCombo: 10 + metric: "image_F1Score" + objective: "maximize" + parameters: + dataset: + category: capsule + image_size: + type: discrete + values: [128, 256] + model: + backbone: + type: categorical + values: ["resnet18", "wide_resnet50_2"] + +The maxCombo defines the total number of experiments to run. The algorithm is the optimization method to be used. The metric is the metric to be used to evaluate the performance of the model. The parameters are the hyperparameters to be optimized. For details on other possible configurations with Comet's Optimizer , refer to the `Comet's `_ documentation. + +A sample configuration file for hyperparameter optimization with Weights and Bias is provided at ``tools/hpo/config/wandb_sweep.yaml`` and is reproduced below: .. code-block:: yaml @@ -26,14 +48,16 @@ A sample configuration file for hyperparameter optimization is provided at ``too backbone: values: [resnet18, wide_resnet50_2] -The observation budget defines the total number of experiments to run. The method is the optimization method to be used. The metric is the metric to be used to evaluate the performance of the model. The parameters are the hyperparameters to be optimized. For details on methods other than ``bayes`` and parameter values apart from list, refer the `Weights and Biases `_ documentation. Everything under the ``parameters`` key overrides the default values defined in the model configuration. Currently, only the dataset and model parameters are overridden for the HPO search. +The observation budget defines the total number of experiments to run. The method is the optimization method to be used. The metric is the metric to be used to evaluate the performance of the model. The parameters are the hyperparameters to be optimized. For details on methods other than ``bayes`` and parameter values apart from list, refer the `Weights and Biases `_ documentation. + +Everything under the ``parameters`` key (in both configuration formats) overrides the default values defined in the model configuration. In these examples, only the dataset and model parameters are overridden for the HPO search. Running HPO ************ .. note:: - You will need to have logged into a wandb account to use HPO search and view the results. + You will need to have logged into a Comet or wandb account to use HPO search and view the results. To run the hyperparameter optimization, use the following command: @@ -41,18 +65,23 @@ To run the hyperparameter optimization, use the following command: python tools/hpo/sweep.py --model padim \ --model_config ./path_to_config.yaml \ - --sweep_config tools/hpo/sweep.yaml + --sweep_config tools/hpo/config/comet_sweep.yaml In case ``model_config`` is not provided, the script looks at the default config location for that model. .. code-block:: bash - python tools/hpo/sweep.py --sweep_config tools/hpo/sweep.yaml + python tools/hpo/sweep.py --sweep_config tools/hpo/config/comet_sweep.yaml Sample Output ************** +.. figure:: ../images/logging/comet_sweep.png + :alt: Sample configuration of a Comet sweep + + Sample Comet sweep on Padim + .. figure:: ../images/logging/wandb_sweep.png :alt: Sample configuration of a wandb sweep diff --git a/docs/source/guides/logging.rst b/docs/source/guides/logging.rst index 38eba09179..162f074def 100644 --- a/docs/source/guides/logging.rst +++ b/docs/source/guides/logging.rst @@ -51,7 +51,7 @@ Anomalib allows you to save predictions to the file system by setting ``log_imag Logging images to Comet,TensorBoard and wandb won't work if you don't have ``logger: [comet, tensorboard, wandb]`` set as well. This ensures that the respective logger is passed to the trainer object. -.. figure:: ../images/logging/comet_media.jpg +.. figure:: ../images/logging/comet_media.png :alt: comet dashboard showing logged images Comet Images in TensorBoard Dashboard @@ -84,7 +84,7 @@ Anomalib makes it easier to log your model graph to Comet, TensorBoard or Weight logger: [comet, tensorboard] log_graph: true -.. figure:: ../images/logging/comet_graph.jpg +.. figure:: ../images/logging/comet_graph.png :alt: comet dashboard showing model graph Model Graph in Comet Dashboard diff --git a/docs/source/images/logging/comet_sweep.png b/docs/source/images/logging/comet_sweep.png new file mode 100644 index 0000000000000000000000000000000000000000..2f14c85ad3c017dbffc9cad0c104cc47321b0904 GIT binary patch literal 350955 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

|e>T40Xa};lTTqPFY4kQU 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 CBF*#w literal 0 HcmV?d00001 diff --git a/tools/hpo/configs/comet_sweep.yaml b/tools/hpo/configs/comet_sweep.yaml new file mode 100644 index 0000000000..dc9decd084 --- /dev/null +++ b/tools/hpo/configs/comet_sweep.yaml @@ -0,0 +1,15 @@ +algorithm: "bayes" +spec: + maxCombo: 10 + metric: "image_F1Score" + objective: "maximize" +parameters: + dataset: + category: capsule + image_size: + type: discrete + values: [128, 256] + model: + backbone: + type: categorical + values: ["resnet18", "wide_resnet50_2"] diff --git a/tools/hpo/sweep.yaml b/tools/hpo/configs/wandb_sweep.yaml similarity index 100% rename from tools/hpo/sweep.yaml rename to tools/hpo/configs/wandb_sweep.yaml diff --git a/tools/hpo/sweep.py b/tools/hpo/sweep.py index 73d65efc7d..df607a24de 100644 --- a/tools/hpo/sweep.py +++ b/tools/hpo/sweep.py @@ -1,4 +1,4 @@ -"""Run wandb sweep.""" +"""Run hpo sweep.""" # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 @@ -8,9 +8,10 @@ from typing import Union import pytorch_lightning as pl +from comet_ml import Optimizer from omegaconf import DictConfig, ListConfig, OmegaConf from pytorch_lightning import seed_everything -from pytorch_lightning.loggers import WandbLogger +from pytorch_lightning.loggers import CometLogger, WandbLogger from utils import flatten_hpo_params import wandb @@ -71,6 +72,51 @@ def sweep(self): trainer.fit(model, datamodule=datamodule) +class CometSweep: + """comet sweep. + + Args: + config (DictConfig): Original model configuration. + sweep_config (DictConfig): Sweep configuration. + """ + + def __init__(self, config: Union[DictConfig, ListConfig], sweep_config: Union[DictConfig, ListConfig]) -> None: + self.config = config + self.sweep_config = sweep_config + + def run(self): + """Run the sweep.""" + flattened_hpo_params = flatten_hpo_params(self.sweep_config.parameters) + self.sweep_config.parameters = flattened_hpo_params + + # comet's Optmizer cannot takes dict as an input, not DictConfig + std_dict = OmegaConf.to_object(self.sweep_config) + + opt = Optimizer(std_dict) + + project_name = f"{self.config.model.name}_{self.config.dataset.name}" + + for exp in opt.get_experiments(project_name=project_name): + comet_logger = CometLogger() + + # allow pytorch-lightning to use the experiment from optimizer + comet_logger._experiment = exp # pylint: disable=W0212 + run_params = exp.params + for param in run_params.keys(): + set_in_nested_config(self.config, param.split("."), run_params[param]) + config = update_input_size_config(self.config) + + model = get_model(config) + datamodule = get_datamodule(config) + callbacks = get_sweep_callbacks(config) + + # Disable saving checkpoints as all checkpoints from the sweep will get uploaded + config.trainer.checkpoint_callback = False + + trainer = pl.Trainer(**config.trainer, logger=comet_logger, callbacks=callbacks) + trainer.fit(model, datamodule=datamodule) + + def get_args(): """Gets parameters from commandline.""" parser = ArgumentParser() @@ -89,5 +135,10 @@ def get_args(): if model_config.project.seed != 0: seed_everything(model_config.project.seed) - sweep = WandbSweep(model_config, hpo_config) + # check hpo config structure to see whether it adheres to comet or wandb format + sweep: Union[CometSweep, WandbSweep] + if "spec" in hpo_config.keys(): + sweep = CometSweep(model_config, hpo_config) + else: + sweep = WandbSweep(model_config, hpo_config) sweep.run() From 6c59e1bdbc20a843ef5fb74b0b93ecf6e4ac32c2 Mon Sep 17 00:00:00 2001 From: Dick Ameln Date: Mon, 26 Sep 2022 14:42:07 +0200 Subject: [PATCH 16/38] =?UTF-8?q?=F0=9F=9B=A0=20Fix=20PatchCore=20image-le?= =?UTF-8?q?vel=20score=20computation=20(#580)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix patchcore image-level score computation * docstring and comment * remove default value for n_neighbors * torch.Tensor -> Tensor --- anomalib/models/patchcore/anomaly_map.py | 57 ++++------------- anomalib/models/patchcore/lightning_model.py | 2 +- anomalib/models/patchcore/torch_model.py | 64 +++++++++++++++----- 3 files changed, 62 insertions(+), 61 deletions(-) diff --git a/anomalib/models/patchcore/anomaly_map.py b/anomalib/models/patchcore/anomaly_map.py index 086f44ff38..7b29f83ef3 100644 --- a/anomalib/models/patchcore/anomaly_map.py +++ b/anomalib/models/patchcore/anomaly_map.py @@ -8,7 +8,7 @@ import torch import torch.nn.functional as F from omegaconf import ListConfig -from torch import nn +from torch import Tensor, nn from anomalib.models.components import GaussianBlur2d @@ -26,67 +26,32 @@ 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: torch.Tensor, feature_map_shape: torch.Size) -> torch.Tensor: + def compute_anomaly_map(self, patch_scores: Tensor) -> torch.Tensor: """Pixel Level Anomaly Heatmap. Args: - patch_scores (torch.Tensor): Patch-level anomaly scores - feature_map_shape (torch.Size): 2-D feature map shape (width, height) + patch_scores (Tensor): Patch-level anomaly scores Returns: torch.Tensor: Map of the pixel-level anomaly scores """ - width, height = feature_map_shape - batch_size = len(patch_scores) // (width * height) - - anomaly_map = patch_scores[:, 0].reshape((batch_size, 1, width, height)) - anomaly_map = F.interpolate(anomaly_map, size=(self.input_size[0], self.input_size[1])) - + anomaly_map = F.interpolate(patch_scores, size=(self.input_size[0], self.input_size[1])) anomaly_map = self.blur(anomaly_map) return anomaly_map - @staticmethod - def compute_anomaly_score(patch_scores: torch.Tensor) -> torch.Tensor: - """Compute Image-Level Anomaly Score. - - Args: - patch_scores (torch.Tensor): Patch-level anomaly scores - Returns: - torch.Tensor: Image-level anomaly scores - """ - max_scores = torch.argmax(patch_scores[:, 0]) - confidence = torch.index_select(patch_scores, 0, max_scores) - weights = 1 - torch.max(F.softmax(confidence, dim=-1)) - score = weights * torch.max(patch_scores[:, 0]) - return score - - def forward(self, **kwargs: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]: + def forward(self, patch_scores: Tensor) -> Tensor: """Returns anomaly_map and anomaly_score. - Expects `patch_scores` keyword to be passed explicitly - Expects `feature_map_shape` keyword to be passed explicitly + Args: + patch_scores (Tensor): Patch-level anomaly scores Example >>> anomaly_map_generator = AnomalyMapGenerator(input_size=input_size) - >>> map, score = anomaly_map_generator(patch_scores=numpy_array, feature_map_shape=feature_map_shape) - - Raises: - ValueError: If `patch_scores` key is not found + >>> map = anomaly_map_generator(patch_scores=patch_scores) Returns: - Tuple[torch.Tensor, torch.Tensor]: anomaly_map, anomaly_score + Tensor: anomaly_map """ - - if "patch_scores" not in kwargs: - raise ValueError(f"Expected key `patch_scores`. Found {kwargs.keys()}") - - if "feature_map_shape" not in kwargs: - raise ValueError(f"Expected key `feature_map_shape`. Found {kwargs.keys()}") - - patch_scores = kwargs["patch_scores"] - feature_map_shape = kwargs["feature_map_shape"] - - anomaly_map = self.compute_anomaly_map(patch_scores, feature_map_shape) - anomaly_score = self.compute_anomaly_score(patch_scores) - return anomaly_map, anomaly_score + anomaly_map = self.compute_anomaly_map(patch_scores) + return anomaly_map diff --git a/anomalib/models/patchcore/lightning_model.py b/anomalib/models/patchcore/lightning_model.py index 631ea10146..5a5583d42b 100644 --- a/anomalib/models/patchcore/lightning_model.py +++ b/anomalib/models/patchcore/lightning_model.py @@ -107,7 +107,7 @@ def validation_step(self, batch, _): # pylint: disable=arguments-differ anomaly_maps, anomaly_score = self.model(batch["image"]) batch["anomaly_maps"] = anomaly_maps - batch["pred_scores"] = anomaly_score.unsqueeze(0) + batch["pred_scores"] = anomaly_score return batch diff --git a/anomalib/models/patchcore/torch_model.py b/anomalib/models/patchcore/torch_model.py index 7c931dff47..21ca16b944 100644 --- a/anomalib/models/patchcore/torch_model.py +++ b/anomalib/models/patchcore/torch_model.py @@ -41,10 +41,10 @@ def __init__( self.feature_pooler = torch.nn.AvgPool2d(3, 1, 1) self.anomaly_map_generator = AnomalyMapGenerator(input_size=input_size) - self.register_buffer("memory_bank", torch.Tensor()) - self.memory_bank: torch.Tensor + self.register_buffer("memory_bank", Tensor()) + self.memory_bank: Tensor - def forward(self, input_tensor: Tensor) -> Union[torch.Tensor, Tuple[torch.Tensor, torch.Tensor]]: + def forward(self, input_tensor: Tensor) -> Union[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[torch.Tensor, Tuple[torch.Tenso input_tensor (Tensor): Input tensor Returns: - Union[torch.Tensor, Tuple[torch.Tensor, torch.Tensor]]: Embedding for training, + Union[Tensor, Tuple[Tensor, Tensor]]: Embedding for training, anomaly map and anomaly score for testing. """ if self.tiler: @@ -71,21 +71,29 @@ def forward(self, input_tensor: Tensor) -> Union[torch.Tensor, Tuple[torch.Tenso if self.tiler: embedding = self.tiler.untile(embedding) - feature_map_shape = embedding.shape[-2:] + batch_size, _, width, height = embedding.shape embedding = self.reshape_embedding(embedding) if self.training: output = embedding else: - patch_scores = self.nearest_neighbors(embedding=embedding, n_neighbors=self.num_neighbors) - anomaly_map, anomaly_score = self.anomaly_map_generator( - patch_scores=patch_scores, feature_map_shape=feature_map_shape - ) + # apply nearest neighbor search + patch_scores, locations = self.nearest_neighbors(embedding=embedding, n_neighbors=1) + # reshape to batch dimension + patch_scores = patch_scores.reshape((batch_size, -1)) + locations = locations.reshape((batch_size, -1)) + # compute anomaly score + anomaly_score = self.compute_anomaly_score(patch_scores, locations, embedding) + # reshape to w, h + patch_scores = patch_scores.reshape((batch_size, 1, width, height)) + # get anomaly map + anomaly_map = self.anomaly_map_generator(patch_scores) + output = (anomaly_map, anomaly_score) return output - def generate_embedding(self, features: Dict[str, Tensor]) -> torch.Tensor: + def generate_embedding(self, features: Dict[str, Tensor]) -> Tensor: """Generate embedding from hierarchical feature map. Args: @@ -121,7 +129,7 @@ def reshape_embedding(embedding: Tensor) -> Tensor: embedding = embedding.permute(0, 2, 3, 1).reshape(-1, embedding_size) return embedding - def subsample_embedding(self, embedding: torch.Tensor, sampling_ratio: float) -> None: + def subsample_embedding(self, embedding: Tensor, sampling_ratio: float) -> None: """Subsample embedding based on coreset sampling and store to memory. Args: @@ -134,7 +142,7 @@ def subsample_embedding(self, embedding: torch.Tensor, sampling_ratio: float) -> coreset = sampler.sample_coreset() self.memory_bank = coreset - def nearest_neighbors(self, embedding: Tensor, n_neighbors: int = 9) -> Tensor: + def nearest_neighbors(self, embedding: Tensor, n_neighbors: int) -> Tuple[Tensor, Tensor]: """Nearest Neighbours using brute force method and euclidean norm. Args: @@ -143,7 +151,35 @@ def nearest_neighbors(self, embedding: Tensor, n_neighbors: int = 9) -> Tensor: Returns: Tensor: Patch scores. + Tensor: Locations of the nearest neighbor(s). """ distances = torch.cdist(embedding, self.memory_bank, p=2.0) # euclidean norm - patch_scores, _ = distances.topk(k=n_neighbors, largest=False, dim=1) - return patch_scores + patch_scores, locations = distances.topk(k=n_neighbors, largest=False, dim=1) + return patch_scores, locations + + def compute_anomaly_score(self, patch_scores: Tensor, locations: Tensor, embedding: Tensor) -> Tensor: + """Compute Image-Level Anomaly Score. + + Args: + patch_scores (Tensor): Patch-level anomaly scores + locations: Memory bank locations of the nearest neighbor for each patch location + embedding: The feature embeddings that generated the patch scores + Returns: + Tensor: Image-level anomaly scores + """ + + # 1. Find the patch with the largest distance to it's nearest neighbor in each image + max_patches = torch.argmax(patch_scores, dim=1) # (m^test,* in the paper) + # 2. Find the distance of the patch to it's nearest neighbor, and the location of the nn in the membank + score = patch_scores[torch.arange(len(patch_scores)), max_patches] # s in the paper + nn_index = locations[torch.arange(len(patch_scores)), max_patches] # m^* in the paper + # 3. Find the support samples of the nearest neighbor in the membank + nn_sample = self.memory_bank[nn_index, :] + _, support_samples = self.nearest_neighbors(nn_sample, n_neighbors=self.num_neighbors) # N_b(m^*) in the paper + # 4. Find the distance of the patch features to each of the support samples + distances = torch.cdist(embedding[max_patches].unsqueeze(1), self.memory_bank[support_samples], p=2.0) + # 5. Apply softmax to find the weights + weights = (1 - F.softmax(distances.squeeze()))[..., 0] + # 6. Apply the weight factor to the score + score = weights * score # S^* in the paper + return score From 7d20aa28433a1db580d80d098bf7777087d48d5c Mon Sep 17 00:00:00 2001 From: Samet Akcay Date: Tue, 27 Sep 2022 12:43:35 +0100 Subject: [PATCH 17/38] =?UTF-8?q?=F0=9F=9B=A0=20Fix=20anomaly=20map=20comp?= =?UTF-8?q?utation=20in=20CFlow=20when=20batch=20size=20is=201.=20(#589)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix CFlow anomaly map generator * Refactor anomaly_map --- anomalib/models/cflow/anomaly_map.py | 31 +++++++++++++--------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/anomalib/models/cflow/anomaly_map.py b/anomalib/models/cflow/anomaly_map.py index e98dba8884..744cf0b3c6 100644 --- a/anomalib/models/cflow/anomaly_map.py +++ b/anomalib/models/cflow/anomaly_map.py @@ -24,9 +24,7 @@ def __init__( self.image_size = image_size if isinstance(image_size, tuple) else tuple(image_size) self.pool_layers: List[str] = pool_layers - def compute_anomaly_map( - self, distribution: Union[List[Tensor], List[List]], 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: @@ -38,26 +36,25 @@ def compute_anomaly_map( Final Anomaly Map """ - - test_map: List[Tensor] = [] + layer_maps: List[Tensor] = [] for layer_idx in range(len(self.pool_layers)): - test_norm = torch.tensor(distribution[layer_idx], dtype=torch.double) # pylint: disable=not-callable - test_norm -= torch.max(test_norm) # normalize likelihoods to (-Inf:0] by subtracting a constant - test_prob = torch.exp(test_norm) # convert to probs in range [0:1] - test_mask = test_prob.reshape(-1, height[layer_idx], width[layer_idx]) + layer_distribution = distribution[layer_idx].clone().detach() + # Normalize the likelihoods to (-Inf:0] and convert to probs in range [0:1] + layer_probabilities = torch.exp(layer_distribution - layer_distribution.max()) + layer_map = layer_probabilities.reshape(-1, height[layer_idx], width[layer_idx]) # upsample - test_map.append( + layer_maps.append( F.interpolate( - test_mask.unsqueeze(1), size=self.image_size, mode="bilinear", align_corners=True - ).squeeze() + layer_map.unsqueeze(1), size=self.image_size, mode="bilinear", align_corners=True + ).squeeze(1) ) # score aggregation - score_map = torch.zeros_like(test_map[0]) + score_map = torch.zeros_like(layer_maps[0]) for layer_idx in range(len(self.pool_layers)): - score_map += test_map[layer_idx] - score_mask = score_map - # invert probs to anomaly scores - anomaly_map = score_mask.max() - score_mask + score_map += layer_maps[layer_idx] + + # Invert probs to anomaly scores + anomaly_map = score_map.max() - score_map return anomaly_map From 598673aa434d094a0c153195beaf8d47ce1568e7 Mon Sep 17 00:00:00 2001 From: Samet Akcay Date: Tue, 27 Sep 2022 13:30:45 +0100 Subject: [PATCH 18/38] =?UTF-8?q?=F0=9F=93=9D=20Documentation=20refactor?= =?UTF-8?q?=20(#576)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add benchmark to tutorial * Move export to tutorials * Move hpo to tutorials * Move inference to tutorials * Move logging to tutorials * Create installation in tutorials * Create training to tutorials * Create tutorials index * Update conf.py file * Add anomalib logos to logos directory * Add data docs * Add algos * Add model docs * Add reference api * Remove blank line in metrics * Add reference guide * Add how to guides * Add developer guide * Add blog to how-to-guide * Remove guides directory * Add train custom data to how-to-guides * Fix typos * Add notebooks to how-to-guides * Add anomalib favicon * Add missing algo descriptions * Rename Reference to Reference Guide * Add how to add a new model * fix typos * Merge PR 544 * Minor refactor (#587) * 🛠 Fix PatchCore image-level score computation (#580) * fix patchcore image-level score computation * docstring and comment * remove default value for n_neighbors * torch.Tensor -> Tensor * Minor refactor Co-authored-by: Dick Ameln Co-authored-by: Ashwin Vaidya * Address Dicks comments Co-authored-by: Ashwin Vaidya Co-authored-by: Dick Ameln Co-authored-by: Ashwin Vaidya --- README.md | 44 ++- anomalib/data/btech.py | 2 +- anomalib/data/folder.py | 2 + anomalib/data/mvtec.py | 4 +- docs/blog/001-train-custom-dataset/README.md | 272 ---------------- docs/source/conf.py | 21 +- docs/source/data/hazelnut_toy.rst | 2 + .../developing_on_docker.rst | 0 .../developer_guide/getting_started.rst | 33 ++ docs/source/developer_guide/index.rst | 11 + .../pre_commit_hooks.rst} | 30 +- docs/source/developer_guide/using_tox.rst | 89 +++++ docs/source/guides/getting_started.rst | 97 ------ .../guides/structure_of_documentation.md | 50 --- docs/source/guides/using_tox.md | 81 ----- .../how_to_guides/adding_a_new_model.rst | 198 ++++++++++++ docs/source/how_to_guides/index.rst | 14 + docs/source/how_to_guides/notebooks | 1 + .../how_to_guides/train_custom_data.rst | 272 ++++++++++++++++ .../train_custom_data}/anomalib-wide-blue.png | Bin .../train_custom_data}/hazelnut_results.gif | Bin .../how_to_guides/train_custom_data}/hpo.gif | Bin .../train_custom_data}/logging.gif | Bin docs/source/images/logos/anomalib-favicon.png | Bin 0 -> 2334 bytes docs/source/images/logos/anomalib-icon.png | Bin 0 -> 4515 bytes docs/source/images/logos/anomalib-text.png | Bin 0 -> 38496 bytes docs/source/index.rst | 125 +++---- docs/source/models.rst | 304 ------------------ .../reference_guide/algorithms/cflow.rst | 39 +++ .../reference_guide/algorithms/dfkde.rst | 37 +++ .../source/reference_guide/algorithms/dfm.rst | 39 +++ .../reference_guide/algorithms/draem.rst | 42 +++ .../reference_guide/algorithms/fastflow.rst | 40 +++ .../reference_guide/algorithms/ganomaly.rst | 28 ++ .../reference_guide/algorithms/index.rst | 62 ++++ .../reference_guide/algorithms/padim.rst | 41 +++ .../reference_guide/algorithms/patchcore.rst | 42 +++ .../algorithms/reverse_distillation.rst | 42 +++ .../reference_guide/algorithms/stfpm.rst | 42 +++ docs/source/reference_guide/api/callbacks.rst | 7 + docs/source/reference_guide/api/cli.rst | 7 + docs/source/reference_guide/api/config.rst | 7 + .../source/reference_guide/api/data/btech.rst | 7 + .../reference_guide/api/data/folder.rst | 7 + .../source/reference_guide/api/data/index.rst | 11 + .../source/reference_guide/api/data/mvtec.rst | 7 + .../source/reference_guide/api/data/utils.rst | 7 + docs/source/reference_guide/api/index.rst | 16 + docs/source/reference_guide/api/loggers.rst | 7 + docs/source/reference_guide/api/metrics.rst | 7 + .../source/reference_guide/api/model/base.rst | 7 + .../api/model/dimensionality_reduction.rst | 7 + .../api/model/feature_extractors.rst | 7 + .../reference_guide/api/model/filters.rst | 7 + .../reference_guide/api/model/index.rst | 14 + .../reference_guide/api/model/layers.rst | 7 + .../reference_guide/api/model/sampling.rst | 7 + .../reference_guide/api/model/stats.rst | 7 + .../reference_guide/api/post_processing.rst | 7 + docs/source/reference_guide/api/sweep.rst | 7 + docs/source/reference_guide/index.rst | 9 + docs/source/research/papers.rst | 55 ---- .../{guides => tutorials}/benchmarking.rst | 0 docs/source/{guides => tutorials}/export.rst | 0 .../hyperparameter_optimization.rst | 8 +- docs/source/tutorials/index.rst | 14 + .../{guides => tutorials}/inference.rst | 6 +- docs/source/tutorials/installation.rst | 26 ++ docs/source/{guides => tutorials}/logging.rst | 0 docs/source/tutorials/training.rst | 32 ++ notebooks/100_datamodules/101_btech.ipynb | 16 +- notebooks/100_datamodules/102_mvtec.ipynb | 16 +- notebooks/100_datamodules/103_folder.ipynb | 16 +- notebooks/200_models/201_fastflow.ipynb | 2 +- requirements/docs.txt | 1 + .../configs/{comet_sweep.yaml => comet.yaml} | 0 .../configs/{wandb_sweep.yaml => wandb.yaml} | 0 tools/hpo/sweep.py | 2 +- 78 files changed, 1507 insertions(+), 969 deletions(-) delete mode 100644 docs/blog/001-train-custom-dataset/README.md rename docs/source/{guides => developer_guide}/developing_on_docker.rst (100%) create mode 100644 docs/source/developer_guide/getting_started.rst create mode 100644 docs/source/developer_guide/index.rst rename docs/source/{guides/using_pre_commit.md => developer_guide/pre_commit_hooks.rst} (51%) create mode 100644 docs/source/developer_guide/using_tox.rst delete mode 100644 docs/source/guides/getting_started.rst delete mode 100644 docs/source/guides/structure_of_documentation.md delete mode 100644 docs/source/guides/using_tox.md create mode 100644 docs/source/how_to_guides/adding_a_new_model.rst create mode 100644 docs/source/how_to_guides/index.rst create mode 120000 docs/source/how_to_guides/notebooks create mode 100644 docs/source/how_to_guides/train_custom_data.rst rename docs/{blog/001-train-custom-dataset/images => source/images/how_to_guides/train_custom_data}/anomalib-wide-blue.png (100%) rename docs/{blog/001-train-custom-dataset/images => source/images/how_to_guides/train_custom_data}/hazelnut_results.gif (100%) rename docs/{blog/001-train-custom-dataset/images => source/images/how_to_guides/train_custom_data}/hpo.gif (100%) rename docs/{blog/001-train-custom-dataset/images => source/images/how_to_guides/train_custom_data}/logging.gif (100%) create mode 100644 docs/source/images/logos/anomalib-favicon.png create mode 100644 docs/source/images/logos/anomalib-icon.png create mode 100644 docs/source/images/logos/anomalib-text.png delete mode 100644 docs/source/models.rst create mode 100644 docs/source/reference_guide/algorithms/cflow.rst create mode 100644 docs/source/reference_guide/algorithms/dfkde.rst create mode 100644 docs/source/reference_guide/algorithms/dfm.rst create mode 100644 docs/source/reference_guide/algorithms/draem.rst create mode 100644 docs/source/reference_guide/algorithms/fastflow.rst create mode 100644 docs/source/reference_guide/algorithms/ganomaly.rst create mode 100644 docs/source/reference_guide/algorithms/index.rst create mode 100644 docs/source/reference_guide/algorithms/padim.rst create mode 100644 docs/source/reference_guide/algorithms/patchcore.rst create mode 100644 docs/source/reference_guide/algorithms/reverse_distillation.rst create mode 100644 docs/source/reference_guide/algorithms/stfpm.rst create mode 100644 docs/source/reference_guide/api/callbacks.rst create mode 100644 docs/source/reference_guide/api/cli.rst create mode 100644 docs/source/reference_guide/api/config.rst create mode 100644 docs/source/reference_guide/api/data/btech.rst create mode 100644 docs/source/reference_guide/api/data/folder.rst create mode 100644 docs/source/reference_guide/api/data/index.rst create mode 100644 docs/source/reference_guide/api/data/mvtec.rst create mode 100644 docs/source/reference_guide/api/data/utils.rst create mode 100644 docs/source/reference_guide/api/index.rst create mode 100644 docs/source/reference_guide/api/loggers.rst create mode 100644 docs/source/reference_guide/api/metrics.rst create mode 100644 docs/source/reference_guide/api/model/base.rst create mode 100644 docs/source/reference_guide/api/model/dimensionality_reduction.rst create mode 100644 docs/source/reference_guide/api/model/feature_extractors.rst create mode 100644 docs/source/reference_guide/api/model/filters.rst create mode 100644 docs/source/reference_guide/api/model/index.rst create mode 100644 docs/source/reference_guide/api/model/layers.rst create mode 100644 docs/source/reference_guide/api/model/sampling.rst create mode 100644 docs/source/reference_guide/api/model/stats.rst create mode 100644 docs/source/reference_guide/api/post_processing.rst create mode 100644 docs/source/reference_guide/api/sweep.rst create mode 100644 docs/source/reference_guide/index.rst delete mode 100644 docs/source/research/papers.rst rename docs/source/{guides => tutorials}/benchmarking.rst (100%) rename docs/source/{guides => tutorials}/export.rst (100%) rename docs/source/{guides => tutorials}/hyperparameter_optimization.rst (90%) create mode 100644 docs/source/tutorials/index.rst rename docs/source/{guides => tutorials}/inference.rst (99%) create mode 100644 docs/source/tutorials/installation.rst rename docs/source/{guides => tutorials}/logging.rst (100%) create mode 100644 docs/source/tutorials/training.rst rename tools/hpo/configs/{comet_sweep.yaml => comet.yaml} (100%) rename tools/hpo/configs/{wandb_sweep.yaml => wandb.yaml} (100%) diff --git a/README.md b/README.md index 56f9683f33..a2433bc9f2 100644 --- a/README.md +++ b/README.md @@ -117,15 +117,38 @@ where the currently available models are: - [STFPM](anomalib/models/stfpm) - [GANomaly](anomalib/models/ganomaly) -## Exporting Model to ONNX or OpenVINO IR +## Feature extraction & (pre-trained) backbones -It is possible to export your model to ONNX or OpenVINO IR +The pre-trained backbones come from [PyTorch Image Models (timm)](https://github.com/rwightman/pytorch-image-models), which are wrapped by `FeatureExtractor`. -If you want to export your PyTorch model to an OpenVINO model, ensure that `export_mode` is set to `"openvino"` in the respective model `config.yaml`. +For more information, please check our documentation or the [section about feature extraction in "Getting Started with PyTorch Image Models (timm): A Practitioner’s Guide"](https://towardsdatascience.com/getting-started-with-pytorch-image-models-timm-a-practitioners-guide-4e77b4bf9055#b83b:~:text=ready%20to%20train!-,Feature%20Extraction,-timm%20models%20also>). + +Tips: + +- Papers With Code has an interface to easily browse models available in timm: [https://paperswithcode.com/lib/timm](https://paperswithcode.com/lib/timm) + +- You can also find them with the function `timm.list_models("resnet*", pretrained=True)` + +The backbone can be set in the config file, two examples below. + +Anomalib < v.0.4.0 ```yaml -optimization: - export_mode: "openvino" # options: openvino, onnx +model: + name: cflow + backbone: wide_resnet50_2 + pre_trained: true +Anomalib > v.0.4.0 Beta - Subject to Change +``` + +Anomalib >= v.0.4.0 + +```yaml +model: + class_path: anomalib.models.Cflow + init_args: + backbone: wide_resnet50_2 + pre_trained: true ``` ## Custom Dataset @@ -222,6 +245,17 @@ python tools/inference/gradio_inference.py \ --weights ./results/padim/mvtec/bottle/weights/model.ckpt ``` +## Exporting Model to ONNX or OpenVINO IR + +It is possible to export your model to ONNX or OpenVINO IR + +If you want to export your PyTorch model to an OpenVINO model, ensure that `export_mode` is set to `"openvino"` in the respective model `config.yaml`. + +```yaml +optimization: + export_mode: "openvino" # options: openvino, onnx +``` + # Hyperparameter Optimization To run hyperparameter optimization, use the following command: diff --git a/anomalib/data/btech.py b/anomalib/data/btech.py index 8b6bac792b..c5246c0097 100644 --- a/anomalib/data/btech.py +++ b/anomalib/data/btech.py @@ -290,7 +290,7 @@ def __init__( seed: seed used for the random subset splitting create_validation_set: Create a validation subset in addition to the train and test subsets - Examples + Examples: >>> from anomalib.data import BTech >>> datamodule = BTech( ... root="./datasets/BTech", diff --git a/anomalib/data/folder.py b/anomalib/data/folder.py index 0f3b47adbd..39a1a150a4 100644 --- a/anomalib/data/folder.py +++ b/anomalib/data/folder.py @@ -350,6 +350,7 @@ def __init__( Examples: Assume that we use Folder Dataset for the MVTec/bottle/broken_large category. We would do: + >>> from anomalib.data import Folder >>> datamodule = Folder( ... root="./datasets/MVTec/bottle/test", @@ -370,6 +371,7 @@ def __init__( The dataset expects that mask annotation filenames must be same as the original filename. To this end, we modified mask filenames in MVTec AD bottle category. Now we could try folder data module using the mvtec bottle broken large category + >>> datamodule = Folder( ... root="./datasets/bottle/test", ... normal="good", diff --git a/anomalib/data/mvtec.py b/anomalib/data/mvtec.py index 9b45699d64..c98350eeaa 100644 --- a/anomalib/data/mvtec.py +++ b/anomalib/data/mvtec.py @@ -89,7 +89,7 @@ def make_mvtec_dataset( MVTec AD dataset does not contain a validation set. Those wanting to create a validation set could set this flag to ``True``. - Example: + Examples: The following example shows how to get training samples from MVTec AD bottle category: >>> root = Path('./MVTec') @@ -313,7 +313,7 @@ def __init__( seed: seed used for the random subset splitting create_validation_set: Create a validation subset in addition to the train and test subsets - Examples + Examples: >>> from anomalib.data import MVTec >>> datamodule = MVTec( ... root="./datasets/MVTec", diff --git a/docs/blog/001-train-custom-dataset/README.md b/docs/blog/001-train-custom-dataset/README.md deleted file mode 100644 index ac47742adb..0000000000 --- a/docs/blog/001-train-custom-dataset/README.md +++ /dev/null @@ -1,272 +0,0 @@ -# How to Train Your Custom Dataset with Anomalib - -

- -## Introducing Anomalib - -A Deep Learning Library called Anomalib was released by a group of Intel AI researchers in early 2022. The library includes cutting-edge algorithms for detecting anomalies in image datasets. Anomalib is a comprehensive end-to-end solution that includes many features for achieving the highest accuracy model as well as inference deployment code with Intel's OpenVino Toolkit. - -## Why a dedicated library for Anomaly Detection - -Anomaly detection is the process of identifying anomalous items in a stream of input data. An example of a real-world anomaly detection problem is industrial defect detection, where the aim is to identify anomalous products in the output of a production line. In this type of application, anomalous items occur much less frequently than normal items, which makes it challenging to collect sufficient representative samples of the anomalous class. On top of that, the anomalous class is not well defined, and can contain a wide range of different defect types. These characteristics make it difficult to solve anomaly detection problems with classical, supervised methods. Instead, anomaly detection algorithms usually rely on unsupervised techniques to learn an implicit representation of normality, the normality model. During inference, new samples are compared against this normality model to determine if they belong to the normal or anomalous category. - -Anomalib aims to collect the most recent deep-learning based anomaly detection algorithms with the purpose of providing a tool that makes it easy to benchmark different anomaly detection algorithms on public and custom datasets. Anomalib is continuously updated with the latest State-of-the-Art algorithms, and contains several tools and interfaces that can be used to run experiments and create and test new algorithms. - -## How to train a Custom dataset with Anomalib - -Anomalib supports a number of datasets in various formats, including the state-of-the-art anomaly detection benchmarks such as MVTec AD and BeanTech. For those who would like to use the library on their custom datasets, anomalib also provides a `Folder` datamodule that can load datasets from a folder on a file system. The scope of this post will be to train anomalib models on custom datasets using the `Folder` datamodule. - -### Step 1: Install Anomalib - -#### Option - 1 : PyPI - -Anomalib can be installed from PyPI via the following: - -```bash -pip install anomalib -``` - -#### Option - 2: Editable Install - -Alternatively, it is also possible to do editable install: - -```bash -git clone https://github.com/openvinotoolkit/anomalib.git -cd anomalib -pip install -e . -``` - -### Step 2: Collect Your Custom Dataset - -Anomalib supports multiple image extensions such as `".jpg", ".jpeg", ".png", ".ppm", ".bmp", ".pgm", ".tif", ".tiff", and ".webp"`. A dataset can be collected from images that have any of these extensions. - -### Step 3: Format your dataset - -Depending on the use-case and collection, custom datasets can have different formats, some of which are listed below: - -- A dataset with good and bad images. -- A dataset with good and bad images as well as mask ground-truths for pixel-wise evaluation. -- A dataset with good and bad images that is already split into training and testing sets. - -Each of these use-cases is addressed by anomalib's `Folder` datamodule. Let's focus on the first use-case as an example of end-to-end model training and inference. In this post, we will use a toy dataset which you can download from [here](https://openvinotoolkit.github.io/anomalib/_downloads/3f2af1d7748194b18c2177a34c03a2c4/hazelnut_toy.zip). The dataset consists of several folders, each containing a set of images. The _colour_ and the _crack_ folders represent two kinds of defects. We can ignore the _masks_ folder for now. - -Load your data to the following directory structure. Anomalib will use all images in the _colour_ folder as part of the validation dataset and then randomly split the good images for training and validation. - -```bash -Hazelnut_toy -├── colour -└── good -``` - -### Step 4: Modify Config File - -A YAML configuration file is necessary to run training for Anomalib. The training configuration parameters are categorized into 5 sections: `dataset`, `model`, `project`, `logging`, `trainer`. - -To get Anomalib functionally working with a custom dataset, one only needs to change the `dataset` section of the configuration file. - -Below is an example of what the dataset parameters would look like for our `hazelnut_toy` folder specified in [Step 2](#step-2-collect-your-data). - -Let's choose [PaDim algorithm](https://arxiv.org/pdf/2011.08785.pdf), copy the sample config and modify the dataset section. - -```bash -cp anomalib/models/padim/config.yaml custom_padim.yaml -``` - -```yaml -# Replace the dataset configs with the following. -dataset: - name: hazelnut - format: folder - path: ./datasets/Hazelnut_toy - normal_dir: good # name of the folder containing normal images. - abnormal_dir: colour # name of the folder containing abnormal images. - task: classification # classification or segmentation - mask: null #optional - normal_test_dir: null # optional - extensions: null - split_ratio: 0.2 # normal images ratio to create a test split - seed: 0 - image_size: 256 - train_batch_size: 32 - test_batch_size: 32 - num_workers: 8 - transform_config: - train: null - val: null - create_validation_set: true - tiling: - apply: false - tile_size: null - stride: null - remove_border_count: 0 - use_random_tiling: False - random_tile_count: 16 - -model: - name: padim - backbone: resnet18 - layer: - - layer1 - ... -``` - -### Step 5: Run training - -As per the config file, move `Hazelnut_toy` to the datasets section in the main root directory of anomalib, and then run - -```bash -python tools/train.py --config custom_padim.yaml -``` - -### Step 6: Interpret Results - -Anomalib will print out results of the trained model on the validation dataset. The printed metrics are dependent on the task mode chosen. The classification example provided in this tutorial prints out two scores: F1 and AUROC. The F1 score is a metric which values both the precision and recall, more information on its calculation can be found in this [blog](https://towardsdatascience.com/understanding-accuracy-recall-precision-f1-scores-and-confusion-matrices-561e0f5e328c). - -**Additional Info** - -Not only does Anomalib classify whether a part is defected or not, it can also be used to segment the defects as well. To do this, simply add a folder called _mask_ at the same directory level as the _good_ and _colour_ folders. This folder should contain binary images for the defects in the _colour_ folder. Here, the white pixels represent the location of the defect. Populate the mask field in the config file with `mask` and change the task to segmentation to see Anomalib segment defects. - -```bash -Hazelnut_toy -├── colour -│ ├── 00.jpg -│ ├── 01.jpg -│ ... -├── good -│ ├── 00.jpg -│ ├── 01.jpg -└── mask - ├── 00.jpg - ├── 01.jpg - ... -``` - -Here is an example of the generated results for a toy dataset containing Hazelnut with colour defects. - -
- -
- -## Logging and Experiment Management - -While it is delightful to know how good your model performed on your preferred metric, it is even more exciting to see the predicted outputs. Anomalib provides a couple of ways to log and track experiments. These can be used individually or in a combination. As of the current release, you can save images to a local folder, or upload to comet, weights and biases, or TensorBoard. - -To select where you would like to save the images, change the `log_images` parameter in the `Visualization` section in the config file to true. - -For example, setting the following `log_images: True` will result in saving the images in the results folder as shown in the tree structure below: - -```bash -results -└── padim - └── Hazelnut_toy - ├── images - │ ├── colour - │ │ ├── 00.jpg - │ │ ├── 01.jpg - │ │ └── ... - │ └── good - │ ├── 00.jpg - │ ├── 01.jpg - │ └── ... - └── weights - └── model.ckpt -``` - -### Logging to Tensorboard and/or W&B - -To use TensorBoard and/or W&B logger and/or Comet logger, ensure that the logger parameter is set to `comet`, `tensorboard`, `wandb` or `[tensorboard, wandb]` in the `logging` section of the config file. - -An example configuration for saving to TensorBoard is shown in the figure below. Similarly after setting logger to `wandb` or 'comet' you will see the images on your wandb and/or comet project dashboard. - -```yaml -visualization: - show_images: False # show images on the screen - save_images: False # save images to the file system - log_images: True # log images to the available loggers (if any) - image_save_path: null # path to which images will be saved - mode: full # options: ["full", "simple"] - -logging: - logger: [comet, tensorboard, wandb] #Choose any combination of these 3 - log_graph: false -``` - -
- -
- -### Hyper-Parameter Optimization - -It is very rare to find a model which works out of the box for a particular dataset. However, fortunately, we support tools which work out of the box to help tune the models in Anomalib to your particular dataset. As of the publication of this blog post, Anomalib supports [weights and biases](https://wandb.ai/) for hyperparameter optimization. To get started have a look at `sweep.yaml` located at `tools/hpo`. It provides a sample of how one can define a hyperparameter sweep. - -```yaml -observation_budget: 10 -method: bayes -metric: - name: pixel_AUROC - goal: maximize -parameters: - dataset: - category: hazelnut - image_size: - values: [128, 256] - model: - backbone: - values: [resnet18, wide_resnet50_2] -``` - -The observation_budget informs wandb about the number of experiments to run. The method section defines the kind of method to use for HPO search. For other available methods, have a look at [Weights and Biases](https://docs.wandb.ai/guides/sweeps/quickstart) documentation. The parameters section contains dataset and model parameters. Any parameter defined here overrides the parameter in the original model configuration. - -To run a sweep, you can just call, - -```bash -python tools/hpo/wandb_sweep.py --model padim --config ./path_to_config.yaml --sweep_config tools/hpo/sweep.yaml" -``` - -In case `model_config` is not provided, the script looks at the default config location for that model. Note, you will need to have logged into a wandb account to use HPO search and view the results. - -A sample run is visible in the screenshot below. - -
- -
- -## Benchmarking - -To add to the suit of experiment tracking and optimization, anomalib also includes a benchmarking script for gathering results across different combinations of models, their parameters, and dataset categories. The model performance and throughputs are logged into a csv file that can also serve as a means to track model drift. Optionally, these same results can be logged to Weights and Biases and TensorBoard. A sample configuration file is shown in the screenshot below. - -```yaml -seed: 42 -compute_openvino: false -hardware: - - cpu - - gpu -writer: - - wandb - - tensorboard -grid_search: - dataset: - category: - - colour - - crack - image_size: [128, 256] - model_name: - - padim - - stfpm -``` - -This configuration computes the throughput and performance metrics for CPU and GPU for two categories of the toy dataset for Padim and STFPM models. The dataset can be configured in the respective model configuration files. By default, `compute_openvino` is set to False to support instances where OpenVINO requirements are not installed in the environment. Once installed, this flag can be set to True to get throughput on OpenVINO optimized models. The writer parameter is optional and can be set to `writer: []` in case the user only requires a csv file without logging to TensorBoard or Weights and Biases. It is also a good practice to set a value of seed to ensure reproducibility across runs and thus, is set to a non-zero value by default. - -Once a configuration is decided, benchmarking can easily be performed by calling - -```bash -python tools/benchmarking/benchmark.py --config tools/benchmarking/benchmark_params.yaml -``` - -A nice feature about the provided benchmarking script is that if the host system has multiple GPUs, the runs are parallelized over all the available GPUs for faster collection of result. - -**Call to Action** - -Intel researchers actively maintain the Anomalib repository. Their mission is to provide the AI community with best-in-class performance and accuracy while also providing a positive developer experience. Check out the repo and start using anomalib right away! diff --git a/docs/source/conf.py b/docs/source/conf.py index c99faa0e75..b57c7945d6 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -28,7 +28,6 @@ AUTHOR = "Anomalib Contributors" VERSION = anomalib.__version__ -html_title = " ".join((PROJECT, COPYRIGHT, "documentation")) # -- General configuration --------------------------------------------------- @@ -36,6 +35,7 @@ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ + "nbsphinx", "sphinx.ext.autodoc", "sphinx.ext.autosummary", "sphinx.ext.viewcode", @@ -43,13 +43,8 @@ "sphinxemoji.sphinxemoji", "sphinx.ext.autosectionlabel", "myst_parser", - "autoapi.extension", ] -autoapi_dirs = ["../../anomalib"] -autoapi_root = "api" -autoapi_type = "python" - autosummary_generate = True autodoc_member_order = "groupwise" autoclass_content = "both" @@ -73,13 +68,21 @@ # exclude_patterns = [] -# -- Options for HTML output ------------------------------------------------- - +# -- Options for HTML output ------------------------------------------------- # # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -# html_theme = "alabaster" +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_favicon = "images/logos/anomalib-favicon.png" +html_title = f"{PROJECT} v{VERSION}" html_theme = "furo" +html_static_path = ["_static"] +html_logo = "images/logos/anomalib-icon.png" +html_theme_options = { + "sidebar_hide_name": True, +} # Sphinx will add “permalinks” for each heading and description environment as paragraph signs that # become visible when the mouse hovers over them. diff --git a/docs/source/data/hazelnut_toy.rst b/docs/source/data/hazelnut_toy.rst index 139147db96..0013400f40 100644 --- a/docs/source/data/hazelnut_toy.rst +++ b/docs/source/data/hazelnut_toy.rst @@ -1,3 +1,5 @@ +.. _hazelnut-toy-dataset: + Hazelnut Toy ============ diff --git a/docs/source/guides/developing_on_docker.rst b/docs/source/developer_guide/developing_on_docker.rst similarity index 100% rename from docs/source/guides/developing_on_docker.rst rename to docs/source/developer_guide/developing_on_docker.rst diff --git a/docs/source/developer_guide/getting_started.rst b/docs/source/developer_guide/getting_started.rst new file mode 100644 index 0000000000..5826575940 --- /dev/null +++ b/docs/source/developer_guide/getting_started.rst @@ -0,0 +1,33 @@ +Getting Started with Development +================================ + +To setup the development environment, you will need to install development requirements. :code:`pip install -r requirements/dev.txt` + +To enforce consistency within the repo, we use several formatters, linters, and style- and type checkers: + +.. list-table:: + :widths: 1 1 1 + :header-rows: 1 + + * - Tool + - Function + - Documentation + * - Black + - Code formatting + - https://black.readthedocs.io/en/stable/ + * - isort + - Organize import statements + - https://pycqa.github.io/isort/ + * - Flake8 + - Code style + - https://flake8.pycqa.org/en/latest/ + * - Pylint + - Linting + - http://pylint.pycqa.org/en/latest/ + * - MyPy + - Type checking + - https://mypy.readthedocs.io/en/stable/ + +Instead of running each of these tools manually, we automatically run them before each commit and after each merge request. To achieve this we use pre-commit hooks and tox. Every developer is expected to use pre-commit hooks to make sure that their code remains free of typing and linting issues, and complies with the coding style requirements. When an MR is submitted, tox will be automatically invoked from the CI pipeline in Gitlab to check if the code quality is up to standard. Developers can also run tox locally before making an MR, though this is not strictly necessary since pre-commit hooks should be sufficient to prevent code quality issues. More detailed explanations of how to work with these tools is given in the respective guides: + +Pre-commit hooks: :ref:`Pre-commit hooks guide` diff --git a/docs/source/developer_guide/index.rst b/docs/source/developer_guide/index.rst new file mode 100644 index 0000000000..6be6eabe10 --- /dev/null +++ b/docs/source/developer_guide/index.rst @@ -0,0 +1,11 @@ +Developer Guide +=============== + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + getting_started + pre_commit_hooks + using_tox + developing_on_docker diff --git a/docs/source/guides/using_pre_commit.md b/docs/source/developer_guide/pre_commit_hooks.rst similarity index 51% rename from docs/source/guides/using_pre_commit.md rename to docs/source/developer_guide/pre_commit_hooks.rst index 0d1a58ba26..c932ce06c8 100644 --- a/docs/source/guides/using_pre_commit.md +++ b/docs/source/developer_guide/pre_commit_hooks.rst @@ -1,24 +1,38 @@ -(pre-commit_hooks)= +.. _pre-commit_hooks: -# Pre-commit Hooks +Pre-commit Hooks +================ First of all, you will need to install development requirements. This will also install the pre-commit pip package in your python environment -`pip install -r requirements/dev.txt` +.. code-block:: bash + + $ pip install -r requirements/dev.txt Then, install pre-commit hooks using the following command: -`pre-commit install` +.. code-block:: bash + + $ pre-commit install Pre-commit hooks will run several formatters, linters and type checkers each time you commit some changes to a branch. Some tools like black and isort will automatically format your files to enforce consistent formatting within the repo. Other tools will provide a list of errors and warnings which you will be required to address before being able to make the commit. -In some cases it might be desired to commit your changes even though some of the checks are failing. For example when you want to address the pre-commit issues at a later time, or when you want to commit a work-in-progress. In these cases, you can skip the pre-commit hooks by adding the `--no-verify` parameter to the commit command. +In some cases it might be desired to commit your changes even though some of the checks are failing. For example when you want to address the pre-commit issues at a later time, or when you want to commit a work-in-progress. In these cases, you can skip the pre-commit hooks by adding the ``--no-verify`` parameter to the commit command. -`git commit -m 'WIP commit' --no-verify` +.. code-block:: bash + + $ git commit -m 'WIP commit' --no-verify When doing so, please make sure to revisit the issues at a later time. A good way to check if all issues have been addressed before making an MR is to run tox. -Apart from tox, you can also run `pre-commit` on all the files to check formatting and style issues. To do this you can use +Apart from tox, you can also run ```pre-commit``` on all the files to check formatting and style issues. To do this you can use + +.. code-block:: bash + + $ pre-commit run --all + + +Tox: :ref:`Using Tox ` -`pre-commit run --all` +In rare cases it might be desired to ignore certain errors or warnings for a particular part of your code. Flake8, Pylint and MyPy allow disabling specific errors for a line or block of code. The instructions for this can be found in the the documentations of each of these tools. Please make sure to only ignore errors/warnings when absolutely necessary, and always add a comment in your code stating why you chose to ignore it. diff --git a/docs/source/developer_guide/using_tox.rst b/docs/source/developer_guide/using_tox.rst new file mode 100644 index 0000000000..a4b788646e --- /dev/null +++ b/docs/source/developer_guide/using_tox.rst @@ -0,0 +1,89 @@ +.. _using_tox: + +Using Tox +========= + +The quality of code for ``anomalib`` maintained by using tools such as ``black``, ``mypy``, and ``flake8``. In order to enforce these, we use Tox. This is also the reason why we don't have dependencies such as ``pytest`` in the ``requirements.txt`` file. + +What is tox?:: + + ``tox`` aims to automate and standardize testing in Python. It is part of a + larger vision of easing the packaging, testing and release process of Python + software. + + It is a generic virtualenv management and test command line tool you can use for: + + * checking that your package installs correctly with different Python versions and interpreters + * running your tests in each of the environments, configuring your test tool of choice + * acting as a frontend to Continuous Integration servers, greatly reducing boilerplate and merging CI and shell-based testing. - from the [docs](https://tox.readthedocs.io/) + + +Setting-up and Getting Started +------------------------------ + +Setting up tox is easy. + +1. Create new Conda environment + +.. code-block:: bash + + $ conda create -n toxenv python=3.8 + +2. Activate the environment + +.. code-block:: bash + + $ conda activate toxenv + +3. Install tox + +.. code-block:: bash + + $ pip install tox + +4. Run + +``tox`` +It should setup the environments and install the dependencies. + + +.. note:: + + All developers are required to run tox before creating their PR. If you have setup pre-commit hooks then this step can be skipped. + +Brief Explanation of ``tox.ini`` +-------------------------------- + +Here is a brief explanation of ``tox.ini`` for those who are interested. However, for more details, the readers are directed to the [official docs](https://tox.readthedocs.io/). + +All ``tox`` files start with +``[tox]`` + +``isolated_build = True`` tells tox to use a virtual environment instead of using the global python. + +``skip_missing_interpreters = True`` allows tox to run even if some specified environments are missing. + +``envlist`` This defines all the environments that tox should create. Take for example that we want to create an environment to run ``black`` formatter. We can name this environment as: + +.. code-block:: ini + + [tox] + envlist = + black_env + +Then to define the commands in this environment all we need to do is start a block with `[testenv:black_env]` and then define the dependencies and call the commands. + +.. code-block:: ini + + [testenv:black_env] + deps = black + commands = black . + +For a more elaborate setup, refer to the `tox.ini` file in this repository. + +Useful Tox CheatSheet +--------------------- + +* `tox -e envname[,name1,name2]` - to run only those environments +* `tox -r [envname..]` - to recreate all/specific environments. Handy when the requirements have changed +* `tox -l` - to list all environments diff --git a/docs/source/guides/getting_started.rst b/docs/source/guides/getting_started.rst deleted file mode 100644 index ec351903a9..0000000000 --- a/docs/source/guides/getting_started.rst +++ /dev/null @@ -1,97 +0,0 @@ -Installation -=============== - -The repo is thoroughly tested based on the following configuration. - -* Ubuntu 20.04 - -* NVIDIA GeForce RTX 3090 - -You will need -`Anaconda `__ installed on -your system before proceeding with the Anomaly Library install. - -After downloading the Anomaly Library, extract the files and navigate to -the extracted location. - -To perform a development install, run the following: - -:: - - yes | conda create -n anomalib python=3.8 - conda activate anomalib - pip install -r requirements.txt - - -Optionally, if you prefer using a Docker image for development, refer to the guide :ref:`developing_on_docker` - -Training -============== - -By default -`python tools/train.py `__ -runs `STFPM `__ model -`MVTec AD `__ -``leather`` dataset. - -:: - - python tools/train.py # Train STFPM on MVTec AD leather - -Training a model on a specific dataset and category requires further -configuration. Each model has its own configuration file, -`config.yaml `__, -which contains data, model and training configurable parameters. To -train a specific model on a specific dataset and category, the config -file is to be provided: - -:: - - python tools/train.py --config - -Alternatively, a model name could also be provided as an argument, where -the scripts automatically finds the corresponding config file. - -:: - - python tools/train.py --model stfpm - -To see a list of currently supported models, refer to page: :ref:`available models` - -Development -=========== - -To setup the development environment, you will need to install development requirements. :code:`pip install -r requirements_dev.txt` - -To enforce consistency within the repo, we use several formatters, linters, and style- and type checkers: - -.. list-table:: - :widths: 1 1 1 - :header-rows: 1 - - * - Tool - - Function - - Documentation - * - Black - - Code formatting - - https://black.readthedocs.io/en/stable/ - * - isort - - Organize import statements - - https://pycqa.github.io/isort/ - * - Flake8 - - Code style - - https://flake8.pycqa.org/en/latest/ - * - Pylint - - Linting - - http://pylint.pycqa.org/en/latest/ - * - MyPy - - Type checking - - https://mypy.readthedocs.io/en/stable/ - -Instead of running each of these tools manually, we automatically run them before each commit and after each merge request. To achieve this we use pre-commit hooks and tox. Every developer is expected to use pre-commit hooks to make sure that their code remains free of typing and linting issues, and complies with the coding style requirements. When an MR is submitted, tox will be automatically invoked from the CI pipeline in Gitlab to check if the code quality is up to standard. Developers can also run tox locally before making an MR, though this is not strictly necessary since pre-commit hooks should be sufficient to prevent code quality issues. More detailed explanations of how to work with these tools is given in the respective guides: - -Pre-commit hooks: :ref:`Pre-commit hooks guide` - -Tox: :ref:`Using Tox` - -In rare cases it might be desired to ignore certain errors or warnings for a particular part of your code. Flake8, Pylint and MyPy allow disabling specific errors for a line or block of code. The instructions for this can be found in the the documentations of each of these tools. Please make sure to only ignore errors/warnings when absolutely necessary, and always add a comment in your code stating why you chose to ignore it. diff --git a/docs/source/guides/structure_of_documentation.md b/docs/source/guides/structure_of_documentation.md deleted file mode 100644 index 502eeaa425..0000000000 --- a/docs/source/guides/structure_of_documentation.md +++ /dev/null @@ -1,50 +0,0 @@ -# Structure of the Documentation - -This documentation is divided into the following sections: - -1. Getting Started -1. Models -1. API Reference -1. Tutorials -1. Guides -1. Research - -## Getting Started - -The Getting Started section contains all the necessary information regarding setting up and installing the package. It -also includes steps needed to train the models. - -## Models - -This page contains all the models implemented in the repository as well as their API. It is the developer's -responsibility to update this page when a new model is added to the repo. - -## API Reference - -This page lists all the modules, classes and functions available within the `anomalib` package. This page is update -automatically for the following modules: - -- config -- core -- datasets -- hpo -- loggers -- utils - -If a change is made to any of these modules, then the document will be automatically updated. However, if a new module -is introduced, then it must be added to `api_references.rst`. - -## Tutorials - -This contains specific examples of using a feature or a model in the library. These are written from the perspective of -beginners. - -## Guides - -This section contains all the guides written from the perspective of a new user or if someone is trying to get some -clarity on a topic. - -## Research - -Contains all the resources related to research such as benchmarks and papers. These are written from the perspective of -researchers. diff --git a/docs/source/guides/using_tox.md b/docs/source/guides/using_tox.md deleted file mode 100644 index 788a78bacf..0000000000 --- a/docs/source/guides/using_tox.md +++ /dev/null @@ -1,81 +0,0 @@ -(using_tox)= - -# Using Tox - -The quality of code for `anomalib` maintained by using tools such as `black`, `mypy`, and `flake8`. In order to enforce these, we use Tox. This is also the reason why we don't have dependencies such as `pytest` in the `requirements.txt` file. - -What is tox? - -> `tox` aims to automate and standardize testing in Python. It is part of a larger vision of easing the packaging, testing and release process of Python software. -> -> It is a generic virtualenv management and test command line tool you can use for: -> -> - checking that your package installs correctly with different Python versions and interpreters -> - running your tests in each of the environments, configuring your test tool of choice -> - acting as a frontend to Continuous Integration servers, greatly reducing boilerplate and merging CI and shell-based testing. - from the [docs](https://tox.readthedocs.io/) - -See [getting started](#setting-up-and-getting-started) to dive in and get `tox` working on your system. If you want to know more about the `tox.ini` file, check out the [brief explanation](#brief-explanation-of-`tox.ini`). - -## Setting-up and Getting Started - -Setting up tox is easy - -1. Create new Conda environment - -`conda create -n toxenv python=3.8` - -1. Activate the environment - -`conda activate toxenv` - -1. Install tox - -`pip install tox` - -1. Run - -`tox` -It should setup the environments and install the dependencies. - ---- - -**NOTE** - -All developers are required to run tox before creating their MR. If you have setup pre-commit hooks then this step can be skipped. - ---- - -## Brief Explanation of `tox.ini` - -Here is a brief explanation of `tox.ini` for those who are interested. However, for more details, the readers are directed to the [official docs](https://tox.readthedocs.io/). - -All `tox` files start with -`[tox]` - -`isolated_build = True` tells tox to use a virtual environment instead of using the global python. - -`skip_missing_interpreters = True` allows tox to run even if some specified environments are missing. - -`envlist` This defines all the environments that tox should create. Take for example that we want to create an environment to run `black` formatter. We can name this environment as: - -```ini -[tox] -envlist = - black_env -``` - -Then to define the commands in this environment all we need to do is start a block with `[testenv:black_env]` and then define the dependencies and call the commands. - -```ini -[testenv:black_env] - deps = black - commands = black . -``` - -For a more elaborate setup, refer to the `tox.ini` file in this repository. - -## Useful Tox CheatSheet - -- `tox -e envname[,name1,name2]` - to run only those environments -- `tox -r [envname..]` - to recreate all/specific environments. Handy when the requirements have changed -- `tox -l` - to list all environments diff --git a/docs/source/how_to_guides/adding_a_new_model.rst b/docs/source/how_to_guides/adding_a_new_model.rst new file mode 100644 index 0000000000..890fe24382 --- /dev/null +++ b/docs/source/how_to_guides/adding_a_new_model.rst @@ -0,0 +1,198 @@ +Adding a New Model to Anomalib +============================== +Anomalib aims to have the implementation of the state-of-the-art algorithms published in the literature. To integrate an anomaly detection model into anomalib, the following steps should be followed: + +* Create a new sub-package +* Create an ``__init__.py`` file. +* Create a ``config.yaml`` file. +* Create a ``torch_model.py`` file. +* Create a ``lightning_model.py`` file. +* [OPTIONAL] Create a ``loss.py`` file. +* [OPTIONAL] Create an ``anomaly_map.py`` file. +* Create a ``README.md`` file. + + +Create a New Sub Package +-------------------------- +This is a new directory to be created in ``anomalib/models`` to store the model-related files. The overall outline would be as follows: + +.. code-block:: bash + + ./anomalib/models/ + ├── __init__.py + ├── config.yaml + ├── torch_model.py + ├── lightning_model.py + ├── loss.py # OPTIONAL + ├── anomaly_map.py # OPTIONAL + └── README.md + + +Create a ``config.yaml`` file +------------------------------- + +This file stores all the configurations, from data to optimization options. An exemplary yaml file is shown below. For a full configuration file, you could refer to one of the existing model implementations, such as `patchcore configuration. `_ + +.. code-block:: yaml + + dataset: + name: mvtec #options: [mvtec, btech, folder] + format: mvtec + ... + model: + name: patchcore + backbone: wide_resnet50_2 + ... + metrics: + image: + - F1Score + ... + visualization: + show_images: False # show images on the screen + ... + # PL Trainer Args. Don't add extra parameter here. + trainer: + accelerator: auto # <"cpu", "gpu", "tpu", "ipu", "hpu", "auto"> + ... + + + +Create a ``torch_model.py`` Module +---------------------------------- +This file contains the torch model implementation that inherits from ``torch.nn.Module``, defines the model architecture and performs a basic forward-pass. The advantage of storing the model in a separate ``torch_model.py`` file is that the model is de-coupled from the rest of the anomalib implementations and could be used outside the library as well. Basic implementation would look like as follows: + +.. code-block:: python + + class NewModelModel(nn.Module): + """New Model Module.""" + def __init__(self): + pass + def forward(self, x): + pass + + +Create a ``lightning_model.py`` Module +-------------------------------------- +This module contains the lightning model implementation that inherits from the ``AnomalModule``, which already has the ``anomalib`` related attributes and methods. The user does not need to worry about the boilerplate code and only needs to implement the training and validation logic of the algorithm. + +.. code-block:: python + + class NewModel(AnomalyModule): + """PL Lightning Module for the New Model.""" + def __init__(self): + super().__init__() + pass + def training_step(self, batch): + pass + ... + def validation_step(self, batch): + pass + +Create a ``loss.py`` File - [Optional] +-------------------------------------- +This module's availability is dependent on the algorithm. If the algorithm requires a custom, complex loss function, this file may contain the subclass of the torch.nn.Module class implementation. This loss would subsequently be utilized by the lightning module. + +.. code-block:: python + + class NewModelLoss(nn.Module): + """NewModel Loss.""" + + def forward(self) -> Tensor: + """Calculate the NewModel loss.""" + pass + +Create an ``anomaly_map.py`` File - [Optional] +--------------------------------------------- +Similar to the loss.py module, the anomaly map.py module is optional depending on the capabilities of the algorithm. This module may be implemented if the algorithm supports segmentation so that the location of the anomaly can be predicted pixel-by-pixel. + +.. code-block:: python + + class AnomalyMapGenerator(nn.Module): + """Generate Anomaly Heatmap.""" + + def __init__(self, input_size: Union[ListConfig, Tuple]): + pass + + def forward(self, x: Tensor) -> Tensor: + """Generate Anomaly Heatmap.""" + ... + return anomaly_map + + +Create a ``README.md`` File +--------------------------- +Once the implementation is done, this readme file would describe the model using the following structure. + +.. code-block:: markdown + + # Name of the Model + + ## Description + Brief description of the paper. + + ## Architecture + A diagram showing the high-level overview. + + ## Usage + python tools/train.py --model + + ## Benchmark + Benchmark results on MVTec categories. + +Add Model to the Tests +---------------------- +It is essential that newly added models do not disrupt the workflow and that their components are continually inspected. In this regard, the model will be added to our list of tested models. + +To test the model, you need to add the model name `here `_. + +The list of models to test would then become, + +.. code-block:: python + + @pytest.mark.parametrize( + ["model_name", "nncf"], + [ + ("cflow", False), + ("dfkde", False), + ... + ("newmodel", False), + ], + ) + @TestDataset(num_train=20, num_test=10) + +This would check if the training works for the model. It is also important to check whether the inference capabilities of the model works as well. To do so, the model is to be added `here `_. + +.. code-block:: python + + class TestInferencers: + @pytest.mark.parametrize( + "model_name", + [ + "cflow", + "dfkde", + ... + "newmodel" + ], + ) + +Add Model to the Docs +--------------------- +Final step would be to add the model to the docs. To do so, one would create a ``newmodel.rst`` file in ``docs/reference_guide/algorithms``, and include it in ``docs/reference_guide/algorithms/index.rst`` as follows: + +.. code-block:: sphinx + + .. _available models: + + Algorithms + ========== + + .. toctree:: + :maxdepth: 3 + :caption: Contents: + + cflow + dfkde + ... + newmodel + +That is all! Now, the model would function flawlessly with anomalib! diff --git a/docs/source/how_to_guides/index.rst b/docs/source/how_to_guides/index.rst new file mode 100644 index 0000000000..26cb62a618 --- /dev/null +++ b/docs/source/how_to_guides/index.rst @@ -0,0 +1,14 @@ +How to Guides +============= + +.. toctree:: + :maxdepth: 1 + :caption: Contents: + + train_custom_data + adding_a_new_model + notebooks/100_datamodules/101_btech.ipynb + notebooks/100_datamodules/102_mvtec.ipynb + notebooks/100_datamodules/103_folder.ipynb + notebooks/200_models/201_fastflow.ipynb + notebooks/300_benchmarking/301_benchmarking.ipynb diff --git a/docs/source/how_to_guides/notebooks b/docs/source/how_to_guides/notebooks new file mode 120000 index 0000000000..32b12ea3a9 --- /dev/null +++ b/docs/source/how_to_guides/notebooks @@ -0,0 +1 @@ +../../../notebooks \ No newline at end of file diff --git a/docs/source/how_to_guides/train_custom_data.rst b/docs/source/how_to_guides/train_custom_data.rst new file mode 100644 index 0000000000..5974ccffed --- /dev/null +++ b/docs/source/how_to_guides/train_custom_data.rst @@ -0,0 +1,272 @@ +Training with Custom Data +========================= + +Anomalib supports a number of datasets in various formats, including the state-of-the-art anomaly detection benchmarks such as MVTec AD and BeanTech. For those who would like to use the library on their custom datasets, anomalib also provides a ``Folder`` datamodule that can load datasets from a folder on a file system. The scope of this post will be to train anomalib models on custom datasets using the ``Folder`` datamodule. + +Step 1: Install Anomalib +------------------------ + +Option - 1 : PyPI +^^^^^^^^^^^^^^^^^ +Anomalib can be installed from PyPI via the following: + +.. code-block:: bash + + pip install anomalib + + +Option - 2: Editable Install +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Alternatively, it is also possible to do editable install: + +.. code-block:: bash + git clone https://github.com/openvinotoolkit/anomalib.git + cd anomalib + pip install -e . + + +.. _collect-your-custom-dataset: + +Step 2: Collect Your Custom Dataset +----------------------------------- +Anomalib supports multiple image extensions such as ``".jpg", ".jpeg", ".png", ".ppm", ".bmp", ".pgm", ".tif", ".tiff", and ".webp"``. A dataset can be collected from images that have any of these extensions. + + +Step 3: Format your dataset +--------------------------- +Depending on the use-case and collection, custom datasets can have different formats, some of which are listed below: + +* A dataset with good and bad images. +* A dataset with good and bad images as well as mask ground-truths for pixel-wise evaluation. +* A dataset with good and bad images that is already split into training and testing sets. + +Each of these use-cases is addressed by anomalib's ``Folder`` datamodule. Let's focus on the first use-case as an example of end-to-end model training and inference. In this post, we will use the :ref:`hazelnut-toy-dataset` dataset which you can download from `here `_. The dataset consists of several folders, each containing a set of images. The ``colour`` and the ``crack`` folders represent two kinds of defects. We can ignore the ``masks`` folder for now. + +Load your data to the following directory structure. Anomalib will use all images in the ``colour`` folder as part of the validation dataset and then randomly split the good images for training and validation. + +.. code-block:: bash + Hazelnut_toy + ├── colour + └── good + + +Step 4: Modify Config File +-------------------------- +A YAML configuration file is necessary to run training for Anomalib. The training configuration parameters are categorized into 5 sections: ``dataset``, ``model``, ``project``, ``logging``, ``trainer``. + +To get Anomalib functionally working with a custom dataset, one only needs to change the ``dataset`` section of the configuration file. + +Below is an example of what the dataset parameters would look like for our ``hazelnut_toy`` folder specified in :ref:`Step 2 `. + +Let's choose `Padim algorithm `_, copy the sample config and modify the dataset section. + +.. code-block:: bash + + $ cp anomalib/models/padim/config.yaml custom_padim.yaml + + +.. code-block:: yaml + + # Replace the dataset configs with the following. + dataset: + name: hazelnut + format: folder + path: ./datasets/Hazelnut_toy + normal_dir: good # name of the folder containing normal images. + abnormal_dir: colour # name of the folder containing abnormal images. + task: classification # classification or segmentation + mask: null #optional + normal_test_dir: null # optional + extensions: null + split_ratio: 0.2 # normal images ratio to create a test split + seed: 0 + image_size: 256 + train_batch_size: 32 + test_batch_size: 32 + num_workers: 8 + transform_config: + train: null + val: null + create_validation_set: true + tiling: + apply: false + tile_size: null + stride: null + remove_border_count: 0 + use_random_tiling: False + random_tile_count: 16 + + model: + name: padim + backbone: resnet18 + layer: + - layer1 + ... + + +Step 5: Run Training +-------------------- + +As per the config file, move ``Hazelnut_toy`` to the datasets section in the main root directory of anomalib, and then run + +.. code-block:: bash + + $ python tools/train.py --config custom_padim.yaml + + +Step 6: Interpret Results +------------------------- + +Anomalib will print out results of the trained model on the validation dataset. The printed metrics are dependent on the task mode chosen. The classification example provided in this tutorial prints out two scores: F1 and AUROC. The F1 score is a metric which values both the precision and recall, more information on its calculation can be found in this `blog `_. + +.. note:: + + Not only does Anomalib classify whether a part is defected or not, it can also be used to segment the defects as well. To do this, simply add a folder called ``mask`` at the same directory level as the ``good`` and ``colour`` folders. This folder should contain binary images for the defects in the ``colour`` folder. Here, the white pixels represent the location of the defect. Populate the mask field in the config file with ``mask`` and change the task to segmentation to see Anomalib segment defects. + +.. code-block:: bash + + Hazelnut_toy + ├── colour + │ ├── 00.jpg + │ ├── 01.jpg + │ ... + ├── good + │ ├── 00.jpg + │ ├── 01.jpg + └── mask + ├── 00.jpg + ├── 01.jpg + ... + +Here is an example of the generated results for a toy dataset containing Hazelnut with colour defects. + +.. image:: ../images/how_to_guides/train_custom_data/hazelnut_results.gif + :align: center + + +Logging and Experiment Management +--------------------------------- + +While it is delightful to know how good your model performed on your preferred metric, it is even more exciting to see the predicted outputs. Anomalib provides a couple of ways to log and track experiments. These can be used individually or in a combination. As of the current release, you can save images to a local folder, or upload to comet, weights and biases, or TensorBoard. + +To select where you would like to save the images, change the ``log_images`` parameter in the ``Visualization`` section in the config file to true. + +For example, setting the following ``log_images: True`` will result in saving the images in the results folder as shown in the tree structure below: + +.. code-block:: bash + + results + └── padim + └── Hazelnut_toy + ├── images + │ ├── colour + │ │ ├── 00.jpg + │ │ ├── 01.jpg + │ │ └── ... + │ └── good + │ ├── 00.jpg + │ ├── 01.jpg + │ └── ... + └── weights + └── model.ckpt + + +Logging to Tensorboard and/or W&B +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +To use TensorBoard and/or W&B logger and/or Comet logger, ensure that the logger parameter is set to ``comet``, ``tensorboard``, ``wandb`` or ``[tensorboard, wandb]`` in the ``logging`` section of the config file. + +An example configuration for saving to TensorBoard is shown in the figure below. Similarly after setting logger to ``wandb`` or 'comet' you will see the images on your wandb and/or comet project dashboard. + +.. code-block:: yaml + + visualization: + show_images: False # show images on the screen + save_images: False # save images to the file system + log_images: True # log images to the available loggers (if any) + image_save_path: null # path to which images will be saved + mode: full # options: ["full", "simple"] + + logging: + logger: [comet, tensorboard, wandb] #Choose any combination of these 3 + log_graph: false + +.. image:: ../images/how_to_guides/train_custom_data/logging.gif + :align: center + + +Hyper-Parameter Optimization +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +It is very rare to find a model which works out of the box for a particular dataset. However, fortunately, we support tools which work out of the box to help tune the models in Anomalib to your particular dataset. As of the publication of this blog post, Anomalib supports `weights and biases `_ for hyperparameter optimization. To get started have a look at ``sweep.yaml`` located at ``tools/hpo``. It provides a sample of how one can define a hyperparameter sweep. + +.. code-block:: yaml + + observation_budget: 10 + method: bayes + metric: + name: pixel_AUROC + goal: maximize + parameters: + dataset: + category: hazelnut + image_size: + values: [128, 256] + model: + backbone: + values: [resnet18, wide_resnet50_2] + +The observation_budget informs wandb about the number of experiments to run. The method section defines the kind of method to use for HPO search. For other available methods, have a look at `Weights and Biases `_ documentation. The parameters section contains dataset and model parameters. Any parameter defined here overrides the parameter in the original model configuration. + +To run a sweep, you can just call, + +.. code-block:: bash + + $ python tools/hpo/wandb_sweep.py \ + --model padim \ + --config ./path_to_config.yaml \ + --sweep_config tools/hpo/sweep.yaml + +In case ``model_config`` is not provided, the script looks at the default config location for that model. Note, you will need to have logged into a wandb account to use HPO search and view the results. + +A sample run is visible in the screenshot below. + +.. image:: ../images/how_to_guides/train_custom_data/hpo.gif + :align: center + + +Benchmarking +------------ +To add to the suit of experiment tracking and optimization, anomalib also includes a benchmarking script for gathering results across different combinations of models, their parameters, and dataset categories. The model performance and throughputs are logged into a csv file that can also serve as a means to track model drift. Optionally, these same results can be logged to Weights and Biases and TensorBoard. A sample configuration file is shown in the screenshot below. + +.. code-block:: yaml + seed: 42 + compute_openvino: false + hardware: + - cpu + - gpu + writer: + - wandb + - tensorboard + grid_search: + dataset: + category: + - colour + - crack + image_size: [128, 256] + model_name: + - padim + - stfpm + +This configuration computes the throughput and performance metrics for CPU and GPU for two categories of the toy dataset for Padim and STFPM models. The dataset can be configured in the respective model configuration files. By default, ``compute_openvino`` is set to False to support instances where OpenVINO requirements are not installed in the environment. Once installed, this flag can be set to True to get throughput on OpenVINO optimized models. The writer parameter is optional and can be set to ``writer: []`` in case the user only requires a csv file without logging to TensorBoard or Weights and Biases. It is also a good practice to set a value of seed to ensure reproducibility across runs and thus, is set to a non-zero value by default. + +Once a configuration is decided, benchmarking can easily be performed by calling + +.. code-block:: bash + + python tools/benchmarking/benchmark.py \ + --config tools/benchmarking/benchmark_params.yaml + +A nice feature about the provided benchmarking script is that if the host system has multiple GPUs, the runs are parallelized over all the available GPUs for faster collection of result. + +.. attention:: + + Intel researchers actively maintain the Anomalib repository. Their mission is to provide the AI community with best-in-class performance and accuracy while also providing a positive developer experience. Check out the repo and start using anomalib right away! diff --git a/docs/blog/001-train-custom-dataset/images/anomalib-wide-blue.png b/docs/source/images/how_to_guides/train_custom_data/anomalib-wide-blue.png similarity index 100% rename from docs/blog/001-train-custom-dataset/images/anomalib-wide-blue.png rename to docs/source/images/how_to_guides/train_custom_data/anomalib-wide-blue.png diff --git a/docs/blog/001-train-custom-dataset/images/hazelnut_results.gif b/docs/source/images/how_to_guides/train_custom_data/hazelnut_results.gif similarity index 100% rename from docs/blog/001-train-custom-dataset/images/hazelnut_results.gif rename to docs/source/images/how_to_guides/train_custom_data/hazelnut_results.gif diff --git a/docs/blog/001-train-custom-dataset/images/hpo.gif b/docs/source/images/how_to_guides/train_custom_data/hpo.gif similarity index 100% rename from docs/blog/001-train-custom-dataset/images/hpo.gif rename to docs/source/images/how_to_guides/train_custom_data/hpo.gif diff --git a/docs/blog/001-train-custom-dataset/images/logging.gif b/docs/source/images/how_to_guides/train_custom_data/logging.gif similarity index 100% rename from docs/blog/001-train-custom-dataset/images/logging.gif rename to docs/source/images/how_to_guides/train_custom_data/logging.gif diff --git a/docs/source/images/logos/anomalib-favicon.png b/docs/source/images/logos/anomalib-favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..459f3660e937bcacd8fcb80d59595ef2bc157abe GIT binary patch literal 2334 zcmZ`)4LDSJ7ypl$G^mitXEDuU7t;({Vj5#bGD6Z|BxQ`jFlJ_FM#fi*SR$6B*DIAG zD_Kvn+e5SEtML)LzS^{{W;XKiDm-42k@DWbme+3gKL2~p{hjkW=YP&U|L30Z^f<6t zSyveVu$k=QcfJh{JIJ)IcHf$M@QqB>-7sEd0C|3BuM;F6C z>X08e;G#SU2_>$JA0GhiX8D5s@lXQw0FGo*r~<0H8-dP=#L2es4K`*-qBJ40!cTcn|A{7GhJ1o1w|tTY z_2@%c|CdZiC-fu>`6YgU?|_$-7Omu!abKL)SC>{*kd3UC%hgV=|y` zy^>=!!&+>OKh$G_BacB7aCj6BhecX{8i@Gy9;;*`UOp>pW@lwEmr-akNy-rOuPG-f z2b2duhY%qlJ2_CON)tZrEQ&Yky(Df%dIP?TLg-d@zy#?9)S?ZsR@iRspdbOwD=RlY zWABBnjsvybDOoCd8hh@3oupf$Vr5;NB`!s0?eu0cN29Q(XO`@Tq9^0tyzcq=G0cKx z^Y0k@Hv{}%qwD#TbzSD!-}HzBP&2p|pQ}?ayx)HbSI!i`tj z^h8X9uo6_(!hnvYjF7dc_>@~CLTDN6G2Qg+nnr5!o0QaY#sH!Tj&@ek9gc{6Kb*DI zTCL=8K&<59Go>H?I(id+JRStPr?l5 zqy}kY6Y=o8nf(viuUO}ZyN{n~VAW<9sgG979+OR8MU1@f9IBBiNNkE)?nhd25Y*6qsMv)iwq%!xtmq*0OO>eokv zHY{4Qnf-s;d|vG!Kkv}F2(t#on&Lpluve?QjN}`XfhoPxRd_12 ztdE6oYHmIIST>~(bWmzu)NPIJS19}{Pw#woPLs3f^zXyE9`UU~gEe1Nro);%?-Z&Q zzQ(qMv{v)Zm&7?3T@*&gNyQUffS)*bw^pt`^0N}cxy#$*RS{~$ZDWU0rbo_axxGK| zBNc9?)tTLB)BDHU{NJVGLf3qDzn-jjCi=uy6PB~kvTus&zY9VBB;l3 zqY|RQvx3%k*ZJEL;+9PVtxfZcT>svFS2u_Mu>GW5Ra$=;LZLg{ z?{rb}*5FdJMi)h?f3Wifv+sAjGS1e2n)>;cNuPx}`>LTwd#n{tIv;*iM=AWXqYNw> zcPIp#C+{9*#YZu{Z&30M9<5CK=~B$e7MCbmdlCA*T_RIr&B)HO7b_gswAUY@7j;e@u fyxbeeS8cGb_+irU=8#FP{2x!=@8MLnFEIIkl1_;j literal 0 HcmV?d00001 diff --git a/docs/source/images/logos/anomalib-icon.png b/docs/source/images/logos/anomalib-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..f53750fb3260cffedd4841bec140eaaae1b0ae25 GIT binary patch literal 4515 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 literal 0 HcmV?d00001 diff --git a/docs/source/images/logos/anomalib-text.png b/docs/source/images/logos/anomalib-text.png new file mode 100644 index 0000000000000000000000000000000000000000..3e7510c6b2076230a85b1fdc94bd58d8cf2562ed GIT binary patch literal 38496 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

-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%0DGUHXb}JK=nB7mH{{ z&fukb)XV%kguK}yYMd_mY{!Xrg~uVK&Z3!Sai;(YOdP9{e&~jm<>Nujgdy{7J27M4 zp81!}AJ+MNJ77kH=D(?Y@GfQ9COFXc;FeUf`WQyl!j?K6e~q7n|4j~?SpqXB+^X6z z|7ZItPXeK}wdnlP>$+WC$3B0aVR36sBh3&|2?*dmY~7>3u#qq#BD8YDiUt zW}E>kp@|`)CZACrCX%4t8ok}`?j8u3`uDpZ%33aER1&kWdE-VMAeIQ)d53XX3IR2}AMQkHKOf|C@M>hQ+3W0t2a|o{MlHyc z1O`)X^mc_&A2*|35v3CNs{e2QZH51G74NmV;qv8it47<{m$|nUUfuc0TXsYQrxPo^ z*B2X2III9ZCOCp{7&bMtr%Vq{%M;D|I9j5p_UQ8NdOmHQm&CUH=lj75F@yxHm8z3z zft`nr+7pl;)B2`v0qmvD8zF0`FR$yDzVHK&ex=_TD%t3q*)dD!fEA|#PjZ9b`}@Yf z$mIk{E%HQtH7D|; zkTYH53ir;Tb*FYtsDaz0D+>d;19PR``;7U0Vy_Wbffshy-qiknUn|C^PxYsv+|Hnd zYewy<_&3V81J)mJ*a?mkGbCI~YP^22`CscP(pEiiF8egVZ!D zuVsImT??2Mq5r^l8ZNn90Z`)m`du}Nsm{|b_)n>+&L|V=Q}ow$?yDo$_b7EZcWjD( zZ>sG^t-g2{Hjc7ERx_46Z50@(+f!#F!H~D8J_RD%yyI&DivWBKSt+lS26xA#Uk3o@ z$E(|KkdCg*qe zoWGK+vTF(PsY&YilEZ0 z;1Y1n!8DjB&gW8(eKhLL;@aRt{`z!&LnwLX zZ#`+@z5RZ@9*ivv#*s|yc6fLLN?)Gt2Z7Lwh3EMIb=;V$1aiY2^OR3))|Lm z=GCks&-RALg^`@z=Dz)|%jN#9!*>ov-be@J+vdW!&CEw6yax^$ao|)IvvW5I4;wa@ zYiTZ`ZTj+ArDs#}r=fyRV9uqS5g;;B5_Nl<9zj^Od#Z;mO%%v40#3X}r19?0AWp#{YTJ7|_wX9VR_SFss*!+g-t@!I zUZV2a3@E?;&8>yvfgmbAe`+D{Ym_#GeDo zD&ftmR|3cuWnb-y<4Rx(rwI7n>oH}jS^D(aoiwY%b%F9DJHIC-UgIobGJ1}Wn^0n!}Q0FQFTt5{~vDOtz}chV8zr!1wk1r42sk1ofwD~lm3e1y*bd2X~l6!WF1^{wdu(3 zj-U-wO;^_)$HluU%lf?Sx&;p8fH_=+$(hdj!-vdNqqaXWo>HQZrZjjuhUBxn6;6(Z zM|2BB%q1>m7sXWx?RqN^FgXzd&0#hD;$D+eyB%ALW!Pk7)B@~lK&sV&Py z*uj`^d(moH{c=C?>EnBYhz+Jh6^kes@C9mANa5*ll9r>pNv)k2ZL<{zzQVILABT%J znE%^9hWq(lfI`7CvU|EfpCb$rfz*&NZRXleSJ!tpxuK3sW4MHF^c_w*pbyL5^6@DO zfmXLLZ5jG@IrYkKqB(s9J`_c|(UGy`%9*kZ72DoItvVD@1%B^La4+PcS9=H`2j|j{ zMya^It>~SY$@uwn-%1LRm|}YZ2|HoVi-tlh{)IXc150!yk6 z@hs#(7}914vE(O57sxN$Pntw6sXpY9wMBcwy9;`|nGLO?VoKH8^fAre!|(95!hgKb zZ|wN-=i}LRNS)M|_;-p!>(Nmd_D)&8SAraR+?wk+qsXgqG?BHprfrpa&sxUB*}lVS z9Y{%FfLdJIt#P}z?)&m!jX*|d`R!nmi~44Pi9kVCR&`sanILT!hGlWR5S*j`*ZF&M zbGDt$asz`C?wV^1QfKBBr#|<{7O-f@cI$*pL_Hx{`lAKR1iN(wxwMho&idBS6=6s?pho2N^0)199Jc=&kYi7nO!M+91fOBb3w3oH*owJ{ol! z{ZNkKT(|K4_fJvysJ!5#*FFXd_QIiwvaSHgA~dWG6^1qKF6dB_v|UBXX<-m3R_>}2 z+`u%hG@cUcohaq}S=KCTH)J*#(m2vO)3VZTcUPJaE*d;;#qvZY2hR0pBlo#|VV77M z$~>)g)qxqc7#TmPpYr1OomQXwZ{$R3E^K4|vlbq{-lui7dweo|zsZ9apY*GGK{)VH zk#Ic55vmDW+WGqpK0e0Nm3-`)Wqc-R4Kw7_^V#XW3K--5uqZ1_WnQNk+}9_hcOKTw zuJ=>EF}ZjS^=Rww2=Tr@fGAMUKT(?+KDHI#yg zJcSIU>%u|#NZrwXFyh|#$BD9Y>-V2%vZ#KqD17m1)Ai+-dc>9x>(BnCbIR%uyfdr` zyVS3gxv@gllJVon)U~EPub++1R_}SL*1EzsVA;eV=gV$}3!Ue((*OPau-+FblD>X^ zNo4)qR>(dC?g`!maETM%(zIte)#^{$ zh+o6s1&lkJg@@JXUtM}c?NnQDYiTBPt|Q4DO6+;*^hTm->BB)Wq|qSlnuh#PLChA^ z>@I*`qHw{$wLjqRkG6T9i7e1z#h-gfBs^CEWY_{>aKtJutnf~fvYP!)5l{XgZFmq3 z1$Y1tK2?d>?U=ON@v5gOvG#N7OG=lKIEK7Gg1#<_)-;BZiNcbn6ZF{@gy(Dq{fcq# zqnA%)OlK0eW>mud(OUSPZ_X;2|NM5Yf7YYXZr9_|S^o<)vd1_vwNld7|Det%^D-Ma+^R2lYa{SB*vls*vjbhkK#7N=5C zCEOxgTYY*$x#^90Bbhu5`-H@R%R<7<{?2+NfL`qYUXTaqU3noxDS7hB|0nP z-It5KMjz6Ih?kFGrQ^Jpw7M@w3`X&SnuCe$NxP3w=qCj%^n@dGI7VB&FR5gz(Cw`l zLNu}BR;IVY>S_Vv&#R>tjQ#Uz&Bxco9D5}4-hnzSK`xHo!lb~H=lqftZS`_ paper. - -Model Type: Classification - -Description -*********** - -Fast anomaly classification algorithm that consists of a deep feature extraction stage followed by anomaly classification stage consisting of PCA and class-conditional Gaussian Density Estimation. - -Feature Extraction -################## - -Features are extracted by feeding the images through a ResNet18 backbone, which was pre-trained on ImageNet. The output of the penultimate layer (average pooling layer) of the network is used to obtain a semantic feature vector with a fixed length of 2048. - -Anomaly Detection -################# - -In the anomaly classification stage, class-conditional PCA transformations and Gaussian Density models are learned. Two types of scores are calculated (i) Feature-reconstruction scores (norm of the difference between the high-dimensional pre-image of a reduced dimension feature and the original high-dimensional feature), and (ii) Negative log-likelihood under the learnt density models. Either of these scores can be used for anomaly detection. - -Usage -***** - -.. code-block:: bash - - $ python tools/train.py --model dfm - -.. autosummary:: - :toctree: models - :nosignatures: - - dfm.lightning_model - dfm.torch_model - -GANomaly ---- - -This is the implementation of the `GANomaly `_ paper. - -Description -*********** - -GANomaly uses the conditional GAN approach to train a Generator to produce images of the normal data. This Generator consists of an encoder-decoder-encoder architecture to generate the normal images. The distance between the latent vector $z$ between the first encoder-decoder and the output vector $\hat{z}$ is minimized during training. - -The key idea here is that, during inference, when an anomalous image is passed through the first encoder the latent vector $z$ will not be able to capture the data correctly. This would leave to poor reconstruction $\hat{x}$ thus resulting in a very different $\hat{z}$. The difference between $z$ and $\hat{z}$ gives the anomaly score. - -Usage -***** - -.. code-block:: bash - - $ python tools/train.py --model ganomaly - -.. autosummary:: - :toctree: models - :nosignatures: - - ganomaly.lightning_model - ganomaly.torch_model - - -CFlow -------- - -This is the implementation of the `CFlow `_ paper. - -Model Type: Segmentation - -Description -*********** - -CFLOW model is based on a conditional normalizing flow framework adopted for anomaly detection with localization. It consists of a discriminatively pretrained encoder followed by a multi-scale generative decoders. The encoder extracts features with multi-scale pyramid pooling to capture both global and local semantic information with the growing from top to bottom receptive fields. Pooled features are processed by a set of decoders to explicitly estimate likelihood of the encoded features. The estimated multi-scale likelyhoods are upsampled to input size and added up to produce the anomaly map. - -Architecture -************ - -.. image:: ./images/cflow/architecture.jpg - :alt: CFlow Architecture - -Usage -***** - -.. code-block:: bash - -$ python tools/train.py --model cflow - -.. autosummary:: - :toctree: models - :nosignatures: - - cflow.anomaly_map - cflow.lightning_model - cflow.torch_model - cflow.utils - -Padim ------- - -This is the implementation of the `PaDiM `_ paper. - -Model Type: Segmentation - -Description -*********** - -PaDiM is a patch based algorithm. It relies on a pre-trained CNN feature extractor. The image is broken into patches and embeddings are extracted from each patch using different layers of the feature extractors. The activation vectors from different layers are concatenated to get embedding vectors carrying information from different semantic levels and resolutions. This helps encode fine grained and global contexts. However, since the generated embedding vectors may carry redundant information, dimensions are reduced using random selection. A multivariate gaussian distribution is generated for each patch embedding across the entire training batch. Thus, for each patch of the set of training images, we have a different multivariate gaussian distribution. These gaussian distributions are represented as a matrix of gaussian parameters. - -During inference, Mahalanobis distance is used to score each patch position of the test image. It uses the inverse of the covariance matrix calculated for the patch during training. The matrix of Mahalanobis distances forms the anomaly map with higher scores indicating anomalous regions. - -Architecture -************ - -.. image:: ./images/padim/architecture.jpg - :alt: PaDiM Architecture - -Usage -***** - -.. code-block:: bash - - $ python tools/train.py --model padim - -.. autosummary:: - :toctree: models - :nosignatures: - - padim.anomaly_map - padim.lightning_model - padim.torch_model - -PatchCore ----------- - -This is the implementation of the `PatchCore `_ paper. - -Model Type: Segmentation - -Description -*********** - -The PatchCore algorithm is based on the idea that an image can be classified as anomalous as soon as a single patch is anomalous. The input image is tiled. These tiles act as patches which are fed into the neural network. It consists of a single pre-trained network which is used to extract "mid" level features patches. The "mid" level here refers to the feature extraction layer of the neural network model. Lower level features are generally too broad and higher level features are specific to the dataset the model is trained on. The features extracted during training phase are stored in a memory bank of neighbourhood aware patch level features. - -During inference this memory bank is coreset subsampled. Coreset subsampling generates a subset which best approximates the structure of the available set and allows for approximate solution finding. This subset helps reduce the search cost associated with nearest neighbour search. The anomaly score is taken as the maximum distance between the test patch in the test patch collection to each respective nearest neighbour. - -Architecture -************ - -.. image:: ./images/patchcore/architecture.jpg - :alt: PatchCore Architecture - -Usage -***** - -.. code-block:: bash - - $ python tools/train.py --model patchcore - - -.. autosummary:: - :toctree: models - :nosignatures: - - patchcore.anomaly_map - patchcore.lightning_model - patchcore.torch_model - -STFPM -------- - -This is the implementation of the `STFPM `_ paper. - -Model Type: Segmentation - -Description -*********** - -STFPM algorithm which consists of a pre-trained teacher network and a student network with identical architecture. The student network learns the distribution of anomaly-free images by matching the features with the counterpart features in the teacher network. Multi-scale feature matching is used to enhance robustness. This hierarchical feature matching enables the student network to receive a mixture of multi-level knowledge from the feature pyramid thus allowing for anomaly detection of various sizes. - -During inference, the feature pyramids of teacher and student networks are compared. Larger difference indicates a higher probability of anomaly occurrence. - -Architecture -************ - -.. image:: ./images/stfpm/architecture.jpg - :alt: STFPM Architecture - -Usage -***** - -.. code-block:: bash - - $ python tools/train.py --model stfpm - -.. autosummary:: - :toctree: models - :nosignatures: - - stfpm.anomaly_map - stfpm.lightning_model - stfpm.torch_model - - -Reverse Distillation -------- - -This is the implementation of the `Anomaly Detection via Reverse Distillation from One-Class Embedding `_ paper. - -Model Type: Segmentation - -Description -*********** - -Reverse Distillation model consists of three networks. The first is a pre-trained feature extractor (E). The next two are the one-class bottleneck embedding (OCBE) and the student decoder network (D). The backbone E is a ResNet model pre-trained on ImageNet dataset. During the forward pass, features from three ResNet block are extracted. These features are encoded by concatenating the three feature maps using the multi-scale feature fusion block of OCBE and passed to the decoder D. The decoder network is symmetrical to the feature extractor but reversed. During training, outputs from these symmetrical blocks are forced to be similar to the corresponding feature extractor layers by using cosine distance as the loss metric. - -During testing, a similar step is followed but this time the cosine distance between the feature maps is used to indicate the presence of anomalies. The distance maps from all the three layers are up-sampled to the image size and added (or multiplied) to produce the final feature map. Gaussian blur is applied to the output map to make it smoother. Finally, the anomaly map is generated by applying min-max normalization on the output map. - -Architecture -************ - -.. image:: ./images/reversedistillation/architecture.jpg - :alt: Reverse Distillation Architecture - -Usage -***** - -.. code-block:: bash - - $ python tools/train.py --model reverse_distillation - -.. autosummary:: - :toctree: models - :nosignatures: - - reverse_distillation.lightning_model - reverse_distillation.torch_model diff --git a/docs/source/reference_guide/algorithms/cflow.rst b/docs/source/reference_guide/algorithms/cflow.rst new file mode 100644 index 0000000000..62198ed75e --- /dev/null +++ b/docs/source/reference_guide/algorithms/cflow.rst @@ -0,0 +1,39 @@ +CFlow +===== + +This is the implementation of the `CFlow `_ paper. + +Model Type: Segmentation + +Description +*********** + +CFLOW model is based on a conditional normalizing flow framework adopted for anomaly detection with localization. It consists of a discriminatively pretrained encoder followed by a multi-scale generative decoders. The encoder extracts features with multi-scale pyramid pooling to capture both global and local semantic information with the growing from top to bottom receptive fields. Pooled features are processed by a set of decoders to explicitly estimate likelihood of the encoded features. The estimated multi-scale likelyhoods are upsampled to input size and added up to produce the anomaly map. + +Architecture +************ + +.. image:: ../../images/cflow/architecture.jpg + :alt: CFlow Architecture + +Usage +***** + +.. code-block:: bash + + python tools/train.py --model cflow + +.. automodule:: anomalib.models.cflow.torch_model + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: anomalib.models.cflow.lightning_model + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: anomalib.models.cflow.anomaly_map + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/reference_guide/algorithms/dfkde.rst b/docs/source/reference_guide/algorithms/dfkde.rst new file mode 100644 index 0000000000..5456e4e580 --- /dev/null +++ b/docs/source/reference_guide/algorithms/dfkde.rst @@ -0,0 +1,37 @@ +DFKDE +------- + +Model Type: Classification + +Description +*********** + +Fast anomaly classification algorithm that consists of a deep feature extraction stage followed by anomaly classification stage consisting of PCA and Gaussian Kernel Density Estimation. + +Feature Extraction +################## + +Features are extracted by feeding the images through a ResNet50 backbone, which was pre-trained on ImageNet. The output of the penultimate layer (average pooling layer) of the network is used to obtain a semantic feature vector with a fixed length of 2048. + +Anomaly Detection +################# + +In the anomaly classification stage, the features are first reduced to the first 16 principal components. Gaussian Kernel Density is then used to obtain an estimate of the probability density of new examples, based on the collection of training features obtained during the training phase. + +Usage +***** + +.. code-block:: bash + + python tools/train.py --model dfkde + + +.. automodule:: anomalib.models.dfkde.torch_model + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: anomalib.models.dfkde.lightning_model + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/reference_guide/algorithms/dfm.rst b/docs/source/reference_guide/algorithms/dfm.rst new file mode 100644 index 0000000000..93446f692a --- /dev/null +++ b/docs/source/reference_guide/algorithms/dfm.rst @@ -0,0 +1,39 @@ +DFM +--- + +This is the implementation of `DFM `_ paper. + +Model Type: Classification + +Description +*********** + +Fast anomaly classification algorithm that consists of a deep feature extraction stage followed by anomaly classification stage consisting of PCA and class-conditional Gaussian Density Estimation. + +Feature Extraction +################## + +Features are extracted by feeding the images through a ResNet18 backbone, which was pre-trained on ImageNet. The output of the penultimate layer (average pooling layer) of the network is used to obtain a semantic feature vector with a fixed length of 2048. + +Anomaly Detection +################# + +In the anomaly classification stage, class-conditional PCA transformations and Gaussian Density models are learned. Two types of scores are calculated (i) Feature-reconstruction scores (norm of the difference between the high-dimensional pre-image of a reduced dimension feature and the original high-dimensional feature), and (ii) Negative log-likelihood under the learnt density models. Either of these scores can be used for anomaly detection. + +Usage +***** + +.. code-block:: bash + + $ python tools/train.py --model dfm + + +.. automodule:: anomalib.models.dfm.torch_model + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: anomalib.models.dfm.lightning_model + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/reference_guide/algorithms/draem.rst b/docs/source/reference_guide/algorithms/draem.rst new file mode 100644 index 0000000000..fde721779c --- /dev/null +++ b/docs/source/reference_guide/algorithms/draem.rst @@ -0,0 +1,42 @@ +Draem +------ + +This is the implementation of the `DRAEM `_ paper. + +Model Type: Segmentation + +Description +*********** + +DRAEM is a reconstruction based algorithm that consists of a reconstructive subnetwork and a discriminative subnetwork. DRAEM is trained on simulated anomaly images, generated by augmenting normal input images from the training set with a random Perlin noise mask extracted from an unrelated source of image data. The reconstructive subnetwork is an autoencoder architecture that is trained to reconstruct the original input images from the augmented images. The reconstructive submodel is trained using a combination of L2 loss and Structural Similarity loss. The input of the discriminative subnetwork consists of the channel-wise concatenation of the (augmented) input image and the output of the reconstructive subnetwork. The output of the discriminative subnetwork is an anomaly map that contains the predicted anomaly scores for each pixel location. The discriminative subnetwork is trained using Focal Loss. + +For optimal results, DRAEM requires specifying the path to a folder of image data that will be used as the source of the anomalous pixel regions in the simulated anomaly images. The path can be specified by editing the value of the model.anomaly_source_path parameter in the config.yaml file. The authors of the original paper recommend using the `DTD `_ dataset as anomaly source. + +Architecture +************ + +.. image:: ../../images/draem/architecture.png + :alt: DRAEM Architecture + +Usage +***** + +.. code-block:: bash + + $ python tools/train.py --model draem + + +.. automodule:: anomalib.models.draem.torch_model + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: anomalib.models.draem.lightning_model + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: anomalib.models.draem.anomaly_map + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/reference_guide/algorithms/fastflow.rst b/docs/source/reference_guide/algorithms/fastflow.rst new file mode 100644 index 0000000000..eaa84b482c --- /dev/null +++ b/docs/source/reference_guide/algorithms/fastflow.rst @@ -0,0 +1,40 @@ +Fast-Flow +--------- + +This is the implementation of the `FastFlow `_ paper. + +Model Type: Segmentation + +Description +*********** + +FastFlow is a two-dimensional normalizing flow-based probability distribution estimator. It can be used as a plug-in module with any deep feature extractor, such as ResNet and vision transformer, for unsupervised anomaly detection and localisation. In the training phase, FastFlow learns to transform the input visual feature into a tractable distribution, and in the inference phase, it assesses the likelihood of identifying anomalies. + +Architecture +************ + +.. image:: ../../images/fastflow/architecture.jpg + :alt: FastFlow Architecture + +Usage +***** + +.. code-block:: bash + + $ python tools/train.py --model fastflow + + +.. automodule:: anomalib.models.fastflow.torch_model + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: anomalib.models.fastflow.lightning_model + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: anomalib.models.fastflow.anomaly_map + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/reference_guide/algorithms/ganomaly.rst b/docs/source/reference_guide/algorithms/ganomaly.rst new file mode 100644 index 0000000000..1318ace180 --- /dev/null +++ b/docs/source/reference_guide/algorithms/ganomaly.rst @@ -0,0 +1,28 @@ +GANomaly +-------- + +This is the implementation of the `GANomaly `_ paper. + +Description +*********** + +GANomaly uses the conditional GAN approach to train a Generator to produce images of the normal data. This Generator consists of an encoder-decoder-encoder architecture to generate the normal images. The distance between the latent vector $z$ between the first encoder-decoder and the output vector $\hat{z}$ is minimized during training. + +The key idea here is that, during inference, when an anomalous image is passed through the first encoder the latent vector $z$ will not be able to capture the data correctly. This would leave to poor reconstruction $\hat{x}$ thus resulting in a very different $\hat{z}$. The difference between $z$ and $\hat{z}$ gives the anomaly score. + +Usage +***** + +.. code-block:: bash + + $ python tools/train.py --model ganomaly + +.. automodule:: anomalib.models.ganomaly.torch_model + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: anomalib.models.ganomaly.lightning_model + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/reference_guide/algorithms/index.rst b/docs/source/reference_guide/algorithms/index.rst new file mode 100644 index 0000000000..3c4ce0a271 --- /dev/null +++ b/docs/source/reference_guide/algorithms/index.rst @@ -0,0 +1,62 @@ +.. _available models: + +Algorithms +========== + +.. toctree:: + :maxdepth: 1 + :caption: List of Models: + + cflow + dfkde + dfm + draem + fastflow + ganomaly + padim + patchcore + reverse_distillation + stfpm + + +Feature extraction & (pre-trained) backbones +--------------------------------------------- + +Several models will use a pre-trained model to extract feature maps from its internal submodules -- the *backbone*. + +All the pre-trained backbones come from the package `PyTorch Image Models (timm) `_ and are wrapped by the class FeatureExtractor. + +For an introduction to timm, please check the `Getting Started with PyTorch Image Models (timm): A Practitioner’s Guide `_, in particular the introduction about models and the section about `feature extraction `_. + +More information at the `section "Multi-scale Feature Maps (Feature Pyramid)" in timm's docummentation about feature extraction `_. + +.. tip:: + + * Papers With Code has an interface to easily browse models available in timm: `https://paperswithcode.com/lib/timm `_ + + * You can also find them with the python package function timm.list_models("resnet*", pretrained=True) + +The backbone can be set in the config file, two examples below. + +.. warning:: + + Anomalib < v.0.4.0 + +.. code-block:: yaml + + model: + name: cflow + backbone: wide_resnet50_2 + pre_trained: true + +.. warning:: + + Anomalib > v.0.4.0 Beta - Subject to Change + +.. code-block:: yaml + + model: + class_path: anomalib.models.Cflow + init_args: + backbone: wide_resnet50_2 + pre_trained: true diff --git a/docs/source/reference_guide/algorithms/padim.rst b/docs/source/reference_guide/algorithms/padim.rst new file mode 100644 index 0000000000..4c74aadab6 --- /dev/null +++ b/docs/source/reference_guide/algorithms/padim.rst @@ -0,0 +1,41 @@ +Padim +------ + +This is the implementation of the `PaDiM `_ paper. + +Model Type: Segmentation + +Description +*********** + +PaDiM is a patch based algorithm. It relies on a pre-trained CNN feature extractor. The image is broken into patches and embeddings are extracted from each patch using different layers of the feature extractors. The activation vectors from different layers are concatenated to get embedding vectors carrying information from different semantic levels and resolutions. This helps encode fine grained and global contexts. However, since the generated embedding vectors may carry redundant information, dimensions are reduced using random selection. A multivariate gaussian distribution is generated for each patch embedding across the entire training batch. Thus, for each patch of the set of training images, we have a different multivariate gaussian distribution. These gaussian distributions are represented as a matrix of gaussian parameters. + +During inference, Mahalanobis distance is used to score each patch position of the test image. It uses the inverse of the covariance matrix calculated for the patch during training. The matrix of Mahalanobis distances forms the anomaly map with higher scores indicating anomalous regions. + +Architecture +************ + +.. image:: ../../images/padim/architecture.jpg + :alt: PaDiM Architecture + +Usage +***** + +.. code-block:: bash + + $ python tools/train.py --model padim + +.. automodule:: anomalib.models.padim.torch_model + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: anomalib.models.padim.lightning_model + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: anomalib.models.padim.anomaly_map + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/reference_guide/algorithms/patchcore.rst b/docs/source/reference_guide/algorithms/patchcore.rst new file mode 100644 index 0000000000..f60e673908 --- /dev/null +++ b/docs/source/reference_guide/algorithms/patchcore.rst @@ -0,0 +1,42 @@ +PatchCore +---------- + +This is the implementation of the `PatchCore `_ paper. + +Model Type: Segmentation + +Description +*********** + +The PatchCore algorithm is based on the idea that an image can be classified as anomalous as soon as a single patch is anomalous. The input image is tiled. These tiles act as patches which are fed into the neural network. It consists of a single pre-trained network which is used to extract "mid" level features patches. The "mid" level here refers to the feature extraction layer of the neural network model. Lower level features are generally too broad and higher level features are specific to the dataset the model is trained on. The features extracted during training phase are stored in a memory bank of neighbourhood aware patch level features. + +During inference this memory bank is coreset subsampled. Coreset subsampling generates a subset which best approximates the structure of the available set and allows for approximate solution finding. This subset helps reduce the search cost associated with nearest neighbour search. The anomaly score is taken as the maximum distance between the test patch in the test patch collection to each respective nearest neighbour. + +Architecture +************ + +.. image:: ../../images/patchcore/architecture.jpg + :alt: PatchCore Architecture + +Usage +***** + +.. code-block:: bash + + $ python tools/train.py --model patchcore + + +.. automodule:: anomalib.models.patchcore.torch_model + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: anomalib.models.patchcore.lightning_model + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: anomalib.models.patchcore.anomaly_map + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/reference_guide/algorithms/reverse_distillation.rst b/docs/source/reference_guide/algorithms/reverse_distillation.rst new file mode 100644 index 0000000000..573506c1d1 --- /dev/null +++ b/docs/source/reference_guide/algorithms/reverse_distillation.rst @@ -0,0 +1,42 @@ +Reverse Distillation +-------------------- + +This is the implementation of the `Anomaly Detection via Reverse Distillation from One-Class Embedding `_ paper. + +Model Type: Segmentation + +Description +*********** + +Reverse Distillation model consists of three networks. The first is a pre-trained feature extractor (E). The next two are the one-class bottleneck embedding (OCBE) and the student decoder network (D). The backbone E is a ResNet model pre-trained on ImageNet dataset. During the forward pass, features from three ResNet block are extracted. These features are encoded by concatenating the three feature maps using the multi-scale feature fusion block of OCBE and passed to the decoder D. The decoder network is symmetrical to the feature extractor but reversed. During training, outputs from these symmetrical blocks are forced to be similar to the corresponding feature extractor layers by using cosine distance as the loss metric. + +During testing, a similar step is followed but this time the cosine distance between the feature maps is used to indicate the presence of anomalies. The distance maps from all the three layers are up-sampled to the image size and added (or multiplied) to produce the final feature map. Gaussian blur is applied to the output map to make it smoother. Finally, the anomaly map is generated by applying min-max normalization on the output map. + +Architecture +************ + +.. image:: ../../images/reverse_distillation/architecture.png + :alt: Reverse Distillation Architecture + +Usage +***** + +.. code-block:: bash + + $ python tools/train.py --model reverse_distillation + + +.. automodule:: anomalib.models.reverse_distillation.torch_model + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: anomalib.models.reverse_distillation.lightning_model + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: anomalib.models.reverse_distillation.anomaly_map + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/reference_guide/algorithms/stfpm.rst b/docs/source/reference_guide/algorithms/stfpm.rst new file mode 100644 index 0000000000..3dc54b13bf --- /dev/null +++ b/docs/source/reference_guide/algorithms/stfpm.rst @@ -0,0 +1,42 @@ +STFPM +------- + +This is the implementation of the `STFPM `_ paper. + +Model Type: Segmentation + +Description +*********** + +STFPM algorithm which consists of a pre-trained teacher network and a student network with identical architecture. The student network learns the distribution of anomaly-free images by matching the features with the counterpart features in the teacher network. Multi-scale feature matching is used to enhance robustness. This hierarchical feature matching enables the student network to receive a mixture of multi-level knowledge from the feature pyramid thus allowing for anomaly detection of various sizes. + +During inference, the feature pyramids of teacher and student networks are compared. Larger difference indicates a higher probability of anomaly occurrence. + +Architecture +************ + +.. image:: ../../images/stfpm/architecture.jpg + :alt: STFPM Architecture + +Usage +***** + +.. code-block:: bash + + $ python tools/train.py --model stfpm + + +.. automodule:: anomalib.models.stfpm.torch_model + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: anomalib.models.stfpm.lightning_model + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: anomalib.models.stfpm.anomaly_map + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/reference_guide/api/callbacks.rst b/docs/source/reference_guide/api/callbacks.rst new file mode 100644 index 0000000000..2e7751c23b --- /dev/null +++ b/docs/source/reference_guide/api/callbacks.rst @@ -0,0 +1,7 @@ +Callbacks +========= + +.. automodule:: anomalib.utils.callbacks + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/reference_guide/api/cli.rst b/docs/source/reference_guide/api/cli.rst new file mode 100644 index 0000000000..c1308a1bbf --- /dev/null +++ b/docs/source/reference_guide/api/cli.rst @@ -0,0 +1,7 @@ +CLI +=== + +.. automodule:: anomalib.utils.cli + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/reference_guide/api/config.rst b/docs/source/reference_guide/api/config.rst new file mode 100644 index 0000000000..867ec181bf --- /dev/null +++ b/docs/source/reference_guide/api/config.rst @@ -0,0 +1,7 @@ +Configuration +============= + +.. automodule:: anomalib.config + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/reference_guide/api/data/btech.rst b/docs/source/reference_guide/api/data/btech.rst new file mode 100644 index 0000000000..f0e680d14d --- /dev/null +++ b/docs/source/reference_guide/api/data/btech.rst @@ -0,0 +1,7 @@ +Btech Dataset +============= + +.. automodule:: anomalib.data.btech + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/reference_guide/api/data/folder.rst b/docs/source/reference_guide/api/data/folder.rst new file mode 100644 index 0000000000..80d15deab4 --- /dev/null +++ b/docs/source/reference_guide/api/data/folder.rst @@ -0,0 +1,7 @@ +Folder Dataset +============== + +.. automodule:: anomalib.data.folder + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/reference_guide/api/data/index.rst b/docs/source/reference_guide/api/data/index.rst new file mode 100644 index 0000000000..d06f866920 --- /dev/null +++ b/docs/source/reference_guide/api/data/index.rst @@ -0,0 +1,11 @@ +Dataset +======= + +.. toctree:: + :maxdepth: 3 + :caption: Contents: + + btech + mvtec + folder + utils diff --git a/docs/source/reference_guide/api/data/mvtec.rst b/docs/source/reference_guide/api/data/mvtec.rst new file mode 100644 index 0000000000..6b3d406d41 --- /dev/null +++ b/docs/source/reference_guide/api/data/mvtec.rst @@ -0,0 +1,7 @@ +MVTec Dataset +============= + +.. automodule:: anomalib.data.mvtec + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/reference_guide/api/data/utils.rst b/docs/source/reference_guide/api/data/utils.rst new file mode 100644 index 0000000000..b0f98a45c4 --- /dev/null +++ b/docs/source/reference_guide/api/data/utils.rst @@ -0,0 +1,7 @@ +Utils +===== + +.. automodule:: anomalib.data.utils + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/reference_guide/api/index.rst b/docs/source/reference_guide/api/index.rst new file mode 100644 index 0000000000..b7bb2b8cdc --- /dev/null +++ b/docs/source/reference_guide/api/index.rst @@ -0,0 +1,16 @@ +API Reference +============= + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + cli + config + data/index + model/index + post_processing + metrics + loggers + sweep + callbacks diff --git a/docs/source/reference_guide/api/loggers.rst b/docs/source/reference_guide/api/loggers.rst new file mode 100644 index 0000000000..b896d04fc2 --- /dev/null +++ b/docs/source/reference_guide/api/loggers.rst @@ -0,0 +1,7 @@ +Loggers +======= + +.. automodule:: anomalib.utils.loggers + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/reference_guide/api/metrics.rst b/docs/source/reference_guide/api/metrics.rst new file mode 100644 index 0000000000..24a8d12ec9 --- /dev/null +++ b/docs/source/reference_guide/api/metrics.rst @@ -0,0 +1,7 @@ +Metrics +======= + +.. automodule:: anomalib.utils.metrics + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/reference_guide/api/model/base.rst b/docs/source/reference_guide/api/model/base.rst new file mode 100644 index 0000000000..1a32177c7d --- /dev/null +++ b/docs/source/reference_guide/api/model/base.rst @@ -0,0 +1,7 @@ +Base Model +========== + +.. automodule:: anomalib.models.components.base + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/reference_guide/api/model/dimensionality_reduction.rst b/docs/source/reference_guide/api/model/dimensionality_reduction.rst new file mode 100644 index 0000000000..2de14f385d --- /dev/null +++ b/docs/source/reference_guide/api/model/dimensionality_reduction.rst @@ -0,0 +1,7 @@ +Dimensionality Reduction +======================== + +.. automodule:: anomalib.models.components.dimensionality_reduction + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/reference_guide/api/model/feature_extractors.rst b/docs/source/reference_guide/api/model/feature_extractors.rst new file mode 100644 index 0000000000..712127f4fd --- /dev/null +++ b/docs/source/reference_guide/api/model/feature_extractors.rst @@ -0,0 +1,7 @@ +Feature Extractors +================== + +.. automodule:: anomalib.models.components.feature_extractors + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/reference_guide/api/model/filters.rst b/docs/source/reference_guide/api/model/filters.rst new file mode 100644 index 0000000000..d8719300f1 --- /dev/null +++ b/docs/source/reference_guide/api/model/filters.rst @@ -0,0 +1,7 @@ +Filters +======= + +.. automodule:: anomalib.models.components.filters + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/reference_guide/api/model/index.rst b/docs/source/reference_guide/api/model/index.rst new file mode 100644 index 0000000000..f151fce5e1 --- /dev/null +++ b/docs/source/reference_guide/api/model/index.rst @@ -0,0 +1,14 @@ +Model +===== + +.. toctree:: + :maxdepth: 3 + :caption: Contents: + + base + dimensionality_reduction + feature_extractors + filters + layers + sampling + stats diff --git a/docs/source/reference_guide/api/model/layers.rst b/docs/source/reference_guide/api/model/layers.rst new file mode 100644 index 0000000000..47b5a0088a --- /dev/null +++ b/docs/source/reference_guide/api/model/layers.rst @@ -0,0 +1,7 @@ +Layers +====== + +.. automodule:: anomalib.models.components.layers + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/reference_guide/api/model/sampling.rst b/docs/source/reference_guide/api/model/sampling.rst new file mode 100644 index 0000000000..ff134e3119 --- /dev/null +++ b/docs/source/reference_guide/api/model/sampling.rst @@ -0,0 +1,7 @@ +Sampling +====== + +.. automodule:: anomalib.models.components.sampling + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/reference_guide/api/model/stats.rst b/docs/source/reference_guide/api/model/stats.rst new file mode 100644 index 0000000000..0d6f431a56 --- /dev/null +++ b/docs/source/reference_guide/api/model/stats.rst @@ -0,0 +1,7 @@ +Stats +===== + +.. automodule:: anomalib.models.components.stats + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/reference_guide/api/post_processing.rst b/docs/source/reference_guide/api/post_processing.rst new file mode 100644 index 0000000000..b1d23ea237 --- /dev/null +++ b/docs/source/reference_guide/api/post_processing.rst @@ -0,0 +1,7 @@ +Post Processing +=============== + +.. automodule:: anomalib.post_processing + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/reference_guide/api/sweep.rst b/docs/source/reference_guide/api/sweep.rst new file mode 100644 index 0000000000..f2543f7e4b --- /dev/null +++ b/docs/source/reference_guide/api/sweep.rst @@ -0,0 +1,7 @@ +Sweep +======= + +.. automodule:: anomalib.utils.sweep + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/reference_guide/index.rst b/docs/source/reference_guide/index.rst new file mode 100644 index 0000000000..c7817dadc9 --- /dev/null +++ b/docs/source/reference_guide/index.rst @@ -0,0 +1,9 @@ +Reference Guide +=============== + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + api/index + algorithms/index diff --git a/docs/source/research/papers.rst b/docs/source/research/papers.rst deleted file mode 100644 index 5a7abb26e9..0000000000 --- a/docs/source/research/papers.rst +++ /dev/null @@ -1,55 +0,0 @@ -Awesome Anomaly Papers: Benchmark -================================= - -This repo lists awesome anomaly detection papers. There could be some -overlap between `Papers with Code`_ as we aim to list top-performing -papers here. - -============== ==================== ================ ================== -Model Featured in Anomalib Segmentation AUC Classification AUC -============== ==================== ================ ================== -`PatchCore`_ ✅ 98.1 99.1 -`PaDiM`_ ✅ 97.5 97.9 -`STFPM`_ ✅ 97.0 95.5 -`InTra`_ 🔜 96.9 95.9 -`SPADE`_ 🔜 96.5 85.5 -`CutPaste`_ ❌ 96.0 95.2 -`FCDD - SS`_ ❌ 96.0 - -`Patch-SVDD`_ ❌ 95.7 92.1 -`DFR`_ ❌ 95.5 93.8 -`IGD`_ ❌ 93.0 93.4 -`RotPred`_ ❌ 93.0 86.3 -`Cavga Weak`_ ❌ 93.0 - -`FCDD`_ ❌ 92.0 88.0 -`RIAD`_ ❌ 92.0 - -`DisAug CLR`_ ❌ 90.4 86.5 -`CAVGA`_ ❌ 89.0 - -`AE-SSIM`_ 🔜 87.0 - -`CutPaste FT`_ ❌ - 97.1 -`Gaussian AD`_ ❌ - 95.8 -`DifferNet`_ ❌ - 94.9 -`MOCCA`_ ❌ 87.5 88.0 -============== ==================== ================ ================== - -.. _Papers with Code: https://paperswithcode.com/sota/anomaly-detection-on-mvtec-ad -.. _PatchCore: https://paperswithcode.com/paper/towards-total-recall-in-industrial-anomaly -.. _PaDiM: https://paperswithcode.com/paper/padim-a-patch-distribution-modeling-framework -.. _STFPM: https://paperswithcode.com/paper/student-teacher-feature-pyramid-matching-for -.. _InTra: https://paperswithcode.com/paper/inpainting-transformer-for-anomaly-detection -.. _SPADE: https://paperswithcode.com/paper/sub-image-anomaly-detection-with-deep-pyramid -.. _CutPaste: https://paperswithcode.com/paper/cutpaste-self-supervised-learning-for-anomaly -.. _FCDD - SS: https://paperswithcode.com/paper/explainable-deep-one-class-classification -.. _Patch-SVDD: https://paperswithcode.com/paper/patch-svdd-patch-level-svdd-for-anomaly -.. _DFR: https://paperswithcode.com/paper/dfr-deep-feature-reconstruction-for -.. _IGD: https://paperswithcode.com/paper/unsupervised-anomaly-detection-and -.. _RotPred: https://paperswithcode.com/paper/learning-and-evaluating-representations-for-1 -.. _Cavga Weak: https://paperswithcode.com/paper/attention-guided-anomaly-detection-and -.. _FCDD: https://paperswithcode.com/paper/explainable-deep-one-class-classification -.. _RIAD: https://paperswithcode.com/paper/reconstruction-by-inpainting-for-visual -.. _DisAug CLR: https://paperswithcode.com/paper/learning-and-evaluating-representations-for-1 -.. _CAVGA: https://paperswithcode.com/paper/attention-guided-anomaly-detection-and -.. _AE-SSIM: https://paperswithcode.com/paper/mvtec-ad-a-comprehensive-real-world-dataset -.. _CutPaste FT: https://paperswithcode.com/paper/cutpaste-self-supervised-learning-for-anomaly -.. _Gaussian AD: https://paperswithcode.com/paper/modeling-the-distribution-of-normal-data-in -.. _DifferNet: https://paperswithcode.com/paper/same-same-but-differnet-semi-supervised -.. _MOCCA: https://paperswithcode.com/paper/mocca-multi-layer-one-class-classification diff --git a/docs/source/guides/benchmarking.rst b/docs/source/tutorials/benchmarking.rst similarity index 100% rename from docs/source/guides/benchmarking.rst rename to docs/source/tutorials/benchmarking.rst diff --git a/docs/source/guides/export.rst b/docs/source/tutorials/export.rst similarity index 100% rename from docs/source/guides/export.rst rename to docs/source/tutorials/export.rst diff --git a/docs/source/guides/hyperparameter_optimization.rst b/docs/source/tutorials/hyperparameter_optimization.rst similarity index 90% rename from docs/source/guides/hyperparameter_optimization.rst rename to docs/source/tutorials/hyperparameter_optimization.rst index 92aa5da882..9c5ddd527e 100644 --- a/docs/source/guides/hyperparameter_optimization.rst +++ b/docs/source/tutorials/hyperparameter_optimization.rst @@ -8,7 +8,7 @@ The default configuration for the models will not always work on a new dataset. YAML file ********** -A Sample configuration files for hyperparameter optimization with Comet is provided at ``tools/hpo/config/comet_sweep.yaml`` and reproduced below: +A Sample configuration files for hyperparameter optimization with Comet is provided at ``tools/hpo/configs/comet.yaml`` and reproduced below: .. code-block:: yaml @@ -30,7 +30,7 @@ A Sample configuration files for hyperparameter optimization with Comet is provi The maxCombo defines the total number of experiments to run. The algorithm is the optimization method to be used. The metric is the metric to be used to evaluate the performance of the model. The parameters are the hyperparameters to be optimized. For details on other possible configurations with Comet's Optimizer , refer to the `Comet's `_ documentation. -A sample configuration file for hyperparameter optimization with Weights and Bias is provided at ``tools/hpo/config/wandb_sweep.yaml`` and is reproduced below: +A sample configuration file for hyperparameter optimization with Weights and Bias is provided at ``tools/hpo/configs/wandb.yaml`` and is reproduced below: .. code-block:: yaml @@ -65,14 +65,14 @@ To run the hyperparameter optimization, use the following command: python tools/hpo/sweep.py --model padim \ --model_config ./path_to_config.yaml \ - --sweep_config tools/hpo/config/comet_sweep.yaml + --sweep_config tools/hpo/configs/comet.yaml In case ``model_config`` is not provided, the script looks at the default config location for that model. .. code-block:: bash - python tools/hpo/sweep.py --sweep_config tools/hpo/config/comet_sweep.yaml + python tools/hpo/sweep.py --sweep_config tools/hpo/configs/comet.yaml Sample Output ************** diff --git a/docs/source/tutorials/index.rst b/docs/source/tutorials/index.rst new file mode 100644 index 0000000000..1dc3a8a112 --- /dev/null +++ b/docs/source/tutorials/index.rst @@ -0,0 +1,14 @@ +Tutorials +========= + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + installation + training + inference + export + benchmarking + logging + hyperparameter_optimization diff --git a/docs/source/guides/inference.rst b/docs/source/tutorials/inference.rst similarity index 99% rename from docs/source/guides/inference.rst rename to docs/source/tutorials/inference.rst index b99060d549..1846e696fe 100644 --- a/docs/source/guides/inference.rst +++ b/docs/source/tutorials/inference.rst @@ -6,7 +6,7 @@ Anomalib provides entrypoint scripts for using a trained model to generate predi PyTorch (Lightning) Inference -============== +============================= The entrypoint script in ``tools/inference/lightning.py`` can be used to run inference with a trained PyTorch model. The script runs inference by loading a previously trained model into a PyTorch Lightning trainer and running the ``predict sequence``. The entrypoint script has several command line arguments that can be used to configure inference: +---------------------+----------+---------------------------------------------------------------------------------+ @@ -34,7 +34,7 @@ This will run inference on the specified image file or all images in the folder. OpenVINO Inference -============== +================== To run OpenVINO inference, first make sure that your model has been exported to the OpenVINO IR format. Once the model has been exported, OpenVINO inference can be triggered by running the OpenVINO entrypoint script in ``tools/inference/openvino.py``. The command line arguments are very similar to PyTorch inference entrypoint script: +-----------+----------+--------------------------------------------------------------------------------------+ @@ -63,7 +63,7 @@ Similar to PyTorch inference, the visualization results will be displayed on the Gradio Inference -============== +================ The gradio inference is supported for both PyTorch and OpenVINO models. diff --git a/docs/source/tutorials/installation.rst b/docs/source/tutorials/installation.rst new file mode 100644 index 0000000000..bf683fa369 --- /dev/null +++ b/docs/source/tutorials/installation.rst @@ -0,0 +1,26 @@ +Installation +=============== + +The repo is thoroughly tested based on the following configuration. + +* Ubuntu 20.04 + +* NVIDIA GeForce RTX 3090 + +You will need +`Anaconda `__ installed on +your system before proceeding with the Anomaly Library install. + +After downloading the Anomaly Library, extract the files and navigate to +the extracted location. + +To perform a development install, run the following: + +.. code-block:: bash + + yes | conda create -n anomalib python=3.8 + conda activate anomalib + pip install -r requirements.txt + + +Optionally, if you prefer using a Docker image for development, refer to the guide :ref:`developing_on_docker` diff --git a/docs/source/guides/logging.rst b/docs/source/tutorials/logging.rst similarity index 100% rename from docs/source/guides/logging.rst rename to docs/source/tutorials/logging.rst diff --git a/docs/source/tutorials/training.rst b/docs/source/tutorials/training.rst new file mode 100644 index 0000000000..8a35dcd70c --- /dev/null +++ b/docs/source/tutorials/training.rst @@ -0,0 +1,32 @@ +Training +============== + +By default +`python tools/train.py `__ +runs `STFPM `__ model +`MVTec AD `__ +``leather`` dataset. + +.. code-block:: bash + + python tools/train.py # Train STFPM on MVTec AD leather + +Training a model on a specific dataset and category requires further +configuration. Each model has its own configuration file, +`config.yaml `__, +which contains data, model and training configurable parameters. To +train a specific model on a specific dataset and category, the config +file is to be provided: + +.. code-block:: bash + + python tools/train.py --config + +Alternatively, a model name could also be provided as an argument, where +the scripts automatically finds the corresponding config file. + +.. code-block:: bash + + python tools/train.py --model stfpm + +To see a list of currently supported models, refer to page: :ref:`available models` diff --git a/notebooks/100_datamodules/101_btech.ipynb b/notebooks/100_datamodules/101_btech.ipynb index b907c2ce1c..fe2bafc5cc 100644 --- a/notebooks/100_datamodules/101_btech.ipynb +++ b/notebooks/100_datamodules/101_btech.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## BeanTech Anomaly Detection Dataset" + "## Use BeanTech Dataset via API" ] }, { @@ -564,11 +564,8 @@ } ], "metadata": { - "interpreter": { - "hash": "f26beec5b578f06009232863ae217b956681fd13da2e828fa5a0ecf8cf2ccd29" - }, "kernelspec": { - "display_name": "Python 3.8.12 ('anomalib')", + "display_name": "Python 3.8.13 64-bit ('anomalib')", "language": "python", "name": "python3" }, @@ -582,9 +579,14 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.12" + "version": "3.8.13" }, - "orig_nbformat": 4 + "orig_nbformat": 4, + "vscode": { + "interpreter": { + "hash": "ae223df28f60859a2f400fae8b3a1034248e0a469f5599fd9a89c32908ed7a84" + } + } }, "nbformat": 4, "nbformat_minor": 2 diff --git a/notebooks/100_datamodules/102_mvtec.ipynb b/notebooks/100_datamodules/102_mvtec.ipynb index c49b9d88b7..d626980d35 100644 --- a/notebooks/100_datamodules/102_mvtec.ipynb +++ b/notebooks/100_datamodules/102_mvtec.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## MVTec AD" + "## Use MVTec AD Dataset via API" ] }, { @@ -555,11 +555,8 @@ } ], "metadata": { - "interpreter": { - "hash": "f26beec5b578f06009232863ae217b956681fd13da2e828fa5a0ecf8cf2ccd29" - }, "kernelspec": { - "display_name": "Python 3.8.12 ('anomalib')", + "display_name": "Python 3.8.13 64-bit ('anomalib')", "language": "python", "name": "python3" }, @@ -573,9 +570,14 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.12" + "version": "3.8.13" }, - "orig_nbformat": 4 + "orig_nbformat": 4, + "vscode": { + "interpreter": { + "hash": "ae223df28f60859a2f400fae8b3a1034248e0a469f5599fd9a89c32908ed7a84" + } + } }, "nbformat": 4, "nbformat_minor": 2 diff --git a/notebooks/100_datamodules/103_folder.ipynb b/notebooks/100_datamodules/103_folder.ipynb index a2e2b26a14..9e229e2495 100644 --- a/notebooks/100_datamodules/103_folder.ipynb +++ b/notebooks/100_datamodules/103_folder.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Folder (for Custom Datasets)\n", + "## Use Folder Dataset (for Custom Datasets) via API\n", "\n", "Here we show how one can utilize custom datasets to train anomalib models. A custom dataset in this model can be of the following types:\n", "\n", @@ -778,11 +778,8 @@ } ], "metadata": { - "interpreter": { - "hash": "f26beec5b578f06009232863ae217b956681fd13da2e828fa5a0ecf8cf2ccd29" - }, "kernelspec": { - "display_name": "Python 3.8.12 ('anomalib')", + "display_name": "Python 3.8.13 64-bit ('anomalib')", "language": "python", "name": "python3" }, @@ -796,9 +793,14 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.12" + "version": "3.8.13" }, - "orig_nbformat": 4 + "orig_nbformat": 4, + "vscode": { + "interpreter": { + "hash": "ae223df28f60859a2f400fae8b3a1034248e0a469f5599fd9a89c32908ed7a84" + } + } }, "nbformat": 4, "nbformat_minor": 2 diff --git a/notebooks/200_models/201_fastflow.ipynb b/notebooks/200_models/201_fastflow.ipynb index 57e5d792b9..8832031f28 100644 --- a/notebooks/200_models/201_fastflow.ipynb +++ b/notebooks/200_models/201_fastflow.ipynb @@ -8,7 +8,7 @@ } }, "source": [ - "# FastFlow\n", + "# Train a Model via API\n", "\n", "This notebook demonstrates how to train, test and infer the FastFlow model via Anomalib API. Compared to the CLI entrypoints such as \\`tools/\\.py, the API offers more flexibility such as modifying the existing model or designing custom approaches.\n", "\n", diff --git a/requirements/docs.txt b/requirements/docs.txt index d7b01d6bcd..7804625b80 100644 --- a/requirements/docs.txt +++ b/requirements/docs.txt @@ -3,3 +3,4 @@ myst-parser sphinx>=4.1.2 sphinx-autoapi sphinxemoji==0.1.8 +nbsphinx>=0.8.9 diff --git a/tools/hpo/configs/comet_sweep.yaml b/tools/hpo/configs/comet.yaml similarity index 100% rename from tools/hpo/configs/comet_sweep.yaml rename to tools/hpo/configs/comet.yaml diff --git a/tools/hpo/configs/wandb_sweep.yaml b/tools/hpo/configs/wandb.yaml similarity index 100% rename from tools/hpo/configs/wandb_sweep.yaml rename to tools/hpo/configs/wandb.yaml diff --git a/tools/hpo/sweep.py b/tools/hpo/sweep.py index df607a24de..2426d01325 100644 --- a/tools/hpo/sweep.py +++ b/tools/hpo/sweep.py @@ -100,7 +100,7 @@ def run(self): comet_logger = CometLogger() # allow pytorch-lightning to use the experiment from optimizer - comet_logger._experiment = exp # pylint: disable=W0212 + comet_logger._experiment = exp # pylint: disable=protected-access run_params = exp.params for param in run_params.keys(): set_in_nested_config(self.config, param.split("."), run_params[param]) From d80d37db79bee58ff69fe856fac8579f7c6340ac Mon Sep 17 00:00:00 2001 From: Ashwin Vaidya Date: Wed, 28 Sep 2022 06:26:02 +0200 Subject: [PATCH 19/38] =?UTF-8?q?=F0=9F=93=9D=20=F0=9F=93=8A=20Add=20noteb?= =?UTF-8?q?ook=20for=20hpo=20(#592)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add notebook for hpo * Reference notebook in docs Co-authored-by: Ashwin Vaidya --- docs/source/how_to_guides/index.rst | 1 + .../300_benchmarking/302_hpo_wandb.ipynb | 389 ++++++++++++++++++ notebooks/README.md | 7 +- 3 files changed, 394 insertions(+), 3 deletions(-) create mode 100644 notebooks/300_benchmarking/302_hpo_wandb.ipynb diff --git a/docs/source/how_to_guides/index.rst b/docs/source/how_to_guides/index.rst index 26cb62a618..b9a6bc07d5 100644 --- a/docs/source/how_to_guides/index.rst +++ b/docs/source/how_to_guides/index.rst @@ -12,3 +12,4 @@ How to Guides notebooks/100_datamodules/103_folder.ipynb notebooks/200_models/201_fastflow.ipynb notebooks/300_benchmarking/301_benchmarking.ipynb + notebooks/300_benchmarking/302_hpo_wandb.ipynb diff --git a/notebooks/300_benchmarking/302_hpo_wandb.ipynb b/notebooks/300_benchmarking/302_hpo_wandb.ipynb new file mode 100644 index 0000000000..a9ede04007 --- /dev/null +++ b/notebooks/300_benchmarking/302_hpo_wandb.ipynb @@ -0,0 +1,389 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "owTxSDKzB9oO" + }, + "source": [ + "# Walkthrough on Hyperparameter Optimization using Weights and Biases" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "z3pB7jatCCxt" + }, + "source": [ + "## Clone and Install Anomalib" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "N8xxNHt8B3yZ", + "outputId": "7cd2713f-2bd2-4279-a600-e85d74e4db73" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Cloning into 'anomalib'...\n", + "remote: Enumerating objects: 21618, done.\u001b[K\n", + "remote: Counting objects: 100% (3157/3157), done.\u001b[K\n", + "remote: Compressing objects: 100% (1047/1047), done.\u001b[K\n", + "remote: Total 21618 (delta 1785), reused 2905 (delta 1622), pack-reused 18461\u001b[K\n", + "Receiving objects: 100% (21618/21618), 49.64 MiB | 35.03 MiB/s, done.\n", + "Resolving deltas: 100% (12658/12658), done.\n" + ] + } + ], + "source": [ + "!git clone https://github.com/openvinotoolkit/anomalib.git" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "yT-ZpzFXCGrU", + "outputId": "dcff1445-1b3b-42ca-de03-176c61a03127" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "/content/anomalib\n" + ] + } + ], + "source": [ + "%cd anomalib" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "w72Q-XRmCKau" + }, + "outputs": [], + "source": [ + "%pip install ." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "BkrI2pI5C6Z-" + }, + "source": [ + "> Note: Restart Runtime if promted by clicking the button at the end of the install logs" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "gsWKy1FUCXoQ" + }, + "source": [ + "## Download and Setup Dataset" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "4l_z-KK0C98t", + "outputId": "1b2dab18-5fcf-406e-d6e0-1ceab4e10c22" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "/content/anomalib\n" + ] + } + ], + "source": [ + "%cd /content/anomalib/" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "7E8nMjLVCXXg" + }, + "outputs": [], + "source": [ + "!wget https://openvinotoolkit.github.io/anomalib/_downloads/3f2af1d7748194b18c2177a34c03a2c4/hazelnut_toy.zip" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "id": "EaQ5BOS5DCA_" + }, + "outputs": [], + "source": [ + "!mkdir datasets && unzip hazelnut_toy.zip -d datasets/ > /dev/null" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Zhc8ai8VGKGZ" + }, + "source": [ + "## Creating training configuration\n", + "\n", + "Since we are using our [custom dataset](https://openvinotoolkit.github.io/anomalib/how_to_guides/train_custom_data.html) we need to modify the default configuration file. The following configuration is based on the one available here `anomalib/anomalib/models/stfpm/config.yaml`" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "id": "ZqhdaJElGkCi" + }, + "outputs": [], + "source": [ + "folder_stfpm = \"\"\"dataset:\n", + " name: hazelnut\n", + " format: folder\n", + " path: /content/anomalib/datasets/hazelnut_toy\n", + " normal_dir: good # name of the folder containing normal images.\n", + " abnormal_dir: colour # name of the folder containing abnormal images.\n", + " normal_test_dir: null # name of the folder containing normal test images.\n", + " task: segmentation # classification or segmentation\n", + " mask: /content/anomalib/datasets/hazelnut_toy/mask/colour # optional\n", + " extensions: null\n", + " split_ratio: 0.2 # ratio of the normal images that will be used to create a test split\n", + " image_size: 256\n", + " train_batch_size: 32\n", + " test_batch_size: 32\n", + " num_workers: 8\n", + " transform_config:\n", + " train: null\n", + " val: null\n", + " create_validation_set: false\n", + " tiling:\n", + " apply: false\n", + " tile_size: null\n", + " stride: null\n", + " remove_border_count: 0\n", + " use_random_tiling: False\n", + " random_tile_count: 16\n", + "\n", + "model:\n", + " name: stfpm\n", + " backbone: resnet18\n", + " layers:\n", + " - layer1\n", + " - layer2\n", + " - layer3\n", + " lr: 0.4\n", + " momentum: 0.9\n", + " weight_decay: 0.0001\n", + " early_stopping:\n", + " patience: 3\n", + " metric: pixel_AUROC\n", + " mode: max\n", + " normalization_method: min_max # options: [null, min_max, cdf]\n", + "\n", + "metrics:\n", + " image:\n", + " - F1Score\n", + " - AUROC\n", + " pixel:\n", + " - F1Score\n", + " - AUROC\n", + " threshold:\n", + " image_default: 0\n", + " pixel_default: 0\n", + " adaptive: true\n", + "\n", + "visualization:\n", + " show_images: False # show images on the screen\n", + " save_images: False # save images to the file system\n", + " log_images: False # log images to the available loggers (if any)\n", + " image_save_path: null # path to which images will be saved\n", + " mode: full # options: [\"full\", \"simple\"]\n", + "\n", + "project:\n", + " seed: 0\n", + " path: ./results\n", + "\n", + "logging:\n", + " logger: [] # options: [comet, tensorboard, wandb, csv] or combinations.\n", + " log_graph: false # Logs the model graph to respective logger.\n", + "\n", + "optimization:\n", + " export_mode: null #options: onnx, openvino\n", + "# PL Trainer Args. Don't add extra parameter here.\n", + "trainer:\n", + " accelerator: auto # <\"cpu\", \"gpu\", \"tpu\", \"ipu\", \"hpu\", \"auto\">\n", + " accumulate_grad_batches: 1\n", + " amp_backend: native\n", + " auto_lr_find: false\n", + " auto_scale_batch_size: false\n", + " auto_select_gpus: false\n", + " benchmark: false\n", + " check_val_every_n_epoch: 1\n", + " default_root_dir: null\n", + " detect_anomaly: false\n", + " deterministic: false\n", + " devices: 1\n", + " enable_checkpointing: true\n", + " enable_model_summary: true\n", + " enable_progress_bar: true\n", + " fast_dev_run: false\n", + " gpus: null # Set automatically\n", + " gradient_clip_val: 0\n", + " ipus: null\n", + " limit_predict_batches: 1.0\n", + " limit_test_batches: 1.0\n", + " limit_train_batches: 1.0\n", + " limit_val_batches: 1.0\n", + " log_every_n_steps: 50\n", + " log_gpu_memory: null\n", + " max_epochs: 100\n", + " max_steps: -1\n", + " max_time: null\n", + " min_epochs: null\n", + " min_steps: null\n", + " move_metrics_to_cpu: false\n", + " multiple_trainloader_mode: max_size_cycle\n", + " num_nodes: 1\n", + " num_processes: null\n", + " num_sanity_val_steps: 0\n", + " overfit_batches: 0.0\n", + " plugins: null\n", + " precision: 32\n", + " profiler: null\n", + " reload_dataloaders_every_n_epochs: 0\n", + " replace_sampler_ddp: true\n", + " strategy: null\n", + " sync_batchnorm: false\n", + " tpu_cores: null\n", + " track_grad_norm: -1\n", + " val_check_interval: 1.0\n", + "\"\"\"\n", + "\n", + "with open(\"model_config.yaml\", \"w\", encoding=\"utf8\") as f:\n", + " f.writelines(folder_stfpm)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "R7broXB-EYnx" + }, + "source": [ + "## Create sweep configuration\n", + "\n", + "The following configuration file is based on the one available at `anomalib/tools/hpo/configs/stfpm.yaml`. For this example we will use the STFPM model." + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": { + "id": "D-7YuDU3G_tG" + }, + "outputs": [], + "source": [ + "sweep_config = \"\"\"observation_budget: 10\n", + "method: bayes\n", + "metric:\n", + " name: pixel_AUROC\n", + " goal: maximize\n", + "parameters:\n", + " dataset:\n", + " category: capsule\n", + " image_size:\n", + " values: [128, 256]\n", + " model:\n", + " backbone:\n", + " values: [resnet18, wide_resnet50_2]\n", + " lr:\n", + " min: 0.1\n", + " max: 0.9\n", + " momentum:\n", + " min: 0.1\n", + " max: 0.9\n", + "\"\"\"\n", + "\n", + "with open(\"sweep_config.yaml\", \"w\", encoding=\"utf8\") as f:\n", + " f.writelines(sweep_config)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "ZVM8GvxUDCfV" + }, + "outputs": [], + "source": [ + "!python ./tools/hpo/sweep.py --model stfpm --model_config model_config.yaml --sweep_config sweep_config.yaml" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "OhS88SuxFFzi" + }, + "source": [ + "While only a few parameters were shown in this example, you can call HPO on any of the parameters defined in the `model` and `dataset` section of the model configuration file." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "FX57TV8gFirU" + }, + "source": [ + "Congratulations 🎉 You have now successfully optimized a model on your dataset.\n", + "\n", + "To go into more detail, refer our documentation on [hyperparameter optimization](https://openvinotoolkit.github.io/anomalib/tutorials/hyperparameter_optimization.html)." + ] + } + ], + "metadata": { + "colab": { + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3.8.0 ('anomalib')", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python", + "version": "3.8.0" + }, + "vscode": { + "interpreter": { + "hash": "ba723bee1893023fba5911c5ba85dac05fe2496fa0862b3e274bad096c0e1e2a" + } + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/notebooks/README.md b/notebooks/README.md index c2f52b517c..a72eb6d3ca 100644 --- a/notebooks/README.md +++ b/notebooks/README.md @@ -22,6 +22,7 @@ ## 3. Benchmarking and Hyperparameter Optimization -| Notebook | GitHub | Colab | -| ------------ | ----------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Benchmarking | [301_benchmarking](300_benchmarking/301_benchmarking.ipynb) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/openvinotoolkit/anomalib/blob/main/notebooks/300_benchmarking/301_benchmarking.ipynb) | +| Notebook | GitHub | Colab | +| -------------- | ----------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Benchmarking | [301_benchmarking](300_benchmarking/301_benchmarking.ipynb) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/openvinotoolkit/anomalib/blob/main/notebooks/300_benchmarking/301_benchmarking.ipynb) | +| HPO with wandb | [302_hpo_wandb](300_benchmarking/302_hpo_wandb.ipynb) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/openvinotoolkit/anomalib/blob/main/notebooks/300_benchmarking/302_hpo_wandb.ipynb) | From 35df574dc7a6d3a6c5b54fd5e73c9572ce80ed69 Mon Sep 17 00:00:00 2001 From: Ashwin Vaidya Date: Thu, 29 Sep 2022 10:11:32 +0200 Subject: [PATCH 20/38] =?UTF-8?q?=F0=9F=90=9E=20Fix=20comet=20HPO=20(#597)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix comet hpo + refactoring + fix metriccallback in benchmarking * Move sweep runners + utils to anomalib Co-authored-by: Ashwin Vaidya --- .../utils => anomalib/utils/hpo}/__init__.py | 4 +- .../utils => anomalib/utils/hpo}/config.py | 0 anomalib/utils/hpo/runners.py | 132 ++++++++++++++++++ anomalib/utils/sweep/helpers/callbacks.py | 12 +- tools/hpo/sweep.py | 119 ++-------------- 5 files changed, 152 insertions(+), 115 deletions(-) rename {tools/hpo/utils => anomalib/utils/hpo}/__init__.py (58%) rename {tools/hpo/utils => anomalib/utils/hpo}/config.py (100%) create mode 100644 anomalib/utils/hpo/runners.py diff --git a/tools/hpo/utils/__init__.py b/anomalib/utils/hpo/__init__.py similarity index 58% rename from tools/hpo/utils/__init__.py rename to anomalib/utils/hpo/__init__.py index dd10e8ac48..66e6bd94ed 100644 --- a/tools/hpo/utils/__init__.py +++ b/anomalib/utils/hpo/__init__.py @@ -3,6 +3,6 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -from .config import flatten_hpo_params +from .runners import CometSweep, WandbSweep -__all__ = ["flatten_hpo_params"] +__all__ = ["CometSweep", "WandbSweep"] diff --git a/tools/hpo/utils/config.py b/anomalib/utils/hpo/config.py similarity index 100% rename from tools/hpo/utils/config.py rename to anomalib/utils/hpo/config.py diff --git a/anomalib/utils/hpo/runners.py b/anomalib/utils/hpo/runners.py new file mode 100644 index 0000000000..d726756224 --- /dev/null +++ b/anomalib/utils/hpo/runners.py @@ -0,0 +1,132 @@ +"""Sweep Backends.""" + +# Copyright (C) 2022 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +from typing import Optional, Union + +import pytorch_lightning as pl +from comet_ml import Optimizer +from omegaconf import DictConfig, ListConfig, OmegaConf +from pytorch_lightning.loggers import CometLogger, WandbLogger + +import wandb +from anomalib.config import update_input_size_config +from anomalib.data import get_datamodule +from anomalib.models import get_model +from anomalib.utils.sweep import ( + flatten_sweep_params, + get_sweep_callbacks, + set_in_nested_config, +) + +from .config import flatten_hpo_params + + +class WandbSweep: + """wandb sweep. + + Args: + config (DictConfig): Original model configuration. + sweep_config (DictConfig): Sweep configuration. + entity (str, optional): Username or workspace to send the project to. Defaults to None. + """ + + def __init__( + self, + config: Union[DictConfig, ListConfig], + sweep_config: Union[DictConfig, ListConfig], + entity: Optional[str] = None, + ) -> None: + self.config = config + self.sweep_config = sweep_config + self.observation_budget = sweep_config.observation_budget + self.entity = entity + if "observation_budget" in self.sweep_config.keys(): + # this instance check is to silence mypy. + if isinstance(self.sweep_config, DictConfig): + self.sweep_config.pop("observation_budget") + + def run(self): + """Run the sweep.""" + flattened_hpo_params = flatten_hpo_params(self.sweep_config.parameters) + self.sweep_config.parameters = flattened_hpo_params + sweep_id = wandb.sweep( + OmegaConf.to_object(self.sweep_config), + project=f"{self.config.model.name}_{self.config.dataset.name}", + entity=self.entity, + ) + wandb.agent(sweep_id, function=self.sweep, count=self.observation_budget) + + def sweep(self): + """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 + + for param in sweep_config.keys(): + set_in_nested_config(self.config, param.split("."), sweep_config[param]) + config = update_input_size_config(self.config) + + model = get_model(config) + datamodule = get_datamodule(config) + callbacks = get_sweep_callbacks(config) + + # Disable saving checkpoints as all checkpoints from the sweep will get uploaded + config.trainer.checkpoint_callback = False + + trainer = pl.Trainer(**config.trainer, logger=wandb_logger, callbacks=callbacks) + trainer.fit(model, datamodule=datamodule) + + +class CometSweep: + """comet sweep. + + Args: + config (DictConfig): Original model configuration. + sweep_config (DictConfig): Sweep configuration. + entity (str, optional): Username or workspace to send the project to. Defaults to None. + """ + + def __init__( + self, + config: Union[DictConfig, ListConfig], + sweep_config: Union[DictConfig, ListConfig], + entity: Optional[str] = None, + ) -> None: + self.config = config + self.sweep_config = sweep_config + self.entity = entity + + def run(self): + """Run the sweep.""" + flattened_hpo_params = flatten_hpo_params(self.sweep_config.parameters) + self.sweep_config.parameters = flattened_hpo_params + + # comet's Optimizer takes dict as an input, not DictConfig + std_dict = OmegaConf.to_object(self.sweep_config) + + opt = Optimizer(std_dict) + + project_name = f"{self.config.model.name}_{self.config.dataset.name}" + + for experiment in opt.get_experiments(project_name=project_name): + comet_logger = CometLogger(workspace=self.entity) + + # allow pytorch-lightning to use the experiment from optimizer + comet_logger._experiment = experiment # pylint: disable=protected-access + run_params = experiment.params + for param in run_params.keys(): + # this check is needed as comet also returns model and sweep_config as keys + if param in self.sweep_config.parameters.keys(): + set_in_nested_config(self.config, param.split("."), run_params[param]) + config = update_input_size_config(self.config) + + model = get_model(config) + datamodule = get_datamodule(config) + callbacks = get_sweep_callbacks(config) + + # Disable saving checkpoints as all checkpoints from the sweep will get uploaded + config.trainer.checkpoint_callback = False + + trainer = pl.Trainer(**config.trainer, logger=comet_logger, callbacks=callbacks) + trainer.fit(model, datamodule=datamodule) diff --git a/anomalib/utils/sweep/helpers/callbacks.py b/anomalib/utils/sweep/helpers/callbacks.py index d4cf90676e..975224b9b7 100644 --- a/anomalib/utils/sweep/helpers/callbacks.py +++ b/anomalib/utils/sweep/helpers/callbacks.py @@ -33,11 +33,13 @@ def get_sweep_callbacks(config: Union[ListConfig, DictConfig]) -> List[Callback] config.metrics.threshold.pixel_default if "pixel_default" in config.metrics.threshold.keys() else None ) metrics_callback = MetricsConfigurationCallback( - config.metrics.threshold.adaptive, - image_threshold, - pixel_threshold, - image_metric_names, - pixel_metric_names, + adaptive_threshold=config.metrics.threshold.adaptive, + task=config.dataset.task, + default_image_threshold=image_threshold, + default_pixel_threshold=pixel_threshold, + image_metric_names=image_metric_names, + pixel_metric_names=pixel_metric_names, + normalization_method=config.model.normalization_method, ) callbacks.append(metrics_callback) diff --git a/tools/hpo/sweep.py b/tools/hpo/sweep.py index 2426d01325..771d93ac58 100644 --- a/tools/hpo/sweep.py +++ b/tools/hpo/sweep.py @@ -7,114 +7,11 @@ from pathlib import Path from typing import Union -import pytorch_lightning as pl -from comet_ml import Optimizer -from omegaconf import DictConfig, ListConfig, OmegaConf +from omegaconf import OmegaConf from pytorch_lightning import seed_everything -from pytorch_lightning.loggers import CometLogger, WandbLogger -from utils import flatten_hpo_params -import wandb -from anomalib.config import get_configurable_parameters, update_input_size_config -from anomalib.data import get_datamodule -from anomalib.models import get_model -from anomalib.utils.sweep import ( - flatten_sweep_params, - get_sweep_callbacks, - set_in_nested_config, -) - - -class WandbSweep: - """wandb sweep. - - Args: - config (DictConfig): Original model configuration. - sweep_config (DictConfig): Sweep configuration. - """ - - def __init__(self, config: Union[DictConfig, ListConfig], sweep_config: Union[DictConfig, ListConfig]) -> None: - self.config = config - self.sweep_config = sweep_config - self.observation_budget = sweep_config.observation_budget - if "observation_budget" in self.sweep_config.keys(): - # this instance check is to silence mypy. - if isinstance(self.sweep_config, DictConfig): - self.sweep_config.pop("observation_budget") - - def run(self): - """Run the sweep.""" - flattened_hpo_params = flatten_hpo_params(self.sweep_config.parameters) - self.sweep_config.parameters = flattened_hpo_params - sweep_id = wandb.sweep( - OmegaConf.to_object(self.sweep_config), - project=f"{self.config.model.name}_{self.config.dataset.name}", - ) - wandb.agent(sweep_id, function=self.sweep, count=self.observation_budget) - - def sweep(self): - """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 - - for param in sweep_config.keys(): - set_in_nested_config(self.config, param.split("."), sweep_config[param]) - config = update_input_size_config(self.config) - - model = get_model(config) - datamodule = get_datamodule(config) - callbacks = get_sweep_callbacks(config) - - # Disable saving checkpoints as all checkpoints from the sweep will get uploaded - config.trainer.checkpoint_callback = False - - trainer = pl.Trainer(**config.trainer, logger=wandb_logger, callbacks=callbacks) - trainer.fit(model, datamodule=datamodule) - - -class CometSweep: - """comet sweep. - - Args: - config (DictConfig): Original model configuration. - sweep_config (DictConfig): Sweep configuration. - """ - - def __init__(self, config: Union[DictConfig, ListConfig], sweep_config: Union[DictConfig, ListConfig]) -> None: - self.config = config - self.sweep_config = sweep_config - - def run(self): - """Run the sweep.""" - flattened_hpo_params = flatten_hpo_params(self.sweep_config.parameters) - self.sweep_config.parameters = flattened_hpo_params - - # comet's Optmizer cannot takes dict as an input, not DictConfig - std_dict = OmegaConf.to_object(self.sweep_config) - - opt = Optimizer(std_dict) - - project_name = f"{self.config.model.name}_{self.config.dataset.name}" - - for exp in opt.get_experiments(project_name=project_name): - comet_logger = CometLogger() - - # allow pytorch-lightning to use the experiment from optimizer - comet_logger._experiment = exp # pylint: disable=protected-access - run_params = exp.params - for param in run_params.keys(): - set_in_nested_config(self.config, param.split("."), run_params[param]) - config = update_input_size_config(self.config) - - model = get_model(config) - datamodule = get_datamodule(config) - callbacks = get_sweep_callbacks(config) - - # Disable saving checkpoints as all checkpoints from the sweep will get uploaded - config.trainer.checkpoint_callback = False - - trainer = pl.Trainer(**config.trainer, logger=comet_logger, callbacks=callbacks) - trainer.fit(model, datamodule=datamodule) +from anomalib.config import get_configurable_parameters +from anomalib.utils.hpo import CometSweep, WandbSweep def get_args(): @@ -123,6 +20,12 @@ def get_args(): parser.add_argument("--model", type=str, default="padim", help="Name of the algorithm to train/test") parser.add_argument("--model_config", type=Path, required=False, help="Path to a model config file") parser.add_argument("--sweep_config", type=Path, required=True, help="Path to sweep configuration") + parser.add_argument( + "--entity", + type=str, + required=False, + help="Username or workspace where you want to send your runs to. If not set, the default workspace is used.", + ) return parser.parse_args() @@ -138,7 +41,7 @@ def get_args(): # check hpo config structure to see whether it adheres to comet or wandb format sweep: Union[CometSweep, WandbSweep] if "spec" in hpo_config.keys(): - sweep = CometSweep(model_config, hpo_config) + sweep = CometSweep(model_config, hpo_config, entity=args.entity) else: - sweep = WandbSweep(model_config, hpo_config) + sweep = WandbSweep(model_config, hpo_config, entity=args.entity) sweep.run() From 2de548d257d54d9f8184390d915bc0f48eeb0f1b Mon Sep 17 00:00:00 2001 From: Ashwin Vaidya Date: Fri, 30 Sep 2022 10:50:17 +0200 Subject: [PATCH 21/38] =?UTF-8?q?=E2=9C=A8=20Replace=20keys=20from=20bench?= =?UTF-8?q?marking=20script=20(#595)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add util to convert single value to tuple * Update documentation * Remove unused pytest import * Address PR comments * update text in documentation Co-authored-by: Ashwin Vaidya --- anomalib/utils/sweep/config.py | 49 +++++++++++++++++--- docs/source/tutorials/benchmarking.rst | 33 ++++++++++++++ tests/pre_merge/utils/sweep/__init__.py | 4 ++ tests/pre_merge/utils/sweep/test_config.py | 52 ++++++++++++++++++++++ 4 files changed, 132 insertions(+), 6 deletions(-) create mode 100644 tests/pre_merge/utils/sweep/__init__.py create mode 100644 tests/pre_merge/utils/sweep/test_config.py diff --git a/anomalib/utils/sweep/config.py b/anomalib/utils/sweep/config.py index d491a661e2..b3a1a369a3 100644 --- a/anomalib/utils/sweep/config.py +++ b/anomalib/utils/sweep/config.py @@ -5,12 +5,48 @@ import itertools import operator +from collections.abc import Iterable, ValuesView from functools import reduce -from typing import Any, Generator, List +from typing import Any, Generator, List, Tuple from omegaconf import DictConfig +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 + are to be patched. Ideally this is useful when used with product. + + Example: + >>> params = DictConfig({ + "dataset.category": [ + "bottle", + "cable", + ], + "dataset.image_size": 224, + "model_name": ["padim"], + }) + >>> convert_to_tuple(params.values()) + [('bottle', 'cable'), (224,), ('padim',)] + >>> list(itertools.product(*convert_to_tuple(params.values()))) + [('bottle', 224, 'padim'), ('cable', 224, 'padim')] + + Args: + values: ValuesView: ValuesView object to be converted to a list of tuples. + + Returns: + List[Tuple]: List of tuples. + """ + return_list = [] + for value in values: + if isinstance(value, Iterable) and not isinstance(value, str): + return_list.append(tuple(value)) + else: + return_list.append((value,)) + return return_list + + def flatten_sweep_params(params_dict: DictConfig) -> DictConfig: """Flatten the nested parameters section of the config object. @@ -63,13 +99,14 @@ def get_run_config(params_dict: DictConfig) -> Generator[DictConfig, None, None] "child1": ['a', 'b', 'c'], "child2": [1, 2, 3] }, - "parent2":['model1', 'model2'] + "parent2":['model1', 'model2'], + "parent3": 'replacement_value' }) >>> for run_config in get_run_config(dummy_config): >>> print(run_config) - {'parent1.child1': 'a', 'parent1.child2': 1, 'parent2': 'model1'} - {'parent1.child1': 'a', 'parent1.child2': 1, 'parent2': 'model2'} - {'parent1.child1': 'a', 'parent1.child2': 2, 'parent2': 'model1'} + {'parent1.child1': 'a', 'parent1.child2': 1, 'parent2': 'model1', 'parent3': 'replacement_value'} + {'parent1.child1': 'a', 'parent1.child2': 1, 'parent2': 'model2', 'parent3': 'replacement_value'} + {'parent1.child1': 'a', 'parent1.child2': 2, 'parent2': 'model1', 'parent3': 'replacement_value'} ... Yields: @@ -77,7 +114,7 @@ def get_run_config(params_dict: DictConfig) -> Generator[DictConfig, None, None] and values for current run. """ params = flatten_sweep_params(params_dict) - combinations = list(itertools.product(*params.values())) + combinations = list(itertools.product(*convert_to_tuple(params.values()))) keys = params.keys() for combination in combinations: run_config = DictConfig({}) diff --git a/docs/source/tutorials/benchmarking.rst b/docs/source/tutorials/benchmarking.rst index cd844537e4..7e231af8e5 100644 --- a/docs/source/tutorials/benchmarking.rst +++ b/docs/source/tutorials/benchmarking.rst @@ -44,6 +44,39 @@ This configuration computes the throughput and performance metrics on CPU and GP seed: 0 image_size: 256 +Additionally, it is possible to pass a single value instead of an array for any specific parameter. This will overwrite the parameter in each of the model configs and thereby ensures that the parameter is kept constant between all runs in the sweep. For example, to ensure that the same dataset is used between runs the configuration file can be modified as shown below. + +.. code-block:: yaml + + seed: 42 + compute_openvino: false + hardware: + - cpu + - gpu + writer: + - comet + - wandb + - tensorboard + grid_search: + dataset: + name: hazelnut + format: folder + path: path/hazelnut_toy + normal_dir: good # name of the folder containing normal images. + abnormal_dir: colour # name of the folder containing abnormal images. + normal_test_dir: null + task: segmentation # classification or segmentation + mask: path/hazelnut_toy/mask/colour + extensions: .jpg + split_ratio: 0.2 + category: + - colour + - crack + image_size: [128, 256] + model_name: + - padim + - stfpm + By default, ``compute_openvino`` is set to ``False`` to support instances where OpenVINO requirements are not installed in the environment. Once installed, this flag can be set to ``True`` to get the throughput on OpenVINO optimized models. The ``writer`` parameter is optional and can be set to ``writer: []`` in case the user only requires a csv file without logging to each respective logger. It is a good practice to set a value of seed to ensure reproducibility across runs and thus, is set to a non-zero value by default. Once a configuration is decided, benchmarking can easily be performed by calling diff --git a/tests/pre_merge/utils/sweep/__init__.py b/tests/pre_merge/utils/sweep/__init__.py new file mode 100644 index 0000000000..f483c3d3f3 --- /dev/null +++ b/tests/pre_merge/utils/sweep/__init__.py @@ -0,0 +1,4 @@ +"""Test sweep utils.""" + +# Copyright (C) 2022 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 diff --git a/tests/pre_merge/utils/sweep/test_config.py b/tests/pre_merge/utils/sweep/test_config.py new file mode 100644 index 0000000000..53778116fc --- /dev/null +++ b/tests/pre_merge/utils/sweep/test_config.py @@ -0,0 +1,52 @@ +"""Test sweep config utils.""" + +# Copyright (C) 2022 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +from omegaconf import DictConfig + +from anomalib.utils.sweep.config import get_run_config, set_in_nested_config + + +class TestSweepConfig: + def test_get_run_config(self): + """Test whether the run config is returned correctly and patches the keys which have only one value.""" + dummy_config = DictConfig( + { + "parent1": {"child1": ["a", "b"], "child2": [1, 2]}, + "parent2": ["model1", "model2"], + "parent3": "replacement_value", + } + ) + run_config = list(get_run_config(dummy_config)) + expected_value = [ + {"parent1.child1": "a", "parent1.child2": 1, "parent2": "model1", "parent3": "replacement_value"}, + {"parent1.child1": "a", "parent1.child2": 1, "parent2": "model2", "parent3": "replacement_value"}, + {"parent1.child1": "a", "parent1.child2": 2, "parent2": "model1", "parent3": "replacement_value"}, + {"parent1.child1": "a", "parent1.child2": 2, "parent2": "model2", "parent3": "replacement_value"}, + {"parent1.child1": "b", "parent1.child2": 1, "parent2": "model1", "parent3": "replacement_value"}, + {"parent1.child1": "b", "parent1.child2": 1, "parent2": "model2", "parent3": "replacement_value"}, + {"parent1.child1": "b", "parent1.child2": 2, "parent2": "model1", "parent3": "replacement_value"}, + {"parent1.child1": "b", "parent1.child2": 2, "parent2": "model2", "parent3": "replacement_value"}, + ] + assert run_config == expected_value + + def set_in_nested_config(self): + dummy_config = DictConfig( + {"parent1": {"child1": ["a", "b", "c"], "child2": [1, 2, 3]}, "parent2": ["model1", "model2"]} + ) + + model_config = DictConfig( + { + "parent1": { + "child1": "e", + "child2": 4, + }, + "parent3": False, + } + ) + + for run_config in get_run_config(dummy_config): + for param in run_config.keys(): + set_in_nested_config(model_config, param.split("."), run_config[param]) + assert model_config == {"parent1": {"child1": "a", "child2": 1}, "parent3": False, "parent2": "model1"} From 8af241737892f6b9e14cacd09202c999777053ce Mon Sep 17 00:00:00 2001 From: Owais Ahmad Date: Mon, 10 Oct 2022 13:02:03 +0530 Subject: [PATCH 22/38] Update README.md (#623) Update older GitLab intel links to this repo --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a2433bc9f2..5dca967878 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,7 @@ pip install -e . ## ⚠️ Anomalib < v.0.4.0 -By default [`python tools/train.py`](https://gitlab-icv.inn.intel.com/algo_rnd_team/anomaly/-/blob/main/train.py) +By default [`python tools/train.py`](https://github.com/openvinotoolkit/anomalib/blob/main/tools/train.py) runs [PADIM](https://arxiv.org/abs/2011.08785) model on `leather` category from the [MVTec AD](https://www.mvtec.com/company/research/datasets/mvtec-ad) [(CC BY-NC-SA 4.0)](https://creativecommons.org/licenses/by-nc-sa/4.0/) dataset. ```bash @@ -86,7 +86,7 @@ python tools/train.py # Train PADIM on MVTec AD leather ``` Training a model on a specific dataset and category requires further configuration. Each model has its own configuration -file, [`config.yaml`](https://gitlab-icv.inn.intel.com/algo_rnd_team/anomaly/-/blob/main/padim/anomalib/models/padim/config.yaml) +file, [`config.yaml`](https://github.com/openvinotoolkit/anomalib/blob/main/configs/model/padim.yaml) , which contains data, model and training configurable parameters. To train a specific model on a specific dataset and category, the config file is to be provided: From 39d895a39a9a0d8ab25fe63434c20ba8532fc444 Mon Sep 17 00:00:00 2001 From: Ashwin Vaidya Date: Tue, 11 Oct 2022 16:30:31 +0200 Subject: [PATCH 23/38] =?UTF-8?q?=F0=9F=90=B3=20Containerize=20CI=20(#616)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add dockerfile + readme * Update workflow * break ci into two stages + add shared memory command to readme + dockerfile precommit check * Add images for cuda 10 and 11 * Address codacy issues --- .ci/README.md | 61 ++++++++++++++++++++++ .ci/cuda10.2.Dockerfile | 74 +++++++++++++++++++++++++++ Dockerfile => .ci/cuda11.4.Dockerfile | 54 +++++++++++-------- .devcontainer/devcontainer.json | 4 +- .dockerignore | 1 + .github/workflows/nightly.yml | 6 +-- .github/workflows/pre_merge.yml | 19 ++++--- .pre-commit-config.yaml | 7 +++ 8 files changed, 191 insertions(+), 35 deletions(-) create mode 100644 .ci/README.md create mode 100644 .ci/cuda10.2.Dockerfile rename Dockerfile => .ci/cuda11.4.Dockerfile (55%) diff --git a/.ci/README.md b/.ci/README.md new file mode 100644 index 0000000000..5536ce0bff --- /dev/null +++ b/.ci/README.md @@ -0,0 +1,61 @@ +# Guide to Setting up the CI using the Docker images + +## Steps + +1. Build the docker image using the Dockerfile in the .ci directory. + Make sure you are in the root directory of `anomalib`. + + ```bash + sudo docker build --build-arg HTTP_PROXY="$http_proxy" --build-arg \ + HTTPS_PROXY="$https_proxy" --build-arg NO_PROXY="$no_proxy" \ + . -t anomalib-ci -f .ci/cuda11.4.Dockerfile + ``` + + Here, `anomalib-ci` is the name of the image. + +1. Create and start a container + + ```bash + sudo docker run --gpus all \ + --shm-size=256M\ + -i -t --mount type=bind,source=,target=/home/user/datasets,readonly\ + -d --name anomalib-ci-container anomalib-ci + ``` + + Note: `--gpus all` is required for the container to have access to the GPUs. + `-d` flag ensure that the container is detached when it is created. + `mount` is required to ensure that tests have access to the dataset. + +1. Enter the container by + + ```bash + sudo docker exec -it anomalib-ci-container /bin/bash + ``` + +1. Install github actions runner in the container by navigating to [https://github.com/openvinotoolkit/anomalib/settings/actions/runners/new](https://github.com/openvinotoolkit/anomalib/settings/actions/runners/new) + + For example: + + ```bash + mkdir actions-runner && cd actions-runner + + curl -o actions-runner-linux-x64-2.296.1.tar.gz -L https://github.com/actions/runner/releases/download/v2.296.1/actions-runner-linux-x64-2.296.1.tar.gz + + tar xzf ./actions-runner-linux-x64-2.296.1.tar.gz + + rm actions-runner-linux-x64-2.296.1.tar.gz + + ./config.sh --url https://github.com/openvinotoolkit/anomalib --token + ``` + + Follow the instructions on the screen to complete the installation. + +1. Now the container is ready. Type `exit` to leave the container. + +1. Start github actions runner in detached mode in the container and set the + codacy token and the anomalib dataset environment variables. + + ```bash + sudo docker exec -d anomalib-ci-container /bin/bash -c \ + "export ANOMALIB_DATASET_PATH=/home/user/datasets;export CODACY_PROJECT_TOKEN= && /home/user/actions-runner/run.sh" + ``` diff --git a/.ci/cuda10.2.Dockerfile b/.ci/cuda10.2.Dockerfile new file mode 100644 index 0000000000..6b2967b460 --- /dev/null +++ b/.ci/cuda10.2.Dockerfile @@ -0,0 +1,74 @@ +######################################################### +## Python Environment with CUDA +######################################################### +ARG HTTP_PROXY +ARG HTTPS_PROXY +ARG NO_PROXY + +FROM nvidia/cuda:10.2-devel-ubuntu18.04 AS python_base_cuda10.2 +LABEL maintainer="Anomalib Development Team" + +# Setup proxies + +ENV http_proxy=$HTTP_PROXY +ENV https_proxy=$HTTPS_PROXY +ENV no_proxy=$NO_PROXY +ENV DEBIAN_FRONTEND="noninteractive" + +# Update system and install wget +RUN apt-get update && \ + apt-get install --no-install-recommends -y \ + curl=7.58.0-2ubuntu3.20 \ + wget=1.19.4-1ubuntu2 \ + ffmpeg=7:3.4.2-2 \ + libpython3.8=3.8.0-3ubuntu1~18.04.2 \ + npm=3.5.2-0ubuntu4 \ + ruby=1:2.5.1 \ + software-properties-common=0.96.24.32.18 && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +# Install latest git for github actions +RUN add-apt-repository ppa:git-core/ppa &&\ + apt-get update && \ + apt-get install --no-install-recommends -y git=1:2.38.0-0ppa1~ubuntu18.04.1 &&\ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +# Prettier requires atleast nodejs 10 +RUN curl -sL https://deb.nodesource.com/setup_14.x > nodesetup.sh && \ + bash - nodesetup.sh && \ + apt-get install --no-install-recommends -y nodejs=14.20.1-1nodesource1 && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +# Create a non-root user +RUN useradd -m user +USER user + +# Install Conda +RUN curl https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh > ~/miniconda.sh -s && \ + bash ~/miniconda.sh -b -p /home/user/conda && \ + rm ~/miniconda.sh +ENV PATH "/home/user/conda/bin:${PATH}" +RUN conda install python=3.8 + + +######################################################### +## Anomalib Development Env +######################################################### + +FROM python_base_cuda10.2 as anomalib_development_env + +# Install all anomalib requirements +COPY ./requirements/base.txt /tmp/anomalib/requirements/base.txt +RUN pip install --no-cache-dir -r /tmp/anomalib/requirements/base.txt + +COPY ./requirements/openvino.txt /tmp/anomalib/requirements/openvino.txt +RUN pip install --no-cache-dir -r /tmp/anomalib/requirements/openvino.txt + +# Install other requirements related to development +COPY ./requirements/dev.txt /tmp/anomalib/requirements/dev.txt +RUN pip install --no-cache-dir -r /tmp/anomalib/requirements/dev.txt + +WORKDIR /home/user diff --git a/Dockerfile b/.ci/cuda11.4.Dockerfile similarity index 55% rename from Dockerfile rename to .ci/cuda11.4.Dockerfile index 8faa03f56a..8efc52abd5 100644 --- a/Dockerfile +++ b/.ci/cuda11.4.Dockerfile @@ -1,26 +1,50 @@ ######################################################### ## Python Environment with CUDA ######################################################### +ARG HTTP_PROXY +ARG HTTPS_PROXY +ARG NO_PROXY -FROM nvidia/cuda:11.4.0-devel-ubuntu20.04 AS python_base_cuda -LABEL MAINTAINER="Anomalib Development Team" +FROM nvidia/cuda:11.4.0-devel-ubuntu20.04 AS python_base_cuda11.4 +LABEL maintainer="Anomalib Development Team" + +# Setup proxies + +ENV http_proxy=$HTTP_PROXY +ENV https_proxy=$HTTPS_PROXY +ENV no_proxy=$NO_PROXY +ENV DEBIAN_FRONTEND="noninteractive" # Update system and install wget RUN apt-get update && \ - DEBIAN_FRONTEND="noninteractive" apt-get install --no-install-recommends -y \ + apt-get install --no-install-recommends -y \ + curl=7.68.0-1ubuntu2.13 \ wget=1.20.3-1ubuntu2 \ ffmpeg=7:4.2.7-0ubuntu0.1 \ libpython3.8=3.8.10-0ubuntu1~20.04.5 \ - git=1:2.25.1-1ubuntu3.5 \ - sudo=1.8.31-1ubuntu1.2 && \ + nodejs=10.19.0~dfsg-3ubuntu1 \ + npm=6.14.4+ds-1ubuntu2 \ + ruby=1:2.7+1 \ + software-properties-common=0.99.9.8 && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +# Install latest git for github actions +RUN add-apt-repository ppa:git-core/ppa &&\ + apt-get update && \ + apt-get install --no-install-recommends -y git=1:2.38.0-0ppa1~ubuntu20.04.1 &&\ apt-get clean && \ rm -rf /var/lib/apt/lists/* +# Create a non-root user +RUN useradd -m user +USER user + # Install Conda -RUN wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O ~/miniconda.sh --quiet && \ - bash ~/miniconda.sh -b -p /opt/conda && \ +RUN curl https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh > ~/miniconda.sh -s && \ + bash ~/miniconda.sh -b -p /home/user/conda && \ rm ~/miniconda.sh -ENV PATH "/opt/conda/bin:${PATH}" +ENV PATH "/home/user/conda/bin:${PATH}" RUN conda install python=3.8 @@ -28,7 +52,7 @@ RUN conda install python=3.8 ## Anomalib Development Env ######################################################### -FROM python_base_cuda as anomalib_development_env +FROM python_base_cuda11.4 as anomalib_development_env # Install all anomalib requirements COPY ./requirements/base.txt /tmp/anomalib/requirements/base.txt @@ -38,17 +62,7 @@ COPY ./requirements/openvino.txt /tmp/anomalib/requirements/openvino.txt RUN pip install --no-cache-dir -r /tmp/anomalib/requirements/openvino.txt # Install other requirements related to development -RUN apt-get update && \ - DEBIAN_FRONTEND="noninteractive" apt-get install --no-install-recommends -y \ - nodejs=10.19.0~dfsg-3ubuntu1 \ - npm=6.14.4+ds-1ubuntu2 \ - ruby=1:2.7+1 && \ - apt-get clean && \ - rm -rf /var/lib/apt/lists/* COPY ./requirements/dev.txt /tmp/anomalib/requirements/dev.txt RUN pip install --no-cache-dir -r /tmp/anomalib/requirements/dev.txt -# Install anomalib -COPY . /anomalib -WORKDIR /anomalib -RUN pip install --no-cache-dir -e . +WORKDIR /home/user diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 08ab657373..2674f5d8c2 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -5,10 +5,10 @@ // Sets the run context to one level up instead of the .devcontainer folder. "context": "..", // Update the 'dockerFile' property if you aren't using the standard 'Dockerfile' filename. - "dockerFile": "../Dockerfile", + "dockerFile": "../.ci/cuda11.4.Dockerfile", // Set *default* container specific settings.json values on container create. "settings": { - "python.pythonPath": "/opt/conda/bin/python", + "python.pythonPath": "/home/user/conda/bin/python", "python.formatting.provider": "black", "editor.formatOnSave": true, "python.formatting.blackArgs": ["--line-length", "120"], diff --git a/.dockerignore b/.dockerignore index bbc8ad9ea9..d1e00bca62 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,4 +1,5 @@ *.dot +.git .idea .pytest_cache .tox diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 83cff4bd9b..f45851a2bd 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -12,16 +12,12 @@ jobs: max-parallel: 1 if: github.ref == 'refs/heads/main' steps: - - name: Print GPU status - run: nvidia-smi - name: CHECKOUT REPOSITORY uses: actions/checkout@v2 - name: Install Tox run: pip install tox - name: Coverage - run: | - export ANOMALIB_DATASET_PATH=/media/data1/datasets/ - tox -e nightly + run: tox -e nightly - name: Upload coverage result uses: actions/upload-artifact@v2 with: diff --git a/.github/workflows/pre_merge.yml b/.github/workflows/pre_merge.yml index 4d40c9c82c..8e77849189 100644 --- a/.github/workflows/pre_merge.yml +++ b/.github/workflows/pre_merge.yml @@ -7,24 +7,27 @@ on: workflow_dispatch: # run on request (no need for PR) jobs: + Code-Quality-Checks: + runs-on: [self-hosted, linux, x64] + steps: + - name: CHECKOUT REPOSITORY + uses: actions/checkout@v2 + - name: Install Tox + run: pip install tox + - name: Code quality checks + run: tox -e pre-commit Tox: runs-on: [self-hosted, linux, x64] + needs: Code-Quality-Checks strategy: max-parallel: 1 steps: - - name: Print GPU status - run: nvidia-smi - name: CHECKOUT REPOSITORY uses: actions/checkout@v2 - name: Install Tox run: pip install tox - - name: Code quality checks - run: tox -e pre-commit - name: Coverage - run: | - export ANOMALIB_DATASET_PATH=/media/data1/datasets/ - export CUDA_VISIBLE_DEVICES=3 - tox -e pre_merge + run: tox -e pre_merge - name: Upload coverage report run: | # If the workflow is triggered from PR then it gets the commit id from the PR. diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a941b1bf88..2e866a4cb6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -82,3 +82,10 @@ repos: rev: 2.1.6 hooks: - id: markdownlint + + - repo: https://github.com/AleksaC/hadolint-py + rev: v2.10.0 + hooks: + - id: hadolint + name: Lint Dockerfiles + description: Runs hadolint to lint Dockerfiles From c1f51a6ccdb7cb26cd201a846f8049ac11b4e5cc Mon Sep 17 00:00:00 2001 From: Dick Ameln Date: Thu, 13 Oct 2022 16:51:34 +0200 Subject: [PATCH 24/38] =?UTF-8?q?=E2=9A=A0=EF=B8=8F=20add=20deprecation=20?= =?UTF-8?q?warning=20to=20denormalize=20class=20(#629)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- anomalib/pre_processing/transforms/custom.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/anomalib/pre_processing/transforms/custom.py b/anomalib/pre_processing/transforms/custom.py index 1f99a19a19..6ca4a67d34 100644 --- a/anomalib/pre_processing/transforms/custom.py +++ b/anomalib/pre_processing/transforms/custom.py @@ -3,6 +3,7 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +import warnings from typing import List, Optional, Tuple import numpy as np @@ -19,6 +20,7 @@ def __init__(self, mean: Optional[List[float]] = None, std: Optional[List[float] mean: Mean std: Standard deviation. """ + warnings.warn("Denormalize is no longer used and will be deprecated in v0.4.0") # If no mean and std provided, assign ImageNet values. if mean is None: mean = [0.485, 0.456, 0.406] From 1ba8d12b9e6ef55193e1a5f24a846fb2e4b850b3 Mon Sep 17 00:00:00 2001 From: Samet Akcay Date: Mon, 17 Oct 2022 14:58:39 +0100 Subject: [PATCH 25/38] Anomalib CLI Improvements - Update metrics and create post_processing section in the config file (#607) * Rename image/pixel_metrics_names to image/pixel_metrics * Rename image/pixel_metrics_names to image/pixel_metrics * Modified config files. * Modify get_callbacks function for the old cli * Create pre-processing-configuration callback * Add new CLI configuration for the post-processing configuration * Add options to normalization_method * Address mypy issues * Fix docstring * Address codacy issues --- anomalib/utils/callbacks/__init__.py | 27 +++++--- .../utils/callbacks/metrics_configuration.py | 41 ++---------- .../post_processing_configuration.py | 63 +++++++++++++++++++ anomalib/utils/cli/cli.py | 22 ++++--- anomalib/utils/sweep/helpers/callbacks.py | 52 ++++++++++----- configs/model/cflow.yaml | 12 ++-- configs/model/dfkde.yaml | 10 ++- configs/model/dfm.yaml | 12 ++-- configs/model/draem.yaml | 12 ++-- configs/model/fastflow.yaml | 12 ++-- configs/model/ganomaly.yaml | 10 ++- configs/model/padim.yaml | 12 ++-- configs/model/patchcore.yaml | 12 ++-- configs/model/reverse_distillation.yaml | 12 ++-- configs/model/stfpm.yaml | 12 ++-- 15 files changed, 208 insertions(+), 113 deletions(-) create mode 100644 anomalib/utils/callbacks/post_processing_configuration.py diff --git a/anomalib/utils/callbacks/__init__.py b/anomalib/utils/callbacks/__init__.py index 270648fb9d..15def2a1f5 100644 --- a/anomalib/utils/callbacks/__init__.py +++ b/anomalib/utils/callbacks/__init__.py @@ -19,19 +19,22 @@ from .metrics_configuration import MetricsConfigurationCallback from .min_max_normalization import MinMaxNormalizationCallback from .model_loader import LoadModelCallback +from .post_processing_configuration import PostProcessingConfigurationCallback from .tiler_configuration import TilerConfigurationCallback from .timer import TimerCallback from .visualizer import ImageVisualizerCallback, MetricVisualizerCallback __all__ = [ "CdfNormalizationCallback", + "GraphLogger", + "ImageVisualizerCallback", "LoadModelCallback", "MetricsConfigurationCallback", + "MetricVisualizerCallback", "MinMaxNormalizationCallback", + "PostProcessingConfigurationCallback", "TilerConfigurationCallback", "TimerCallback", - "ImageVisualizerCallback", - "MetricVisualizerCallback", ] @@ -64,20 +67,25 @@ def get_callbacks(config: Union[ListConfig, DictConfig]) -> List[Callback]: callbacks.extend([checkpoint, TimerCallback()]) - # Add metric configuration to the model via MetricsConfigurationCallback - image_metric_names = config.metrics.image if "image" in config.metrics.keys() else None - pixel_metric_names = config.metrics.pixel if "pixel" in config.metrics.keys() else None + # Add post-processing configurations to AnomalyModule. image_threshold = ( config.metrics.threshold.image_default if "image_default" in config.metrics.threshold.keys() else None ) pixel_threshold = ( config.metrics.threshold.pixel_default if "pixel_default" in config.metrics.threshold.keys() else None ) + post_processing_callback = PostProcessingConfigurationCallback( + adaptive_threshold=config.metrics.threshold.adaptive, + default_image_threshold=image_threshold, + default_pixel_threshold=pixel_threshold, + ) + callbacks.append(post_processing_callback) + + # Add metric configuration to the model via MetricsConfigurationCallback + image_metric_names = config.metrics.image if "image" in config.metrics.keys() else None + pixel_metric_names = config.metrics.pixel if "pixel" in config.metrics.keys() else None metrics_callback = MetricsConfigurationCallback( - config.metrics.threshold.adaptive, config.dataset.task, - image_threshold, - pixel_threshold, image_metric_names, pixel_metric_names, ) @@ -172,7 +180,8 @@ def add_visualizer_callback(callbacks: List[Callback], config: Union[DictConfig, config.visualization.inputs_are_normalized = not config.model.normalization_method == "none" else: config.visualization.task = config.data.init_args.task - config.visualization.inputs_are_normalized = not config.metrics.normalization_method == "none" + 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 diff --git a/anomalib/utils/callbacks/metrics_configuration.py b/anomalib/utils/callbacks/metrics_configuration.py index bc91c23e83..2abc47f848 100644 --- a/anomalib/utils/callbacks/metrics_configuration.py +++ b/anomalib/utils/callbacks/metrics_configuration.py @@ -8,7 +8,6 @@ from typing import List, Optional import pytorch_lightning as pl -import torch from pytorch_lightning.callbacks import Callback from pytorch_lightning.utilities.cli import CALLBACK_REGISTRY @@ -26,13 +25,9 @@ class MetricsConfigurationCallback(Callback): def __init__( self, - adaptive_threshold: bool, task: str = "segmentation", - default_image_threshold: Optional[float] = None, - default_pixel_threshold: Optional[float] = None, - image_metric_names: Optional[List[str]] = None, - pixel_metric_names: Optional[List[str]] = None, - normalization_method: str = "min_max", + image_metrics: Optional[List[str]] = None, + pixel_metrics: Optional[List[str]] = None, ): """Create image and pixel-level AnomalibMetricsCollection. @@ -43,30 +38,12 @@ def __init__( Args: task (str): Task type of the current run. - adaptive_threshold (bool): Flag indicating whether threshold should be adaptive. - default_image_threshold (Optional[float]): Default image threshold value. - default_pixel_threshold (Optional[float]): Default pixel threshold value. - image_metric_names (Optional[List[str]]): List of image-level metrics. - pixel_metric_names (Optional[List[str]]): List of pixel-level metrics. - normalization_method(Optional[str]): Normalization method. + image_metrics (Optional[List[str]]): List of image-level metrics. + pixel_metrics (Optional[List[str]]): List of pixel-level metrics. """ - # TODO: https://github.com/openvinotoolkit/anomalib/issues/384 self.task = task - self.image_metric_names = image_metric_names - self.pixel_metric_names = pixel_metric_names - - # TODO: https://github.com/openvinotoolkit/anomalib/issues/384 - # TODO: This is a workaround. normalization-method is actually not used in metrics. - # It's only accessed from `before_instantiate` method in `AnomalibCLI` to configure - # its callback. - self.normalization_method = normalization_method - - assert ( - adaptive_threshold or default_image_threshold is not None and default_pixel_threshold is not None - ), "Default thresholds must be specified when adaptive threshold is disabled." - self.adaptive_threshold = adaptive_threshold - self.default_image_threshold = default_image_threshold - self.default_pixel_threshold = default_pixel_threshold + self.image_metric_names = image_metrics + self.pixel_metric_names = pixel_metrics def setup( self, @@ -97,12 +74,6 @@ def setup( pixel_metric_names = self.pixel_metric_names if isinstance(pl_module, AnomalyModule): - pl_module.adaptive_threshold = self.adaptive_threshold - if not self.adaptive_threshold: - # pylint: disable=not-callable - pl_module.image_threshold.value = torch.tensor(self.default_image_threshold).cpu() - pl_module.pixel_threshold.value = torch.tensor(self.default_pixel_threshold).cpu() - pl_module.image_metrics = metric_collection_from_names(image_metric_names, "image_") pl_module.pixel_metrics = metric_collection_from_names(pixel_metric_names, "pixel_") diff --git a/anomalib/utils/callbacks/post_processing_configuration.py b/anomalib/utils/callbacks/post_processing_configuration.py new file mode 100644 index 0000000000..9549a87fd9 --- /dev/null +++ b/anomalib/utils/callbacks/post_processing_configuration.py @@ -0,0 +1,63 @@ +"""Post-Processing Configuration Callback.""" + +# Copyright (C) 2022 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + + +import logging +from typing import Optional + +import torch +from pytorch_lightning import Callback, LightningModule, Trainer +from pytorch_lightning.utilities.cli import CALLBACK_REGISTRY + +from anomalib.models.components.base.anomaly_module import AnomalyModule + +logger = logging.getLogger(__name__) + +__all__ = ["PostProcessingConfigurationCallback"] + + +@CALLBACK_REGISTRY +class PostProcessingConfigurationCallback(Callback): + """Post-Processing Configuration Callback. + + Args: + normalization_method(Optional[str]): Normalization method. + adaptive_threshold (bool): Flag indicating whether threshold should be adaptive. + default_image_threshold (Optional[float]): Default image threshold value. + default_pixel_threshold (Optional[float]): Default pixel threshold value. + """ + + def __init__( + self, + normalization_method: str = "min_max", + adaptive_threshold: bool = True, + default_image_threshold: Optional[float] = None, + default_pixel_threshold: Optional[float] = None, + ) -> None: + super().__init__() + self.normalization_method = normalization_method + + assert ( + adaptive_threshold or default_image_threshold is not None and default_pixel_threshold is not None + ), "Default thresholds must be specified when adaptive threshold is disabled." + + self.adaptive_threshold = adaptive_threshold + self.default_image_threshold = default_image_threshold + self.default_pixel_threshold = default_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. + + 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. + """ + if isinstance(pl_module, AnomalyModule): + pl_module.adaptive_threshold = self.adaptive_threshold + if pl_module.adaptive_threshold is False: + pl_module.image_threshold.value = torch.tensor(self.default_image_threshold).cpu() + pl_module.pixel_threshold.value = torch.tensor(self.default_pixel_threshold).cpu() diff --git a/anomalib/utils/cli/cli.py b/anomalib/utils/cli/cli.py index 48c1769163..7ce51a918a 100644 --- a/anomalib/utils/cli/cli.py +++ b/anomalib/utils/cli/cli.py @@ -26,6 +26,7 @@ MetricsConfigurationCallback, MinMaxNormalizationCallback, ModelCheckpoint, + PostProcessingConfigurationCallback, TilerConfigurationCallback, TimerCallback, add_visualizer_callback, @@ -90,7 +91,6 @@ def add_arguments_to_parser(self, parser: LightningArgumentParser) -> None: Args: parser (LightningArgumentParser): Lightning Argument Parser. """ - # TODO: https://github.com/openvinotoolkit/anomalib/issues/19 # TODO: https://github.com/openvinotoolkit/anomalib/issues/20 parser.add_argument( "--export_mode", type=str, default="", help="Select export mode to ONNX or OpenVINO IR format." @@ -105,18 +105,24 @@ def add_arguments_to_parser(self, parser: LightningArgumentParser) -> None: parser.add_lightning_class_args(TilerConfigurationCallback, "tiling") # type: ignore parser.set_defaults({"tiling.enable": False}) + parser.add_lightning_class_args(PostProcessingConfigurationCallback, "post_processing") # type: ignore + parser.set_defaults( + { + "post_processing.normalization_method": "min_max", + "post_processing.adaptive_threshold": True, + "post_processing.default_image_threshold": None, + "post_processing.default_pixel_threshold": None, + } + ) + # TODO: Assign these default values within the MetricsConfigurationCallback # - https://github.com/openvinotoolkit/anomalib/issues/384 parser.add_lightning_class_args(MetricsConfigurationCallback, "metrics") # type: ignore parser.set_defaults( { - "metrics.adaptive_threshold": True, "metrics.task": "segmentation", - "metrics.default_image_threshold": None, - "metrics.default_pixel_threshold": None, - "metrics.image_metric_names": ["F1Score", "AUROC"], - "metrics.pixel_metric_names": ["F1Score", "AUROC"], - "metrics.normalization_method": "min_max", + "metrics.image_metrics": ["F1Score", "AUROC"], + "metrics.pixel_metrics": ["F1Score", "AUROC"], } ) @@ -203,7 +209,7 @@ def __set_callbacks(self) -> None: # TODO: This could be set in PostProcessingConfiguration callback # - https://github.com/openvinotoolkit/anomalib/issues/384 # Normalization. - normalization = config.metrics.normalization_method + normalization = config.post_processing.normalization_method if normalization: if normalization == "min_max": callbacks.append(MinMaxNormalizationCallback()) diff --git a/anomalib/utils/sweep/helpers/callbacks.py b/anomalib/utils/sweep/helpers/callbacks.py index 975224b9b7..3f1057a5d5 100644 --- a/anomalib/utils/sweep/helpers/callbacks.py +++ b/anomalib/utils/sweep/helpers/callbacks.py @@ -9,7 +9,10 @@ from omegaconf import DictConfig, ListConfig from pytorch_lightning import Callback -from anomalib.utils.callbacks import MetricsConfigurationCallback +from anomalib.utils.callbacks import ( + MetricsConfigurationCallback, + PostProcessingConfigurationCallback, +) from anomalib.utils.callbacks.timer import TimerCallback @@ -24,23 +27,40 @@ def get_sweep_callbacks(config: Union[ListConfig, DictConfig]) -> List[Callback] """ callbacks: List[Callback] = [TimerCallback()] # Add metric configuration to the model via MetricsConfigurationCallback - image_metric_names = config.metrics.image if "image" in config.metrics.keys() else None - pixel_metric_names = config.metrics.pixel if "pixel" in config.metrics.keys() else None - image_threshold = ( - config.metrics.threshold.image_default if "image_default" in config.metrics.threshold.keys() else None - ) - pixel_threshold = ( - config.metrics.threshold.pixel_default if "pixel_default" in config.metrics.threshold.keys() else None - ) - metrics_callback = MetricsConfigurationCallback( - adaptive_threshold=config.metrics.threshold.adaptive, - task=config.dataset.task, + + # TODO: Remove this once the old CLI is deprecated. + if isinstance(config, DictConfig): + image_metrics = config.metrics.image if "image" in config.metrics.keys() else None + pixel_metrics = config.metrics.pixel if "pixel" in config.metrics.keys() else None + image_threshold = ( + config.metrics.threshold.image_default if "image_default" in config.metrics.threshold.keys() else None + ) + pixel_threshold = ( + config.metrics.threshold.pixel_default if "pixel_default" in config.metrics.threshold.keys() else None + ) + normalization_method = config.model.normalization_method + # NOTE: This is for the new anomalib CLI. + else: + image_metrics = config.metrics.image_metrics if "image_metrics" in config.metrics else None + pixel_metrics = config.metrics.pixel_metrics if "pixel_metrics" in config.metrics else None + image_threshold = ( + config.post_processing.default_image_threshold if "image_default" in config.post_processing.keys() else None + ) + pixel_threshold = ( + config.post_processing.default_pixel_threshold if "pixel_default" in config.post_processing.keys() else None + ) + normalization_method = config.post_processing.normalization_method + + post_processing_configuration_callback = PostProcessingConfigurationCallback( + normalization_method=normalization_method, default_image_threshold=image_threshold, default_pixel_threshold=pixel_threshold, - image_metric_names=image_metric_names, - pixel_metric_names=pixel_metric_names, - normalization_method=config.model.normalization_method, ) - callbacks.append(metrics_callback) + callbacks.append(post_processing_configuration_callback) + + metrics_configuration_callback = MetricsConfigurationCallback( + task=config.dataset.task, image_metrics=image_metrics, pixel_metrics=pixel_metrics + ) + callbacks.append(metrics_configuration_callback) return callbacks diff --git a/configs/model/cflow.yaml b/configs/model/cflow.yaml index 2f925071e1..672b101fa1 100644 --- a/configs/model/cflow.yaml +++ b/configs/model/cflow.yaml @@ -37,21 +37,23 @@ model: permute_soft: false lr: 0.0001 -metrics: +post_processing: + normalization_method: min_max # adaptive_threshold: true default_image_threshold: null default_pixel_threshold: null - image_metric_names: + +metrics: + image_metrics: - F1Score - AUROC - pixel_metric_names: + pixel_metrics: - F1Score - AUROC - normalization_method: min_max visualization: show_images: False # show images on the screen - save_images: False # save images to the file system + save_images: True # save images to the file system log_images: False # log images to the available loggers (if any) mode: full # options: ["full", "simple"] diff --git a/configs/model/dfkde.yaml b/configs/model/dfkde.yaml index 4a43a344b9..54ba782db0 100644 --- a/configs/model/dfkde.yaml +++ b/configs/model/dfkde.yaml @@ -32,16 +32,20 @@ model: threshold_steepness: 0.05 threshold_offset: 12 -metrics: +post_processing: + normalization_method: min_max # adaptive_threshold: true default_image_threshold: null - image_metric_names: + default_pixel_threshold: null + +metrics: + image_metrics: - F1Score - AUROC visualization: show_images: False # show images on the screen - save_images: False # save images to the file system + save_images: True # save images to the file system log_images: False # log images to the available loggers (if any) mode: full # options: ["full", "simple"] diff --git a/configs/model/dfm.yaml b/configs/model/dfm.yaml index 2670510a16..69f7fd0a8d 100644 --- a/configs/model/dfm.yaml +++ b/configs/model/dfm.yaml @@ -29,21 +29,23 @@ model: pca_level: 0.97 score_type: fre -metrics: +post_processing: + normalization_method: min_max # adaptive_threshold: true default_image_threshold: null default_pixel_threshold: null - image_metric_names: + +metrics: + image_metrics: - F1Score - AUROC - pixel_metric_names: + pixel_metrics: - F1Score - AUROC - normalization_method: min_max visualization: show_images: False # show images on the screen - save_images: False # save images to the file system + save_images: True # save images to the file system log_images: False # log images to the available loggers (if any) mode: full # options: ["full", "simple"] diff --git a/configs/model/draem.yaml b/configs/model/draem.yaml index 78d9defd8f..9cda0e1ac0 100644 --- a/configs/model/draem.yaml +++ b/configs/model/draem.yaml @@ -29,21 +29,23 @@ optimizer: init_args: lr: 0.0001 -metrics: +post_processing: + normalization_method: min_max # adaptive_threshold: true default_image_threshold: null default_pixel_threshold: null - image_metric_names: + +metrics: + image_metrics: - F1Score - AUROC - pixel_metric_names: + pixel_metrics: - F1Score - AUROC - normalization_method: min_max visualization: show_images: False # show images on the screen - save_images: False # save images to the file system + save_images: True # save images to the file system log_images: False # log images to the available loggers (if any) mode: full # options: ["full", "simple"] diff --git a/configs/model/fastflow.yaml b/configs/model/fastflow.yaml index 1a2217ab6f..285be59ec4 100644 --- a/configs/model/fastflow.yaml +++ b/configs/model/fastflow.yaml @@ -35,21 +35,23 @@ optimizer: lr: 0.001 weight_decay: 0.00001 -metrics: +post_processing: + normalization_method: min_max # adaptive_threshold: true default_image_threshold: null default_pixel_threshold: null - image_metric_names: + +metrics: + image_metrics: - F1Score - AUROC - pixel_metric_names: + pixel_metrics: - F1Score - AUROC - normalization_method: min_max visualization: show_images: False # show images on the screen - save_images: False # save images to the file system + save_images: True # save images to the file system log_images: False # log images to the available loggers (if any) mode: full # options: ["full", "simple"] diff --git a/configs/model/ganomaly.yaml b/configs/model/ganomaly.yaml index 23ca106244..3ba6708e5e 100644 --- a/configs/model/ganomaly.yaml +++ b/configs/model/ganomaly.yaml @@ -35,16 +35,22 @@ model: beta1: 0.5 beta2: 0.999 +post_processing: + normalization_method: min_max # + adaptive_threshold: true + default_image_threshold: null + default_pixel_threshold: null + metrics: adaptive_threshold: true default_image_threshold: null - image_metric_names: + image_metrics: - F1Score - AUROC visualization: show_images: False # show images on the screen - save_images: False # save images to the file system + save_images: True # save images to the file system log_images: False # log images to the available loggers (if any) mode: full # options: ["full", "simple"] diff --git a/configs/model/padim.yaml b/configs/model/padim.yaml index 6b9871feb4..013d0485d8 100644 --- a/configs/model/padim.yaml +++ b/configs/model/padim.yaml @@ -32,21 +32,23 @@ model: - layer2 - layer3 -metrics: +post_processing: + normalization_method: min_max # adaptive_threshold: true default_image_threshold: null default_pixel_threshold: null - image_metric_names: + +metrics: + image_metrics: - F1Score - AUROC - pixel_metric_names: + pixel_metrics: - F1Score - AUROC - normalization_method: min_max visualization: show_images: False # show images on the screen - save_images: False # save images to the file system + save_images: True # save images to the file system log_images: False # log images to the available loggers (if any) mode: full # options: ["full", "simple"] diff --git a/configs/model/patchcore.yaml b/configs/model/patchcore.yaml index d357b06aca..4e99bf931e 100644 --- a/configs/model/patchcore.yaml +++ b/configs/model/patchcore.yaml @@ -32,21 +32,23 @@ model: coreset_sampling_ratio: 0.1 num_neighbors: 9 -metrics: +post_processing: + normalization_method: min_max # adaptive_threshold: true default_image_threshold: null default_pixel_threshold: null - image_metric_names: + +metrics: + image_metrics: - F1Score - AUROC - pixel_metric_names: + pixel_metrics: - F1Score - AUROC - normalization_method: min_max visualization: show_images: False # show images on the screen - save_images: False # save images to the file system + save_images: True # save images to the file system log_images: False # log images to the available loggers (if any) mode: full # options: ["full", "simple"] diff --git a/configs/model/reverse_distillation.yaml b/configs/model/reverse_distillation.yaml index f1276bcdd7..f067b33846 100644 --- a/configs/model/reverse_distillation.yaml +++ b/configs/model/reverse_distillation.yaml @@ -35,21 +35,23 @@ model: beta1: 0.5 beta2: 0.99 -metrics: +post_processing: + normalization_method: min_max # adaptive_threshold: true default_image_threshold: null default_pixel_threshold: null - image_metric_names: + +metrics: + image_metrics: - F1Score - AUROC - pixel_metric_names: + pixel_metrics: - F1Score - AUROC - normalization_method: min_max visualization: show_images: False # show images on the screen - save_images: False # save images to the file system + save_images: True # save images to the file system log_images: False # log images to the available loggers (if any) mode: full # options: ["full", "simple"] diff --git a/configs/model/stfpm.yaml b/configs/model/stfpm.yaml index bd5ea0be43..7fc8df2d7f 100644 --- a/configs/model/stfpm.yaml +++ b/configs/model/stfpm.yaml @@ -37,21 +37,23 @@ optimizer: momentum: 0.9 weight_decay: 0.0001 -metrics: +post_processing: + normalization_method: min_max # adaptive_threshold: true default_image_threshold: null default_pixel_threshold: null - image_metric_names: + +metrics: + image_metrics: - F1Score - AUROC - pixel_metric_names: + pixel_metrics: - F1Score - AUROC - normalization_method: min_max visualization: show_images: False # show images on the screen - save_images: False # save images to the file system + save_images: True # save images to the file system log_images: False # log images to the available loggers (if any) mode: full # options: ["full", "simple"] From dacf3f49f5efa5d0aab9afaf8e3e5c4a85514499 Mon Sep 17 00:00:00 2001 From: Samet Akcay Date: Tue, 18 Oct 2022 15:17:20 +0100 Subject: [PATCH 26/38] =?UTF-8?q?=F0=9F=8F=B7=20Convert=20adaptive=5Fthres?= =?UTF-8?q?hold=20to=20Enum=20in=20configs=20(#637)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Rename image/pixel_metrics_names to image/pixel_metrics * Rename image/pixel_metrics_names to image/pixel_metrics * Modified config files. * Modify get_callbacks function for the old cli * Create pre-processing-configuration callback * Add new CLI configuration for the post-processing configuration * Add options to normalization_method * Address mypy issues * Fix docstring * Address codacy issues * renamed adaptive: true to threshold_method: adaptive in config.yaml files * Reorder threshold params in config.yaml * Add backward compatibility to threshold configs * renamed the new cli config files * Rename threshold_method to method in config.yaml * Convert names to Enum * Fix tests * Rename AdaptiveThreshold to AnomalyScoreThreshold * Fixed import * Rename fixed to manual * Rename some left-over variables. --- anomalib/config/config.py | 16 +++++- anomalib/models/cflow/config.yaml | 6 +-- .../models/components/base/anomaly_module.py | 12 ++--- anomalib/models/dfkde/config.yaml | 4 +- anomalib/models/dfm/config.yaml | 4 +- anomalib/models/draem/config.yaml | 6 +-- anomalib/models/fastflow/config.yaml | 6 +-- anomalib/models/ganomaly/config.yaml | 4 +- anomalib/models/padim/config.yaml | 6 +-- anomalib/models/patchcore/config.yaml | 6 +-- .../models/reverse_distillation/config.yaml | 6 +-- anomalib/models/stfpm/config.yaml | 6 +-- anomalib/post_processing/__init__.py | 4 ++ .../post_processing/normalization/__init__.py | 10 ++++ anomalib/post_processing/post_process.py | 8 +++ anomalib/utils/callbacks/__init__.py | 10 ++-- .../post_processing_configuration.py | 49 ++++++++++++------- anomalib/utils/cli/cli.py | 6 +-- anomalib/utils/metrics/__init__.py | 4 +- ...hreshold.py => anomaly_score_threshold.py} | 15 ++++-- anomalib/utils/sweep/helpers/callbacks.py | 12 ++--- configs/model/cflow.yaml | 6 +-- configs/model/dfkde.yaml | 6 +-- configs/model/dfm.yaml | 6 +-- configs/model/draem.yaml | 6 +-- configs/model/fastflow.yaml | 6 +-- configs/model/ganomaly.yaml | 10 ++-- configs/model/padim.yaml | 6 +-- configs/model/patchcore.yaml | 6 +-- configs/model/reverse_distillation.yaml | 6 +-- configs/model/stfpm.yaml | 6 +-- .../export_callback/dummy_lightning_model.py | 10 ++-- .../test_normalization_callback.py | 2 +- .../utils/metrics/test_adaptive_threshold.py | 14 +++--- 34 files changed, 173 insertions(+), 117 deletions(-) rename anomalib/utils/metrics/{adaptive_threshold.py => anomaly_score_threshold.py} (71%) diff --git a/anomalib/config/config.py b/anomalib/config/config.py index 6312c1012f..65adf5d292 100644 --- a/anomalib/config/config.py +++ b/anomalib/config/config.py @@ -156,7 +156,19 @@ def get_configurable_parameters( # thresholding if "metrics" in config.keys(): - if "pixel_default" not in config.metrics.threshold.keys(): - config.metrics.threshold.pixel_default = config.metrics.threshold.image_default + # NOTE: Deprecate this after v0.4.0. + if "adaptive" in config.metrics.threshold.keys(): + warn("adaptive will be deprecated in favor of method in config.metrics.threshold in v0.4.0.") + config.metrics.threshold.method = "adaptive" if config.metrics.threshold.adaptive else "manual" + if "image_default" in config.metrics.threshold.keys(): + warn("image_default will be deprecated in favor of manual_image in config.metrics.threshold in v0.4.0.") + config.metrics.threshold.manual_image = ( + None if config.metrics.threshold.adaptive else config.metrics.threshold.image_default + ) + if "pixel_default" in config.metrics.threshold.keys(): + warn("pixel_default will be deprecated in favor of manual_pixel in config.metrics.threshold in v0.4.0.") + config.metrics.threshold.manual_pixel = ( + None if config.metrics.threshold.adaptive else config.metrics.threshold.pixel_default + ) return config diff --git a/anomalib/models/cflow/config.yaml b/anomalib/models/cflow/config.yaml index be166c2417..4337538445 100644 --- a/anomalib/models/cflow/config.yaml +++ b/anomalib/models/cflow/config.yaml @@ -43,9 +43,9 @@ metrics: - F1Score - AUROC threshold: - image_default: 0 - pixel_default: 0 - adaptive: true + method: adaptive #options: [adaptive, manual] + manual_image: null + manual_pixel: null visualization: show_images: False # show images on the screen diff --git a/anomalib/models/components/base/anomaly_module.py b/anomalib/models/components/base/anomaly_module.py index 16fabb9ef3..b6127a6dbd 100644 --- a/anomalib/models/components/base/anomaly_module.py +++ b/anomalib/models/components/base/anomaly_module.py @@ -13,10 +13,11 @@ from torch import Tensor, nn from torchmetrics import Metric +from anomalib.post_processing import ThresholdMethod from anomalib.utils.metrics import ( - AdaptiveThreshold, AnomalibMetricCollection, AnomalyScoreDistribution, + AnomalyScoreThreshold, MinMax, ) @@ -38,10 +39,9 @@ def __init__(self): self.loss: Tensor self.callbacks: List[Callback] - self.adaptive_threshold: bool - - self.image_threshold = AdaptiveThreshold().cpu() - self.pixel_threshold = AdaptiveThreshold().cpu() + self.threshold_method: ThresholdMethod + self.image_threshold = AnomalyScoreThreshold().cpu() + self.pixel_threshold = AnomalyScoreThreshold().cpu() self.normalization_metrics: Metric @@ -115,7 +115,7 @@ def validation_epoch_end(self, outputs): Args: outputs: Batch of outputs from the validation step """ - if self.adaptive_threshold: + if self.threshold_method == ThresholdMethod.ADAPTIVE: self._compute_adaptive_threshold(outputs) self._collect_outputs(self.image_metrics, self.pixel_metrics, outputs) self._log_metrics() diff --git a/anomalib/models/dfkde/config.yaml b/anomalib/models/dfkde/config.yaml index 070f0c2456..d98bef0fec 100644 --- a/anomalib/models/dfkde/config.yaml +++ b/anomalib/models/dfkde/config.yaml @@ -32,8 +32,8 @@ metrics: - F1Score - AUROC threshold: - image_default: 0 - adaptive: true + method: adaptive #options: [adaptive, manual] + manual_image: null visualization: show_images: False # show images on the screen diff --git a/anomalib/models/dfm/config.yaml b/anomalib/models/dfm/config.yaml index 47db50fb4e..fc64d78bd5 100755 --- a/anomalib/models/dfm/config.yaml +++ b/anomalib/models/dfm/config.yaml @@ -32,8 +32,8 @@ metrics: - F1Score - AUROC threshold: - image_default: 0 - adaptive: true + method: adaptive #options: [adaptive, manual] + manual_image: null visualization: show_images: False # show images on the screen diff --git a/anomalib/models/draem/config.yaml b/anomalib/models/draem/config.yaml index 9f3326daa1..f76e163a27 100644 --- a/anomalib/models/draem/config.yaml +++ b/anomalib/models/draem/config.yaml @@ -40,9 +40,9 @@ metrics: - F1Score - AUROC threshold: - image_default: 3 - pixel_default: 3 - adaptive: true + method: adaptive #options: [adaptive, manual] + manual_image: null + manual_pixel: null visualization: show_images: False # show images on the screen diff --git a/anomalib/models/fastflow/config.yaml b/anomalib/models/fastflow/config.yaml index b02f430fd9..efe749afdd 100644 --- a/anomalib/models/fastflow/config.yaml +++ b/anomalib/models/fastflow/config.yaml @@ -43,9 +43,9 @@ metrics: - F1Score - AUROC threshold: - image_default: 0 - pixel_default: 0 - adaptive: true + method: adaptive #options: [adaptive, manual] + manual_image: null + manual_pixel: null visualization: show_images: False # show images on the screen diff --git a/anomalib/models/ganomaly/config.yaml b/anomalib/models/ganomaly/config.yaml index 2e5dfb6bba..b511da3160 100644 --- a/anomalib/models/ganomaly/config.yaml +++ b/anomalib/models/ganomaly/config.yaml @@ -44,8 +44,8 @@ metrics: - F1Score - AUROC threshold: - image_default: 0 - adaptive: true + method: adaptive #options: [adaptive, manual] + manual_image: null visualization: show_images: False # show images on the screen diff --git a/anomalib/models/padim/config.yaml b/anomalib/models/padim/config.yaml index 92e66618dc..63d7df90c5 100644 --- a/anomalib/models/padim/config.yaml +++ b/anomalib/models/padim/config.yaml @@ -38,9 +38,9 @@ metrics: - F1Score - AUROC threshold: - image_default: 3 - pixel_default: 3 - adaptive: true + method: adaptive #options: [adaptive, manual] + manual_image: null + manual_pixel: null visualization: show_images: False # show images on the screen diff --git a/anomalib/models/patchcore/config.yaml b/anomalib/models/patchcore/config.yaml index 31567ad530..66a55f1e5c 100644 --- a/anomalib/models/patchcore/config.yaml +++ b/anomalib/models/patchcore/config.yaml @@ -39,9 +39,9 @@ metrics: - F1Score - AUROC threshold: - image_default: 0 - pixel_default: 0 - adaptive: true + method: adaptive #options: [adaptive, manual] + manual_image: null + manual_pixel: null visualization: show_images: False # show images on the screen diff --git a/anomalib/models/reverse_distillation/config.yaml b/anomalib/models/reverse_distillation/config.yaml index d30f3baedf..696e76874b 100644 --- a/anomalib/models/reverse_distillation/config.yaml +++ b/anomalib/models/reverse_distillation/config.yaml @@ -47,9 +47,9 @@ metrics: - F1Score - AUROC threshold: - image_default: 0 - pixel_default: 0 - adaptive: true + method: adaptive #options: [adaptive, manual] + manual_image: null + manual_pixel: null visualization: show_images: False # show images on the screen diff --git a/anomalib/models/stfpm/config.yaml b/anomalib/models/stfpm/config.yaml index fe3637bf27..153e92b3db 100644 --- a/anomalib/models/stfpm/config.yaml +++ b/anomalib/models/stfpm/config.yaml @@ -45,9 +45,9 @@ metrics: - F1Score - AUROC threshold: - image_default: 0 - pixel_default: 0 - adaptive: true + method: adaptive #options: [adaptive, manual] + manual_image: null + manual_pixel: null visualization: show_images: False # show images on the screen diff --git a/anomalib/post_processing/__init__.py b/anomalib/post_processing/__init__.py index dcdf1ab7de..17eb3f84aa 100644 --- a/anomalib/post_processing/__init__.py +++ b/anomalib/post_processing/__init__.py @@ -3,7 +3,9 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +from .normalization import NormalizationMethod from .post_process import ( + ThresholdMethod, add_anomalous_label, add_normal_label, anomaly_map_to_color_map, @@ -19,5 +21,7 @@ "superimpose_anomaly_map", "compute_mask", "ImageResult", + "NormalizationMethod", "Visualizer", + "ThresholdMethod", ] diff --git a/anomalib/post_processing/normalization/__init__.py b/anomalib/post_processing/normalization/__init__.py index fb2c272cc2..b8a57f8077 100644 --- a/anomalib/post_processing/normalization/__init__.py +++ b/anomalib/post_processing/normalization/__init__.py @@ -2,3 +2,13 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 + +from enum import Enum + + +class NormalizationMethod(str, Enum): + """Normalization method for normalization.""" + + CDF = "cdf" + MIN_MAX = "min_max" + NONE = "none" diff --git a/anomalib/post_processing/post_process.py b/anomalib/post_processing/post_process.py index e8c4debbad..000d1ac53b 100644 --- a/anomalib/post_processing/post_process.py +++ b/anomalib/post_processing/post_process.py @@ -5,6 +5,7 @@ import math +from enum import Enum from typing import Optional, Tuple import cv2 @@ -12,6 +13,13 @@ from skimage import morphology +class ThresholdMethod(str, Enum): + """Threshold method to apply post-processing to the output predictions.""" + + ADAPTIVE = "adaptive" + MANUAL = "manual" + + def add_label( image: np.ndarray, label_name: str, diff --git a/anomalib/utils/callbacks/__init__.py b/anomalib/utils/callbacks/__init__.py index 15def2a1f5..8f9126d1b5 100644 --- a/anomalib/utils/callbacks/__init__.py +++ b/anomalib/utils/callbacks/__init__.py @@ -69,15 +69,15 @@ def get_callbacks(config: Union[ListConfig, DictConfig]) -> List[Callback]: # Add post-processing configurations to AnomalyModule. image_threshold = ( - config.metrics.threshold.image_default if "image_default" in config.metrics.threshold.keys() else None + config.metrics.threshold.manual_image if "manual_image" in config.metrics.threshold.keys() else None ) pixel_threshold = ( - config.metrics.threshold.pixel_default if "pixel_default" in config.metrics.threshold.keys() else None + config.metrics.threshold.manual_pixel if "manual_pixel" in config.metrics.threshold.keys() else None ) post_processing_callback = PostProcessingConfigurationCallback( - adaptive_threshold=config.metrics.threshold.adaptive, - default_image_threshold=image_threshold, - default_pixel_threshold=pixel_threshold, + threshold_method=config.metrics.threshold.method, + manual_image_threshold=image_threshold, + manual_pixel_threshold=pixel_threshold, ) callbacks.append(post_processing_callback) diff --git a/anomalib/utils/callbacks/post_processing_configuration.py b/anomalib/utils/callbacks/post_processing_configuration.py index 9549a87fd9..9437d3ba2e 100644 --- a/anomalib/utils/callbacks/post_processing_configuration.py +++ b/anomalib/utils/callbacks/post_processing_configuration.py @@ -12,6 +12,7 @@ from pytorch_lightning.utilities.cli import CALLBACK_REGISTRY from anomalib.models.components.base.anomaly_module import AnomalyModule +from anomalib.post_processing import NormalizationMethod, ThresholdMethod logger = logging.getLogger(__name__) @@ -23,29 +24,41 @@ class PostProcessingConfigurationCallback(Callback): """Post-Processing Configuration Callback. Args: - normalization_method(Optional[str]): Normalization method. - adaptive_threshold (bool): Flag indicating whether threshold should be adaptive. - default_image_threshold (Optional[float]): Default image threshold value. - default_pixel_threshold (Optional[float]): Default pixel threshold value. + normalization_method(NormalizationMethod): Normalization method. + threshold_method (ThresholdMethod): Flag indicating whether threshold should be manual or adaptive. + manual_image_threshold (Optional[float]): Default manual image threshold value. + manual_pixel_threshold (Optional[float]): Default manual pixel threshold value. """ def __init__( self, - normalization_method: str = "min_max", - adaptive_threshold: bool = True, - default_image_threshold: Optional[float] = None, - default_pixel_threshold: Optional[float] = None, + normalization_method: NormalizationMethod = NormalizationMethod.MIN_MAX, + threshold_method: ThresholdMethod = ThresholdMethod.ADAPTIVE, + manual_image_threshold: Optional[float] = None, + manual_pixel_threshold: Optional[float] = None, ) -> None: super().__init__() self.normalization_method = normalization_method - assert ( - adaptive_threshold or default_image_threshold is not None and default_pixel_threshold is not None - ), "Default thresholds must be specified when adaptive threshold is disabled." + if threshold_method == ThresholdMethod.ADAPTIVE and all( + i is not None for i in [manual_image_threshold, manual_pixel_threshold] + ): + raise ValueError( + "When `threshold_method` is set to `adaptive`, `manual_image_threshold` and `manual_pixel_threshold` " + "must not be set." + ) - self.adaptive_threshold = adaptive_threshold - self.default_image_threshold = default_image_threshold - self.default_pixel_threshold = default_pixel_threshold + if threshold_method == ThresholdMethod.MANUAL and all( + i is None for i in [manual_image_threshold, manual_pixel_threshold] + ): + raise ValueError( + "When `threshold_method` is set to `manual`, `manual_image_threshold` and `manual_pixel_threshold` " + "must be set." + ) + + self.threshold_method = threshold_method + self.manual_image_threshold = manual_image_threshold + self.manual_pixel_threshold = manual_pixel_threshold # pylint: disable=unused-argument def setup(self, trainer: Trainer, pl_module: LightningModule, stage: Optional[str] = None) -> None: @@ -57,7 +70,7 @@ def setup(self, trainer: Trainer, pl_module: LightningModule, stage: Optional[st stage (Optional[str], optional): fit, validate, test or predict. Defaults to None. """ if isinstance(pl_module, AnomalyModule): - pl_module.adaptive_threshold = self.adaptive_threshold - if pl_module.adaptive_threshold is False: - pl_module.image_threshold.value = torch.tensor(self.default_image_threshold).cpu() - pl_module.pixel_threshold.value = torch.tensor(self.default_pixel_threshold).cpu() + pl_module.threshold_method = self.threshold_method + if pl_module.threshold_method == ThresholdMethod.MANUAL: + pl_module.image_threshold.value = torch.tensor(self.manual_image_threshold).cpu() + pl_module.pixel_threshold.value = torch.tensor(self.manual_pixel_threshold).cpu() diff --git a/anomalib/utils/cli/cli.py b/anomalib/utils/cli/cli.py index 7ce51a918a..14a0bc8470 100644 --- a/anomalib/utils/cli/cli.py +++ b/anomalib/utils/cli/cli.py @@ -109,9 +109,9 @@ def add_arguments_to_parser(self, parser: LightningArgumentParser) -> None: parser.set_defaults( { "post_processing.normalization_method": "min_max", - "post_processing.adaptive_threshold": True, - "post_processing.default_image_threshold": None, - "post_processing.default_pixel_threshold": None, + "post_processing.threshold_method": "adaptive", + "post_processing.manual_image_threshold": None, + "post_processing.manual_pixel_threshold": None, } ) diff --git a/anomalib/utils/metrics/__init__.py b/anomalib/utils/metrics/__init__.py index c0c44d4e4d..a31a94a559 100644 --- a/anomalib/utils/metrics/__init__.py +++ b/anomalib/utils/metrics/__init__.py @@ -10,8 +10,8 @@ import torchmetrics from omegaconf import DictConfig, ListConfig -from .adaptive_threshold import AdaptiveThreshold from .anomaly_score_distribution import AnomalyScoreDistribution +from .anomaly_score_threshold import AnomalyScoreThreshold from .aupr import AUPR from .aupro import AUPRO from .auroc import AUROC @@ -20,7 +20,7 @@ from .optimal_f1 import OptimalF1 from .pro import PRO -__all__ = ["AUROC", "AUPR", "AUPRO", "OptimalF1", "AdaptiveThreshold", "AnomalyScoreDistribution", "MinMax", "PRO"] +__all__ = ["AUROC", "AUPR", "AUPRO", "OptimalF1", "AnomalyScoreThreshold", "AnomalyScoreDistribution", "MinMax", "PRO"] def get_metrics(config: Union[ListConfig, DictConfig]) -> Tuple[AnomalibMetricCollection, AnomalibMetricCollection]: diff --git a/anomalib/utils/metrics/adaptive_threshold.py b/anomalib/utils/metrics/anomaly_score_threshold.py similarity index 71% rename from anomalib/utils/metrics/adaptive_threshold.py rename to anomalib/utils/metrics/anomaly_score_threshold.py index fd112433f1..36709b55e0 100644 --- a/anomalib/utils/metrics/adaptive_threshold.py +++ b/anomalib/utils/metrics/anomaly_score_threshold.py @@ -1,4 +1,4 @@ -"""Implementation of Optimal F1 score based on TorchMetrics.""" +"""Implementation of AnomalyScoreThreshold based on TorchMetrics.""" # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 @@ -7,11 +7,16 @@ from torchmetrics import PrecisionRecallCurve -class AdaptiveThreshold(PrecisionRecallCurve): - """Optimal F1 Metric. +class AnomalyScoreThreshold(PrecisionRecallCurve): + """Anomaly Score Threshold. - Compute the optimal F1 score at the adaptive threshold, based on the F1 metric of the true labels and the - predicted anomaly scores. + This class computes/stores the threshold that determines the anomalous label + given anomaly scores. If the threshold method is ``manual``, the class only + stores the manual threshold values. + + If the threshold method is ``adaptive``, the class initially computes the + adaptive threshold to find the optimal f1_score and stores the computed + adaptive threshold value. """ def __init__(self, default_value: float = 0.5, **kwargs): diff --git a/anomalib/utils/sweep/helpers/callbacks.py b/anomalib/utils/sweep/helpers/callbacks.py index 3f1057a5d5..62dacda2d1 100644 --- a/anomalib/utils/sweep/helpers/callbacks.py +++ b/anomalib/utils/sweep/helpers/callbacks.py @@ -33,10 +33,10 @@ def get_sweep_callbacks(config: Union[ListConfig, DictConfig]) -> List[Callback] image_metrics = config.metrics.image if "image" in config.metrics.keys() else None pixel_metrics = config.metrics.pixel if "pixel" in config.metrics.keys() else None image_threshold = ( - config.metrics.threshold.image_default if "image_default" in config.metrics.threshold.keys() else None + config.metrics.threshold.manual_image if "manual_image" in config.metrics.threshold.keys() else None ) pixel_threshold = ( - config.metrics.threshold.pixel_default if "pixel_default" in config.metrics.threshold.keys() else None + config.metrics.threshold.manual_pixel if "manual_pixel" in config.metrics.threshold.keys() else None ) normalization_method = config.model.normalization_method # NOTE: This is for the new anomalib CLI. @@ -44,17 +44,17 @@ def get_sweep_callbacks(config: Union[ListConfig, DictConfig]) -> List[Callback] image_metrics = config.metrics.image_metrics if "image_metrics" in config.metrics else None pixel_metrics = config.metrics.pixel_metrics if "pixel_metrics" in config.metrics else None image_threshold = ( - config.post_processing.default_image_threshold if "image_default" in config.post_processing.keys() else None + config.post_processing.manual_image_threshold if "image_default" in config.post_processing.keys() else None ) pixel_threshold = ( - config.post_processing.default_pixel_threshold if "pixel_default" in config.post_processing.keys() else None + config.post_processing.manual_pixel_threshold if "pixel_default" in config.post_processing.keys() else None ) normalization_method = config.post_processing.normalization_method post_processing_configuration_callback = PostProcessingConfigurationCallback( normalization_method=normalization_method, - default_image_threshold=image_threshold, - default_pixel_threshold=pixel_threshold, + manual_image_threshold=image_threshold, + manual_pixel_threshold=pixel_threshold, ) callbacks.append(post_processing_configuration_callback) diff --git a/configs/model/cflow.yaml b/configs/model/cflow.yaml index 672b101fa1..8290475400 100644 --- a/configs/model/cflow.yaml +++ b/configs/model/cflow.yaml @@ -39,9 +39,9 @@ model: post_processing: normalization_method: min_max # - adaptive_threshold: true - default_image_threshold: null - default_pixel_threshold: null + threshold_method: adaptive # options: [adaptive, manual] + manual_image_threshold: null + manual_pixel_threshold: null metrics: image_metrics: diff --git a/configs/model/dfkde.yaml b/configs/model/dfkde.yaml index 54ba782db0..86a427042f 100644 --- a/configs/model/dfkde.yaml +++ b/configs/model/dfkde.yaml @@ -34,9 +34,9 @@ model: post_processing: normalization_method: min_max # - adaptive_threshold: true - default_image_threshold: null - default_pixel_threshold: null + threshold_method: adaptive # options: [adaptive, manual] + manual_image_threshold: null + manual_pixel_threshold: null metrics: image_metrics: diff --git a/configs/model/dfm.yaml b/configs/model/dfm.yaml index 69f7fd0a8d..75d994895d 100644 --- a/configs/model/dfm.yaml +++ b/configs/model/dfm.yaml @@ -31,9 +31,9 @@ model: post_processing: normalization_method: min_max # - adaptive_threshold: true - default_image_threshold: null - default_pixel_threshold: null + threshold_method: adaptive # options: [adaptive, manual] + manual_image_threshold: null + manual_pixel_threshold: null metrics: image_metrics: diff --git a/configs/model/draem.yaml b/configs/model/draem.yaml index 9cda0e1ac0..0e428e076e 100644 --- a/configs/model/draem.yaml +++ b/configs/model/draem.yaml @@ -31,9 +31,9 @@ optimizer: post_processing: normalization_method: min_max # - adaptive_threshold: true - default_image_threshold: null - default_pixel_threshold: null + threshold_method: adaptive # options: [adaptive, manual] + manual_image_threshold: null + manual_pixel_threshold: null metrics: image_metrics: diff --git a/configs/model/fastflow.yaml b/configs/model/fastflow.yaml index 285be59ec4..23b5aa3b65 100644 --- a/configs/model/fastflow.yaml +++ b/configs/model/fastflow.yaml @@ -37,9 +37,9 @@ optimizer: post_processing: normalization_method: min_max # - adaptive_threshold: true - default_image_threshold: null - default_pixel_threshold: null + threshold_method: adaptive # options: [adaptive, manual] + manual_image_threshold: null + manual_pixel_threshold: null metrics: image_metrics: diff --git a/configs/model/ganomaly.yaml b/configs/model/ganomaly.yaml index 3ba6708e5e..a8e3a70dbd 100644 --- a/configs/model/ganomaly.yaml +++ b/configs/model/ganomaly.yaml @@ -37,13 +37,13 @@ model: post_processing: normalization_method: min_max # - adaptive_threshold: true - default_image_threshold: null - default_pixel_threshold: null + threshold_method: adaptive # options: [adaptive, manual] + manual_image_threshold: null + manual_pixel_threshold: null metrics: - adaptive_threshold: true - default_image_threshold: null + threshold_method: adaptive # options: [adaptive, manual] + manual_image_threshold: null image_metrics: - F1Score - AUROC diff --git a/configs/model/padim.yaml b/configs/model/padim.yaml index 013d0485d8..b1c8e3f876 100644 --- a/configs/model/padim.yaml +++ b/configs/model/padim.yaml @@ -34,9 +34,9 @@ model: post_processing: normalization_method: min_max # - adaptive_threshold: true - default_image_threshold: null - default_pixel_threshold: null + threshold_method: adaptive # options: [adaptive, manual] + manual_image_threshold: null + manual_pixel_threshold: null metrics: image_metrics: diff --git a/configs/model/patchcore.yaml b/configs/model/patchcore.yaml index 4e99bf931e..b3d3f54f6b 100644 --- a/configs/model/patchcore.yaml +++ b/configs/model/patchcore.yaml @@ -34,9 +34,9 @@ model: post_processing: normalization_method: min_max # - adaptive_threshold: true - default_image_threshold: null - default_pixel_threshold: null + threshold_method: adaptive # options: [adaptive, manual] + manual_image_threshold: null + manual_pixel_threshold: null metrics: image_metrics: diff --git a/configs/model/reverse_distillation.yaml b/configs/model/reverse_distillation.yaml index f067b33846..cf1abd1f07 100644 --- a/configs/model/reverse_distillation.yaml +++ b/configs/model/reverse_distillation.yaml @@ -37,9 +37,9 @@ model: post_processing: normalization_method: min_max # - adaptive_threshold: true - default_image_threshold: null - default_pixel_threshold: null + threshold_method: adaptive # options: [adaptive, manual] + manual_image_threshold: null + manual_pixel_threshold: null metrics: image_metrics: diff --git a/configs/model/stfpm.yaml b/configs/model/stfpm.yaml index 7fc8df2d7f..b9b2914643 100644 --- a/configs/model/stfpm.yaml +++ b/configs/model/stfpm.yaml @@ -39,9 +39,9 @@ optimizer: post_processing: normalization_method: min_max # - adaptive_threshold: true - default_image_threshold: null - default_pixel_threshold: null + threshold_method: adaptive # options: [adaptive, manual] + manual_image_threshold: null + manual_pixel_threshold: null metrics: image_metrics: 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 0a6e4c9a44..c9e0929181 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 @@ -9,7 +9,11 @@ from torchvision.datasets import FakeData from anomalib.utils.callbacks import ImageVisualizerCallback -from anomalib.utils.metrics import AdaptiveThreshold, AnomalyScoreDistribution, MinMax +from anomalib.utils.metrics import ( + AnomalyScoreDistribution, + AnomalyScoreThreshold, + MinMax, +) class FakeDataModule(pl.LightningDataModule): @@ -84,8 +88,8 @@ def __init__(self, hparams: Union[DictConfig, ListConfig]): ) ] # test if this is removed - self.image_threshold = AdaptiveThreshold(hparams.model.threshold.image_default).cpu() - self.pixel_threshold = AdaptiveThreshold(hparams.model.threshold.pixel_default).cpu() + self.image_threshold = AnomalyScoreThreshold(hparams.model.threshold.image_default).cpu() + self.pixel_threshold = AnomalyScoreThreshold(hparams.model.threshold.pixel_default).cpu() self.training_distribution = AnomalyScoreDistribution().cpu() self.min_max = MinMax().cpu() diff --git a/tests/pre_merge/utils/callbacks/normalization_callback/test_normalization_callback.py b/tests/pre_merge/utils/callbacks/normalization_callback/test_normalization_callback.py index 6b2ddf6bf9..e7db2b48ee 100644 --- a/tests/pre_merge/utils/callbacks/normalization_callback/test_normalization_callback.py +++ b/tests/pre_merge/utils/callbacks/normalization_callback/test_normalization_callback.py @@ -23,7 +23,7 @@ def test_normalizer(path=get_dataset_path(), category="shapes"): config = get_configurable_parameters(config_path="anomalib/models/padim/config.yaml") config.dataset.path = path config.dataset.category = category - config.metrics.threshold.adaptive = True + config.metrics.threshold.method = "adaptive" config.project.log_images_to = [] config.metrics.image = ["F1Score", "AUROC"] diff --git a/tests/pre_merge/utils/metrics/test_adaptive_threshold.py b/tests/pre_merge/utils/metrics/test_adaptive_threshold.py index 1a7eef5b61..eb9669d23b 100644 --- a/tests/pre_merge/utils/metrics/test_adaptive_threshold.py +++ b/tests/pre_merge/utils/metrics/test_adaptive_threshold.py @@ -11,7 +11,7 @@ from anomalib.data import get_datamodule from anomalib.models import get_model from anomalib.utils.callbacks import get_callbacks -from anomalib.utils.metrics import AdaptiveThreshold +from anomalib.utils.metrics import AnomalyScoreThreshold from tests.helpers.config import get_test_configurable_parameters @@ -25,30 +25,30 @@ def test_adaptive_threshold(labels, preds, target_threshold): """Test if the adaptive threshold computation returns the desired value.""" - adaptive_threshold = AdaptiveThreshold(default_value=0.5) + adaptive_threshold = AnomalyScoreThreshold(default_value=0.5) adaptive_threshold.update(preds, labels) threshold_value = adaptive_threshold.compute() assert threshold_value == target_threshold -def test_non_adaptive_threshold(): +def test_manual_threshold(): """ - Test if the non-adaptive threshold gets used in the F1 score computation when + Test if the manual threshold gets used in the F1 score computation when adaptive thresholding is disabled and no normalization is used. """ config = get_test_configurable_parameters(config_path="anomalib/models/padim/config.yaml") config.model.normalization_method = "none" - config.metrics.threshold.adaptive = False + config.metrics.threshold.method = "manual" config.trainer.fast_dev_run = True config.metrics.image = ["F1Score"] config.metrics.pixel = ["F1Score"] image_threshold = random.random() pixel_threshold = random.random() - config.metrics.threshold.image_default = image_threshold - config.metrics.threshold.pixel_default = pixel_threshold + config.metrics.threshold.manual_image = image_threshold + config.metrics.threshold.manual_pixel = pixel_threshold model = get_model(config) datamodule = get_datamodule(config) From 84a8e066725a584a95623ebe2dc06bbc472914ad Mon Sep 17 00:00:00 2001 From: calebmm Date: Wed, 19 Oct 2022 01:39:47 -0700 Subject: [PATCH 27/38] Create meta_data.json with ONNX export as well as OpenVINO export (#636) * When exporting, create metadata.json for both ONNX and OpenVINO export (used in EII deployment) This change was developed and tested with @pmudgal-Intel * Updated export to include metadata.json in both ONNX and OpenVINO export modes. Changes developed and tested by @pmudgal-Intel @calebmm * code formatting * code formatting * Modified export_mode while exporting meta_data.json Co-authored-by: pmudgal-Intel --- anomalib/deploy/optimize.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/anomalib/deploy/optimize.py b/anomalib/deploy/optimize.py index 93b433da5b..746099359c 100644 --- a/anomalib/deploy/optimize.py +++ b/anomalib/deploy/optimize.py @@ -47,7 +47,7 @@ def export_convert( export_mode: str, export_path: Optional[Union[str, Path]] = None, ): - """Export the model to onnx format and convert to OpenVINO IR. + """Export the model to onnx format and convert to OpenVINO IR. Metadata.json is generated regardless of export mode. Args: model (AnomalyModule): Model to convert. @@ -65,14 +65,14 @@ def export_convert( input_names=["input"], output_names=["output"], ) + export_path = os.path.join(str(export_path), export_mode) if export_mode == "openvino": - export_path = os.path.join(str(export_path), "openvino") optimize_command = "mo --input_model " + str(onnx_path) + " --output_dir " + str(export_path) assert os.system(optimize_command) == 0, "OpenVINO conversion failed" - with open(Path(export_path) / "meta_data.json", "w", encoding="utf-8") as metadata_file: - meta_data = get_model_metadata(model) - # Convert metadata from torch - for key, value in meta_data.items(): - if isinstance(value, Tensor): - meta_data[key] = value.numpy().tolist() - json.dump(meta_data, metadata_file, ensure_ascii=False, indent=4) + with open(Path(export_path) / "meta_data.json", "w", encoding="utf-8") as metadata_file: + meta_data = get_model_metadata(model) + # Convert metadata from torch + for key, value in meta_data.items(): + if isinstance(value, Tensor): + meta_data[key] = value.numpy().tolist() + json.dump(meta_data, metadata_file, ensure_ascii=False, indent=4) From 406f79a613e4752e50a8768ec95102a190df488d Mon Sep 17 00:00:00 2001 From: Ashwin Vaidya Date: Thu, 20 Oct 2022 10:52:40 +0200 Subject: [PATCH 28/38] =?UTF-8?q?=F0=9F=96=8C=20refactor=20export=20callba?= =?UTF-8?q?ck=20(#640)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor export callback * refactor export functions * Rename export_convert to export * Rename optimize to export + fix tests * Fix imports * Address tests * Add nosec to surpress subprocess warnings * Add nosec to surpress run --- anomalib/deploy/__init__.py | 4 +- anomalib/deploy/{optimize.py => export.py} | 82 ++++++++++++++----- .../deploy/inferencers/torch_inferencer.py | 2 +- anomalib/models/ganomaly/config.yaml | 2 +- anomalib/utils/callbacks/__init__.py | 4 +- anomalib/utils/callbacks/export.py | 8 +- anomalib/utils/sweep/helpers/inference.py | 4 +- tests/pre_merge/deploy/test_inferencer.py | 9 +- .../callbacks/export_callback/test_export.py | 9 +- tools/benchmarking/benchmark.py | 6 +- tools/benchmarking/utils/__init__.py | 3 +- tools/benchmarking/utils/convert.py | 16 ---- 12 files changed, 89 insertions(+), 60 deletions(-) rename anomalib/deploy/{optimize.py => export.py} (51%) delete mode 100644 tools/benchmarking/utils/convert.py diff --git a/anomalib/deploy/__init__.py b/anomalib/deploy/__init__.py index ff7eb94f19..2a9bf1fc7a 100644 --- a/anomalib/deploy/__init__.py +++ b/anomalib/deploy/__init__.py @@ -3,7 +3,7 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +from .export import ExportMode, export, get_model_metadata from .inferencers import Inferencer, OpenVINOInferencer, TorchInferencer -from .optimize import export_convert, get_model_metadata -__all__ = ["Inferencer", "OpenVINOInferencer", "TorchInferencer", "export_convert", "get_model_metadata"] +__all__ = ["ExportMode", "Inferencer", "OpenVINOInferencer", "TorchInferencer", "export", "get_model_metadata"] diff --git a/anomalib/deploy/optimize.py b/anomalib/deploy/export.py similarity index 51% rename from anomalib/deploy/optimize.py rename to anomalib/deploy/export.py index 746099359c..25641bc826 100644 --- a/anomalib/deploy/optimize.py +++ b/anomalib/deploy/export.py @@ -4,9 +4,10 @@ # SPDX-License-Identifier: Apache-2.0 import json -import os +import subprocess # nosec +from enum import Enum from pathlib import Path -from typing import Dict, List, Optional, Tuple, Union +from typing import Dict, List, Tuple, Union import numpy as np import torch @@ -16,6 +17,13 @@ from anomalib.models.components import AnomalyModule +class ExportMode(str, Enum): + """Model export mode.""" + + ONNX = "onnx" + OPENVINO = "openvino" + + def get_model_metadata(model: AnomalyModule) -> Dict[str, Tensor]: """Get meta data related to normalization from model. @@ -41,34 +49,25 @@ def get_model_metadata(model: AnomalyModule) -> Dict[str, Tensor]: return meta_data -def export_convert( +def export( model: AnomalyModule, input_size: Union[List[int], Tuple[int, int]], - export_mode: str, - export_path: Optional[Union[str, Path]] = None, + export_mode: ExportMode, + export_root: Union[str, Path], ): - """Export the model to onnx format and convert to OpenVINO IR. Metadata.json is generated regardless of export mode. + """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. Args: model (AnomalyModule): Model to convert. input_size (Union[List[int], Tuple[int, int]]): Image size used as the input for onnx converter. - export_path (Union[str, Path]): Path to exported OpenVINO IR. - export_mode (str): Mode to export onnx or openvino + export_root (Union[str, Path]): Path to exported ONNX/OpenVINO IR. + export_mode (ExportMode): Mode to export the model. ONNX or OpenVINO. """ - height, width = input_size - onnx_path = os.path.join(str(export_path), "model.onnx") - torch.onnx.export( - model.model, - torch.zeros((1, 3, height, width)).to(model.device), - onnx_path, - opset_version=11, - input_names=["input"], - output_names=["output"], - ) - export_path = os.path.join(str(export_path), export_mode) - if export_mode == "openvino": - optimize_command = "mo --input_model " + str(onnx_path) + " --output_dir " + str(export_path) - assert os.system(optimize_command) == 0, "OpenVINO conversion failed" + # 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: meta_data = get_model_metadata(model) # Convert metadata from torch @@ -76,3 +75,42 @@ def export_convert( if isinstance(value, Tensor): meta_data[key] = value.numpy().tolist() json.dump(meta_data, metadata_file, ensure_ascii=False, indent=4) + + onnx_path = _export_to_onnx(model, input_size, export_path) + if export_mode == ExportMode.OPENVINO: + _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: + """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. + export_path (Path): Path to the root folder of the exported model. + + Returns: + Path: Path to the exported onnx model. + """ + onnx_path = export_path / "model.onnx" + torch.onnx.export( + model.model, + torch.zeros((1, 3, *input_size)).to(model.device), + onnx_path, + opset_version=11, + input_names=["input"], + output_names=["output"], + ) + + return onnx_path + + +def _export_to_openvino(export_path: Union[str, Path], onnx_path: Path): + """Convert onnx model to OpenVINO IR. + + Args: + export_path (Union[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)] + subprocess.run(optimize_command, check=True) # nosec diff --git a/anomalib/deploy/inferencers/torch_inferencer.py b/anomalib/deploy/inferencers/torch_inferencer.py index 795149e6c6..e6d742f31b 100644 --- a/anomalib/deploy/inferencers/torch_inferencer.py +++ b/anomalib/deploy/inferencers/torch_inferencer.py @@ -13,7 +13,7 @@ from torch import Tensor from anomalib.config import get_configurable_parameters -from anomalib.deploy.optimize import get_model_metadata +from anomalib.deploy.export import get_model_metadata from anomalib.models import get_model from anomalib.models.components import AnomalyModule from anomalib.pre_processing import PreProcessor diff --git a/anomalib/models/ganomaly/config.yaml b/anomalib/models/ganomaly/config.yaml index b511da3160..e8adfa6dc0 100644 --- a/anomalib/models/ganomaly/config.yaml +++ b/anomalib/models/ganomaly/config.yaml @@ -63,7 +63,7 @@ logging: log_graph: false # Logs the model graph to respective logger. optimization: - export_mode: "" + export_mode: null # PL Trainer Args. Don't add extra parameter here. trainer: diff --git a/anomalib/utils/callbacks/__init__.py b/anomalib/utils/callbacks/__init__.py index 8f9126d1b5..59cc25db0c 100644 --- a/anomalib/utils/callbacks/__init__.py +++ b/anomalib/utils/callbacks/__init__.py @@ -14,6 +14,8 @@ from omegaconf import DictConfig, ListConfig, OmegaConf from pytorch_lightning.callbacks import Callback, ModelCheckpoint +from anomalib.deploy import ExportMode + from .cdf_normalization import CdfNormalizationCallback from .graph import GraphLogger from .metrics_configuration import MetricsConfigurationCallback @@ -134,7 +136,7 @@ def get_callbacks(config: Union[ListConfig, DictConfig]) -> List[Callback]: input_size=config.model.input_size, dirpath=config.project.path, filename="model", - export_mode=config.optimization.export_mode, + export_mode=ExportMode(config.optimization.export_mode), ) ) else: diff --git a/anomalib/utils/callbacks/export.py b/anomalib/utils/callbacks/export.py index e859d50f59..c81eccbb72 100644 --- a/anomalib/utils/callbacks/export.py +++ b/anomalib/utils/callbacks/export.py @@ -10,7 +10,7 @@ from pytorch_lightning import Callback from pytorch_lightning.utilities.cli import CALLBACK_REGISTRY -from anomalib.deploy import export_convert +from anomalib.deploy import ExportMode, export from anomalib.models.components import AnomalyModule logger = logging.getLogger(__name__) @@ -28,7 +28,7 @@ class ExportCallback(Callback): filename (str): Name of output model """ - def __init__(self, input_size: Tuple[int, int], dirpath: str, filename: str, export_mode: str): + def __init__(self, input_size: Tuple[int, int], dirpath: str, filename: str, export_mode: ExportMode): self.input_size = input_size self.dirpath = dirpath self.filename = filename @@ -42,9 +42,9 @@ def on_train_end(self, trainer, pl_module: AnomalyModule) -> None: # pylint: di """ logger.info("Exporting the model") os.makedirs(self.dirpath, exist_ok=True) - export_convert( + export( model=pl_module, input_size=self.input_size, - export_path=self.dirpath, + export_root=self.dirpath, export_mode=self.export_mode, ) diff --git a/anomalib/utils/sweep/helpers/inference.py b/anomalib/utils/sweep/helpers/inference.py index fbbdfed2d0..289d5e7388 100644 --- a/anomalib/utils/sweep/helpers/inference.py +++ b/anomalib/utils/sweep/helpers/inference.py @@ -86,7 +86,9 @@ def get_openvino_throughput(config: Union[DictConfig, ListConfig], model_path: P Returns: float: Inference throughput """ - inferencer = OpenVINOInferencer(config, model_path / "model.xml", model_path / "meta_data.json") + inferencer = OpenVINOInferencer( + config, model_path / "openvino" / "model.xml", model_path / "openvino" / "meta_data.json" + ) openvino_dataloader = MockImageLoader(config.dataset.image_size, total_count=len(test_dataset)) start_time = time.time() # Create test images on CPU. Since we don't care about performance metrics and just the throughput, use mock data. diff --git a/tests/pre_merge/deploy/test_inferencer.py b/tests/pre_merge/deploy/test_inferencer.py index 2a86d20482..cc3b0647d3 100644 --- a/tests/pre_merge/deploy/test_inferencer.py +++ b/tests/pre_merge/deploy/test_inferencer.py @@ -14,7 +14,8 @@ from anomalib.config import get_configurable_parameters from anomalib.data import get_datamodule -from anomalib.deploy import OpenVINOInferencer, TorchInferencer, export_convert +from anomalib.deploy import OpenVINOInferencer, TorchInferencer, export +from anomalib.deploy.export import ExportMode from anomalib.models import get_model from anomalib.utils.callbacks import get_callbacks from tests.helpers.dataset import TestDataset, get_dataset_path @@ -102,11 +103,11 @@ def test_openvino_inference(self, model_name: str, category: str = "shapes", pat trainer.fit(model=model, datamodule=datamodule) - export_convert( + export( model=model, input_size=model_config.dataset.image_size, - export_path=export_path, - export_mode="openvino", + export_root=export_path, + export_mode=ExportMode.OPENVINO, ) # Test OpenVINO inferencer diff --git a/tests/pre_merge/utils/callbacks/export_callback/test_export.py b/tests/pre_merge/utils/callbacks/export_callback/test_export.py index 3e8efa3e5d..23865b2c25 100644 --- a/tests/pre_merge/utils/callbacks/export_callback/test_export.py +++ b/tests/pre_merge/utils/callbacks/export_callback/test_export.py @@ -5,6 +5,7 @@ import pytorch_lightning as pl from pytorch_lightning.callbacks.early_stopping import EarlyStopping +from anomalib.deploy import ExportMode from anomalib.utils.callbacks.export import ExportCallback from tests.helpers.config import get_test_configurable_parameters from tests.pre_merge.utils.callbacks.export_callback.dummy_lightning_model import ( @@ -15,7 +16,7 @@ @pytest.mark.parametrize( "export_mode", - ["openvino", "onnx"], + [ExportMode.OPENVINO, ExportMode.ONNX], ) def test_export_model_callback(export_mode): """Tests if an optimized model is created.""" @@ -47,9 +48,9 @@ def test_export_model_callback(export_mode): ) trainer.fit(model, datamodule=datamodule) - if "openvino" in export_mode: + if export_mode == ExportMode.OPENVINO: assert os.path.exists(os.path.join(tmp_dir, "openvino/model.bin")), "Failed to generate OpenVINO model" - elif "onnx" in export_mode: - assert os.path.exists(os.path.join(tmp_dir, "model.onnx")), "Failed to generate ONNX model" + elif export_mode == ExportMode.ONNX: + assert os.path.exists(os.path.join(tmp_dir, "onnx/model.onnx")), "Failed to generate ONNX model" else: raise ValueError(f"Unknown export_mode {export_mode}. Supported modes: onnx or openvino.") diff --git a/tools/benchmarking/benchmark.py b/tools/benchmarking/benchmark.py index 3de36e464d..b7d0263ba1 100644 --- a/tools/benchmarking/benchmark.py +++ b/tools/benchmarking/benchmark.py @@ -21,10 +21,12 @@ import torch from omegaconf import DictConfig, ListConfig, OmegaConf from pytorch_lightning import Trainer, seed_everything -from utils import convert_to_openvino, upload_to_comet, upload_to_wandb, write_metrics +from utils import upload_to_comet, upload_to_wandb, write_metrics from anomalib.config import get_configurable_parameters, update_input_size_config from anomalib.data import get_datamodule +from anomalib.deploy import export +from anomalib.deploy.export import ExportMode from anomalib.models import get_model from anomalib.utils.loggers import configure_logger from anomalib.utils.sweep import ( @@ -115,7 +117,7 @@ def get_single_model_metrics(model_config: Union[DictConfig, ListConfig], openvi # Create dirs for openvino model export openvino_export_path = project_path / Path("exported_models") openvino_export_path.mkdir(parents=True, exist_ok=True) - convert_to_openvino(model, openvino_export_path, model_config.model.input_size) + export(model, model_config.model.input_size, ExportMode.OPENVINO, openvino_export_path) openvino_throughput = get_openvino_throughput( model_config, openvino_export_path, datamodule.test_dataloader().dataset ) diff --git a/tools/benchmarking/utils/__init__.py b/tools/benchmarking/utils/__init__.py index 3c5124abaa..b9eebfed78 100644 --- a/tools/benchmarking/utils/__init__.py +++ b/tools/benchmarking/utils/__init__.py @@ -3,7 +3,6 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -from .convert import convert_to_openvino from .metrics import upload_to_comet, upload_to_wandb, write_metrics -__all__ = ["convert_to_openvino", "write_metrics", "upload_to_comet", "upload_to_wandb"] +__all__ = ["write_metrics", "upload_to_comet", "upload_to_wandb"] diff --git a/tools/benchmarking/utils/convert.py b/tools/benchmarking/utils/convert.py deleted file mode 100644 index 37040eefe7..0000000000 --- a/tools/benchmarking/utils/convert.py +++ /dev/null @@ -1,16 +0,0 @@ -"""Model converters.""" - -# Copyright (C) 2022 Intel Corporation -# SPDX-License-Identifier: Apache-2.0 - -from pathlib import Path -from typing import List, Union - -from anomalib.deploy import export_convert -from anomalib.models import AnomalyModule - - -def convert_to_openvino(model: AnomalyModule, export_path: Union[Path, str], input_size: List[int]): - """Convert the trained model to OpenVINO.""" - export_path = export_path if isinstance(export_path, Path) else Path(export_path) - export_convert(model, input_size, export_path=export_path, export_mode="openvino") From d78f995ff11632b19bcffbae2f0e92777b7b7576 Mon Sep 17 00:00:00 2001 From: Ashwin Vaidya Date: Thu, 20 Oct 2022 13:04:10 +0200 Subject: [PATCH 29/38] =?UTF-8?q?=F0=9F=90=9E=20Address=20docs=20build=20(?= =?UTF-8?q?#639)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address docs build dependency issues --- .ci/cuda10.2.Dockerfile | 3 ++- .ci/cuda11.4.Dockerfile | 3 ++- requirements/docs.txt | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.ci/cuda10.2.Dockerfile b/.ci/cuda10.2.Dockerfile index 6b2967b460..a5fb715ff8 100644 --- a/.ci/cuda10.2.Dockerfile +++ b/.ci/cuda10.2.Dockerfile @@ -23,6 +23,7 @@ RUN apt-get update && \ ffmpeg=7:3.4.2-2 \ libpython3.8=3.8.0-3ubuntu1~18.04.2 \ npm=3.5.2-0ubuntu4 \ + pandoc=1.19.2.4~dfsg-1build4 \ ruby=1:2.5.1 \ software-properties-common=0.96.24.32.18 && \ apt-get clean && \ @@ -31,7 +32,7 @@ RUN apt-get update && \ # Install latest git for github actions RUN add-apt-repository ppa:git-core/ppa &&\ apt-get update && \ - apt-get install --no-install-recommends -y git=1:2.38.0-0ppa1~ubuntu18.04.1 &&\ + apt-get install --no-install-recommends -y git=1:2.38.1-0ppa1~ubuntu18.04.1 &&\ apt-get clean && \ rm -rf /var/lib/apt/lists/* diff --git a/.ci/cuda11.4.Dockerfile b/.ci/cuda11.4.Dockerfile index 8efc52abd5..aca2262e0b 100644 --- a/.ci/cuda11.4.Dockerfile +++ b/.ci/cuda11.4.Dockerfile @@ -24,6 +24,7 @@ RUN apt-get update && \ libpython3.8=3.8.10-0ubuntu1~20.04.5 \ nodejs=10.19.0~dfsg-3ubuntu1 \ npm=6.14.4+ds-1ubuntu2 \ + pandoc=2.5-3build2 \ ruby=1:2.7+1 \ software-properties-common=0.99.9.8 && \ apt-get clean && \ @@ -32,7 +33,7 @@ RUN apt-get update && \ # Install latest git for github actions RUN add-apt-repository ppa:git-core/ppa &&\ apt-get update && \ - apt-get install --no-install-recommends -y git=1:2.38.0-0ppa1~ubuntu20.04.1 &&\ + apt-get install --no-install-recommends -y git=1:2.38.1-0ppa1~ubuntu20.04.1 &&\ apt-get clean && \ rm -rf /var/lib/apt/lists/* diff --git a/requirements/docs.txt b/requirements/docs.txt index 7804625b80..c66660429f 100644 --- a/requirements/docs.txt +++ b/requirements/docs.txt @@ -1,5 +1,6 @@ -furo==2021.7.31b41 +furo==2022.9.29 myst-parser +pandoc sphinx>=4.1.2 sphinx-autoapi sphinxemoji==0.1.8 From b21045b9507bc329db7de60ced9035cda49f6ea0 Mon Sep 17 00:00:00 2001 From: Dick Ameln Date: Mon, 31 Oct 2022 11:26:42 +0100 Subject: [PATCH 30/38] New datamodules design (#572) * move sample generation to datamodule instead of dataset * move sample generation from init to setup * remove inference stage and add base classes * replace dataset classes with AnomalibDataset * move setup to base class, create samples as class method * update docstrings * refactor btech to new format * allow training with no anomalous data * remove MVTec name from comment * raise NotImplementedError in base class * allow both png and bmp images for btech * use label_index to check if dataset contains anomalous images * refactor getitem in dataset class * use iloc for indexing * move dataloader getters to base class * refactor to add validate stage in setup * implement alternative datamodules solution * small improvements * improve design * remove unused constructor arguments * adapt btech to new design * add prepare_data method for mvtec * implement more generic random splitting function * update docstrings for folder module * ensure type consistency when performing operations on dataset * change imports * change variable names * replace pass with NotImplementedError * allow training on folder without test images * use relative path for normal_test_dir * fix dataset tests * update validation set parameter in configs * change default argument * use setter for samples * hint options for val_split_mode * update assert message and docstring * revert name change dataset vs datamodule * typing and docstrings * remove samples argument from dataset constructor * val/test -> eval * remove Split.Full from enum * sort samples when setting * update warn message * formatting * use setter when creating samples in dataset classes * add tests for new dataset class * add test case for label aware random split * update parameter name in inferencers * move _setup implementation to base class * address codacy issues * fix pylint issues * codacy * update example dataset config in docs * fix test * move base classes to separate files (avoid circular import) * add base classes * update docstring * fix imports * validation_split_mode -> val_split_mode * update docs * Update anomalib/data/base/dataset.py Co-authored-by: Joao P C Bertoldo <24547377+jpcbertoldo@users.noreply.github.com> * get length from self.samples * assert unique indices * check is_setup for individual datasets Co-authored-by: Joao P C Bertoldo <24547377+jpcbertoldo@users.noreply.github.com> * remove assert in __getitem_\ Co-authored-by: Joao P C Bertoldo <24547377+jpcbertoldo@users.noreply.github.com> * Update anomalib/data/btech.py Co-authored-by: Joao P C Bertoldo <24547377+jpcbertoldo@users.noreply.github.com> * clearer assert message * clarify list inversion in comment * comments and typing * validate contents of samples dataframe before setting * add file paths check * add seed to random_split function * fix expected columns * fix typo * add seed parameter to datamodules * set global seed in test entrypoint * add NONE option to valsplitmode * clarify setup behaviour in docstring * fix typo Co-authored-by: Joao P C Bertoldo <24547377+jpcbertoldo@users.noreply.github.com> Co-authored-by: Joao P C Bertoldo <24547377+jpcbertoldo@users.noreply.github.com> --- anomalib/config/config.py | 21 + anomalib/data/__init__.py | 31 +- anomalib/data/base/__init__.py | 10 + anomalib/data/base/datamodule.py | 108 ++++ anomalib/data/base/dataset.py | 171 ++++++ anomalib/data/btech.py | 229 +------- anomalib/data/folder.py | 541 +++++------------- anomalib/data/mvtec.py | 364 ++---------- anomalib/data/utils/__init__.py | 5 + anomalib/data/utils/split.py | 142 +++-- .../deploy/inferencers/openvino_inferencer.py | 2 +- .../deploy/inferencers/torch_inferencer.py | 2 +- anomalib/models/cflow/config.yaml | 6 +- anomalib/models/dfkde/config.yaml | 6 +- anomalib/models/dfm/config.yaml | 6 +- anomalib/models/draem/config.yaml | 6 +- anomalib/models/fastflow/config.yaml | 6 +- anomalib/models/ganomaly/config.yaml | 6 +- anomalib/models/padim/config.yaml | 6 +- anomalib/models/patchcore/config.yaml | 6 +- .../models/reverse_distillation/config.yaml | 6 +- anomalib/models/stfpm/config.yaml | 6 +- .../utils/metrics/anomaly_score_threshold.py | 10 + .../how_to_guides/train_custom_data.rst | 6 +- tests/pre_merge/datasets/test_datamodule.py | 244 ++++++++ tests/pre_merge/datasets/test_dataset.py | 293 ++-------- .../utils/metrics/test_adaptive_threshold.py | 1 + tools/test.py | 5 +- tools/train.py | 7 +- 29 files changed, 1018 insertions(+), 1234 deletions(-) create mode 100644 anomalib/data/base/__init__.py create mode 100644 anomalib/data/base/datamodule.py create mode 100644 anomalib/data/base/dataset.py create mode 100644 tests/pre_merge/datasets/test_datamodule.py diff --git a/anomalib/config/config.py b/anomalib/config/config.py index 65adf5d292..7bc4396234 100644 --- a/anomalib/config/config.py +++ b/anomalib/config/config.py @@ -136,6 +136,27 @@ def get_configurable_parameters( if "format" not in config.dataset.keys(): config.dataset.format = "mvtec" + if "create_validation_set" in config.dataset.keys(): + warn( + "The 'create_validation_set' parameter is deprecated and will be removed in v0.4.0. Please use " + "'validation_split_mode' instead." + ) + config.dataset.validation_split_mode = "from_test" if config.dataset.create_validation_set else "same_as_test" + + if "test_batch_size" in config.dataset.keys(): + warn( + "The 'test_batch_size' parameter is deprecated and will be removed in v0.4.0. Please use " + "'eval_batch_size' instead." + ) + config.dataset.eval_batch_size = config.dataset.test_batch_size + + if "transform_config" in config.dataset.keys() and "val" in config.dataset.transform_config.keys(): + warn( + "The 'transform_config.val' parameter is deprecated and will be removed in v0.4.0. Please use " + "'transform_config.eval' instead." + ) + config.dataset.transform_config.eval = config.dataset.transform_config.val + config = update_input_size_config(config) # Project Configs diff --git a/anomalib/data/__init__.py b/anomalib/data/__init__.py index 8c295a1061..55cdd7aa11 100644 --- a/anomalib/data/__init__.py +++ b/anomalib/data/__init__.py @@ -7,8 +7,8 @@ from typing import Union from omegaconf import DictConfig, ListConfig -from pytorch_lightning import LightningDataModule +from .base import AnomalibDataModule, AnomalibDataset from .btech import BTech from .folder import Folder from .inference import InferenceDataset @@ -17,7 +17,7 @@ logger = logging.getLogger(__name__) -def get_datamodule(config: Union[DictConfig, ListConfig]) -> LightningDataModule: +def get_datamodule(config: Union[DictConfig, ListConfig]) -> AnomalibDataModule: """Get Anomaly Datamodule. Args: @@ -28,37 +28,33 @@ def get_datamodule(config: Union[DictConfig, ListConfig]) -> LightningDataModule """ logger.info("Loading the datamodule") - datamodule: LightningDataModule + datamodule: AnomalibDataModule if config.dataset.format.lower() == "mvtec": datamodule = MVTec( - # TODO: Remove config values. IAAALD-211 root=config.dataset.path, category=config.dataset.category, image_size=(config.dataset.image_size[0], config.dataset.image_size[1]), train_batch_size=config.dataset.train_batch_size, - test_batch_size=config.dataset.test_batch_size, + eval_batch_size=config.dataset.eval_batch_size, num_workers=config.dataset.num_workers, - seed=config.project.seed, task=config.dataset.task, transform_config_train=config.dataset.transform_config.train, - transform_config_val=config.dataset.transform_config.val, - create_validation_set=config.dataset.create_validation_set, + transform_config_eval=config.dataset.transform_config.eval, + val_split_mode=config.dataset.val_split_mode, ) elif config.dataset.format.lower() == "btech": datamodule = BTech( - # TODO: Remove config values. IAAALD-211 root=config.dataset.path, category=config.dataset.category, image_size=(config.dataset.image_size[0], config.dataset.image_size[1]), train_batch_size=config.dataset.train_batch_size, - test_batch_size=config.dataset.test_batch_size, + eval_batch_size=config.dataset.eval_batch_size, num_workers=config.dataset.num_workers, - seed=config.project.seed, task=config.dataset.task, transform_config_train=config.dataset.transform_config.train, - transform_config_val=config.dataset.transform_config.val, - create_validation_set=config.dataset.create_validation_set, + transform_config_eval=config.dataset.transform_config.eval, + val_split_mode=config.dataset.val_split_mode, ) elif config.dataset.format.lower() == "folder": datamodule = Folder( @@ -70,14 +66,13 @@ def get_datamodule(config: Union[DictConfig, ListConfig]) -> LightningDataModule mask_dir=config.dataset.mask, extensions=config.dataset.extensions, split_ratio=config.dataset.split_ratio, - seed=config.project.seed, image_size=(config.dataset.image_size[0], config.dataset.image_size[1]), train_batch_size=config.dataset.train_batch_size, - test_batch_size=config.dataset.test_batch_size, + eval_batch_size=config.dataset.eval_batch_size, num_workers=config.dataset.num_workers, transform_config_train=config.dataset.transform_config.train, - transform_config_val=config.dataset.transform_config.val, - create_validation_set=config.dataset.create_validation_set, + transform_config_eval=config.dataset.transform_config.eval, + val_split_mode=config.dataset.val_split_mode, ) else: raise ValueError( @@ -90,6 +85,8 @@ def get_datamodule(config: Union[DictConfig, ListConfig]) -> LightningDataModule __all__ = [ + "AnomalibDataset", + "AnomalibDataModule", "get_datamodule", "BTech", "Folder", diff --git a/anomalib/data/base/__init__.py b/anomalib/data/base/__init__.py new file mode 100644 index 0000000000..afb5a62463 --- /dev/null +++ b/anomalib/data/base/__init__.py @@ -0,0 +1,10 @@ +"""Base classes for custom dataset and datamodules.""" + +# Copyright (C) 2022 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + + +from .datamodule import AnomalibDataModule +from .dataset import AnomalibDataset + +__all__ = ["AnomalibDataset", "AnomalibDataModule"] diff --git a/anomalib/data/base/datamodule.py b/anomalib/data/base/datamodule.py new file mode 100644 index 0000000000..d6d01824d6 --- /dev/null +++ b/anomalib/data/base/datamodule.py @@ -0,0 +1,108 @@ +"""Anomalib datamodule base class.""" + +# Copyright (C) 2022 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +import logging +from abc import ABC +from typing import Optional + +from pandas import DataFrame +from pytorch_lightning import LightningDataModule +from pytorch_lightning.utilities.types import EVAL_DATALOADERS, TRAIN_DATALOADERS +from torch.utils.data import DataLoader + +from anomalib.data.base.dataset import AnomalibDataset +from anomalib.data.utils import ValSplitMode, random_split + +logger = logging.getLogger(__name__) + + +class AnomalibDataModule(LightningDataModule, ABC): + """Base Anomalib data module. + + Args: + train_batch_size (int): Batch size used by the train dataloader. + test_batch_size (int): Batch size used by the val and test dataloaders. + num_workers (int): Number of workers used by the train, val and test dataloaders. + seed (Optional[int], optional): Seed used during random subset splitting. + """ + + def __init__( + self, + train_batch_size: int, + eval_batch_size: int, + num_workers: int, + val_split_mode: ValSplitMode, + seed: Optional[int] = None, + ): + super().__init__() + self.train_batch_size = train_batch_size + self.eval_batch_size = eval_batch_size + self.num_workers = num_workers + self.val_split_mode = val_split_mode + self.seed = seed + + self.train_data: Optional[AnomalibDataset] = None + self.val_data: Optional[AnomalibDataset] = None + self.test_data: Optional[AnomalibDataset] = None + + self._samples: Optional[DataFrame] = None + + def setup(self, stage: Optional[str] = None): + """Setup train, validation and test data. + + Args: + stage: Optional[str]: Train/Val/Test stages. (Default value = None) + """ + if not self.is_setup: + self._setup(stage) + assert self.is_setup + + def _setup(self, _stage: Optional[str] = None) -> None: + """Set up the datasets and perform dynamic subset splitting. + + This method may be overridden in subclass for custom splitting behaviour. + + Note: The stage argument is not used here. This is because, for a given instance of an AnomalibDataModule + subclass, all three subsets are created at the first call of setup(). This is to accommodate the subset + splitting behaviour of anomaly tasks, where the validation set is usually extracted from the test set, and + the test set must therefore be created as early as the `fit` stage. + """ + assert self.train_data is not None + assert self.test_data is not None + + self.train_data.setup() + self.test_data.setup() + if self.val_split_mode == ValSplitMode.FROM_TEST: + self.val_data, self.test_data = random_split(self.test_data, [0.5, 0.5], label_aware=True, seed=self.seed) + elif self.val_split_mode == ValSplitMode.SAME_AS_TEST: + self.val_data = self.test_data + elif self.val_split_mode != ValSplitMode.NONE: + 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 train_dataloader(self) -> TRAIN_DATALOADERS: + """Get train dataloader.""" + return DataLoader(self.train_data, shuffle=True, batch_size=self.train_batch_size, num_workers=self.num_workers) + + def val_dataloader(self) -> EVAL_DATALOADERS: + """Get validation dataloader.""" + return DataLoader(self.val_data, shuffle=False, batch_size=self.eval_batch_size, num_workers=self.num_workers) + + def test_dataloader(self) -> EVAL_DATALOADERS: + """Get test dataloader.""" + return DataLoader(self.test_data, shuffle=False, batch_size=self.eval_batch_size, num_workers=self.num_workers) diff --git a/anomalib/data/base/dataset.py b/anomalib/data/base/dataset.py new file mode 100644 index 0000000000..6b2c9aefd4 --- /dev/null +++ b/anomalib/data/base/dataset.py @@ -0,0 +1,171 @@ +"""Anomalib dataset base class.""" + +# Copyright (C) 2022 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +import copy +import logging +from abc import ABC, abstractmethod +from pathlib import Path +from typing import Dict, Sequence, Union + +import cv2 +import numpy as np +import pandas as pd +from pandas import DataFrame +from torch import Tensor +from torch.utils.data import Dataset + +from anomalib.data.utils import read_image +from anomalib.pre_processing import PreProcessor + +_EXPECTED_COLS_CLASSIFICATION = ["image_path", "label", "label_index", "split"] +_EXPECTED_COLS_SEGMENTATION = _EXPECTED_COLS_CLASSIFICATION + ["mask_path"] +_EXPECTED_COLS_PERTASK = { + "classification": _EXPECTED_COLS_CLASSIFICATION, + "segmentation": _EXPECTED_COLS_SEGMENTATION, +} + +logger = logging.getLogger(__name__) + + +class AnomalibDataset(Dataset, ABC): + """Anomalib dataset.""" + + def __init__(self, task: str, pre_process: PreProcessor): + super().__init__() + self.task = task + self.pre_process = pre_process + self._samples: DataFrame = None + + def __len__(self) -> int: + """Get length of the dataset.""" + return len(self.samples) + + def subsample(self, indices: Sequence[int], inplace=False) -> AnomalibDataset: + """Subsamples the dataset at the provided indices. + + Args: + indices (Sequence[int]): Indices at which the dataset is to be subsampled. + inplace (bool): When true, the subsampling will be performed on the instance itself. + """ + assert len(set(indices)) == len(indices), "No duplicates allowed in indices." + dataset = self if inplace else copy.deepcopy(self) + dataset.samples = self.samples.iloc[indices].reset_index(drop=True) + return dataset + + @property + def is_setup(self) -> bool: + """Checks if setup() been called.""" + return isinstance(self._samples, DataFrame) + + @property + def samples(self) -> DataFrame: + """Get the samples dataframe.""" + if not self.is_setup: + raise RuntimeError("Dataset is not setup yet. Call setup() first.") + return self._samples + + @samples.setter + def samples(self, samples: DataFrame): + """Overwrite the samples with a new dataframe. + + Args: + samples (DataFrame): DataFrame with new samples. + """ + # validate the passed samples by checking the + assert isinstance(samples, DataFrame), f"samples must be a pandas.DataFrame, found {type(samples)}" + expected_columns = _EXPECTED_COLS_PERTASK[self.task] + assert all( + col in samples.columns for col in expected_columns + ), f"samples must have (at least) columns {expected_columns}, found {samples.columns}" + assert samples["image_path"].apply(lambda p: Path(p).exists()).all(), "missing file path(s) in samples" + + self._samples = samples.sort_values(by="image_path", ignore_index=True) + + @property + def has_normal(self) -> bool: + """Check if the dataset contains any normal samples.""" + return 0 in list(self.samples.label_index) + + @property + 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]]: + """Get dataset item for the index ``index``. + + Args: + index (int): Index to get the item. + + Returns: + Union[Dict[str, Tensor], Dict[str, Union[str, Tensor]]]: Dict of image tensor during training. + Otherwise, Dict containing image path, target path, image tensor, label and transformed bounding box. + """ + + image_path = self._samples.iloc[index].image_path + image = read_image(image_path) + label_index = self._samples.iloc[index].label_index + + item = dict(image_path=image_path, label=label_index) + + if self.task == "classification": + pre_processed = self.pre_process(image=image) + elif self.task == "segmentation": + mask_path = self._samples.iloc[index].mask_path + + # Only Anomalous (1) images have masks in anomaly datasets + # Therefore, create empty mask for Normal (0) images. + if label_index == 0: + mask = np.zeros(shape=image.shape[:2]) + else: + mask = cv2.imread(mask_path, flags=0) / 255.0 + + pre_processed = self.pre_process(image=image, mask=mask) + + item["mask_path"] = mask_path + item["mask"] = pre_processed["mask"] + else: + raise ValueError(f"Unknown task type: {self.task}") + item["image"] = pre_processed["image"] + + return item + + def __add__(self, other_dataset: AnomalibDataset) -> AnomalibDataset: + """Concatenate this dataset with another dataset.""" + assert isinstance(other_dataset, self.__class__), "Cannot concatenate datasets that are not of the same type." + assert self.is_setup, "Cannot concatenate uninitialized datasets. Call setup first." + assert other_dataset.is_setup, "Cannot concatenate uninitialized datasets. Call setup first." + dataset = copy.deepcopy(self) + dataset.samples = pd.concat([self.samples, other_dataset.samples], ignore_index=True) + return dataset + + def setup(self) -> None: + """Load data/metadata into memory.""" + if not self.is_setup: + self._setup() + assert self.is_setup, "setup() should set self._samples" + + @abstractmethod + def _setup(self) -> DataFrame: + """Set up the data module. + + This method should return a dataframe that contains the information needed by the dataloader to load each of + the dataset items into memory. + The dataframe must at least contain the following columns: + split: the subset to which the dataset item is assigned. + image_path: path to file system location where the image is stored. + label_index: index of the anomaly label, typically 0 for "normal" and 1 for "anomalous". + mask_path (if task == "segmentation"): path to the ground truth masks (for the anomalous images only). + + Example: + |---|-------------------|-----------|-------------|------------------|-------| + | | image_path | label | label_index | mask_path | split | + |---|-------------------|-----------|-------------|------------------|-------| + | 0 | path/to/image.png | anomalous | 1 | path/to/mask.png | train | + |---|-------------------|-----------|-------------|------------------|-------| + """ + raise NotImplementedError diff --git a/anomalib/data/btech.py b/anomalib/data/btech.py index c5246c0097..74b341c9a4 100644 --- a/anomalib/data/btech.py +++ b/anomalib/data/btech.py @@ -11,44 +11,26 @@ import logging import shutil -import warnings import zipfile from pathlib import Path -from typing import Dict, Optional, Tuple, Union +from typing import Optional, Tuple, Union from urllib.request import urlretrieve import albumentations as A import cv2 -import numpy as np import pandas as pd from pandas.core.frame import DataFrame -from pytorch_lightning.core.datamodule import LightningDataModule from pytorch_lightning.utilities.cli import DATAMODULE_REGISTRY -from pytorch_lightning.utilities.types import EVAL_DATALOADERS, TRAIN_DATALOADERS -from torch import Tensor -from torch.utils.data import DataLoader -from torch.utils.data.dataset import Dataset -from torchvision.datasets.folder import VisionDataset from tqdm import tqdm -from anomalib.data.inference import InferenceDataset -from anomalib.data.utils import DownloadProgressBar, hash_check, read_image -from anomalib.data.utils.split import ( - create_validation_set_from_test_set, - split_normal_images_in_train_set, -) +from anomalib.data.base import AnomalibDataModule, AnomalibDataset +from anomalib.data.utils import DownloadProgressBar, Split, ValSplitMode, hash_check from anomalib.pre_processing import PreProcessor logger = logging.getLogger(__name__) -def make_btech_dataset( - path: Path, - split: Optional[str] = None, - split_ratio: float = 0.1, - seed: Optional[int] = None, - create_validation_set: bool = False, -) -> DataFrame: +def make_btech_dataset(path: Path, split: Optional[Union[Split, str]] = None) -> DataFrame: """Create BTech samples by parsing the BTech data file structure. The files are expected to follow the structure: @@ -57,7 +39,7 @@ def make_btech_dataset( Args: path (Path): Path to dataset - split (str, optional): Dataset split (ie., either train or test). Defaults to None. + split (Optional[Union[Split, str]], 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. @@ -107,12 +89,6 @@ def make_btech_dataset( # Modify image_path column by converting to absolute path samples["image_path"] = samples.path + "/" + samples.split + "/" + samples.label + "/" + samples.image_path - # Split the normal images in training set if test set doesn't - # contain any normal images. This is needed because AUC score - # cannot be computed based on 1-class - if sum((samples.split == "test") & (samples.label == "ok")) == 0: - samples = split_normal_images_in_train_set(samples, split_ratio, seed) - # Good images don't have mask samples.loc[(samples.split == "test") & (samples.label == "ok"), "mask_path"] = "" @@ -121,18 +97,15 @@ def make_btech_dataset( samples.loc[(samples.label != "ok"), "label_index"] = 1 samples.label_index = samples.label_index.astype(int) - if create_validation_set: - samples = create_validation_set_from_test_set(samples, seed=seed) - # Get the data frame for the split. - if split is not None and split in ["train", "val", "test"]: + if split: samples = samples[samples.split == split] samples = samples.reset_index(drop=True) return samples -class BTechDataset(VisionDataset): +class BTechDataset(AnomalibDataset): """BTech PyTorch Dataset.""" def __init__( @@ -140,10 +113,8 @@ def __init__( root: Union[Path, str], category: str, pre_process: PreProcessor, - split: str, + split: Optional[Union[Split, str]] = None, task: str = "segmentation", - seed: Optional[int] = None, - create_validation_set: bool = False, ) -> None: """Btech Dataset class. @@ -153,7 +124,6 @@ def __init__( pre_process: List of pre_processing object containing albumentation compose. split: 'train', 'val' or 'test' task: ``classification`` or ``segmentation`` - seed: seed used for the random subset splitting create_validation_set: Create a validation subset in addition to the train and test subsets Examples: @@ -186,94 +156,32 @@ def __init__( >>> dataset[0]["image"].shape, dataset[0]["mask"].shape (torch.Size([3, 256, 256]), torch.Size([256, 256])) """ - super().__init__(root) + super().__init__(task, pre_process) - if seed is None: - warnings.warn( - "seed is None." - " When seed is not set, images from the normal directory are split between training and test dir." - " This will lead to inconsistency between runs." - ) - - self.root = Path(root) if isinstance(root, str) else root - self.category: str = category + self.root_category = Path(root) / category self.split = split - self.task = task - - self.pre_process = pre_process - - self.samples = make_btech_dataset( - path=self.root / category, - split=self.split, - seed=seed, - create_validation_set=create_validation_set, - ) - - def __len__(self) -> int: - """Get length of the dataset.""" - return len(self.samples) - - def __getitem__(self, index: int) -> Dict[str, Union[str, Tensor]]: - """Get dataset item for the index ``index``. - - Args: - index (int): Index to get the item. - - Returns: - Union[Dict[str, Tensor], Dict[str, Union[str, Tensor]]]: Dict of image tensor during training. - Otherwise, Dict containing image path, target path, image tensor, label and transformed bounding box. - """ - item: Dict[str, Union[str, Tensor]] = {} - - image_path = self.samples.image_path[index] - image = read_image(image_path) - - pre_processed = self.pre_process(image=image) - item = {"image": pre_processed["image"]} - - if self.split in ["val", "test"]: - label_index = self.samples.label_index[index] - - item["image_path"] = image_path - item["label"] = label_index - - if self.task == "segmentation": - mask_path = self.samples.mask_path[index] - - # Only Anomalous (1) images has masks in BTech dataset. - # Therefore, create empty mask for Normal (0) images. - if label_index == 0: - mask = np.zeros(shape=image.shape[:2]) - else: - mask = cv2.imread(mask_path, flags=0) / 255.0 - - pre_processed = self.pre_process(image=image, mask=mask) - - item["mask_path"] = mask_path - item["image"] = pre_processed["image"] - item["mask"] = pre_processed["mask"] - return item + def _setup(self): + self.samples = make_btech_dataset(path=self.root_category, split=self.split) @DATAMODULE_REGISTRY -class BTech(LightningDataModule): +class BTech(AnomalibDataModule): """BTechDataModule Lightning Data Module.""" def __init__( self, root: str, category: str, - # TODO: Remove default values. IAAALD-211 image_size: Optional[Union[int, Tuple[int, int]]] = None, train_batch_size: int = 32, - test_batch_size: int = 32, + eval_batch_size: int = 32, num_workers: int = 8, task: str = "segmentation", transform_config_train: Optional[Union[str, A.Compose]] = None, - transform_config_val: Optional[Union[str, A.Compose]] = None, + transform_config_eval: Optional[Union[str, A.Compose]] = None, + val_split_mode: ValSplitMode = ValSplitMode.SAME_AS_TEST, seed: Optional[int] = None, - create_validation_set: bool = False, ) -> None: """Instantiate BTech Lightning Data Module. @@ -287,8 +195,8 @@ def __init__( task: ``classification`` or ``segmentation`` transform_config_train: Config for pre-processing during training. transform_config_val: Config for pre-processing during validation. - seed: seed used for the random subset splitting 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. Examples: >>> from anomalib.data import BTech @@ -316,34 +224,20 @@ def __init__( >>> data["image"].shape, data["mask"].shape (torch.Size([32, 3, 256, 256]), torch.Size([32, 256, 256])) """ - super().__init__() + super().__init__(train_batch_size, eval_batch_size, num_workers, val_split_mode, seed) - self.root = root if isinstance(root, Path) else Path(root) - self.category = category - self.dataset_path = self.root / self.category - self.transform_config_train = transform_config_train - self.transform_config_val = transform_config_val - self.image_size = image_size + self.root = Path(root) + self.category = Path(category) - if self.transform_config_train is not None and self.transform_config_val is None: - self.transform_config_val = self.transform_config_train + pre_process_train = PreProcessor(config=transform_config_train, image_size=image_size) + pre_process_eval = PreProcessor(config=transform_config_eval, image_size=image_size) - self.pre_process_train = PreProcessor(config=self.transform_config_train, image_size=self.image_size) - self.pre_process_val = PreProcessor(config=self.transform_config_val, image_size=self.image_size) - - self.train_batch_size = train_batch_size - self.test_batch_size = test_batch_size - self.num_workers = num_workers - - self.create_validation_set = create_validation_set - self.task = task - self.seed = seed - - self.train_data: Dataset - self.test_data: Dataset - if create_validation_set: - self.val_data: Dataset - self.inference_data: Dataset + self.train_data = BTechDataset( + task=task, pre_process=pre_process_train, split=Split.TRAIN, root=root, category=category + ) + self.test_data = BTechDataset( + task=task, pre_process=pre_process_eval, split=Split.TEST, root=root, category=category + ) def prepare_data(self) -> None: """Download the dataset if not available.""" @@ -385,70 +279,3 @@ def prepare_data(self) -> None: logger.info("Cleaning the tar file") zip_filename.unlink() - - def setup(self, stage: Optional[str] = None) -> None: - """Setup train, validation and test data. - - BTech dataset uses BTech dataset structure, which is the reason for - using `anomalib.data.btech.BTech` class to get the dataset items. - - Args: - stage: Optional[str]: Train/Val/Test stages. (Default value = None) - - """ - logger.info("Setting up train, validation, test and prediction datasets.") - if stage in (None, "fit"): - self.train_data = BTechDataset( - root=self.root, - category=self.category, - pre_process=self.pre_process_train, - split="train", - task=self.task, - seed=self.seed, - create_validation_set=self.create_validation_set, - ) - - if self.create_validation_set: - self.val_data = BTechDataset( - root=self.root, - category=self.category, - pre_process=self.pre_process_val, - split="val", - task=self.task, - seed=self.seed, - create_validation_set=self.create_validation_set, - ) - - self.test_data = BTechDataset( - root=self.root, - category=self.category, - pre_process=self.pre_process_val, - split="test", - task=self.task, - seed=self.seed, - create_validation_set=self.create_validation_set, - ) - - if stage == "predict": - self.inference_data = InferenceDataset( - path=self.root, image_size=self.image_size, transform_config=self.transform_config_val - ) - - def train_dataloader(self) -> TRAIN_DATALOADERS: - """Get train dataloader.""" - return DataLoader(self.train_data, shuffle=True, batch_size=self.train_batch_size, num_workers=self.num_workers) - - def val_dataloader(self) -> EVAL_DATALOADERS: - """Get validation dataloader.""" - dataset = self.val_data if self.create_validation_set else self.test_data - return DataLoader(dataset=dataset, shuffle=False, batch_size=self.test_batch_size, num_workers=self.num_workers) - - def test_dataloader(self) -> EVAL_DATALOADERS: - """Get test dataloader.""" - return DataLoader(self.test_data, shuffle=False, batch_size=self.test_batch_size, num_workers=self.num_workers) - - def predict_dataloader(self) -> EVAL_DATALOADERS: - """Get predict dataloader.""" - return DataLoader( - self.inference_data, shuffle=False, batch_size=self.test_batch_size, num_workers=self.num_workers - ) diff --git a/anomalib/data/folder.py b/anomalib/data/folder.py index 39a1a150a4..53f5d922d7 100644 --- a/anomalib/data/folder.py +++ b/anomalib/data/folder.py @@ -6,31 +6,16 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -import logging -import warnings from pathlib import Path -from typing import Dict, Optional, Tuple, Union +from typing import Optional, Tuple, Union import albumentations as A -import cv2 -import numpy as np -from pandas.core.frame import DataFrame -from pytorch_lightning.core.datamodule import LightningDataModule -from pytorch_lightning.utilities.cli import DATAMODULE_REGISTRY -from pytorch_lightning.utilities.types import EVAL_DATALOADERS, TRAIN_DATALOADERS -from torch import Tensor -from torch.utils.data import DataLoader, Dataset +from pandas import DataFrame from torchvision.datasets.folder import IMG_EXTENSIONS -from anomalib.data.inference import InferenceDataset -from anomalib.data.utils import read_image -from anomalib.data.utils.split import ( - create_validation_set_from_test_set, - split_normal_images_in_train_set, -) -from anomalib.pre_processing import PreProcessor - -logger = logging.getLogger(__name__) +from anomalib.data.base import AnomalibDataModule, AnomalibDataset +from anomalib.data.utils import Split, ValSplitMode, random_split +from anomalib.pre_processing.pre_process import PreProcessor def _check_and_convert_path(path: Union[str, Path]) -> Path: @@ -77,34 +62,26 @@ def _prepare_files_labels( return filenames, labels -def make_dataset( +def make_folder_dataset( normal_dir: Union[str, Path], - abnormal_dir: Union[str, Path], + abnormal_dir: Optional[Union[str, Path]] = None, normal_test_dir: Optional[Union[str, Path]] = None, mask_dir: Optional[Union[str, Path]] = None, - split: Optional[str] = None, - split_ratio: float = 0.2, - seed: Optional[int] = None, - create_validation_set: bool = True, + split: Optional[Union[Split, str]] = None, extensions: Optional[Tuple[str, ...]] = None, ): """Make Folder Dataset. Args: normal_dir (Union[str, Path]): Path to the directory containing normal images. - abnormal_dir (Union[str, Path]): Path to the directory containing abnormal images. + 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 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 the mask annotations. Defaults to None. - split (Optional[str], 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.2. - seed (int, optional): Random seed to ensure reproducibility when splitting. Defaults to 0. - create_validation_set (bool, optional):Boolean to create a validation set from the test set. - Those wanting to create a validation set could set this flag to ``True``. + split (Optional[Union[Split, str]], 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 directory. @@ -114,7 +91,10 @@ def make_dataset( filenames = [] labels = [] - dirs = {"normal": normal_dir, "abnormal": abnormal_dir} + dirs = {"normal": normal_dir} + + if abnormal_dir: + dirs = {**dirs, **{"abnormal": abnormal_dir}} if normal_test_dir: dirs = {**dirs, **{"normal_test": normal_test_dir}} @@ -149,392 +129,179 @@ def make_dataset( samples.loc[(samples.label == "normal"), "split"] = "train" samples.loc[(samples.label == "abnormal") | (samples.label == "normal_test"), "split"] = "test" - if not normal_test_dir: - samples = split_normal_images_in_train_set( - samples=samples, split_ratio=split_ratio, seed=seed, normal_label="normal" - ) - - # If `create_validation_set` is set to True, the test set is split into half. - if create_validation_set: - samples = create_validation_set_from_test_set(samples, seed=seed, normal_label="normal") - # Get the data frame for the split. - if split is not None and split in ["train", "val", "test"]: + if split: samples = samples[samples.split == split] samples = samples.reset_index(drop=True) return samples -class FolderDataset(Dataset): - """Folder Dataset.""" +class FolderDataset(AnomalibDataset): + """Folder dataset. + + Args: + task (str): Task type. (classification or segmentation). + pre_process (PreProcessor): Image Pre-processor to apply transform. + split (Optional[Union[Split, str]]): Fixed subset split that follows from folder structure on file system. + Choose from [Split.FULL, Split.TRAIN, Split.TEST] + + root (Union[str, Path]): Root folder of the dataset. + normal_dir (Union[str, Path]): Path to the directory containing normal images. + 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 images for the test dataset. Defaults to None. + mask_dir (Optional[Union[str, Path]], 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 + directory. + val_split_mode (ValSplitMode): Setting that determines how the validation subset is obtained. + + Raises: + ValueError: When task is set to classification and `mask_dir` is provided. When `mask_dir` is + provided, `task` should be set to `segmentation`. + """ def __init__( self, - normal_dir: Union[Path, str], - abnormal_dir: Union[Path, str], - split: str, + task: str, pre_process: PreProcessor, - normal_test_dir: Optional[Union[Path, str]] = None, - split_ratio: float = 0.2, - mask_dir: Optional[Union[Path, str]] = None, + root: Union[str, Path], + normal_dir: Union[str, Path], + 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, + val_split_mode: ValSplitMode = ValSplitMode.SAME_AS_TEST, extensions: Optional[Tuple[str, ...]] = None, - task: Optional[str] = None, - seed: Optional[int] = None, - create_validation_set: bool = False, ) -> None: - """Create Folder Folder Dataset. - - Args: - normal_dir (Union[str, Path]): Path to the directory containing normal images. - abnormal_dir (Union[str, Path]): Path to the directory containing abnormal images. - split (Optional[str], optional): Dataset split (ie., either train or test). Defaults to None. - pre_process (Optional[PreProcessor], optional): Image Pro-processor to apply transform. - Defaults to None. - normal_test_dir (Optional[Union[str, Path]], optional): Path to the directory containing - normal images for the test dataset. 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.2. - mask_dir (Optional[Union[str, Path]], 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 - directory. - task (Optional[str], optional): Task type. (classification or segmentation) Defaults to None. - seed (int, optional): Random seed to ensure reproducibility when splitting. Defaults to 0. - create_validation_set (bool, optional):Boolean to create a validation set from the test set. - Those wanting to create a validation set could set this flag to ``True``. - - Raises: - ValueError: When task is set to classification and `mask_dir` is provided. When `mask_dir` is - provided, `task` should be set to `segmentation`. - - """ - self.split = split - - if task == "segmentation" and mask_dir is None: - warnings.warn( - "Segmentation task is requested, but mask directory is not provided. " - "Classification is to be chosen if mask directory is not provided." - ) - self.task = "classification" - - if task == "classification" and mask_dir: - warnings.warn( - "Classification task is requested, but mask directory is provided. " - "Segmentation task is to be chosen if mask directory is provided." - ) - self.task = "segmentation" - - if task is None or mask_dir is None: - self.task = "classification" - else: - self.task = task - - self.pre_process = pre_process - self.samples = make_dataset( - normal_dir=normal_dir, - abnormal_dir=abnormal_dir, - normal_test_dir=normal_test_dir, - mask_dir=mask_dir, - split=split, - split_ratio=split_ratio, - seed=seed, - create_validation_set=create_validation_set, - extensions=extensions, - ) - - def __len__(self) -> int: - """Get length of the dataset.""" - return len(self.samples) + super().__init__(task, pre_process) - def __getitem__(self, index: int) -> Dict[str, Union[str, Tensor]]: - """Get dataset item for the index ``index``. - - Args: - index (int): Index to get the item. - - Returns: - Union[Dict[str, Tensor], Dict[str, Union[str, Tensor]]]: Dict of image tensor during training. - Otherwise, Dict containing image path, target path, image tensor, label and transformed bounding box. - """ - item: Dict[str, Union[str, Tensor]] = {} - - image_path = self.samples.image_path[index] - image = read_image(image_path) - - pre_processed = self.pre_process(image=image) - item = {"image": pre_processed["image"]} - - if self.split in ["val", "test"]: - label_index = self.samples.label_index[index] - - item["image_path"] = image_path - item["label"] = label_index - - if self.task == "segmentation": - mask_path = self.samples.mask_path[index] - - # Only Anomalous (1) images has masks in MVTec AD dataset. - # Therefore, create empty mask for Normal (0) images. - if label_index == 0: - mask = np.zeros(shape=image.shape[:2]) - else: - mask = cv2.imread(mask_path, flags=0) / 255.0 + self.split = split + self.normal_dir = Path(root) / Path(normal_dir) + self.abnormal_dir = Path(root) / Path(abnormal_dir) if abnormal_dir else None + self.normal_test_dir = Path(root) / Path(normal_test_dir) if normal_test_dir else None + self.mask_dir = mask_dir + self.extensions = extensions - pre_processed = self.pre_process(image=image, mask=mask) + self.val_split_mode = val_split_mode - item["mask_path"] = mask_path - item["image"] = pre_processed["image"] - item["mask"] = pre_processed["mask"] + def _setup(self): + """Assign samples.""" + self.samples = make_folder_dataset( + normal_dir=self.normal_dir, + abnormal_dir=self.abnormal_dir, + normal_test_dir=self.normal_test_dir, + mask_dir=self.mask_dir, + split=self.split, + extensions=self.extensions, + ) - return item +class Folder(AnomalibDataModule): + """Folder DataModule. -@DATAMODULE_REGISTRY -class Folder(LightningDataModule): - """Folder Lightning Data Module.""" + Args: + root (Union[str, Path]): Path to the root folder containing normal and abnormal dirs. + normal_dir (Union[str, Path]): Name of the directory containing normal images. + Defaults to "normal". + abnormal_dir (Union[str, Path]): Name of the directory containing abnormal images. + Defaults to "abnormal". + normal_test_dir (Optional[Union[str, Path]], 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 + the mask annotations. 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.2. + extensions (Optional[Tuple[str, ...]], 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. + Defaults to None. + train_batch_size (int, optional): Training batch size. Defaults to 32. + test_batch_size (int, optional): Test batch size. Defaults to 32. + num_workers (int, optional): Number of workers. Defaults to 8. + task (str, optional): Task type. Could be either classification or segmentation. + Defaults to "classification". + transform_config_train (Optional[Union[str, A.Compose]], optional): Config for pre-processing + during training. + Defaults to None. + transform_config_val (Optional[Union[str, A.Compose]], optional): Config for pre-processing + during validation. + Defaults to None. + val_split_mode (ValSplitMode): Setting that determines how the validation subset is obtained. + seed (Optional[int], optional): Seed used during random subset splitting. + """ def __init__( self, root: Union[str, Path], - normal_dir: str = "normal", - abnormal_dir: str = "abnormal", - task: str = "classification", - normal_test_dir: Optional[Union[Path, str]] = None, - mask_dir: Optional[Union[Path, str]] = None, - extensions: Optional[Tuple[str, ...]] = None, + normal_dir: Union[str, Path], + abnormal_dir: Union[str, Path], + normal_test_dir: Optional[Union[str, Path]] = None, + mask_dir: Optional[Union[str, Path]] = None, split_ratio: float = 0.2, - seed: Optional[int] = None, + extensions: Optional[Tuple[str]] = None, + # image_size: Optional[Union[int, Tuple[int, int]]] = None, train_batch_size: int = 32, - test_batch_size: int = 32, + eval_batch_size: int = 32, num_workers: int = 8, + task: str = "segmentation", transform_config_train: Optional[Union[str, A.Compose]] = None, - transform_config_val: Optional[Union[str, A.Compose]] = None, - create_validation_set: bool = False, - ) -> None: - """Folder Dataset PL Datamodule. - - Args: - root (Union[str, Path]): Path to the root folder containing normal and abnormal dirs. - normal_dir (str, optional): Name of the directory containing normal images. - Defaults to "normal". - abnormal_dir (str, optional): Name of the directory containing abnormal images. - Defaults to "abnormal". - task (str, optional): Task type. Could be either classification or segmentation. - Defaults to "classification". - normal_test_dir (Optional[Union[str, Path]], 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 - the mask annotations. Defaults to None. - extensions (Optional[Tuple[str, ...]], optional): Type of the image extensions to read from the - directory. 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.2. - seed (int, optional): Random seed to ensure reproducibility when splitting. Defaults to 0. - image_size (Optional[Union[int, Tuple[int, int]]], optional): Size of the input image. - Defaults to None. - train_batch_size (int, optional): Training batch size. Defaults to 32. - test_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 - during training. - Defaults to None. - transform_config_val (Optional[Union[str, A.Compose]], optional): Config for pre-processing - during validation. - Defaults to None. - create_validation_set (bool, optional):Boolean to create a validation set from the test set. - Those wanting to create a validation set could set this flag to ``True``. - - Examples: - Assume that we use Folder Dataset for the MVTec/bottle/broken_large category. We would do: - - >>> from anomalib.data import Folder - >>> datamodule = Folder( - ... root="./datasets/MVTec/bottle/test", - ... normal="good", - ... abnormal="broken_large", - ... image_size=256 - ... ) - >>> datamodule.setup() - >>> i, data = next(enumerate(datamodule.train_dataloader())) - >>> data["image"].shape - torch.Size([16, 3, 256, 256]) - - >>> i, test_data = next(enumerate(datamodule.test_dataloader())) - >>> test_data.keys() - dict_keys(['image']) - - We could also create a Folder DataModule for datasets containing mask annotations. - The dataset expects that mask annotation filenames must be same as the original filename. - To this end, we modified mask filenames in MVTec AD bottle category. - Now we could try folder data module using the mvtec bottle broken large category - - >>> datamodule = Folder( - ... root="./datasets/bottle/test", - ... normal="good", - ... abnormal="broken_large", - ... mask_dir="./datasets/bottle/ground_truth/broken_large", - ... image_size=256 - ... ) - - >>> i , train_data = next(enumerate(datamodule.train_dataloader())) - >>> train_data.keys() - dict_keys(['image']) - >>> train_data["image"].shape - torch.Size([16, 3, 256, 256]) - - >>> i, test_data = next(enumerate(datamodule.test_dataloader())) - dict_keys(['image_path', 'label', 'mask_path', 'image', 'mask']) - >>> print(test_data["image"].shape, test_data["mask"].shape) - torch.Size([24, 3, 256, 256]) torch.Size([24, 256, 256]) - - By default, Folder Data Module does not create a validation set. If a validation set - is needed it could be set as follows: - - >>> datamodule = Folder( - ... root="./datasets/bottle/test", - ... normal="good", - ... abnormal="broken_large", - ... mask_dir="./datasets/bottle/ground_truth/broken_large", - ... image_size=256, - ... create_validation_set=True, - ... ) - - >>> i, val_data = next(enumerate(datamodule.val_dataloader())) - >>> val_data.keys() - dict_keys(['image_path', 'label', 'mask_path', 'image', 'mask']) - >>> print(val_data["image"].shape, val_data["mask"].shape) - torch.Size([12, 3, 256, 256]) torch.Size([12, 256, 256]) - - >>> i, test_data = next(enumerate(datamodule.test_dataloader())) - >>> print(test_data["image"].shape, test_data["mask"].shape) - torch.Size([12, 3, 256, 256]) torch.Size([12, 256, 256]) - - """ - super().__init__() - - if seed is None and normal_test_dir is None: - raise ValueError( - "Both seed and normal_test_dir cannot be None." - " When seed is not set, images from the normal directory are split between training and test dir." - " This will lead to inconsistency between runs." - ) - - self.root = _check_and_convert_path(root) - self.normal_dir = self.root / normal_dir - self.abnormal_dir = self.root / abnormal_dir - self.normal_test = normal_test_dir - if normal_test_dir: - self.normal_test = self.root / normal_test_dir - self.mask_dir = mask_dir - self.extensions = extensions + transform_config_eval: Optional[Union[str, A.Compose]] = None, + val_split_mode: ValSplitMode = ValSplitMode.FROM_TEST, + seed: Optional[int] = None, + ): + super().__init__( + train_batch_size=train_batch_size, + eval_batch_size=eval_batch_size, + num_workers=num_workers, + val_split_mode=val_split_mode, + seed=seed, + ) + self.split_ratio = split_ratio - if task == "classification" and mask_dir is not None: - raise ValueError( - "Classification type is set but mask_dir provided. " - "If mask_dir is provided task type must be segmentation. " - "Check your configuration." - ) - self.task = task - self.transform_config_train = transform_config_train - self.transform_config_val = transform_config_val - self.image_size = image_size - - if self.transform_config_train is not None and self.transform_config_val is None: - self.transform_config_val = self.transform_config_train - - self.pre_process_train = PreProcessor(config=self.transform_config_train, image_size=self.image_size) - self.pre_process_val = PreProcessor(config=self.transform_config_val, image_size=self.image_size) - - self.train_batch_size = train_batch_size - self.test_batch_size = test_batch_size - self.num_workers = num_workers - - self.create_validation_set = create_validation_set - self.seed = seed - - self.train_data: Dataset - self.test_data: Dataset - if create_validation_set: - self.val_data: Dataset - self.inference_data: Dataset - - def setup(self, stage: Optional[str] = None) -> None: - """Setup train, validation and test data. - - Args: - stage: Optional[str]: Train/Val/Test stages. (Default value = None) - - """ - logger.info("Setting up train, validation, test and prediction datasets.") - if stage in (None, "fit"): - self.train_data = FolderDataset( - normal_dir=self.normal_dir, - abnormal_dir=self.abnormal_dir, - normal_test_dir=self.normal_test, - split="train", - split_ratio=self.split_ratio, - mask_dir=self.mask_dir, - pre_process=self.pre_process_train, - extensions=self.extensions, - task=self.task, - seed=self.seed, - create_validation_set=self.create_validation_set, - ) - - if self.create_validation_set: - self.val_data = FolderDataset( - normal_dir=self.normal_dir, - abnormal_dir=self.abnormal_dir, - normal_test_dir=self.normal_test, - split="val", - split_ratio=self.split_ratio, - mask_dir=self.mask_dir, - pre_process=self.pre_process_val, - extensions=self.extensions, - task=self.task, - seed=self.seed, - create_validation_set=self.create_validation_set, - ) + pre_process_train = PreProcessor(config=transform_config_train, image_size=image_size) + pre_process_eval = PreProcessor(config=transform_config_eval, image_size=image_size) - self.test_data = FolderDataset( - normal_dir=self.normal_dir, - abnormal_dir=self.abnormal_dir, - split="test", - normal_test_dir=self.normal_test, - split_ratio=self.split_ratio, - mask_dir=self.mask_dir, - pre_process=self.pre_process_val, - extensions=self.extensions, - task=self.task, - seed=self.seed, - create_validation_set=self.create_validation_set, + self.train_data = FolderDataset( + task=task, + pre_process=pre_process_train, + split=Split.TRAIN, + root=root, + normal_dir=normal_dir, + abnormal_dir=abnormal_dir, + normal_test_dir=normal_test_dir, + mask_dir=mask_dir, + extensions=extensions, ) - if stage == "predict": - self.inference_data = InferenceDataset( - path=self.root, image_size=self.image_size, transform_config=self.transform_config_val - ) + self.test_data = FolderDataset( + task=task, + pre_process=pre_process_eval, + split=Split.TEST, + root=root, + normal_dir=normal_dir, + abnormal_dir=abnormal_dir, + normal_test_dir=normal_test_dir, + mask_dir=mask_dir, + extensions=extensions, + ) - def train_dataloader(self) -> TRAIN_DATALOADERS: - """Get train dataloader.""" - return DataLoader(self.train_data, shuffle=True, batch_size=self.train_batch_size, num_workers=self.num_workers) + def _setup(self, _stage: Optional[str] = None): + """Set up the datasets for the Folder Data Module.""" + assert self.train_data is not None + assert self.test_data is not None - def val_dataloader(self) -> EVAL_DATALOADERS: - """Get validation dataloader.""" - dataset = self.val_data if self.create_validation_set else self.test_data - return DataLoader(dataset=dataset, shuffle=False, batch_size=self.test_batch_size, num_workers=self.num_workers) + self.train_data.setup() + self.test_data.setup() - def test_dataloader(self) -> EVAL_DATALOADERS: - """Get test dataloader.""" - return DataLoader(self.test_data, shuffle=False, batch_size=self.test_batch_size, num_workers=self.num_workers) + # add some normal images to the test set + if not self.test_data.has_normal: + self.train_data, normal_test_data = random_split(self.train_data, self.split_ratio, seed=self.seed) + self.test_data += normal_test_data - def predict_dataloader(self) -> EVAL_DATALOADERS: - """Get predict dataloader.""" - return DataLoader( - self.inference_data, shuffle=False, batch_size=self.test_batch_size, num_workers=self.num_workers - ) + super()._setup() diff --git a/anomalib/data/mvtec.py b/anomalib/data/mvtec.py index c98350eeaa..2b21edf180 100644 --- a/anomalib/data/mvtec.py +++ b/anomalib/data/mvtec.py @@ -3,21 +3,17 @@ Description: This script contains PyTorch Dataset, Dataloader and PyTorch Lightning DataModule for the MVTec AD dataset. - If the dataset is not on the file system, the script downloads and extracts the dataset and create PyTorch data objects. - License: MVTec AD dataset is released under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License (CC BY-NC-SA 4.0)(https://creativecommons.org/licenses/by-nc-sa/4.0/). - Reference: - Paul Bergmann, Kilian Batzner, Michael Fauser, David Sattlegger, Carsten Steger: The MVTec Anomaly Detection Dataset: A Comprehensive Real-World Dataset for Unsupervised Anomaly Detection; in: International Journal of Computer Vision 129(4):1038-1059, 2021, DOI: 10.1007/s11263-020-01400-4. - - Paul Bergmann, Michael Fauser, David Sattlegger, Carsten Steger: MVTec AD — A Comprehensive Real-World Dataset for Unsupervised Anomaly Detection; in: IEEE/CVF Conference on Computer Vision and Pattern Recognition (CVPR), @@ -29,42 +25,21 @@ import logging import tarfile -import warnings from pathlib import Path -from typing import Dict, Optional, Tuple, Union +from typing import Optional, Tuple, Union from urllib.request import urlretrieve import albumentations as A -import cv2 -import numpy as np -import pandas as pd -from pandas.core.frame import DataFrame -from pytorch_lightning.core.datamodule import LightningDataModule -from pytorch_lightning.utilities.cli import DATAMODULE_REGISTRY -from pytorch_lightning.utilities.types import EVAL_DATALOADERS, TRAIN_DATALOADERS -from torch import Tensor -from torch.utils.data import DataLoader -from torch.utils.data.dataset import Dataset -from torchvision.datasets.folder import VisionDataset - -from anomalib.data.inference import InferenceDataset -from anomalib.data.utils import DownloadProgressBar, hash_check, read_image -from anomalib.data.utils.split import ( - create_validation_set_from_test_set, - split_normal_images_in_train_set, -) +from pandas import DataFrame + +from anomalib.data.base import AnomalibDataModule, AnomalibDataset +from anomalib.data.utils import DownloadProgressBar, Split, ValSplitMode, hash_check from anomalib.pre_processing import PreProcessor logger = logging.getLogger(__name__) -def make_mvtec_dataset( - path: Path, - split: Optional[str] = None, - split_ratio: float = 0.1, - seed: Optional[int] = None, - create_validation_set: bool = False, -) -> DataFrame: +def make_mvtec_dataset(root: Union[str, Path], split: Optional[Union[Split, str]] = None) -> DataFrame: """Create MVTec AD samples by parsing the MVTec AD data file structure. The files are expected to follow the structure: @@ -80,7 +55,7 @@ def make_mvtec_dataset( Args: path (Path): Path to dataset - split (str, optional): Dataset split (ie., either train or test). Defaults to None. + split (Optional[Union[Split, str]], 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. @@ -108,13 +83,13 @@ def make_mvtec_dataset( 4 MVTec/bottle train good MVTec/bottle/train/good/109.png MVTec/bottle/ground_truth/good/109_mask.png 0 Returns: - DataFrame: an output dataframe containing samples for the requested split (ie., train or test) + DataFrame: an output dataframe containing the samples of the dataset. """ - samples_list = [(str(path),) + filename.parts[-3:] for filename in path.glob("**/*.png")] + samples_list = [(str(root),) + filename.parts[-3:] for filename in Path(root).glob("**/*.png")] if len(samples_list) == 0: - raise RuntimeError(f"Found 0 images in {path}") + raise RuntimeError(f"Found 0 images in {root}") - samples = pd.DataFrame(samples_list, columns=["path", "split", "label", "image_path"]) + samples = DataFrame(samples_list, columns=["path", "split", "label", "image_path"]) samples = samples[samples.split != "ground_truth"] # Create mask_path column @@ -130,12 +105,6 @@ def make_mvtec_dataset( # Modify image_path column by converting to absolute path samples["image_path"] = samples.path + "/" + samples.split + "/" + samples.label + "/" + samples.image_path - # Split the normal images in training set if test set doesn't - # contain any normal images. This is needed because AUC score - # cannot be computed based on 1-class - if sum((samples.split == "test") & (samples.label == "good")) == 0: - samples = split_normal_images_in_train_set(samples, split_ratio, seed) - # Good images don't have mask samples.loc[(samples.split == "test") & (samples.label == "good"), "mask_path"] = "" @@ -144,229 +113,78 @@ def make_mvtec_dataset( samples.loc[(samples.label != "good"), "label_index"] = 1 samples.label_index = samples.label_index.astype(int) - if create_validation_set: - samples = create_validation_set_from_test_set(samples, seed=seed) - - # Get the data frame for the split. - if split is not None and split in ["train", "val", "test"]: - samples = samples[samples.split == split] - samples = samples.reset_index(drop=True) + if split: + samples = samples[samples.split == split].reset_index(drop=True) return samples -class MVTecDataset(VisionDataset): - """MVTec AD PyTorch Dataset.""" +class MVTecDataset(AnomalibDataset): + """MVTec dataset class. + + Args: + task (str): Task type, either 'classification' or 'segmentation' + pre_process (PreProcessor): Pre-processor object + split (Optional[Union[Split, str]]): 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' + """ def __init__( self, - root: Union[Path, str], - category: str, + task: str, pre_process: PreProcessor, - split: str, - task: str = "segmentation", - seed: Optional[int] = None, - create_validation_set: bool = False, + root: str, + category: str, + split: Optional[Union[Split, str]] = None, ) -> None: - """Mvtec AD Dataset class. - - Args: - root: Path to the MVTec AD dataset - category: Name of the MVTec AD category. - pre_process: List of pre_processing object containing albumentation compose. - split: 'train', 'val' or 'test' - task: ``classification`` or ``segmentation`` - seed: seed used for the random subset splitting - create_validation_set: Create a validation subset in addition to the train and test subsets - - Examples: - >>> from anomalib.data.mvtec import MVTecDataset - >>> from anomalib.data.transforms import PreProcessor - >>> pre_process = PreProcessor(image_size=256) - >>> dataset = MVTecDataset( - ... root='./datasets/MVTec', - ... category='leather', - ... pre_process=pre_process, - ... task="classification", - ... is_train=True, - ... ) - >>> dataset[0].keys() - dict_keys(['image']) - - >>> dataset.split = "test" - >>> dataset[0].keys() - dict_keys(['image', 'image_path', 'label']) - - >>> dataset.task = "segmentation" - >>> dataset.split = "train" - >>> dataset[0].keys() - dict_keys(['image']) - - >>> dataset.split = "test" - >>> dataset[0].keys() - dict_keys(['image_path', 'label', 'mask_path', 'image', 'mask']) - - >>> dataset[0]["image"].shape, dataset[0]["mask"].shape - (torch.Size([3, 256, 256]), torch.Size([256, 256])) - """ - super().__init__(root) - - if seed is None: - warnings.warn( - "seed is None." - " When seed is not set, images from the normal directory are split between training and test dir." - " This will lead to inconsistency between runs." - ) - - self.root = Path(root) if isinstance(root, str) else root - self.category: str = category - self.split = split - self.task = task - - self.pre_process = pre_process - - self.samples = make_mvtec_dataset( - path=self.root / category, - split=self.split, - seed=seed, - create_validation_set=create_validation_set, - ) - - def __len__(self) -> int: - """Get length of the dataset.""" - return len(self.samples) - - def __getitem__(self, index: int) -> Dict[str, Union[str, Tensor]]: - """Get dataset item for the index ``index``. - - Args: - index (int): Index to get the item. - - Returns: - Union[Dict[str, Tensor], Dict[str, Union[str, Tensor]]]: Dict of image tensor during training. - Otherwise, Dict containing image path, target path, image tensor, label and transformed bounding box. - """ - item: Dict[str, Union[str, Tensor]] = {} - - image_path = self.samples.image_path[index] - image = read_image(image_path) - - pre_processed = self.pre_process(image=image) - item = {"image": pre_processed["image"]} + super().__init__(task=task, pre_process=pre_process) - if self.split in ["val", "test"]: - label_index = self.samples.label_index[index] - - item["image_path"] = image_path - item["label"] = label_index - - if self.task == "segmentation": - mask_path = self.samples.mask_path[index] - - # Only Anomalous (1) images has masks in MVTec AD dataset. - # Therefore, create empty mask for Normal (0) images. - if label_index == 0: - mask = np.zeros(shape=image.shape[:2]) - else: - mask = cv2.imread(mask_path, flags=0) / 255.0 - - pre_processed = self.pre_process(image=image, mask=mask) - - item["mask_path"] = mask_path - item["image"] = pre_processed["image"] - item["mask"] = pre_processed["mask"] + self.root_category = Path(root) / Path(category) + self.split = split - return item + def _setup(self): + self.samples = make_mvtec_dataset(self.root_category, split=self.split) -@DATAMODULE_REGISTRY -class MVTec(LightningDataModule): - """MVTec AD Lightning Data Module.""" +class MVTec(AnomalibDataModule): + """MVTec Datamodule.""" def __init__( self, root: str, category: str, - # TODO: Remove default values. IAAALD-211 image_size: Optional[Union[int, Tuple[int, int]]] = None, train_batch_size: int = 32, - test_batch_size: int = 32, + eval_batch_size: int = 32, num_workers: int = 8, task: str = "segmentation", transform_config_train: Optional[Union[str, A.Compose]] = None, - transform_config_val: Optional[Union[str, A.Compose]] = None, + transform_config_eval: Optional[Union[str, A.Compose]] = None, + val_split_mode: ValSplitMode = ValSplitMode.SAME_AS_TEST, seed: Optional[int] = None, - create_validation_set: bool = False, - ) -> None: - """Mvtec AD Lightning Data Module. - - Args: - root: Path to the MVTec AD dataset - category: Name of the MVTec AD category. - image_size: Variable to which image is resized. - train_batch_size: Training batch size. - test_batch_size: Testing batch size. - num_workers: Number of workers. - task: ``classification`` or ``segmentation`` - transform_config_train: Config for pre-processing during training. - transform_config_val: Config for pre-processing during validation. - seed: seed used for the random subset splitting - create_validation_set: Create a validation subset in addition to the train and test subsets - - Examples: - >>> from anomalib.data import MVTec - >>> datamodule = MVTec( - ... root="./datasets/MVTec", - ... category="leather", - ... image_size=256, - ... train_batch_size=32, - ... test_batch_size=32, - ... num_workers=8, - ... transform_config_train=None, - ... transform_config_val=None, - ... ) - >>> datamodule.setup() - - >>> i, data = next(enumerate(datamodule.train_dataloader())) - >>> data.keys() - dict_keys(['image']) - >>> data["image"].shape - torch.Size([32, 3, 256, 256]) - - >>> i, data = next(enumerate(datamodule.val_dataloader())) - >>> data.keys() - dict_keys(['image_path', 'label', 'mask_path', 'image', 'mask']) - >>> data["image"].shape, data["mask"].shape - (torch.Size([32, 3, 256, 256]), torch.Size([32, 256, 256])) - """ - super().__init__() - - self.root = root if isinstance(root, Path) else Path(root) - self.category = category - self.dataset_path = self.root / self.category - self.transform_config_train = transform_config_train - self.transform_config_val = transform_config_val - self.image_size = image_size - - if self.transform_config_train is not None and self.transform_config_val is None: - self.transform_config_val = self.transform_config_train - - self.pre_process_train = PreProcessor(config=self.transform_config_train, image_size=self.image_size) - self.pre_process_val = PreProcessor(config=self.transform_config_val, image_size=self.image_size) - - self.train_batch_size = train_batch_size - self.test_batch_size = test_batch_size - self.num_workers = num_workers - - self.create_validation_set = create_validation_set - self.task = task - self.seed = seed - - self.train_data: Dataset - self.test_data: Dataset - if create_validation_set: - self.val_data: Dataset - self.inference_data: Dataset + ): + super().__init__( + train_batch_size=train_batch_size, + eval_batch_size=eval_batch_size, + num_workers=num_workers, + val_split_mode=val_split_mode, + seed=seed, + ) + + self.root = Path(root) + self.category = Path(category) + + # TODO: Get rid of PreProcessor by passing transform directly + pre_process_train = PreProcessor(config=transform_config_train, image_size=image_size) + pre_process_eval = PreProcessor(config=transform_config_eval, image_size=image_size) + + self.train_data = MVTecDataset( + task=task, pre_process=pre_process_train, split=Split.TRAIN, root=root, category=category + ) + self.test_data = MVTecDataset( + task=task, pre_process=pre_process_eval, split=Split.TEST, root=root, category=category + ) def prepare_data(self) -> None: """Download the dataset if not available.""" @@ -394,67 +212,3 @@ def prepare_data(self) -> None: logger.info("Cleaning the tar file") (zip_filename).unlink() - - def setup(self, stage: Optional[str] = None) -> None: - """Setup train, validation and test data. - - Args: - stage: Optional[str]: Train/Val/Test stages. (Default value = None) - - """ - logger.info("Setting up train, validation, test and prediction datasets.") - if stage in (None, "fit"): - self.train_data = MVTecDataset( - root=self.root, - category=self.category, - pre_process=self.pre_process_train, - split="train", - task=self.task, - seed=self.seed, - create_validation_set=self.create_validation_set, - ) - - if self.create_validation_set: - self.val_data = MVTecDataset( - root=self.root, - category=self.category, - pre_process=self.pre_process_val, - split="val", - task=self.task, - seed=self.seed, - create_validation_set=self.create_validation_set, - ) - - self.test_data = MVTecDataset( - root=self.root, - category=self.category, - pre_process=self.pre_process_val, - split="test", - task=self.task, - seed=self.seed, - create_validation_set=self.create_validation_set, - ) - - if stage == "predict": - self.inference_data = InferenceDataset( - path=self.root, image_size=self.image_size, transform_config=self.transform_config_val - ) - - def train_dataloader(self) -> TRAIN_DATALOADERS: - """Get train dataloader.""" - return DataLoader(self.train_data, shuffle=True, batch_size=self.train_batch_size, num_workers=self.num_workers) - - def val_dataloader(self) -> EVAL_DATALOADERS: - """Get validation dataloader.""" - dataset = self.val_data if self.create_validation_set else self.test_data - return DataLoader(dataset=dataset, shuffle=False, batch_size=self.test_batch_size, num_workers=self.num_workers) - - def test_dataloader(self) -> EVAL_DATALOADERS: - """Get test dataloader.""" - return DataLoader(self.test_data, shuffle=False, batch_size=self.test_batch_size, num_workers=self.num_workers) - - def predict_dataloader(self) -> EVAL_DATALOADERS: - """Get predict dataloader.""" - return DataLoader( - self.inference_data, shuffle=False, batch_size=self.test_batch_size, num_workers=self.num_workers - ) diff --git a/anomalib/data/utils/__init__.py b/anomalib/data/utils/__init__.py index 5059b51c06..53eb3f8ef2 100644 --- a/anomalib/data/utils/__init__.py +++ b/anomalib/data/utils/__init__.py @@ -11,6 +11,7 @@ get_image_height_and_width, read_image, ) +from .split import Split, ValSplitMode, concatenate_datasets, random_split __all__ = [ "generate_output_image_filename", @@ -20,4 +21,8 @@ "random_2d_perlin", "read_image", "DownloadProgressBar", + "random_split", + "concatenate_datasets", + "Split", + "ValSplitMode", ] diff --git a/anomalib/data/utils/split.py b/anomalib/data/utils/split.py index 311928bb6b..86249086c2 100644 --- a/anomalib/data/utils/split.py +++ b/anomalib/data/utils/split.py @@ -11,76 +11,108 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -import random -from typing import Optional +from __future__ import annotations -from pandas.core.frame import DataFrame +import math +import warnings +from enum import Enum +from typing import TYPE_CHECKING, List, Optional, Sequence, Union +import torch -def split_normal_images_in_train_set( - samples: DataFrame, split_ratio: float = 0.1, seed: Optional[int] = None, normal_label: str = "good" -) -> DataFrame: - """Split normal images in train set. +if TYPE_CHECKING: + from anomalib.data import AnomalibDataset - This function splits the normal images in training set and assigns the - values to the test set. This is particularly useful especially when the - test set does not contain any normal images. - This is important because when the test set doesn't have any normal images, - AUC computation fails due to having single class. +class Split(str, Enum): + """Split of a subset.""" - Args: - samples (DataFrame): Dataframe containing dataset info such as filenames, splits etc. - split_ratio (float, optional): Train-Test normal image split ratio. Defaults to 0.1. - seed (int, optional): Random seed to ensure reproducibility. Defaults to 0. - normal_label (str): Name of the normal label. For MVTec AD, for instance, this is normal_label. - - Returns: - DataFrame: Output dataframe where the part of the training set is assigned to test set. - """ - - if seed is not None: - random.seed(seed) - - normal_train_image_indices = samples.index[(samples.split == "train") & (samples.label == normal_label)].to_list() - num_normal_train_images = len(normal_train_image_indices) - num_normal_valid_images = int(num_normal_train_images * split_ratio) + TRAIN = "train" + VAL = "val" + TEST = "test" - indices_to_split_from_train_set = random.sample(population=normal_train_image_indices, k=num_normal_valid_images) - samples.loc[indices_to_split_from_train_set, "split"] = "test" - return samples +class ValSplitMode(str, Enum): + """Splitting mode used to obtain validation subset.""" + NONE = "none" + SAME_AS_TEST = "same_as_test" + FROM_TEST = "from_test" -def create_validation_set_from_test_set( - samples: DataFrame, seed: Optional[int] = None, normal_label: str = "good" -) -> DataFrame: - """Craete Validation Set from Test Set. - This function creates a validation set from test set by splitting both - normal and abnormal samples to two. +def concatenate_datasets(datasets: Sequence[AnomalibDataset]) -> AnomalibDataset: + """Concatenate multiple datasets into a single dataset object. Args: - samples (DataFrame): Dataframe containing dataset info such as filenames, splits etc. - seed (int, optional): Random seed to ensure reproducibility. Defaults to 0. - normal_label (str): Name of the normal label. For MVTec AD, for instance, this is normal_label. - """ - - if seed is not None: - random.seed(seed) + datasets (Sequence[AnomalibDataset]): Sequence of at least two datasets. - # Split normal images. - normal_test_image_indices = samples.index[(samples.split == "test") & (samples.label == normal_label)].to_list() - num_normal_valid_images = len(normal_test_image_indices) // 2 + Returns: + AnomalibDataset: Dataset that contains the combined samples of all input datasets. + """ + concat_dataset = datasets[0] + for dataset in datasets[1:]: + concat_dataset += dataset + return concat_dataset - indices_to_sample = random.sample(population=normal_test_image_indices, k=num_normal_valid_images) - samples.loc[indices_to_sample, "split"] = "val" - # Split abnormal images. - abnormal_test_image_indices = samples.index[(samples.split == "test") & (samples.label != normal_label)].to_list() - num_abnormal_valid_images = len(abnormal_test_image_indices) // 2 +def random_split( + dataset: AnomalibDataset, + split_ratio: Union[float, Sequence[float]], + label_aware: bool = False, + seed: Optional[int] = None, +) -> List[AnomalibDataset]: + """Perform a random split of a dataset. - indices_to_sample = random.sample(population=abnormal_test_image_indices, k=num_abnormal_valid_images) - samples.loc[indices_to_sample, "split"] = "val" + Args: + dataset (AnomalibDataset): Source dataset + split_ratio (Union[float, Sequence[float]]): Fractions of the splits that will be produced. The values in the + sequence must sum to 1. If a single value is passed, the ratio will be converted to + [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 + """ - return samples + if isinstance(split_ratio, float): + split_ratio = [1 - split_ratio, split_ratio] + + assert ( + math.isclose(sum(split_ratio), 1) and sum(split_ratio) <= 1 + ), f"split ratios must sum to 1, found {sum(split_ratio)}" + assert all(0 < ratio < 1 for ratio in split_ratio), f"all split ratios must be between 0 and 1, found {split_ratio}" + + # create list of source data + if label_aware: + indices_per_label = [group.index for _, group in dataset.samples.groupby("label_index")] + per_label_datasets = [dataset.subsample(indices) for indices in indices_per_label] + else: + per_label_datasets = [dataset] + + # outer list: per-label unique, inner list: random subsets with the given ratio + subsets: List[List[AnomalibDataset]] = [] + # 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) * ratio))) + for i in range(len(label_dataset) - sum(subset_lengths)): + subset_idx = i % sum(subset_lengths) + subset_lengths[subset_idx] += 1 + if 0 in subset_lengths: + warnings.warn( + "Zero subset length encountered during splitting. This means one of your subsets might be" + " empty or devoid of either normal or anomalous images." + ) + + # perform random subsampling + random_state = torch.Generator().manual_seed(seed) if seed else None + indices = torch.randperm(len(label_dataset), generator=random_state) + subsets.append( + [label_dataset.subsample(subset_indices) for subset_indices in torch.split(indices, subset_lengths)] + ) + + # invert outer/inner lists + # outer list: subsets with the given ratio, inner list: per-label unique + subsets = list(map(list, zip(*subsets))) + return [concatenate_datasets(subset) for subset in subsets] diff --git a/anomalib/deploy/inferencers/openvino_inferencer.py b/anomalib/deploy/inferencers/openvino_inferencer.py index 804abb52d0..e68ef0f294 100644 --- a/anomalib/deploy/inferencers/openvino_inferencer.py +++ b/anomalib/deploy/inferencers/openvino_inferencer.py @@ -94,7 +94,7 @@ def pre_process(self, image: np.ndarray) -> np.ndarray: np.ndarray: pre-processed image. """ transform_config = ( - self.config.dataset.transform_config.val if "transform_config" in self.config.dataset.keys() else None + self.config.dataset.transform_config.eval if "transform_config" in self.config.dataset.keys() else None ) image_size = tuple(self.config.dataset.image_size) pre_processor = PreProcessor(transform_config, image_size) diff --git a/anomalib/deploy/inferencers/torch_inferencer.py b/anomalib/deploy/inferencers/torch_inferencer.py index e6d742f31b..fdd0646e1b 100644 --- a/anomalib/deploy/inferencers/torch_inferencer.py +++ b/anomalib/deploy/inferencers/torch_inferencer.py @@ -96,7 +96,7 @@ def pre_process(self, image: np.ndarray) -> Tensor: Tensor: pre-processed image. """ transform_config = ( - self.config.dataset.transform_config.val if "transform_config" in self.config.dataset.keys() else None + self.config.dataset.transform_config.eval if "transform_config" in self.config.dataset.keys() else None ) image_size = tuple(self.config.dataset.image_size) pre_processor = PreProcessor(transform_config, image_size) diff --git a/anomalib/models/cflow/config.yaml b/anomalib/models/cflow/config.yaml index 4337538445..d11989141f 100644 --- a/anomalib/models/cflow/config.yaml +++ b/anomalib/models/cflow/config.yaml @@ -6,14 +6,14 @@ dataset: task: segmentation image_size: 256 train_batch_size: 16 - test_batch_size: 16 + eval_batch_size: 16 inference_batch_size: 16 fiber_batch_size: 64 num_workers: 8 transform_config: train: null - val: null - create_validation_set: false + eval: null + val_split_mode: same_as_test # options: [same_as_test, from_test] model: name: cflow diff --git a/anomalib/models/dfkde/config.yaml b/anomalib/models/dfkde/config.yaml index d98bef0fec..02b80fa8e9 100644 --- a/anomalib/models/dfkde/config.yaml +++ b/anomalib/models/dfkde/config.yaml @@ -6,12 +6,12 @@ dataset: task: classification image_size: 256 train_batch_size: 32 - test_batch_size: 32 + eval_batch_size: 32 num_workers: 8 transform_config: train: null - val: null - create_validation_set: false + eval: null + val_split_mode: same_as_test # options: [same_as_test, from_test] model: name: dfkde diff --git a/anomalib/models/dfm/config.yaml b/anomalib/models/dfm/config.yaml index fc64d78bd5..be7a62b889 100755 --- a/anomalib/models/dfm/config.yaml +++ b/anomalib/models/dfm/config.yaml @@ -6,12 +6,12 @@ dataset: task: classification image_size: 256 train_batch_size: 32 - test_batch_size: 32 + eval_batch_size: 32 num_workers: 8 transform_config: train: null - val: null - create_validation_set: false + eval: null + val_split_mode: same_as_test # options: [same_as_test, from_test] model: name: dfm diff --git a/anomalib/models/draem/config.yaml b/anomalib/models/draem/config.yaml index f76e163a27..870c9aa2c9 100644 --- a/anomalib/models/draem/config.yaml +++ b/anomalib/models/draem/config.yaml @@ -6,12 +6,12 @@ dataset: task: segmentation image_size: 256 train_batch_size: 8 - test_batch_size: 32 + eval_batch_size: 32 num_workers: 8 transform_config: train: ./anomalib/models/draem/transform_config.yaml - val: ./anomalib/models/draem/transform_config.yaml - create_validation_set: false + eval: ./anomalib/models/draem/transform_config.yaml + val_split_mode: same_as_test # options: [same_as_test, from_test] tiling: apply: false tile_size: null diff --git a/anomalib/models/fastflow/config.yaml b/anomalib/models/fastflow/config.yaml index efe749afdd..373eccd534 100644 --- a/anomalib/models/fastflow/config.yaml +++ b/anomalib/models/fastflow/config.yaml @@ -6,12 +6,12 @@ dataset: category: bottle image_size: 256 # options: [256, 256, 448, 384] - for each supported backbone train_batch_size: 32 - test_batch_size: 32 + eval_batch_size: 32 num_workers: 8 transform_config: train: null - val: null - create_validation_set: false + eval: null + val_split_mode: same_as_test # options: [same_as_test, from_test] tiling: apply: false tile_size: null diff --git a/anomalib/models/ganomaly/config.yaml b/anomalib/models/ganomaly/config.yaml index e8adfa6dc0..e8ae474af4 100644 --- a/anomalib/models/ganomaly/config.yaml +++ b/anomalib/models/ganomaly/config.yaml @@ -6,13 +6,13 @@ dataset: task: classification image_size: 256 train_batch_size: 32 - test_batch_size: 32 + eval_batch_size: 32 inference_batch_size: 32 num_workers: 8 transform_config: train: null - val: null - create_validation_set: false + eval: null + val_split_mode: same_as_test # options: [same_as_test, from_test] tiling: apply: true tile_size: 64 diff --git a/anomalib/models/padim/config.yaml b/anomalib/models/padim/config.yaml index 63d7df90c5..ad772f47d2 100644 --- a/anomalib/models/padim/config.yaml +++ b/anomalib/models/padim/config.yaml @@ -6,12 +6,12 @@ dataset: task: segmentation image_size: 256 train_batch_size: 32 - test_batch_size: 32 + eval_batch_size: 32 num_workers: 8 transform_config: train: null - val: null - create_validation_set: false + eval: null + val_split_mode: same_as_test # options: [same_as_test, from_test] tiling: apply: false tile_size: null diff --git a/anomalib/models/patchcore/config.yaml b/anomalib/models/patchcore/config.yaml index 66a55f1e5c..5cebd3c2ff 100644 --- a/anomalib/models/patchcore/config.yaml +++ b/anomalib/models/patchcore/config.yaml @@ -6,12 +6,12 @@ dataset: category: bottle image_size: 224 train_batch_size: 32 - test_batch_size: 1 + eval_batch_size: 1 num_workers: 8 transform_config: train: null - val: null - create_validation_set: false + eval: null + val_split_mode: same_as_test # options: [same_as_test, from_test] tiling: apply: false tile_size: null diff --git a/anomalib/models/reverse_distillation/config.yaml b/anomalib/models/reverse_distillation/config.yaml index 696e76874b..869703cb42 100644 --- a/anomalib/models/reverse_distillation/config.yaml +++ b/anomalib/models/reverse_distillation/config.yaml @@ -6,13 +6,13 @@ dataset: task: segmentation image_size: 256 train_batch_size: 32 - test_batch_size: 32 + eval_batch_size: 32 inference_batch_size: 32 num_workers: 8 transform_config: train: null - val: null - create_validation_set: false + eval: null + val_split_mode: same_as_test # options: [same_as_test, from_test] tiling: apply: false tile_size: 64 diff --git a/anomalib/models/stfpm/config.yaml b/anomalib/models/stfpm/config.yaml index 153e92b3db..b185647463 100644 --- a/anomalib/models/stfpm/config.yaml +++ b/anomalib/models/stfpm/config.yaml @@ -6,13 +6,13 @@ dataset: task: segmentation image_size: 256 train_batch_size: 32 - test_batch_size: 32 + eval_batch_size: 32 inference_batch_size: 32 num_workers: 36 transform_config: train: null - val: null - create_validation_set: false + eval: null + val_split_mode: same_as_test # options: [same_as_test, from_test] tiling: apply: false tile_size: null diff --git a/anomalib/utils/metrics/anomaly_score_threshold.py b/anomalib/utils/metrics/anomaly_score_threshold.py index 36709b55e0..0a16c35f00 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 +import warnings + import torch from torchmetrics import PrecisionRecallCurve @@ -38,6 +40,14 @@ def compute(self) -> torch.Tensor: recall: torch.Tensor thresholds: torch.Tensor + if not any(1 in batch for batch in self.target): + warnings.warn( + "The validation set does not contain any anomalous images. As a result, the adaptive threshold will " + "take the value of the highest anomaly score observed in the normal validation images, which may lead " + "to poor predictions. For a more reliable adaptive threshold computation, please add some anomalous " + "images to the validation set." + ) + precision, recall, thresholds = super().compute() f1_score = (2 * precision * recall) / (precision + recall + 1e-10) if thresholds.dim() == 0: diff --git a/docs/source/how_to_guides/train_custom_data.rst b/docs/source/how_to_guides/train_custom_data.rst index 5974ccffed..4d1652462f 100644 --- a/docs/source/how_to_guides/train_custom_data.rst +++ b/docs/source/how_to_guides/train_custom_data.rst @@ -82,12 +82,12 @@ Let's choose `Padim algorithm `_, copy the seed: 0 image_size: 256 train_batch_size: 32 - test_batch_size: 32 + eval_batch_size: 32 num_workers: 8 transform_config: train: null - val: null - create_validation_set: true + eval: null + val_split_mode: from_test # determines how the validation set is created, options [same_as_test, from_test] tiling: apply: false tile_size: null diff --git a/tests/pre_merge/datasets/test_datamodule.py b/tests/pre_merge/datasets/test_datamodule.py new file mode 100644 index 0000000000..6b41137f69 --- /dev/null +++ b/tests/pre_merge/datasets/test_datamodule.py @@ -0,0 +1,244 @@ +"""Test Dataset.""" + +import os + +import numpy as np +import pytest + +from anomalib.config import update_input_size_config +from anomalib.data import BTech, Folder, MVTec, get_datamodule +from anomalib.pre_processing.transforms import Denormalize, ToNumpy +from tests.helpers.config import get_test_configurable_parameters +from tests.helpers.dataset import TestDataset, get_dataset_path + + +@pytest.fixture(autouse=True) +def mvtec_data_module(): + datamodule = MVTec( + root=get_dataset_path(dataset="MVTec"), + category="leather", + image_size=(256, 256), + train_batch_size=1, + eval_batch_size=1, + num_workers=0, + val_split_mode="from_test", + ) + datamodule.prepare_data() + datamodule.setup() + + return datamodule + + +@pytest.fixture(autouse=True) +def btech_data_module(): + """Create BTech Data Module.""" + datamodule = BTech( + root=get_dataset_path(dataset="BTech"), + category="01", + image_size=(256, 256), + train_batch_size=1, + eval_batch_size=1, + num_workers=0, + val_split_mode="from_test", + ) + datamodule.prepare_data() + datamodule.setup() + + return datamodule + + +@pytest.fixture(autouse=True) +def folder_data_module(): + """Create Folder Data Module.""" + root = get_dataset_path(dataset="bottle") + datamodule = Folder( + root=root, + normal_dir="good", + abnormal_dir="broken_large", + mask_dir=os.path.join(root, "ground_truth/broken_large"), + task="segmentation", + split_ratio=0.2, + image_size=(256, 256), + train_batch_size=32, + eval_batch_size=32, + num_workers=8, + val_split_mode="from_test", + ) + datamodule.setup() + + return datamodule + + +@pytest.fixture(autouse=True) +def data_sample(mvtec_data_module): + _, data = next(enumerate(mvtec_data_module.train_dataloader())) + return data + + +class TestMVTecDataModule: + """Test MVTec AD Data Module.""" + + def test_batch_size(self, mvtec_data_module): + """test_mvtec_datamodule [summary]""" + _, train_data_sample = next(enumerate(mvtec_data_module.train_dataloader())) + _, val_data_sample = next(enumerate(mvtec_data_module.val_dataloader())) + assert train_data_sample["image"].shape[0] == 1 + assert val_data_sample["image"].shape[0] == 1 + + def test_val_and_test_dataloaders_has_mask_and_gt(self, mvtec_data_module): + """Test Validation and Test dataloaders should return filenames, image, mask and label.""" + _, val_data = next(enumerate(mvtec_data_module.val_dataloader())) + _, test_data = next(enumerate(mvtec_data_module.test_dataloader())) + + assert sorted(["image_path", "mask_path", "image", "label", "mask"]) == sorted(val_data.keys()) + assert sorted(["image_path", "mask_path", "image", "label", "mask"]) == sorted(test_data.keys()) + + def test_non_overlapping_splits(self, mvtec_data_module): + """This test ensures that the train and test splits generated are non-overlapping.""" + assert ( + len( + set(mvtec_data_module.test_data.samples["image_path"].values).intersection( + set(mvtec_data_module.train_data.samples["image_path"].values) + ) + ) + == 0 + ), "Found train and test split contamination" + + +class TestBTechDataModule: + """Test BTech Data Module.""" + + def test_batch_size(self, btech_data_module): + """Test batch size.""" + _, train_data_sample = next(enumerate(btech_data_module.train_dataloader())) + _, val_data_sample = next(enumerate(btech_data_module.val_dataloader())) + assert train_data_sample["image"].shape[0] == 1 + assert val_data_sample["image"].shape[0] == 1 + + def test_val_and_test_dataloaders_has_mask_and_gt(self, btech_data_module): + """Test Validation and Test dataloaders should return filenames, image, mask and label.""" + _, val_data = next(enumerate(btech_data_module.val_dataloader())) + _, test_data = next(enumerate(btech_data_module.test_dataloader())) + + assert sorted(["image_path", "mask_path", "image", "label", "mask"]) == sorted(val_data.keys()) + assert sorted(["image_path", "mask_path", "image", "label", "mask"]) == sorted(test_data.keys()) + + def test_non_overlapping_splits(self, btech_data_module): + """This test ensures that the train and test splits generated are non-overlapping.""" + assert ( + len( + set(btech_data_module.test_data.samples["image_path"].values).intersection( + set(btech_data_module.train_data.samples["image_path"].values) + ) + ) + == 0 + ), "Found train and test split contamination" + + +class TestFolderDataModule: + """Test Folder Data Module.""" + + def test_batch_size(self, folder_data_module): + """Test batch size.""" + _, train_data_sample = next(enumerate(folder_data_module.train_dataloader())) + _, val_data_sample = next(enumerate(folder_data_module.val_dataloader())) + assert train_data_sample["image"].shape[0] == 16 + assert val_data_sample["image"].shape[0] == 12 + + def test_val_and_test_dataloaders_has_mask_and_gt(self, folder_data_module): + """Test Validation and Test dataloaders should return filenames, image, mask and label.""" + _, val_data = next(enumerate(folder_data_module.val_dataloader())) + _, test_data = next(enumerate(folder_data_module.test_dataloader())) + + assert sorted(["image_path", "mask_path", "image", "label", "mask"]) == sorted(val_data.keys()) + assert sorted(["image_path", "mask_path", "image", "label", "mask"]) == sorted(test_data.keys()) + + def test_non_overlapping_splits(self, folder_data_module): + """This test ensures that the train and test splits generated are non-overlapping.""" + assert ( + len( + set(folder_data_module.test_data.samples["image_path"].values).intersection( + set(folder_data_module.train_data.samples["image_path"].values) + ) + ) + == 0 + ), "Found train and test split contamination" + + +class TestDenormalize: + """Test Denormalize Util.""" + + def test_denormalize_image_pixel_values(self, data_sample): + """Test Denormalize denormalizes tensor into [0, 256] range.""" + denormalized_sample = Denormalize()(data_sample["image"].squeeze()) + assert denormalized_sample.min() >= 0 and denormalized_sample.max() <= 256 + + def test_denormalize_return_numpy(self, data_sample): + """Denormalize should return a numpy array.""" + denormalized_sample = Denormalize()(data_sample["image"].squeeze()) + assert isinstance(denormalized_sample, np.ndarray) + + def test_denormalize_channel_order(self, data_sample): + """Denormalize should return a numpy array of order [HxWxC]""" + denormalized_sample = Denormalize()(data_sample["image"].squeeze()) + assert len(denormalized_sample.shape) == 3 and denormalized_sample.shape[-1] == 3 + + def test_representation(self): + """Test Denormalize representation should return string + Denormalize()""" + assert str(Denormalize()) == "Denormalize()" + + +class TestToNumpy: + """Test ToNumpy whether it properly converts tensor into numpy array.""" + + def test_to_numpy_image_pixel_values(self, data_sample): + """Test ToNumpy should return an array whose pixels in the range of [0, + 256]""" + array = ToNumpy()(data_sample["image"]) + assert array.min() >= 0 and array.max() <= 256 + + def test_to_numpy_converts_tensor_to_np_array(self, data_sample): + """ToNumpy returns a numpy array.""" + array = ToNumpy()(data_sample["image"]) + assert isinstance(array, np.ndarray) + + def test_to_numpy_channel_order(self, data_sample): + """ToNumpy() should return a numpy array of order [HxWxC]""" + array = ToNumpy()(data_sample["image"]) + assert len(array.shape) == 3 and array.shape[-1] == 3 + + def test_one_channel_images(self, data_sample): + """One channel tensor should be converted to HxW np array.""" + data = data_sample["image"][:, 0, :, :].unsqueeze(0) + array = ToNumpy()(data) + assert len(array.shape) == 2 + + def test_representation(self): + """Test ToNumpy() representation should return string `ToNumpy()`""" + assert str(ToNumpy()) == "ToNumpy()" + + +class TestConfigToDataModule: + """Tests that check if the dataset parameters in the config achieve the desired effect.""" + + @pytest.mark.parametrize( + ["input_size", "effective_image_size"], + [ + (512, (512, 512)), + ((245, 276), (245, 276)), + ((263, 134), (263, 134)), + ((267, 267), (267, 267)), + ], + ) + @TestDataset(num_train=20, num_test=10) + def test_image_size(self, input_size, effective_image_size, category="shapes", path=None): + """Test if the image size parameter works as expected.""" + configurable_parameters = get_test_configurable_parameters(dataset_path=path, model_name="stfpm") + configurable_parameters.dataset.category = category + configurable_parameters.dataset.image_size = input_size + configurable_parameters = update_input_size_config(configurable_parameters) + + data_module = get_datamodule(configurable_parameters) + data_module.setup() + assert next(iter(data_module.train_dataloader()))["image"].shape[-2:] == effective_image_size diff --git a/tests/pre_merge/datasets/test_dataset.py b/tests/pre_merge/datasets/test_dataset.py index 06d9629b45..81daad062c 100644 --- a/tests/pre_merge/datasets/test_dataset.py +++ b/tests/pre_merge/datasets/test_dataset.py @@ -1,243 +1,74 @@ -"""Test Dataset.""" +"""Test the AnomalibDataset class.""" -import os +import random -import numpy as np +import pandas as pd import pytest -from anomalib.config import update_input_size_config -from anomalib.data import BTech, Folder, MVTec, get_datamodule -from anomalib.pre_processing.transforms import Denormalize, ToNumpy -from tests.helpers.config import get_test_configurable_parameters -from tests.helpers.dataset import TestDataset, get_dataset_path +from anomalib.data.folder import FolderDataset +from anomalib.data.utils.split import concatenate_datasets, random_split +from anomalib.pre_processing import PreProcessor +from tests.helpers.dataset import get_dataset_path @pytest.fixture(autouse=True) -def mvtec_data_module(): - datamodule = MVTec( - root=get_dataset_path(dataset="MVTec"), - category="leather", - image_size=(256, 256), - train_batch_size=1, - test_batch_size=1, - num_workers=0, - ) - datamodule.prepare_data() - datamodule.setup() - - return datamodule - - -@pytest.fixture(autouse=True) -def btech_data_module(): - """Create BTech Data Module.""" - datamodule = BTech( - root=get_dataset_path(dataset="BTech"), - category="01", - image_size=(256, 256), - train_batch_size=1, - test_batch_size=1, - num_workers=0, - ) - datamodule.prepare_data() - datamodule.setup() - - return datamodule - - -@pytest.fixture(autouse=True) -def folder_data_module(): - """Create Folder Data Module.""" +def folder_dataset(): + """Create Folder Dataset.""" root = get_dataset_path(dataset="bottle") - datamodule = Folder( + pre_process = PreProcessor(image_size=(256, 256)) + dataset = FolderDataset( + task="classification", + pre_process=pre_process, root=root, normal_dir="good", abnormal_dir="broken_large", - mask_dir=os.path.join(root, "ground_truth/broken_large"), - task="segmentation", - split_ratio=0.2, - seed=0, - image_size=(256, 256), - train_batch_size=32, - test_batch_size=32, - num_workers=8, - create_validation_set=True, - ) - datamodule.setup() - - return datamodule - - -@pytest.fixture(autouse=True) -def data_sample(mvtec_data_module): - _, data = next(enumerate(mvtec_data_module.train_dataloader())) - return data - - -class TestMVTecDataModule: - """Test MVTec AD Data Module.""" - - def test_batch_size(self, mvtec_data_module): - """test_mvtec_datamodule [summary]""" - _, train_data_sample = next(enumerate(mvtec_data_module.train_dataloader())) - _, val_data_sample = next(enumerate(mvtec_data_module.val_dataloader())) - assert train_data_sample["image"].shape[0] == 1 - assert val_data_sample["image"].shape[0] == 1 - - def test_val_and_test_dataloaders_has_mask_and_gt(self, mvtec_data_module): - """Test Validation and Test dataloaders should return filenames, image, mask and label.""" - _, val_data = next(enumerate(mvtec_data_module.val_dataloader())) - _, test_data = next(enumerate(mvtec_data_module.test_dataloader())) - - assert sorted(["image_path", "mask_path", "image", "label", "mask"]) == sorted(val_data.keys()) - assert sorted(["image_path", "mask_path", "image", "label", "mask"]) == sorted(test_data.keys()) - - def test_non_overlapping_splits(self, mvtec_data_module): - """This test ensures that the train and test splits generated are non-overlapping.""" - assert ( - len( - set(mvtec_data_module.test_data.samples["image_path"].values).intersection( - set(mvtec_data_module.train_data.samples["image_path"].values) - ) - ) - == 0 - ), "Found train and test split contamination" - - -class TestBTechDataModule: - """Test BTech Data Module.""" - - def test_batch_size(self, btech_data_module): - """Test batch size.""" - _, train_data_sample = next(enumerate(btech_data_module.train_dataloader())) - _, val_data_sample = next(enumerate(btech_data_module.val_dataloader())) - assert train_data_sample["image"].shape[0] == 1 - assert val_data_sample["image"].shape[0] == 1 - - def test_val_and_test_dataloaders_has_mask_and_gt(self, btech_data_module): - """Test Validation and Test dataloaders should return filenames, image, mask and label.""" - _, val_data = next(enumerate(btech_data_module.val_dataloader())) - _, test_data = next(enumerate(btech_data_module.test_dataloader())) - - assert sorted(["image_path", "mask_path", "image", "label", "mask"]) == sorted(val_data.keys()) - assert sorted(["image_path", "mask_path", "image", "label", "mask"]) == sorted(test_data.keys()) - - def test_non_overlapping_splits(self, btech_data_module): - """This test ensures that the train and test splits generated are non-overlapping.""" - assert ( - len( - set(btech_data_module.test_data.samples["image_path"].values).intersection( - set(btech_data_module.train_data.samples["image_path"].values) - ) - ) - == 0 - ), "Found train and test split contamination" - - -class TestFolderDataModule: - """Test Folder Data Module.""" - - def test_batch_size(self, folder_data_module): - """Test batch size.""" - _, train_data_sample = next(enumerate(folder_data_module.train_dataloader())) - _, val_data_sample = next(enumerate(folder_data_module.val_dataloader())) - assert train_data_sample["image"].shape[0] == 16 - assert val_data_sample["image"].shape[0] == 12 - - def test_val_and_test_dataloaders_has_mask_and_gt(self, folder_data_module): - """Test Validation and Test dataloaders should return filenames, image, mask and label.""" - _, val_data = next(enumerate(folder_data_module.val_dataloader())) - _, test_data = next(enumerate(folder_data_module.test_dataloader())) - - assert sorted(["image_path", "mask_path", "image", "label", "mask"]) == sorted(val_data.keys()) - assert sorted(["image_path", "mask_path", "image", "label", "mask"]) == sorted(test_data.keys()) - - def test_non_overlapping_splits(self, folder_data_module): - """This test ensures that the train and test splits generated are non-overlapping.""" - assert ( - len( - set(folder_data_module.test_data.samples["image_path"].values).intersection( - set(folder_data_module.train_data.samples["image_path"].values) - ) - ) - == 0 - ), "Found train and test split contamination" - - -class TestDenormalize: - """Test Denormalize Util.""" - - def test_denormalize_image_pixel_values(self, data_sample): - """Test Denormalize denormalizes tensor into [0, 256] range.""" - denormalized_sample = Denormalize().__call__(data_sample["image"].squeeze()) - assert denormalized_sample.min() >= 0 and denormalized_sample.max() <= 256 - - def test_denormalize_return_numpy(self, data_sample): - """Denormalize should return a numpy array.""" - denormalized_sample = Denormalize()(data_sample["image"].squeeze()) - assert isinstance(denormalized_sample, np.ndarray) - - def test_denormalize_channel_order(self, data_sample): - """Denormalize should return a numpy array of order [HxWxC]""" - denormalized_sample = Denormalize().__call__(data_sample["image"].squeeze()) - assert len(denormalized_sample.shape) == 3 and denormalized_sample.shape[-1] == 3 - - def test_representation(self): - """Test Denormalize representation should return string - Denormalize()""" - assert str(Denormalize()) == "Denormalize()" - - -class TestToNumpy: - """Test ToNumpy whether it properly converts tensor into numpy array.""" - - def test_to_numpy_image_pixel_values(self, data_sample): - """Test ToNumpy should return an array whose pixels in the range of [0, - 256]""" - array = ToNumpy()(data_sample["image"]) - assert array.min() >= 0 and array.max() <= 256 - - def test_to_numpy_converts_tensor_to_np_array(self, data_sample): - """ToNumpy returns a numpy array.""" - array = ToNumpy()(data_sample["image"]) - assert isinstance(array, np.ndarray) - - def test_to_numpy_channel_order(self, data_sample): - """ToNumpy() should return a numpy array of order [HxWxC]""" - array = ToNumpy()(data_sample["image"]) - assert len(array.shape) == 3 and array.shape[-1] == 3 - - def test_one_channel_images(self, data_sample): - """One channel tensor should be converted to HxW np array.""" - data = data_sample["image"][:, 0, :, :].unsqueeze(0) - array = ToNumpy()(data) - assert len(array.shape) == 2 - - def test_representation(self): - """Test ToNumpy() representation should return string `ToNumpy()`""" - assert str(ToNumpy()) == "ToNumpy()" - - -class TestConfigToDataModule: - """Tests that check if the dataset parameters in the config achieve the desired effect.""" - - @pytest.mark.parametrize( - ["input_size", "effective_image_size"], - [ - (512, (512, 512)), - ((245, 276), (245, 276)), - ((263, 134), (263, 134)), - ((267, 267), (267, 267)), - ], ) - @TestDataset(num_train=20, num_test=10) - def test_image_size(self, input_size, effective_image_size, category="shapes", path=None): - """Test if the image size parameter works as expected.""" - configurable_parameters = get_test_configurable_parameters(dataset_path=path, model_name="stfpm") - configurable_parameters.dataset.category = category - configurable_parameters.dataset.image_size = input_size - configurable_parameters = update_input_size_config(configurable_parameters) - - data_module = get_datamodule(configurable_parameters) - data_module.setup() - assert iter(data_module.train_dataloader()).__next__()["image"].shape[-2:] == effective_image_size + dataset.setup() + + return dataset + + +class TestAnomalibDataset: + def test_subsample(self, folder_dataset): + """Test the subsample functionality.""" + + sample_size = int(0.5 * len(folder_dataset)) + indices = random.sample(range(len(folder_dataset)), sample_size) + subset = folder_dataset.subsample(indices) + + # check if the dataset has been subsampled to correct size + assert len(subset) == sample_size + # check if index has been reset + assert subset.samples.index.start == 0 + assert subset.samples.index.stop == sample_size + + def test_random_split(self, folder_dataset): + """Test the random subset splitting.""" + + # subset splitting + subsets = random_split(folder_dataset, [0.4, 0.35, 0.25]) + # check if subset splitting has been performed correctly + assert len(subsets) == 3 + # reconstruct the original dataset by concatenating the subsets + reconstructed_dataset = concatenate_datasets(subsets) + # check if reconstructed dataset is equal to original dataset + assert folder_dataset.samples.equals(reconstructed_dataset.samples) + + # check if warning raised when one of the subsets is empty + split_ratios = [1 - (1 / (len(folder_dataset) + 1)), 1 / (len(folder_dataset) + 1)] + with pytest.warns(): + subsets = random_split(folder_dataset, split_ratios) + + # label-aware subset splitting + samples = folder_dataset.samples + normal_samples = samples[samples["label_index"] == 0] + anomalous_samples = samples[samples["label_index"] == 1] + samples = pd.concat([normal_samples, anomalous_samples[0:5]]) + folder_dataset.samples = samples + + subsets = random_split(folder_dataset, [0.4, 0.4, 0.2], label_aware=True) + + # 5 anomalous images in total, so the first two subsets should each have 2, and the last subset 1 + assert len(subsets[0].samples[subsets[0].samples["label_index"] == 1]) == 2 + assert len(subsets[1].samples[subsets[1].samples["label_index"] == 1]) == 2 + assert len(subsets[2].samples[subsets[2].samples["label_index"] == 1]) == 1 diff --git a/tests/pre_merge/utils/metrics/test_adaptive_threshold.py b/tests/pre_merge/utils/metrics/test_adaptive_threshold.py index eb9669d23b..00c55b8465 100644 --- a/tests/pre_merge/utils/metrics/test_adaptive_threshold.py +++ b/tests/pre_merge/utils/metrics/test_adaptive_threshold.py @@ -39,6 +39,7 @@ def test_manual_threshold(): """ config = get_test_configurable_parameters(config_path="anomalib/models/padim/config.yaml") + config.dataset.num_workers = 0 config.model.normalization_method = "none" config.metrics.threshold.method = "manual" config.trainer.fast_dev_run = True diff --git a/tools/test.py b/tools/test.py index 5427cf9f06..b2772aaf5d 100644 --- a/tools/test.py +++ b/tools/test.py @@ -5,7 +5,7 @@ from argparse import ArgumentParser, Namespace -from pytorch_lightning import Trainer +from pytorch_lightning import Trainer, seed_everything from anomalib.config import get_configurable_parameters from anomalib.data import get_datamodule @@ -40,6 +40,9 @@ def test(): weight_file=args.weight_file, ) + if config.project.seed: + seed_everything(config.project.seed) + datamodule = get_datamodule(config) model = get_model(config) diff --git a/tools/train.py b/tools/train.py index 0e5daa3b10..37b894af79 100644 --- a/tools/train.py +++ b/tools/train.py @@ -63,8 +63,11 @@ def train(): load_model_callback = LoadModelCallback(weights_path=trainer.checkpoint_callback.best_model_path) trainer.callbacks.insert(0, load_model_callback) - logger.info("Testing the model.") - trainer.test(model=model, datamodule=datamodule) + if datamodule.test_data.has_anomalous: + logger.info("Testing the model.") + trainer.test(model=model, datamodule=datamodule) + else: + logger.info("No anomalous images found in dataset. Skipping test stage.") if __name__ == "__main__": From 668ce5d07114f9b5d0101f2caee6a7af25b3ada1 Mon Sep 17 00:00:00 2001 From: jpcbertoldo <24547377+jpcbertoldo@users.noreply.github.com> Date: Thu, 1 Sep 2022 21:14:53 +0200 Subject: [PATCH 31/38] copy mvtec and add some config conts in comments --- anomalib/data/mvtec_loco.py | 556 ++++++++++++++++++++++++++++++++++++ 1 file changed, 556 insertions(+) create mode 100644 anomalib/data/mvtec_loco.py diff --git a/anomalib/data/mvtec_loco.py b/anomalib/data/mvtec_loco.py new file mode 100644 index 0000000000..940f2b4927 --- /dev/null +++ b/anomalib/data/mvtec_loco.py @@ -0,0 +1,556 @@ +""" + +distinguishes structural and logical anomalies +n_image: 3644 +splits: +no overlap and fixed +train +normal-only +n_image_train: 1772 +validation +normal-only +n_image_validation: 304 +test +normal + anomalous (structural and logical) +n_image_test: 1568 +n_category: 5 +breakfast_box +juice_bottle +pushpins +screw_bag +splicing_connectors +n_defect_type: 89 + +################################################## + +configs +https://docs.google.com/spreadsheets/d/1qHbyTsU2At1fusQsV8KH3_qdp3SYHHLy7QtpohKJIk0/edit?usp=sharing + +stats overview +https://docs.google.com/spreadsheets/d/11GSf1SVsHFYDSwMAULEd7g5QK7P3Y21YMB10D_g0-Gk/edit?usp=sharing + +################################################## + +assumptions +objects are in a fixed position (mechanical alignment) +illumination is well suited +the access to images with real anomalies is limited (“impossible”) +images only show a single object or logically ensemble set of objects (i.e. one-class setting although a “class” here is a composed object) +no training annotations -- although it is assumed that the images in training are indeed from the target class (i.e. no noise) +problem 1 (image-wise anomaly detection): “is there an anomaly in the image?” +problem 2 (pixel-wise anomaly detection or anomaly segmentation): “which pixels belong to the anomaly?” +pixel-wise metric: Saturated Per-Region Overlap (sPRO) +structural anomaly pixel annotation policy +defects are confined to local regions +each pixel that introduces a visual structure that is not present in the anomaly-free images is anomalous +logical anomaly pixel annotation policy +the union of all areas of the image that could be the cause for the anomaly is anomalous +a method is not necessarily required to predict the whole ground truth area as anomalous + +################################################## + +breakfast box +n_anomaly_type (n_structural, n_logical): 22 (5, 17) +logical constraints +contains 2 tangerines +contains 1 nectarine +the tangerines and the nectarine on the left +cereals (C) and a mix of banana chips and almonds (B&A) on the right +the ratio between C and B&A is fixed +the relative position of C and B&A is fixed +examples of logical defects +too many banana chips and almonds + +################################################## + +juice bottle +n_anomaly_type (n_structural, n_logical): 18 (7, 11) +logical constraints +there is 1 bottle +the bottle is filled with a liquid and the fill level is always the same +the liquid is of 1 out of 3 colors (red, yellow, white-ish) +the bottle carries 2 labels +the first label is attached to the center of the bottle +the first label displays an icon that determines the type of liquid (cherry, orange, banana) +cherry: red +orange: yellow +banana: white-ish +the second label is attached to the lower part of the bottle +the second label contains the text “100% Juice” +examples of logical defects +(left) the icon does not match the type of juice +(middle) the icon is slightly misplaced +(right) the fill level is too high + +################################################## + +pushpins +n_anomaly_type (n_structural, n_logical): 8 (4, 4) +logical constraints +each compartment contains 1 pushpin +examples of logical defects +1 compartment has a missing pin + +################################################## + +screw bag +n_anomaly_type (n_structural, n_logical): 20 (4, 16) +logical constraints +the bag contains +2 washers +2 nuts +1 long screw +2 short screw +examples of logical defects +two long screws and lacks a short one + +################################################## + +splicing connectors +n_anomaly_type (n_structural, n_logical): 21 (8, 13) +logical constraints +there are 2 splicing connectors +they have the same number of cable clamps +they are linked by 1 cable +the number of clamps has a one-to-one correspondence to the color of the cable +2: yellow +3: blue +5: red +the cable has to terminate in the same relative position on its two ends such that the whole construction exhibits a mirror symmetry +examples of logical defects +(left) the two splicing connectors do not have the same number of clamps +(center) the color of the cable does not match the number of clamps +(right) the cable terminates in different positions + +################################################## + +missing objects +the area in which the object could occur +the saturation threshold is chosen to be equal to the area of the missing object +the saturation threshold for an object is chosen from the lower end of the distribution of its (manually annotated) area +example (image): pushpin +the missing pushpin can occur anywhere inside its compartment, therefore its entire area is annotated +the saturation threshold is set to the size of a pushpin + +################################################## + +additional objects +too many instances of an object: all instances of the object are annotated +the saturation threshold is set to the area of the extraneous objects +example (image): splicing connectors +an additional cable is present between the two splicing connectors +it is not clear which of the two cables represents the anomaly, therefore both are annotated +the saturation threshold is set to the area of one cable (i.e., half of the annotated region) +properties +a method can obtain a perfect score even if it only marks one of the two cables as an anomaly +a method that marks both is neither penalized nor (extra-)rewarded + +################################################## + +other logical constraints +example (image, left): juice bottle +the bottle is filled with orange juice but carries the label of the cherry juice +both the orange juice and the label with the cherry are present in the training set, but the logical anomaly arises due to the erroneous combination of the two in the same image +either the area filled with juice or the cherry as could be considered anomalous, therefore the union of the two regions is annotated +the saturation threshold is set to the area of the cherry because the segmentation of the cherry is sufficient to solve the anomaly localization + +""" + + +""" +category anomaly type gt_value saturation_definition saturation_parameter +breakfast_box missing_almonds logical 255 relative_to_anomaly 1.0000000 +breakfast_box missing_bananas logical 254 relative_to_anomaly 1.0000000 +breakfast_box missing_toppings logical 253 relative_to_anomaly 1.0000000 +breakfast_box missing_cereals logical 252 relative_to_anomaly 1.0000000 +breakfast_box missing_cereals_and_toppings logical 251 relative_to_anomaly 1.0000000 +breakfast_box nectarines_2_tangerine_1 logical 250 relative_to_image 0.0488770 +breakfast_box nectarine_1_tangerine_1 logical 249 relative_to_image 0.0411621 +breakfast_box nectarines_0_tangerines_2 logical 248 relative_to_image 0.0488770 +breakfast_box nectarines_0_tangerines_3 logical 247 relative_to_image 0.0488770 +breakfast_box nectarines_3_tangerines_0 logical 246 relative_to_image 0.0977539 +breakfast_box nectarines_0_tangerine_1 logical 245 relative_to_image 0.0900391 +breakfast_box nectarines_0_tangerines_0 logical 244 relative_to_image 0.1312012 +breakfast_box nectarines_0_tangerines_4 logical 243 relative_to_image 0.0823242 +breakfast_box compartments_swapped logical 242 relative_to_anomaly 1.0000000 +breakfast_box overflow logical 241 relative_to_anomaly 1.0000000 +breakfast_box underflow logical 240 relative_to_anomaly 1.0000000 +breakfast_box wrong_ratio logical 239 relative_to_anomaly 1.0000000 +breakfast_box mixed_cereals structural 238 relative_to_anomaly 1.0000000 +breakfast_box fruit_damaged structural 237 relative_to_anomaly 1.0000000 +breakfast_box box_damaged structural 236 relative_to_anomaly 1.0000000 +breakfast_box toppings_crushed structural 235 relative_to_anomaly 1.0000000 +breakfast_box contamination structural 234 relative_to_anomaly 1.0000000 +juice_box missing_top_label logical 255 relative_to_image 0.0550000 +juice_box missing_bottom_label logical 254 relative_to_image 0.0255469 +juice_box swapped_labels logical 253 relative_to_image 0.1100000 +juice_box damaged_label structural 252 relative_to_anomaly 1.0000000 +juice_box rotated_label structural 251 relative_to_anomaly 1.0000000 +juice_box misplaced_label_top logical 250 relative_to_image 0.0550000 +juice_box misplaced_label_bottom logical 249 relative_to_image 0.0255469 +juice_box label_text_incomplete structural 248 relative_to_anomaly 1.0000000 +juice_box empty_bottle logical 247 relative_to_anomaly 1.0000000 +juice_box wrong_fill_level_too_much logical 246 relative_to_anomaly 1.0000000 +juice_box wrong_fill_level_not_enough logical 245 relative_to_anomaly 1.0000000 +juice_box misplaced_fruit_icon logical 244 relative_to_anomaly 1.0000000 +juice_box missing_fruit_icon logical 243 relative_to_anomaly 1.0000000 +juice_box unknown_fruit_icon structural 242 relative_to_anomaly 1.0000000 +juice_box incomplete_fruit_icon structural 241 relative_to_anomaly 1.0000000 +juice_box wrong_juice_type logical 240 relative_to_image 0.0035156 +juice_box juice_color structural 239 relative_to_anomaly 1.0000000 +juice_box contamination structural 238 relative_to_anomaly 1.0000000 +pushpins additional_1_pushpin logical 255 relative_to_image 0.0037059 +pushpins additional_2_pushpins logical 254 relative_to_image 0.0074118 +pushpins missing_pushpin logical 253 relative_to_image 0.0037059 +pushpins missing_separator logical 252 relative_to_anomaly 1.0000000 +pushpins front_bent structural 251 relative_to_anomaly 1.0000000 +pushpins broken structural 250 relative_to_anomaly 1.0000000 +pushpins color structural 249 relative_to_anomaly 1.0000000 +pushpins contamination structural 248 relative_to_anomaly 1.0000000 +screw_bag screw_too_long logical 255 relative_to_image 0.0051136 +screw_bag screw_too_shor logical 254 relative_to_image 0.0051136 +screw_bag screws_1_very_short logical 253 relative_to_anomaly 1.0000000 +screw_bag screws_2_very_short logical 252 relative_to_image 0.0102273 +screw_bag additional_1_long_screw logical 251 relative_to_image 0.0168182 +screw_bag additional_1_short_screw logical 250 relative_to_image 0.0117045 +screw_bag additional_1_nut_ logical 249 relative_to_image 0.0042614 +screw_bag additional_2_nuts_ logical 248 relative_to_image 0.0085227 +screw_bag additional_1_washer_ logical 247 relative_to_image 0.0031250 +screw_bag additional_2_washers_ logical 246 relative_to_image 0.0062500 +screw_bag missing_1_long_screw logical 245 relative_to_image 0.0168182 +screw_bag missing_1_short_screw logical 244 relative_to_image 0.0117045 +screw_bag missing_1_nut logical 243 relative_to_image 0.0042614 +screw_bag missing_2_nuts logical 242 relative_to_image 0.0085227 +screw_bag missing_1_washer logical 241 relative_to_image 0.0031250 +screw_bag missing_2_washers logical 240 relative_to_image 0.0062500 +screw_bag bag_broken structural 239 relative_to_anomaly 1.0000000 +screw_bag color structural 238 relative_to_anomaly 1.0000000 +screw_bag contamination structural 237 relative_to_anomaly 1.0000000 +screw_bag part_broken structural 236 relative_to_anomaly 1.0000000 +splicing_connectors wrong_connector_type_5_2 logical 255 relative_to_image 0.0464360 +splicing_connectors wrong_connector_type_5_3 logical 254 relative_to_image 0.0306574 +splicing_connectors wrong_connector_type_3_2 logical 253 relative_to_image 0.0152941 +splicing_connectors cable_too_short_t2 logical 252 relative_to_image 0.0368858 +splicing_connectors cable_too_short_t3 logical 251 relative_to_image 0.0526644 +splicing_connectors cable_too_short_t5 logical 250 relative_to_image 0.0830450 +splicing_connectors missing_connector logical 249 relative_to_anomaly 1.0000000 +splicing_connectors missing_connector_and_cable logical 248 relative_to_image 0.0716955 +splicing_connectors missing_cable logical 247 relative_to_image 0.0124567 +splicing_connectors extra_cable logical 246 relative_to_anomaly 0.5000000 +splicing_connectors cable_color logical 245 relative_to_image 0.0124567 +splicing_connectors broken_cable structural 244 relative_to_anomaly 1.0000000 +splicing_connectors cable_cut logical 243 relative_to_anomaly 1.0000000 +splicing_connectors cable_not_plugged structural 242 relative_to_anomaly 1.0000000 +splicing_connectors unknown_cable_color structural 241 relative_to_anomaly 1.0000000 +splicing_connectors wrong_cable_location logical 240 relative_to_image 0.0124567 +splicing_connectors flipped_connector structural 239 relative_to_anomaly 1.0000000 +splicing_connectors broken_connector structural 238 relative_to_anomaly 1.0000000 +splicing_connectors open_lever structural 237 relative_to_anomaly 1.0000000 +splicing_connectors color structural 236 relative_to_anomaly 1.0000000 +splicing_connectors contamination structural 235 relative_to_anomaly 1.0000000 +""" + +import logging +import tarfile +import warnings +from pathlib import Path +from typing import Dict, Optional, Tuple, Union +from urllib.request import urlretrieve + +import albumentations as A +import cv2 +import numpy as np +import pandas as pd +from pandas.core.frame import DataFrame +from pytorch_lightning.core.datamodule import LightningDataModule +from pytorch_lightning.utilities.cli import DATAMODULE_REGISTRY +from pytorch_lightning.utilities.types import EVAL_DATALOADERS, TRAIN_DATALOADERS +from torch import Tensor +from torch.utils.data import DataLoader +from torch.utils.data.dataset import Dataset +from torchvision.datasets.folder import VisionDataset + +from anomalib.data.inference import InferenceDataset +from anomalib.data.utils import DownloadProgressBar, hash_check, read_image +from anomalib.data.utils.split import ( + create_validation_set_from_test_set, + split_normal_images_in_train_set, +) +from anomalib.pre_processing import PreProcessor + +logger = logging.getLogger(__name__) + + +def make_mvtec_loco_dataset( + path: Path, + split: Optional[str] = None, + split_ratio: float = 0.1, + seed: Optional[int] = None, + create_validation_set: bool = False, +) -> DataFrame: + samples_list = [(str(path),) + filename.parts[-3:] for filename in path.glob("**/*.png")] + if len(samples_list) == 0: + raise RuntimeError(f"Found 0 images in {path}") + + samples = pd.DataFrame(samples_list, columns=["path", "split", "label", "image_path"]) + samples = samples[samples.split != "ground_truth"] + + # Create mask_path column + samples["mask_path"] = ( + samples.path + + "/ground_truth/" + + samples.label + + "/" + + samples.image_path.str.rstrip("png").str.rstrip(".") + + "_mask.png" + ) + + # Modify image_path column by converting to absolute path + samples["image_path"] = samples.path + "/" + samples.split + "/" + samples.label + "/" + samples.image_path + + # Split the normal images in training set if test set doesn't + # contain any normal images. This is needed because AUC score + # cannot be computed based on 1-class + if sum((samples.split == "test") & (samples.label == "good")) == 0: + samples = split_normal_images_in_train_set(samples, split_ratio, seed) + + # Good images don't have mask + samples.loc[(samples.split == "test") & (samples.label == "good"), "mask_path"] = "" + + # Create label index for normal (0) and anomalous (1) images. + samples.loc[(samples.label == "good"), "label_index"] = 0 + samples.loc[(samples.label != "good"), "label_index"] = 1 + samples.label_index = samples.label_index.astype(int) + + if create_validation_set: + samples = create_validation_set_from_test_set(samples, seed=seed) + + # Get the data frame for the split. + if split is not None and split in ["train", "val", "test"]: + samples = samples[samples.split == split] + samples = samples.reset_index(drop=True) + + return samples + + +class MVTecLOCODataset(VisionDataset): + """MVTec LOCO AD PyTorch Dataset.""" + + def __init__( + self, + root: Union[Path, str], + category: str, + pre_process: PreProcessor, + split: str, + task: str = "segmentation", + seed: Optional[int] = None, + create_validation_set: bool = False, + ) -> None: + super().__init__(root) + + if seed is None: + warnings.warn( + "seed is None." + " When seed is not set, images from the normal directory are split between training and test dir." + " This will lead to inconsistency between runs." + ) + + self.root = Path(root) if isinstance(root, str) else root + self.category: str = category + self.split = split + self.task = task + + self.pre_process = pre_process + + self.samples = make_mvtec_loco_dataset( + path=self.root / category, + split=self.split, + seed=seed, + create_validation_set=create_validation_set, + ) + + def __len__(self) -> int: + """Get length of the dataset.""" + return len(self.samples) + + def __getitem__(self, index: int) -> Dict[str, Union[str, Tensor]]: + """Get dataset item for the index ``index``. + + Args: + index (int): Index to get the item. + + Returns: + Union[Dict[str, Tensor], Dict[str, Union[str, Tensor]]]: Dict of image tensor during training. + Otherwise, Dict containing image path, target path, image tensor, label and transformed bounding box. + """ + item: Dict[str, Union[str, Tensor]] = {} + + image_path = self.samples.image_path[index] + image = read_image(image_path) + + pre_processed = self.pre_process(image=image) + item = {"image": pre_processed["image"]} + + if self.split in ["val", "test"]: + label_index = self.samples.label_index[index] + + item["image_path"] = image_path + item["label"] = label_index + + if self.task == "segmentation": + mask_path = self.samples.mask_path[index] + + # Only Anomalous (1) images has masks in MVTec AD dataset. + # Therefore, create empty mask for Normal (0) images. + if label_index == 0: + mask = np.zeros(shape=image.shape[:2]) + else: + mask = cv2.imread(mask_path, flags=0) / 255.0 + + pre_processed = self.pre_process(image=image, mask=mask) + + item["mask_path"] = mask_path + item["image"] = pre_processed["image"] + item["mask"] = pre_processed["mask"] + + return item + + +@DATAMODULE_REGISTRY +class MVTecLOCO(LightningDataModule): + """MVTec LOCO AD Lightning Data Module.""" + + def __init__( + self, + root: str, + category: str, + # TODO: Remove default values. IAAALD-211 + image_size: Optional[Union[int, Tuple[int, int]]] = None, + train_batch_size: int = 32, + test_batch_size: int = 32, + num_workers: int = 8, + task: str = "segmentation", + transform_config_train: Optional[Union[str, A.Compose]] = None, + transform_config_val: Optional[Union[str, A.Compose]] = None, + seed: Optional[int] = None, + create_validation_set: bool = False, + ) -> None: + super().__init__() + + self.root = root if isinstance(root, Path) else Path(root) + self.category = category + self.dataset_path = self.root / self.category + self.transform_config_train = transform_config_train + self.transform_config_val = transform_config_val + self.image_size = image_size + + if self.transform_config_train is not None and self.transform_config_val is None: + self.transform_config_val = self.transform_config_train + + self.pre_process_train = PreProcessor(config=self.transform_config_train, image_size=self.image_size) + self.pre_process_val = PreProcessor(config=self.transform_config_val, image_size=self.image_size) + + self.train_batch_size = train_batch_size + self.test_batch_size = test_batch_size + self.num_workers = num_workers + + self.create_validation_set = create_validation_set + self.task = task + self.seed = seed + + self.train_data: Dataset + self.test_data: Dataset + if create_validation_set: + self.val_data: Dataset + self.inference_data: Dataset + + def prepare_data(self) -> None: + """Download the dataset if not available.""" + if (self.root / self.category).is_dir(): + logger.info("Found the dataset.") + else: + self.root.mkdir(parents=True, exist_ok=True) + + logger.info("Downloading the Mvtec AD dataset.") + url = "https://www.mydrive.ch/shares/38536/3830184030e49fe74747669442f0f282/download/420938113-1629952094" + dataset_name = "mvtec_anomaly_detection.tar.xz" + zip_filename = self.root / dataset_name + with DownloadProgressBar(unit="B", unit_scale=True, miniters=1, desc="MVTec AD") as progress_bar: + urlretrieve( + url=f"{url}/{dataset_name}", + filename=zip_filename, + reporthook=progress_bar.update_to, + ) + logger.info("Checking hash") + hash_check(zip_filename, "eefca59f2cede9c3fc5b6befbfec275e") + + logger.info("Extracting the dataset.") + with tarfile.open(zip_filename) as tar_file: + tar_file.extractall(self.root) + + logger.info("Cleaning the tar file") + (zip_filename).unlink() + + def setup(self, stage: Optional[str] = None) -> None: + """Setup train, validation and test data. + + Args: + stage: Optional[str]: Train/Val/Test stages. (Default value = None) + + """ + logger.info("Setting up train, validation, test and prediction datasets.") + if stage in (None, "fit"): + self.train_data = MVTecDataset( + root=self.root, + category=self.category, + pre_process=self.pre_process_train, + split="train", + task=self.task, + seed=self.seed, + create_validation_set=self.create_validation_set, + ) + + if self.create_validation_set: + self.val_data = MVTecDataset( + root=self.root, + category=self.category, + pre_process=self.pre_process_val, + split="val", + task=self.task, + seed=self.seed, + create_validation_set=self.create_validation_set, + ) + + self.test_data = MVTecLOCODataset( + root=self.root, + category=self.category, + pre_process=self.pre_process_val, + split="test", + task=self.task, + seed=self.seed, + create_validation_set=self.create_validation_set, + ) + + if stage == "predict": + self.inference_data = InferenceDataset( + path=self.root, image_size=self.image_size, transform_config=self.transform_config_val + ) + + def train_dataloader(self) -> TRAIN_DATALOADERS: + """Get train dataloader.""" + return DataLoader(self.train_data, shuffle=True, batch_size=self.train_batch_size, num_workers=self.num_workers) + + def val_dataloader(self) -> EVAL_DATALOADERS: + """Get validation dataloader.""" + dataset = self.val_data if self.create_validation_set else self.test_data + return DataLoader(dataset=dataset, shuffle=False, batch_size=self.test_batch_size, num_workers=self.num_workers) + + def test_dataloader(self) -> EVAL_DATALOADERS: + """Get test dataloader.""" + return DataLoader(self.test_data, shuffle=False, batch_size=self.test_batch_size, num_workers=self.num_workers) + + def predict_dataloader(self) -> EVAL_DATALOADERS: + """Get predict dataloader.""" + return DataLoader( + self.inference_data, shuffle=False, batch_size=self.test_batch_size, num_workers=self.num_workers + ) From c0ea4d6a0e36a34a9b6650114b1daca239710381 Mon Sep 17 00:00:00 2001 From: jpcbertoldo <24547377+jpcbertoldo@users.noreply.github.com> Date: Sat, 3 Sep 2022 22:02:29 +0200 Subject: [PATCH 32/38] first version building the dataset --- anomalib/data/mvtec_loco.py | 857 +++++++++++++++++++++++--------- anomalib/data/utils/__init__.py | 2 + anomalib/data/utils/download.py | 13 + anomalib/data/utils/image.py | 5 + 4 files changed, 634 insertions(+), 243 deletions(-) diff --git a/anomalib/data/mvtec_loco.py b/anomalib/data/mvtec_loco.py index 940f2b4927..639ddc2f64 100644 --- a/anomalib/data/mvtec_loco.py +++ b/anomalib/data/mvtec_loco.py @@ -39,7 +39,7 @@ no training annotations -- although it is assumed that the images in training are indeed from the target class (i.e. no noise) problem 1 (image-wise anomaly detection): “is there an anomaly in the image?” problem 2 (pixel-wise anomaly detection or anomaly segmentation): “which pixels belong to the anomaly?” -pixel-wise metric: Saturated Per-Region Overlap (sPRO) +pixel-wise metric: Saturated Per-Region Overlap (sPRO) structural anomaly pixel annotation policy defects are confined to local regions each pixel that introduces a visual structure that is not present in the anomaly-free images is anomalous @@ -53,11 +53,11 @@ n_anomaly_type (n_structural, n_logical): 22 (5, 17) logical constraints contains 2 tangerines -contains 1 nectarine -the tangerines and the nectarine on the left +contains 1 nectarine +the tangerines and the nectarine on the left cereals (C) and a mix of banana chips and almonds (B&A) on the right -the ratio between C and B&A is fixed -the relative position of C and B&A is fixed +the ratio between C and B&A is fixed +the relative position of C and B&A is fixed examples of logical defects too many banana chips and almonds @@ -66,7 +66,7 @@ juice bottle n_anomaly_type (n_structural, n_logical): 18 (7, 11) logical constraints -there is 1 bottle +there is 1 bottle the bottle is filled with a liquid and the fill level is always the same the liquid is of 1 out of 3 colors (red, yellow, white-ish) the bottle carries 2 labels @@ -74,8 +74,8 @@ the first label displays an icon that determines the type of liquid (cherry, orange, banana) cherry: red orange: yellow -banana: white-ish -the second label is attached to the lower part of the bottle +banana: white-ish +the second label is attached to the lower part of the bottle the second label contains the text “100% Juice” examples of logical defects (left) the icon does not match the type of juice @@ -87,7 +87,7 @@ pushpins n_anomaly_type (n_structural, n_logical): 8 (4, 4) logical constraints -each compartment contains 1 pushpin +each compartment contains 1 pushpin examples of logical defects 1 compartment has a missing pin @@ -96,7 +96,7 @@ screw bag n_anomaly_type (n_structural, n_logical): 20 (4, 16) logical constraints -the bag contains +the bag contains 2 washers 2 nuts 1 long screw @@ -120,7 +120,7 @@ examples of logical defects (left) the two splicing connectors do not have the same number of clamps (center) the color of the cable does not match the number of clamps -(right) the cable terminates in different positions +(right) the cable terminates in different positions ################################################## @@ -128,9 +128,9 @@ the area in which the object could occur the saturation threshold is chosen to be equal to the area of the missing object the saturation threshold for an object is chosen from the lower end of the distribution of its (manually annotated) area -example (image): pushpin +example (image): pushpin the missing pushpin can occur anywhere inside its compartment, therefore its entire area is annotated -the saturation threshold is set to the size of a pushpin +the saturation threshold is set to the size of a pushpin ################################################## @@ -142,127 +142,38 @@ it is not clear which of the two cables represents the anomaly, therefore both are annotated the saturation threshold is set to the area of one cable (i.e., half of the annotated region) properties -a method can obtain a perfect score even if it only marks one of the two cables as an anomaly -a method that marks both is neither penalized nor (extra-)rewarded +a method can obtain a perfect score even if it only marks one of the two cables as an anomaly +a method that marks both is neither penalized nor (extra-)rewarded ################################################## -other logical constraints +other logical constraints example (image, left): juice bottle the bottle is filled with orange juice but carries the label of the cherry juice both the orange juice and the label with the cherry are present in the training set, but the logical anomaly arises due to the erroneous combination of the two in the same image either the area filled with juice or the cherry as could be considered anomalous, therefore the union of the two regions is annotated the saturation threshold is set to the area of the cherry because the segmentation of the cherry is sufficient to solve the anomaly localization - """ - -""" -category anomaly type gt_value saturation_definition saturation_parameter -breakfast_box missing_almonds logical 255 relative_to_anomaly 1.0000000 -breakfast_box missing_bananas logical 254 relative_to_anomaly 1.0000000 -breakfast_box missing_toppings logical 253 relative_to_anomaly 1.0000000 -breakfast_box missing_cereals logical 252 relative_to_anomaly 1.0000000 -breakfast_box missing_cereals_and_toppings logical 251 relative_to_anomaly 1.0000000 -breakfast_box nectarines_2_tangerine_1 logical 250 relative_to_image 0.0488770 -breakfast_box nectarine_1_tangerine_1 logical 249 relative_to_image 0.0411621 -breakfast_box nectarines_0_tangerines_2 logical 248 relative_to_image 0.0488770 -breakfast_box nectarines_0_tangerines_3 logical 247 relative_to_image 0.0488770 -breakfast_box nectarines_3_tangerines_0 logical 246 relative_to_image 0.0977539 -breakfast_box nectarines_0_tangerine_1 logical 245 relative_to_image 0.0900391 -breakfast_box nectarines_0_tangerines_0 logical 244 relative_to_image 0.1312012 -breakfast_box nectarines_0_tangerines_4 logical 243 relative_to_image 0.0823242 -breakfast_box compartments_swapped logical 242 relative_to_anomaly 1.0000000 -breakfast_box overflow logical 241 relative_to_anomaly 1.0000000 -breakfast_box underflow logical 240 relative_to_anomaly 1.0000000 -breakfast_box wrong_ratio logical 239 relative_to_anomaly 1.0000000 -breakfast_box mixed_cereals structural 238 relative_to_anomaly 1.0000000 -breakfast_box fruit_damaged structural 237 relative_to_anomaly 1.0000000 -breakfast_box box_damaged structural 236 relative_to_anomaly 1.0000000 -breakfast_box toppings_crushed structural 235 relative_to_anomaly 1.0000000 -breakfast_box contamination structural 234 relative_to_anomaly 1.0000000 -juice_box missing_top_label logical 255 relative_to_image 0.0550000 -juice_box missing_bottom_label logical 254 relative_to_image 0.0255469 -juice_box swapped_labels logical 253 relative_to_image 0.1100000 -juice_box damaged_label structural 252 relative_to_anomaly 1.0000000 -juice_box rotated_label structural 251 relative_to_anomaly 1.0000000 -juice_box misplaced_label_top logical 250 relative_to_image 0.0550000 -juice_box misplaced_label_bottom logical 249 relative_to_image 0.0255469 -juice_box label_text_incomplete structural 248 relative_to_anomaly 1.0000000 -juice_box empty_bottle logical 247 relative_to_anomaly 1.0000000 -juice_box wrong_fill_level_too_much logical 246 relative_to_anomaly 1.0000000 -juice_box wrong_fill_level_not_enough logical 245 relative_to_anomaly 1.0000000 -juice_box misplaced_fruit_icon logical 244 relative_to_anomaly 1.0000000 -juice_box missing_fruit_icon logical 243 relative_to_anomaly 1.0000000 -juice_box unknown_fruit_icon structural 242 relative_to_anomaly 1.0000000 -juice_box incomplete_fruit_icon structural 241 relative_to_anomaly 1.0000000 -juice_box wrong_juice_type logical 240 relative_to_image 0.0035156 -juice_box juice_color structural 239 relative_to_anomaly 1.0000000 -juice_box contamination structural 238 relative_to_anomaly 1.0000000 -pushpins additional_1_pushpin logical 255 relative_to_image 0.0037059 -pushpins additional_2_pushpins logical 254 relative_to_image 0.0074118 -pushpins missing_pushpin logical 253 relative_to_image 0.0037059 -pushpins missing_separator logical 252 relative_to_anomaly 1.0000000 -pushpins front_bent structural 251 relative_to_anomaly 1.0000000 -pushpins broken structural 250 relative_to_anomaly 1.0000000 -pushpins color structural 249 relative_to_anomaly 1.0000000 -pushpins contamination structural 248 relative_to_anomaly 1.0000000 -screw_bag screw_too_long logical 255 relative_to_image 0.0051136 -screw_bag screw_too_shor logical 254 relative_to_image 0.0051136 -screw_bag screws_1_very_short logical 253 relative_to_anomaly 1.0000000 -screw_bag screws_2_very_short logical 252 relative_to_image 0.0102273 -screw_bag additional_1_long_screw logical 251 relative_to_image 0.0168182 -screw_bag additional_1_short_screw logical 250 relative_to_image 0.0117045 -screw_bag additional_1_nut_ logical 249 relative_to_image 0.0042614 -screw_bag additional_2_nuts_ logical 248 relative_to_image 0.0085227 -screw_bag additional_1_washer_ logical 247 relative_to_image 0.0031250 -screw_bag additional_2_washers_ logical 246 relative_to_image 0.0062500 -screw_bag missing_1_long_screw logical 245 relative_to_image 0.0168182 -screw_bag missing_1_short_screw logical 244 relative_to_image 0.0117045 -screw_bag missing_1_nut logical 243 relative_to_image 0.0042614 -screw_bag missing_2_nuts logical 242 relative_to_image 0.0085227 -screw_bag missing_1_washer logical 241 relative_to_image 0.0031250 -screw_bag missing_2_washers logical 240 relative_to_image 0.0062500 -screw_bag bag_broken structural 239 relative_to_anomaly 1.0000000 -screw_bag color structural 238 relative_to_anomaly 1.0000000 -screw_bag contamination structural 237 relative_to_anomaly 1.0000000 -screw_bag part_broken structural 236 relative_to_anomaly 1.0000000 -splicing_connectors wrong_connector_type_5_2 logical 255 relative_to_image 0.0464360 -splicing_connectors wrong_connector_type_5_3 logical 254 relative_to_image 0.0306574 -splicing_connectors wrong_connector_type_3_2 logical 253 relative_to_image 0.0152941 -splicing_connectors cable_too_short_t2 logical 252 relative_to_image 0.0368858 -splicing_connectors cable_too_short_t3 logical 251 relative_to_image 0.0526644 -splicing_connectors cable_too_short_t5 logical 250 relative_to_image 0.0830450 -splicing_connectors missing_connector logical 249 relative_to_anomaly 1.0000000 -splicing_connectors missing_connector_and_cable logical 248 relative_to_image 0.0716955 -splicing_connectors missing_cable logical 247 relative_to_image 0.0124567 -splicing_connectors extra_cable logical 246 relative_to_anomaly 0.5000000 -splicing_connectors cable_color logical 245 relative_to_image 0.0124567 -splicing_connectors broken_cable structural 244 relative_to_anomaly 1.0000000 -splicing_connectors cable_cut logical 243 relative_to_anomaly 1.0000000 -splicing_connectors cable_not_plugged structural 242 relative_to_anomaly 1.0000000 -splicing_connectors unknown_cable_color structural 241 relative_to_anomaly 1.0000000 -splicing_connectors wrong_cable_location logical 240 relative_to_image 0.0124567 -splicing_connectors flipped_connector structural 239 relative_to_anomaly 1.0000000 -splicing_connectors broken_connector structural 238 relative_to_anomaly 1.0000000 -splicing_connectors open_lever structural 237 relative_to_anomaly 1.0000000 -splicing_connectors color structural 236 relative_to_anomaly 1.0000000 -splicing_connectors contamination structural 235 relative_to_anomaly 1.0000000 -""" +# TODO: clear module docstring import logging import tarfile import warnings from pathlib import Path -from typing import Dict, Optional, Tuple, Union +from posixpath import split +from typing import Dict, List, Optional, Tuple, Union +from unicodedata import category from urllib.request import urlretrieve import albumentations as A import cv2 import numpy as np import pandas as pd +from numpy import ndarray from pandas.core.frame import DataFrame from pytorch_lightning.core.datamodule import LightningDataModule +from pytorch_lightning.trainer.states import TrainerFn from pytorch_lightning.utilities.cli import DATAMODULE_REGISTRY from pytorch_lightning.utilities.types import EVAL_DATALOADERS, TRAIN_DATALOADERS from torch import Tensor @@ -271,64 +182,425 @@ from torchvision.datasets.folder import VisionDataset from anomalib.data.inference import InferenceDataset -from anomalib.data.utils import DownloadProgressBar, hash_check, read_image -from anomalib.data.utils.split import ( - create_validation_set_from_test_set, - split_normal_images_in_train_set, -) +from anomalib.data.utils import DownloadProgressBar, hash_check, read_image, read_mask +from anomalib.data.utils.download import tar_extract_all from anomalib.pre_processing import PreProcessor +# TODO: open discussion about keeping pre-resized tensors in the dataset folder +# TODO: create an issue in mvtec so the dataset will retain the information abou the anomaly type (label) so one can do per-label evaluation +# TODO: document the notion of label and superlabel + logger = logging.getLogger(__name__) -def make_mvtec_loco_dataset( +TASK_SEGMENTATION = "segmentation" +TASKS = TASK_SEGMENTATION + +SPLIT_TRAIN = "train" +# "validation" instead of "val" is an explicit choice because this will match the name of the folder +SPLIT_VALIDATION = "validation" +SPLIT_TEST = "test" +SPLITS = (SPLIT_TRAIN, SPLIT_VALIDATION, SPLIT_TEST) + +# each image is read upon demand +IMREAD_STRATEGY_ONTHEFLY = "onthefly" +# all images are pre-loaded into memory at initialization +IMREAD_STRATEGY_PRELOAD = "preload" +IMREAD_STRATEGIES = (IMREAD_STRATEGY_ONTHEFLY, IMREAD_STRATEGY_PRELOAD) +# TODO make a strategy preload_tensor + +CATEGORY_BREAKFAST_BOX = "breakfast_box" +CATEGORY_JUICE_BOTTLE = "juice_bottle" +CATEGORY_PUSHPINS = "pushpins" +CATEGORY_SCREW_BAG = "screw_bag" +CATEGORY_SPLICING_CONNECTORS = "splicing_connectors" +CATEGORIES: Tuple[str, ...] = ( + CATEGORY_BREAKFAST_BOX, + CATEGORY_JUICE_BOTTLE, + CATEGORY_PUSHPINS, + CATEGORY_SCREW_BAG, + CATEGORY_SPLICING_CONNECTORS, +) + +LABEL_NORMAL = 0 +LABEL_ANOMALOUS = 1 + +SUPER_ANOTYPE_GOOD = "good" +SUPER_ANOTYPE_LOGICAL = "logical_anomalies" +SUPER_ANOTYPE_STRUCTURAL = "structural_anomalies" +SUPER_ANOTYPES: Tuple[str, ...] = (SUPER_ANOTYPE_GOOD, SUPER_ANOTYPE_LOGICAL, SUPER_ANOTYPE_STRUCTURAL) + +ANOTYPE_GOOD = "good" + +ANOTYPES_BREAKFAST_BOX: Tuple[str, ...] = ( + ANOTYPE_BB_GOOD := "good", + ANOTYPE_BB_COMPARTMENTS_SWAPPED := "compartments_swapped", + ANOTYPE_BB_MISSING_ALMONDS := "missing_almonds", + ANOTYPE_BB_MISSING_BANANAS := "missing_bananas", + ANOTYPE_BB_MISSING_CEREALS := "missing_cereals", + ANOTYPE_BB_MISSING_CEREALS_AND_TOPPINGS := "missing_cereals_and_toppings", + ANOTYPE_BB_MISSING_TOPPINGS := "missing_toppings", + ANOTYPE_BB_NECTARINE_1_TANGERINE_1 := "nectarine_1_tangerine_1", + ANOTYPE_BB_NECTARINES_0_TANGERINE_1 := "nectarines_0_tangerine_1", + ANOTYPE_BB_NECTARINES_0_TANGERINES_0 := "nectarines_0_tangerines_0", + ANOTYPE_BB_NECTARINES_0_TANGERINES_2 := "nectarines_0_tangerines_2", + ANOTYPE_BB_NECTARINES_0_TANGERINES_3 := "nectarines_0_tangerines_3", + ANOTYPE_BB_NECTARINES_0_TANGERINES_4 := "nectarines_0_tangerines_4", + ANOTYPE_BB_NECTARINES_2_TANGERINE_1 := "nectarines_2_tangerine_1", + ANOTYPE_BB_NECTARINES_3_TANGERINES_0 := "nectarines_3_tangerines_0", + ANOTYPE_BB_OVERFLOW := "overflow", + ANOTYPE_BB_UNDERFLOW := "underflow", + ANOTYPE_BB_WRONG_RATIO := "wrong_ratio", + ANOTYPE_BB_BOX_DAMAGED := "box_damaged", + ANOTYPE_BB_CONTAMINATION := "contamination", + ANOTYPE_BB_FRUIT_DAMAGED := "fruit_damaged", + ANOTYPE_BB_MIXED_CEREALS := "mixed_cereals", + ANOTYPE_BB_TOPPINGS_CRUSHED := "toppings_crushed", +) + +ANOTYPES_JUICE_BOTTLE: Tuple[str, ...] = ( + ANOTYPE_JB_GOOD := "good", + ANOTYPE_JB_EMPTY_BOTTLE := "empty_bottle", + ANOTYPE_JB_MISPLACED_FRUIT_ICON := "misplaced_fruit_icon", + ANOTYPE_JB_MISPLACED_LABEL_BOTTOM := "misplaced_label_bottom", + ANOTYPE_JB_MISPLACED_LABEL_TOP := "misplaced_label_top", + ANOTYPE_JB_MISSING_BOTTOM_LABEL := "missing_bottom_label", + ANOTYPE_JB_MISSING_FRUIT_ICON := "missing_fruit_icon", + ANOTYPE_JB_MISSING_TOP_LABEL := "missing_top_label", + ANOTYPE_JB_SWAPPED_LABELS := "swapped_labels", + ANOTYPE_JB_WRONG_FILL_LEVEL_NOT_ENOUGH := "wrong_fill_level_not_enough", + ANOTYPE_JB_WRONG_FILL_LEVEL_TOO_MUCH := "wrong_fill_level_too_much", + ANOTYPE_JB_WRONG_JUICE_TYPE := "wrong_juice_type", + ANOTYPE_JB_CONTAMINATION := "contamination", + ANOTYPE_JB_DAMAGED_LABEL := "damaged_label", + ANOTYPE_JB_INCOMPLETE_FRUIT_ICON := "incomplete_fruit_icon", + ANOTYPE_JB_JUICE_COLOR := "juice_color", + ANOTYPE_JB_LABEL_TEXT_INCOMPLETE := "label_text_incomplete", + ANOTYPE_JB_ROTATED_LABEL := "rotated_label", + ANOTYPE_JB_UNKNOWN_FRUIT_ICON := "unknown_fruit_icon", +) + +ANOTYPES_PUSHPINS: Tuple[str, ...] = ( + ANOTYPE_P_GOOD := "good", + ANOTYPE_P_ADDITIONAL_1_PUSHPIN := "additional_1_pushpin", + ANOTYPE_P_ADDITIONAL_2_PUSHPINS := "additional_2_pushpins", + ANOTYPE_P_MISSING_PUSHPIN := "missing_pushpin", + ANOTYPE_P_MISSING_SEPARATOR := "missing_separator", + ANOTYPE_P_BROKEN := "broken", + ANOTYPE_P_COLOR := "color", + ANOTYPE_P_CONTAMINATION := "contamination", + ANOTYPE_P_FRONT_BENT := "front_bent", +) + +ANOTYPES_SCREW_BAG: Tuple[str, ...] = ( + ANOTYPE_SB_GOOD := "good", + ANOTYPE_SB_ADDITIONAL_1_LONG_SCREW := "additional_1_long_screw", + ANOTYPE_SB_ADDITIONAL_1_NUT_ := "additional_1_nut_", + ANOTYPE_SB_ADDITIONAL_1_SHORT_SCREW := "additional_1_short_screw", + ANOTYPE_SB_ADDITIONAL_1_WASHER_ := "additional_1_washer_", + ANOTYPE_SB_ADDITIONAL_2_NUTS_ := "additional_2_nuts_", + ANOTYPE_SB_ADDITIONAL_2_WASHERS_ := "additional_2_washers_", + ANOTYPE_SB_MISSING_1_LONG_SCREW := "missing_1_long_screw", + ANOTYPE_SB_MISSING_1_NUT := "missing_1_nut", + ANOTYPE_SB_MISSING_1_SHORT_SCREW := "missing_1_short_screw", + ANOTYPE_SB_MISSING_1_WASHER := "missing_1_washer", + ANOTYPE_SB_MISSING_2_NUTS := "missing_2_nuts", + ANOTYPE_SB_MISSING_2_WASHERS := "missing_2_washers", + ANOTYPE_SB_SCREW_TOO_LONG := "screw_too_long", + ANOTYPE_SB_SCREW_TOO_SHOR := "screw_too_shor", + ANOTYPE_SB_SCREWS_1_VERY_SHORT := "screws_1_very_short", + ANOTYPE_SB_SCREWS_2_VERY_SHORT := "screws_2_very_short", + ANOTYPE_SB_BAG_BROKEN := "bag_broken", + ANOTYPE_SB_COLOR := "color", + ANOTYPE_SB_CONTAMINATION := "contamination", + ANOTYPE_SB_PART_BROKEN := "part_broken", +) + +ANOTYPES_SPLICING_CONNECTORS: Tuple[str, ...] = ( + ANOTYPE_SC_GOOD := "good", + ANOTYPE_SC_CABLE_COLOR := "cable_color", + ANOTYPE_SC_CABLE_CUT := "cable_cut", + ANOTYPE_SC_CABLE_TOO_SHORT_T2 := "cable_too_short_t2", + ANOTYPE_SC_CABLE_TOO_SHORT_T3 := "cable_too_short_t3", + ANOTYPE_SC_CABLE_TOO_SHORT_T5 := "cable_too_short_t5", + ANOTYPE_SC_EXTRA_CABLE := "extra_cable", + ANOTYPE_SC_MISSING_CABLE := "missing_cable", + ANOTYPE_SC_MISSING_CONNECTOR := "missing_connector", + ANOTYPE_SC_MISSING_CONNECTOR_AND_CABLE := "missing_connector_and_cable", + ANOTYPE_SC_WRONG_CABLE_LOCATION := "wrong_cable_location", + ANOTYPE_SC_WRONG_CONNECTOR_TYPE_3_2 := "wrong_connector_type_3_2", + ANOTYPE_SC_WRONG_CONNECTOR_TYPE_5_2 := "wrong_connector_type_5_2", + ANOTYPE_SC_WRONG_CONNECTOR_TYPE_5_3 := "wrong_connector_type_5_3", + ANOTYPE_SC_BROKEN_CABLE := "broken_cable", + ANOTYPE_SC_BROKEN_CONNECTOR := "broken_connector", + ANOTYPE_SC_CABLE_NOT_PLUGGED := "cable_not_plugged", + ANOTYPE_SC_COLOR := "color", + ANOTYPE_SC_CONTAMINATION := "contamination", + ANOTYPE_SC_FLIPPED_CONNECTOR := "flipped_connector", + ANOTYPE_SC_OPEN_LEVER := "open_lever", + ANOTYPE_SC_UNKNOWN_CABLE_COLOR := "unknown_cable_color", +) + +# this is given at the paper, each anomaly type (label) has a different gtvalue in the mask +# source: Beyond Dents and Scratches: Logical Constraints in Unsupervised Anomaly Detection and Localization (Bergmann, P. et al, 2022). +_MAP_ANOTYPE_2_GTVALUE: Dict[Tuple[str, str, str], int] = { + (CATEGORY_BREAKFAST_BOX, SUPER_ANOTYPE_LOGICAL, ANOTYPE_BB_COMPARTMENTS_SWAPPED): 242, + (CATEGORY_BREAKFAST_BOX, SUPER_ANOTYPE_LOGICAL, ANOTYPE_BB_MISSING_ALMONDS): 255, + (CATEGORY_BREAKFAST_BOX, SUPER_ANOTYPE_LOGICAL, ANOTYPE_BB_MISSING_BANANAS): 254, + (CATEGORY_BREAKFAST_BOX, SUPER_ANOTYPE_LOGICAL, ANOTYPE_BB_MISSING_CEREALS): 252, + (CATEGORY_BREAKFAST_BOX, SUPER_ANOTYPE_LOGICAL, ANOTYPE_BB_MISSING_CEREALS_AND_TOPPINGS): 251, + (CATEGORY_BREAKFAST_BOX, SUPER_ANOTYPE_LOGICAL, ANOTYPE_BB_MISSING_TOPPINGS): 253, + (CATEGORY_BREAKFAST_BOX, SUPER_ANOTYPE_LOGICAL, ANOTYPE_BB_NECTARINE_1_TANGERINE_1): 249, + (CATEGORY_BREAKFAST_BOX, SUPER_ANOTYPE_LOGICAL, ANOTYPE_BB_NECTARINES_0_TANGERINE_1): 245, + (CATEGORY_BREAKFAST_BOX, SUPER_ANOTYPE_LOGICAL, ANOTYPE_BB_NECTARINES_0_TANGERINES_0): 244, + (CATEGORY_BREAKFAST_BOX, SUPER_ANOTYPE_LOGICAL, ANOTYPE_BB_NECTARINES_0_TANGERINES_2): 248, + (CATEGORY_BREAKFAST_BOX, SUPER_ANOTYPE_LOGICAL, ANOTYPE_BB_NECTARINES_0_TANGERINES_3): 247, + (CATEGORY_BREAKFAST_BOX, SUPER_ANOTYPE_LOGICAL, ANOTYPE_BB_NECTARINES_0_TANGERINES_4): 243, + (CATEGORY_BREAKFAST_BOX, SUPER_ANOTYPE_LOGICAL, ANOTYPE_BB_NECTARINES_2_TANGERINE_1): 250, + (CATEGORY_BREAKFAST_BOX, SUPER_ANOTYPE_LOGICAL, ANOTYPE_BB_NECTARINES_3_TANGERINES_0): 246, + (CATEGORY_BREAKFAST_BOX, SUPER_ANOTYPE_LOGICAL, ANOTYPE_BB_OVERFLOW): 241, + (CATEGORY_BREAKFAST_BOX, SUPER_ANOTYPE_LOGICAL, ANOTYPE_BB_UNDERFLOW): 240, + (CATEGORY_BREAKFAST_BOX, SUPER_ANOTYPE_LOGICAL, ANOTYPE_BB_WRONG_RATIO): 239, + (CATEGORY_BREAKFAST_BOX, SUPER_ANOTYPE_STRUCTURAL, ANOTYPE_BB_BOX_DAMAGED): 236, + (CATEGORY_BREAKFAST_BOX, SUPER_ANOTYPE_STRUCTURAL, ANOTYPE_BB_CONTAMINATION): 234, + (CATEGORY_BREAKFAST_BOX, SUPER_ANOTYPE_STRUCTURAL, ANOTYPE_BB_FRUIT_DAMAGED): 237, + (CATEGORY_BREAKFAST_BOX, SUPER_ANOTYPE_STRUCTURAL, ANOTYPE_BB_MIXED_CEREALS): 238, + (CATEGORY_BREAKFAST_BOX, SUPER_ANOTYPE_STRUCTURAL, ANOTYPE_BB_TOPPINGS_CRUSHED): 235, + (CATEGORY_JUICE_BOTTLE, SUPER_ANOTYPE_LOGICAL, ANOTYPE_JB_EMPTY_BOTTLE): 247, + (CATEGORY_JUICE_BOTTLE, SUPER_ANOTYPE_LOGICAL, ANOTYPE_JB_MISPLACED_FRUIT_ICON): 244, + (CATEGORY_JUICE_BOTTLE, SUPER_ANOTYPE_LOGICAL, ANOTYPE_JB_MISPLACED_LABEL_BOTTOM): 249, + (CATEGORY_JUICE_BOTTLE, SUPER_ANOTYPE_LOGICAL, ANOTYPE_JB_MISPLACED_LABEL_TOP): 250, + (CATEGORY_JUICE_BOTTLE, SUPER_ANOTYPE_LOGICAL, ANOTYPE_JB_MISSING_BOTTOM_LABEL): 254, + (CATEGORY_JUICE_BOTTLE, SUPER_ANOTYPE_LOGICAL, ANOTYPE_JB_MISSING_FRUIT_ICON): 243, + (CATEGORY_JUICE_BOTTLE, SUPER_ANOTYPE_LOGICAL, ANOTYPE_JB_MISSING_TOP_LABEL): 255, + (CATEGORY_JUICE_BOTTLE, SUPER_ANOTYPE_LOGICAL, ANOTYPE_JB_SWAPPED_LABELS): 253, + (CATEGORY_JUICE_BOTTLE, SUPER_ANOTYPE_LOGICAL, ANOTYPE_JB_WRONG_FILL_LEVEL_NOT_ENOUGH): 245, + (CATEGORY_JUICE_BOTTLE, SUPER_ANOTYPE_LOGICAL, ANOTYPE_JB_WRONG_FILL_LEVEL_TOO_MUCH): 246, + (CATEGORY_JUICE_BOTTLE, SUPER_ANOTYPE_LOGICAL, ANOTYPE_JB_WRONG_JUICE_TYPE): 240, + (CATEGORY_JUICE_BOTTLE, SUPER_ANOTYPE_STRUCTURAL, ANOTYPE_JB_CONTAMINATION): 238, + (CATEGORY_JUICE_BOTTLE, SUPER_ANOTYPE_STRUCTURAL, ANOTYPE_JB_DAMAGED_LABEL): 252, + (CATEGORY_JUICE_BOTTLE, SUPER_ANOTYPE_STRUCTURAL, ANOTYPE_JB_INCOMPLETE_FRUIT_ICON): 241, + (CATEGORY_JUICE_BOTTLE, SUPER_ANOTYPE_STRUCTURAL, ANOTYPE_JB_JUICE_COLOR): 239, + (CATEGORY_JUICE_BOTTLE, SUPER_ANOTYPE_STRUCTURAL, ANOTYPE_JB_LABEL_TEXT_INCOMPLETE): 248, + (CATEGORY_JUICE_BOTTLE, SUPER_ANOTYPE_STRUCTURAL, ANOTYPE_JB_ROTATED_LABEL): 251, + (CATEGORY_JUICE_BOTTLE, SUPER_ANOTYPE_STRUCTURAL, ANOTYPE_JB_UNKNOWN_FRUIT_ICON): 242, + (CATEGORY_PUSHPINS, SUPER_ANOTYPE_LOGICAL, ANOTYPE_P_ADDITIONAL_1_PUSHPIN): 255, + (CATEGORY_PUSHPINS, SUPER_ANOTYPE_LOGICAL, ANOTYPE_P_ADDITIONAL_2_PUSHPINS): 254, + (CATEGORY_PUSHPINS, SUPER_ANOTYPE_LOGICAL, ANOTYPE_P_MISSING_PUSHPIN): 253, + (CATEGORY_PUSHPINS, SUPER_ANOTYPE_LOGICAL, ANOTYPE_P_MISSING_SEPARATOR): 252, + (CATEGORY_PUSHPINS, SUPER_ANOTYPE_STRUCTURAL, ANOTYPE_P_BROKEN): 250, + (CATEGORY_PUSHPINS, SUPER_ANOTYPE_STRUCTURAL, ANOTYPE_P_COLOR): 249, + (CATEGORY_PUSHPINS, SUPER_ANOTYPE_STRUCTURAL, ANOTYPE_P_CONTAMINATION): 248, + (CATEGORY_PUSHPINS, SUPER_ANOTYPE_STRUCTURAL, ANOTYPE_P_FRONT_BENT): 251, + (CATEGORY_SCREW_BAG, SUPER_ANOTYPE_LOGICAL, ANOTYPE_SB_ADDITIONAL_1_LONG_SCREW): 251, + (CATEGORY_SCREW_BAG, SUPER_ANOTYPE_LOGICAL, ANOTYPE_SB_ADDITIONAL_1_NUT_): 249, + (CATEGORY_SCREW_BAG, SUPER_ANOTYPE_LOGICAL, ANOTYPE_SB_ADDITIONAL_1_SHORT_SCREW): 250, + (CATEGORY_SCREW_BAG, SUPER_ANOTYPE_LOGICAL, ANOTYPE_SB_ADDITIONAL_1_WASHER_): 247, + (CATEGORY_SCREW_BAG, SUPER_ANOTYPE_LOGICAL, ANOTYPE_SB_ADDITIONAL_2_NUTS_): 248, + (CATEGORY_SCREW_BAG, SUPER_ANOTYPE_LOGICAL, ANOTYPE_SB_ADDITIONAL_2_WASHERS_): 246, + (CATEGORY_SCREW_BAG, SUPER_ANOTYPE_LOGICAL, ANOTYPE_SB_MISSING_1_LONG_SCREW): 245, + (CATEGORY_SCREW_BAG, SUPER_ANOTYPE_LOGICAL, ANOTYPE_SB_MISSING_1_NUT): 243, + (CATEGORY_SCREW_BAG, SUPER_ANOTYPE_LOGICAL, ANOTYPE_SB_MISSING_1_SHORT_SCREW): 244, + (CATEGORY_SCREW_BAG, SUPER_ANOTYPE_LOGICAL, ANOTYPE_SB_MISSING_1_WASHER): 241, + (CATEGORY_SCREW_BAG, SUPER_ANOTYPE_LOGICAL, ANOTYPE_SB_MISSING_2_NUTS): 242, + (CATEGORY_SCREW_BAG, SUPER_ANOTYPE_LOGICAL, ANOTYPE_SB_MISSING_2_WASHERS): 240, + (CATEGORY_SCREW_BAG, SUPER_ANOTYPE_LOGICAL, ANOTYPE_SB_SCREW_TOO_LONG): 255, + (CATEGORY_SCREW_BAG, SUPER_ANOTYPE_LOGICAL, ANOTYPE_SB_SCREW_TOO_SHOR): 254, + (CATEGORY_SCREW_BAG, SUPER_ANOTYPE_LOGICAL, ANOTYPE_SB_SCREWS_1_VERY_SHORT): 253, + (CATEGORY_SCREW_BAG, SUPER_ANOTYPE_LOGICAL, ANOTYPE_SB_SCREWS_2_VERY_SHORT): 252, + (CATEGORY_SCREW_BAG, SUPER_ANOTYPE_STRUCTURAL, ANOTYPE_SB_BAG_BROKEN): 239, + (CATEGORY_SCREW_BAG, SUPER_ANOTYPE_STRUCTURAL, ANOTYPE_SB_COLOR): 238, + (CATEGORY_SCREW_BAG, SUPER_ANOTYPE_STRUCTURAL, ANOTYPE_SB_CONTAMINATION): 237, + (CATEGORY_SCREW_BAG, SUPER_ANOTYPE_STRUCTURAL, ANOTYPE_SB_PART_BROKEN): 236, + (CATEGORY_SPLICING_CONNECTORS, SUPER_ANOTYPE_LOGICAL, ANOTYPE_SC_CABLE_COLOR): 245, + (CATEGORY_SPLICING_CONNECTORS, SUPER_ANOTYPE_LOGICAL, ANOTYPE_SC_CABLE_CUT): 243, + (CATEGORY_SPLICING_CONNECTORS, SUPER_ANOTYPE_LOGICAL, ANOTYPE_SC_CABLE_TOO_SHORT_T2): 252, + (CATEGORY_SPLICING_CONNECTORS, SUPER_ANOTYPE_LOGICAL, ANOTYPE_SC_CABLE_TOO_SHORT_T3): 251, + (CATEGORY_SPLICING_CONNECTORS, SUPER_ANOTYPE_LOGICAL, ANOTYPE_SC_CABLE_TOO_SHORT_T5): 250, + (CATEGORY_SPLICING_CONNECTORS, SUPER_ANOTYPE_LOGICAL, ANOTYPE_SC_EXTRA_CABLE): 246, + (CATEGORY_SPLICING_CONNECTORS, SUPER_ANOTYPE_LOGICAL, ANOTYPE_SC_MISSING_CABLE): 247, + (CATEGORY_SPLICING_CONNECTORS, SUPER_ANOTYPE_LOGICAL, ANOTYPE_SC_MISSING_CONNECTOR): 249, + (CATEGORY_SPLICING_CONNECTORS, SUPER_ANOTYPE_LOGICAL, ANOTYPE_SC_MISSING_CONNECTOR_AND_CABLE): 248, + (CATEGORY_SPLICING_CONNECTORS, SUPER_ANOTYPE_LOGICAL, ANOTYPE_SC_WRONG_CABLE_LOCATION): 240, + (CATEGORY_SPLICING_CONNECTORS, SUPER_ANOTYPE_LOGICAL, ANOTYPE_SC_WRONG_CONNECTOR_TYPE_3_2): 253, + (CATEGORY_SPLICING_CONNECTORS, SUPER_ANOTYPE_LOGICAL, ANOTYPE_SC_WRONG_CONNECTOR_TYPE_5_2): 255, + (CATEGORY_SPLICING_CONNECTORS, SUPER_ANOTYPE_LOGICAL, ANOTYPE_SC_WRONG_CONNECTOR_TYPE_5_3): 254, + (CATEGORY_SPLICING_CONNECTORS, SUPER_ANOTYPE_STRUCTURAL, ANOTYPE_SC_BROKEN_CABLE): 244, + (CATEGORY_SPLICING_CONNECTORS, SUPER_ANOTYPE_STRUCTURAL, ANOTYPE_SC_BROKEN_CONNECTOR): 238, + (CATEGORY_SPLICING_CONNECTORS, SUPER_ANOTYPE_STRUCTURAL, ANOTYPE_SC_CABLE_NOT_PLUGGED): 242, + (CATEGORY_SPLICING_CONNECTORS, SUPER_ANOTYPE_STRUCTURAL, ANOTYPE_SC_COLOR): 236, + (CATEGORY_SPLICING_CONNECTORS, SUPER_ANOTYPE_STRUCTURAL, ANOTYPE_SC_CONTAMINATION): 235, + (CATEGORY_SPLICING_CONNECTORS, SUPER_ANOTYPE_STRUCTURAL, ANOTYPE_SC_FLIPPED_CONNECTOR): 239, + (CATEGORY_SPLICING_CONNECTORS, SUPER_ANOTYPE_STRUCTURAL, ANOTYPE_SC_OPEN_LEVER): 237, + (CATEGORY_SPLICING_CONNECTORS, SUPER_ANOTYPE_STRUCTURAL, ANOTYPE_SC_UNKNOWN_CABLE_COLOR): 241, +} + +# map: (category, gtvalue) -> (superlabel, label) +_MAP_GTVALUE_2_ANOTYPE: Dict[Tuple[str, int], Tuple[str, str]] = { + (category, gtvalue): (super_anotype, anotype) + for (category, super_anotype, anotype), gtvalue in _MAP_ANOTYPE_2_GTVALUE.items() +} + +# expected number of images in each category split so that we can check if the dataset is complete +# source: Beyond Dents and Scratches: Logical Constraints in Unsupervised Anomaly Detection and Localization (Bergmann, P. et al, 2022). +_EXPECTED_NSAMPLES: Dict[Tuple[str, str], int] = { + (CATEGORY_BREAKFAST_BOX, SPLIT_TRAIN): 351, + (CATEGORY_BREAKFAST_BOX, SPLIT_VALIDATION): 62, + (CATEGORY_BREAKFAST_BOX, SPLIT_TEST): 275, + (CATEGORY_JUICE_BOTTLE, SPLIT_TRAIN): 335, + (CATEGORY_JUICE_BOTTLE, SPLIT_VALIDATION): 54, + (CATEGORY_JUICE_BOTTLE, SPLIT_TEST): 330, + (CATEGORY_PUSHPINS, SPLIT_TRAIN): 372, + (CATEGORY_PUSHPINS, SPLIT_VALIDATION): 69, + (CATEGORY_PUSHPINS, SPLIT_TEST): 310, + (CATEGORY_SCREW_BAG, SPLIT_TRAIN): 360, + (CATEGORY_SCREW_BAG, SPLIT_VALIDATION): 60, + (CATEGORY_SCREW_BAG, SPLIT_TEST): 341, + # these two below were wrong in the paper + # TODO send a correction to the authors + # (CATEGORY_SPLICING_CONNECTORS, SPLIT_TRAIN): 354, + # (CATEGORY_SPLICING_CONNECTORS, SPLIT_VALIDATION): 59, + (CATEGORY_SPLICING_CONNECTORS, SPLIT_TRAIN): 360, + (CATEGORY_SPLICING_CONNECTORS, SPLIT_VALIDATION): 60, + (CATEGORY_SPLICING_CONNECTORS, SPLIT_TEST): 312, +} + + +def _binarize_mask_float(mask: np.ndarray) -> np.ndarray: + """ + the masks use different gtvalue values for the different anomaly types so the > 0 is making it binary + this operation is very simple but it is in a function to make sure its standard because it is used in different places + e.g. preloading while building the dataset and on the fly while training + """ + return (mask > 0).astype(float) + + +def _make_dataset( path: Path, split: Optional[str] = None, - split_ratio: float = 0.1, - seed: Optional[int] = None, - create_validation_set: bool = False, + imread_strategy: str = IMREAD_STRATEGY_PRELOAD, ) -> DataFrame: - samples_list = [(str(path),) + filename.parts[-3:] for filename in path.glob("**/*.png")] - if len(samples_list) == 0: - raise RuntimeError(f"Found 0 images in {path}") - - samples = pd.DataFrame(samples_list, columns=["path", "split", "label", "image_path"]) - samples = samples[samples.split != "ground_truth"] - - # Create mask_path column - samples["mask_path"] = ( - samples.path - + "/ground_truth/" - + samples.label - + "/" - + samples.image_path.str.rstrip("png").str.rstrip(".") - + "_mask.png" - ) - - # Modify image_path column by converting to absolute path - samples["image_path"] = samples.path + "/" + samples.split + "/" + samples.label + "/" + samples.image_path - - # Split the normal images in training set if test set doesn't - # contain any normal images. This is needed because AUC score - # cannot be computed based on 1-class - if sum((samples.split == "test") & (samples.label == "good")) == 0: - samples = split_normal_images_in_train_set(samples, split_ratio, seed) - - # Good images don't have mask - samples.loc[(samples.split == "test") & (samples.label == "good"), "mask_path"] = "" - - # Create label index for normal (0) and anomalous (1) images. - samples.loc[(samples.label == "good"), "label_index"] = 0 - samples.loc[(samples.label != "good"), "label_index"] = 1 - samples.label_index = samples.label_index.astype(int) - - if create_validation_set: - samples = create_validation_set_from_test_set(samples, seed=seed) - - # Get the data frame for the split. - if split is not None and split in ["train", "val", "test"]: - samples = samples[samples.split == split] - samples = samples.reset_index(drop=True) + # todo create optional to get a subset of anomlies in the test + + assert split is None or split in SPLITS, f"Invalid split: {split}" + assert imread_strategy in IMREAD_STRATEGIES, f"Invalid imread strategy: {imread_strategy}" + + category = path.resolve().name + assert category in CATEGORIES, f"Invalid path '{path}'. The directory ('{category}') must be one of {CATEGORIES}" + + if split is None: + return pd.concat([_make_dataset(path, split_, imread_strategy) for split_ in SPLITS], axis=0) + + logger.info(f"Creating MVTec LOCO AD dataset for category '{category}' split '{split}'") + + """ + structure of the files in the dataset ("/" is 'path') + + images: /{split}/{super_anotype}/{image_index}.png + + /train/good/000.png + /train/good/... + /train/good/350.png + + /validation/good/... + + /test/good/... + /test/logical_anomalies/... + /test/structural_anomalies/... + + masks: /ground_truth/{super_anotype}/{image_index}/000.png + + /ground_truth/logical_anomalies/000/000.png + /ground_truth/logical_anomalies/.../000.png + /ground_truth/logical_anomalies/079/000.png + + /ground_truth/structural_anomalies/.../000.png + ... + """ + + # these values look like "(train|validation|test)/(good|logical_anomalies|structural_anomalies)/(000|...|n).png" + # where (a|b) means either a or b + samples_paths = sorted(path.glob(f"{split}/**/*.png")) + expected_nsamples = _EXPECTED_NSAMPLES[(category, split)] + + if len(samples_paths) != expected_nsamples: + warnings.warn( + f"Expected {expected_nsamples} samples for split '{split}' in category '{category}' but found {len(samples_paths)}." + "Is the dataset corrupted?" + ) + + def build_record(sample_path: Path): + + ret: Dict[str, Union[Path, None, str, int]] = { + "image_path": sample_path, + **dict(zip(("split", "super_anotype", "image_filename"), sample_path.parts[-3:])), + } + + super_anotype: str = ret["super_anotype"] # type: ignore + + if super_anotype == SUPER_ANOTYPE_GOOD: + ret.update( + { + "mask_path": None, + "label": LABEL_NORMAL, + "super_anotype": SUPER_ANOTYPE_GOOD, + "anotype": ANOTYPE_GOOD, + } + ) + return ret + + elif super_anotype in (SUPER_ANOTYPE_LOGICAL, SUPER_ANOTYPE_STRUCTURAL): + + mask_path: Path = path / "ground_truth" / super_anotype / sample_path.stem / "000.png" + + assert mask_path.exists(), f"Mask file '{mask_path}' does not exist. Is the dataset corrupted?" + + # mask images are supposed to have only two values: 0 and GTVALUE_ANOMALY + # GTVALUE_ANOMALY \in {234, ..., 255} and is given in the paper, encoded in the mapping below + # TODO create an issue to cache this info so the mask is not read here + gtvalue = read_mask(mask_path).astype(int).max() + _, anotype = _MAP_GTVALUE_2_ANOTYPE[(category, gtvalue)] + + ret.update( + { + "mask_path": mask_path, + "gtvalue": gtvalue, + "label": LABEL_ANOMALOUS, + "super_anotype": super_anotype, + "anotype": anotype, + } + ) + + return ret + + else: + # there should only be the folders "good", "logical_anomalies" and "structural_anomalies" + raise RuntimeError( + f"Something wrong in the dataset folder. Unknown folder {super_anotype}, path={sample_path}" + ) + + samples = pd.DataFrame.from_records([build_record(sp) for sp in samples_paths]) + + if imread_strategy == IMREAD_STRATEGY_PRELOAD: + + # warnings.warn( + # "Preloading images into memory. " + # "If your dataset is too large, consider using another imread_strategy instead.", + # stacklevel=3 + # ) + + logger.debug(f"Preloading images into memory") + samples["image"] = samples.image_path.map(read_image) + + logger.debug(f"Preloading masks into memory") + + # this is used to select the rows in the dataframe + has_mask = ~samples.mask_path.isnull() + + samples.loc[has_mask, "mask"] = samples.loc[has_mask, "mask_path"].map( + lambda x: _binarize_mask_float(read_mask(x)) + ) + samples.loc[~has_mask, "mask"] = None return samples @@ -342,40 +614,71 @@ def __init__( category: str, pre_process: PreProcessor, split: str, - task: str = "segmentation", - seed: Optional[int] = None, - create_validation_set: bool = False, + task: str = TASK_SEGMENTATION, + # create_validation_set: bool = False, + imread_strategy: str = IMREAD_STRATEGY_PRELOAD, ) -> None: super().__init__(root) - if seed is None: - warnings.warn( - "seed is None." - " When seed is not set, images from the normal directory are split between training and test dir." - " This will lead to inconsistency between runs." - ) + assert split in SPLITS, f"Split '{split}' is not supported. Supported splits are {SPLITS}" + assert task in TASKS, f"Task '{task}' is not supported. Supported tasks are {TASKS}" + assert ( + imread_strategy in IMREAD_STRATEGIES + ), f"Imread strategy '{imread_strategy}' is not supported. Supported imread strategies are {IMREAD_STRATEGIES}" self.root = Path(root) if isinstance(root, str) else root self.category: str = category self.split = split self.task = task - self.pre_process = pre_process + self.imread_strategy = imread_strategy - self.samples = make_mvtec_loco_dataset( - path=self.root / category, + self.samples = _make_dataset( + path=self.dataset_path, split=self.split, - seed=seed, - create_validation_set=create_validation_set, + imread_strategy=self.imread_strategy, ) + @property + def dataset_path(self) -> Path: + """Path to the dataset folder.""" + return self.root / self.category + def __len__(self) -> int: """Get length of the dataset.""" return len(self.samples) + def _get_image(self, index: int) -> ndarray: + """Get image at index.""" + + if self.imread_strategy == IMREAD_STRATEGY_PRELOAD: + return self.samples.image[index] + + elif self.imread_strategy == IMREAD_STRATEGY_ONTHEFLY: + return read_image(self.samples.image_path[index]) + + else: + raise NotImplementedError(f"Imread strategy '{self.imread_strategy}' is not supported.") + + def _get_mask(self, index: int) -> ndarray: + """Get mask at index.""" + + if self.imread_strategy == IMREAD_STRATEGY_PRELOAD: + return self.samples.mask[index] + + elif self.imread_strategy == IMREAD_STRATEGY_ONTHEFLY: + return _binarize_mask_float(read_mask(self.samples.mask_path[index])) + + else: + raise NotImplementedError(f"Imread strategy '{self.imread_strategy}' is not supported.") + def __getitem__(self, index: int) -> Dict[str, Union[str, Tensor]]: """Get dataset item for the index ``index``. + TODO: include the label string + TODO: include the label anomaly type (strutural, logical) + TODO: here? probably better to separate it... return the sPRO saturation value + Args: index (int): Index to get the item. @@ -385,33 +688,41 @@ def __getitem__(self, index: int) -> Dict[str, Union[str, Tensor]]: """ item: Dict[str, Union[str, Tensor]] = {} - image_path = self.samples.image_path[index] - image = read_image(image_path) - + image = self._get_image(index) pre_processed = self.pre_process(image=image) - item = {"image": pre_processed["image"]} - - if self.split in ["val", "test"]: - label_index = self.samples.label_index[index] - - item["image_path"] = image_path - item["label"] = label_index - - if self.task == "segmentation": - mask_path = self.samples.mask_path[index] + item = { + "image": pre_processed["image"], + } + + if self.split not in (SPLIT_VALIDATION, SPLIT_TEST): + return item + + item.update( + { + "label": self.samples.label[index], + "image_path": self.samples.image_path[index], + } + ) - # Only Anomalous (1) images has masks in MVTec AD dataset. - # Therefore, create empty mask for Normal (0) images. - if label_index == 0: - mask = np.zeros(shape=image.shape[:2]) - else: - mask = cv2.imread(mask_path, flags=0) / 255.0 + if self.task != TASK_SEGMENTATION: + return item - pre_processed = self.pre_process(image=image, mask=mask) + # Only Anomalous (1) images has masks in MVTec LOCO AD dataset. + # Therefore, create empty mask for Normal (0) images. + if self.samples.label[index] == LABEL_NORMAL: + mask = np.zeros(shape=image.shape[:2]) # shape: (H, W, C) - item["mask_path"] = mask_path - item["image"] = pre_processed["image"] - item["mask"] = pre_processed["mask"] + else: + mask = self._get_mask(index) + + # TODO: ask how this works, does the transform re-apply the last call when mask is not None? + pre_processed = self.pre_process(image=image, mask=mask) + item.update( + { + "mask_path": self.samples.mask_path[index], + "mask": pre_processed["mask"], + } + ) return item @@ -420,33 +731,37 @@ def __getitem__(self, index: int) -> Dict[str, Union[str, Tensor]]: class MVTecLOCO(LightningDataModule): """MVTec LOCO AD Lightning Data Module.""" + # todo correct inconsistency: `transform_config_*val*` used for val and test set, but `*test*_batch_size` used for val and set + def __init__( self, root: str, category: str, - # TODO: Remove default values. IAAALD-211 + # TODO: add a parameter to specify the anomaly types and (more specifically) the anomaly classes -- "label" image_size: Optional[Union[int, Tuple[int, int]]] = None, train_batch_size: int = 32, test_batch_size: int = 32, num_workers: int = 8, - task: str = "segmentation", + task: str = TASK_SEGMENTATION, transform_config_train: Optional[Union[str, A.Compose]] = None, transform_config_val: Optional[Union[str, A.Compose]] = None, seed: Optional[int] = None, - create_validation_set: bool = False, + imread_strategy: str = IMREAD_STRATEGY_PRELOAD, ) -> None: super().__init__() + # todo? add input assertions/validations + assert task in TASKS, f"Task '{task}' is not supported. Supported tasks are {TASKS}" + assert ( + imread_strategy in IMREAD_STRATEGIES + ), f"Imread strategy '{imread_strategy}' is not supported. Supported imread strategies are {IMREAD_STRATEGIES}" + self.root = root if isinstance(root, Path) else Path(root) self.category = category - self.dataset_path = self.root / self.category self.transform_config_train = transform_config_train self.transform_config_val = transform_config_val self.image_size = image_size - if self.transform_config_train is not None and self.transform_config_val is None: - self.transform_config_val = self.transform_config_train - self.pre_process_train = PreProcessor(config=self.transform_config_train, image_size=self.image_size) self.pre_process_val = PreProcessor(config=self.transform_config_val, image_size=self.image_size) @@ -454,84 +769,90 @@ def __init__( self.test_batch_size = test_batch_size self.num_workers = num_workers - self.create_validation_set = create_validation_set self.task = task self.seed = seed + self.imread_strategy = imread_strategy self.train_data: Dataset self.test_data: Dataset - if create_validation_set: - self.val_data: Dataset + self.val_data: Dataset self.inference_data: Dataset + @property + def dataset_path(self) -> Path: + return self.root / self.category + def prepare_data(self) -> None: """Download the dataset if not available.""" - if (self.root / self.category).is_dir(): + + if self.dataset_path.is_dir(): logger.info("Found the dataset.") + else: self.root.mkdir(parents=True, exist_ok=True) - logger.info("Downloading the Mvtec AD dataset.") - url = "https://www.mydrive.ch/shares/38536/3830184030e49fe74747669442f0f282/download/420938113-1629952094" - dataset_name = "mvtec_anomaly_detection.tar.xz" + logger.info("Downloading the Mvtec LOCO AD dataset.") + URL_MVTEC_LOCO_TARGZ = "https://www.mydrive.ch/shares/48237/1b9106ccdfbb09a0c414bd49fe44a14a/download/430647091-1646842701/mvtec_loco_anomaly_detection.tar.xz" + dataset_name = "mvtec_loco_anomaly_detection.tar.xz" zip_filename = self.root / dataset_name - with DownloadProgressBar(unit="B", unit_scale=True, miniters=1, desc="MVTec AD") as progress_bar: + with DownloadProgressBar(unit="B", unit_scale=True, miniters=1, desc="MVTec LOCO download") as progress_bar: urlretrieve( - url=f"{url}/{dataset_name}", + url=URL_MVTEC_LOCO_TARGZ, filename=zip_filename, reporthook=progress_bar.update_to, ) + logger.info("Checking hash") - hash_check(zip_filename, "eefca59f2cede9c3fc5b6befbfec275e") + MD5HASH_MVTEC_LOCO = "d40f092ac6f88433f609583c4a05f56f" + hash_check(zip_filename, MD5HASH_MVTEC_LOCO) - logger.info("Extracting the dataset.") - with tarfile.open(zip_filename) as tar_file: - tar_file.extractall(self.root) + logger.info(f"Extracting the dataset.") + logger.debug(f"Extracting to {self.root}") + tar_extract_all(zip_filename, self.root) logger.info("Cleaning the tar file") - (zip_filename).unlink() + zip_filename.unlink() def setup(self, stage: Optional[str] = None) -> None: """Setup train, validation and test data. Args: - stage: Optional[str]: Train/Val/Test stages. (Default value = None) + stage: Optional[str]: fit/validate/test/predict stages. (Default value = None = fit) """ - logger.info("Setting up train, validation, test and prediction datasets.") - if stage in (None, "fit"): - self.train_data = MVTecDataset( + logger.info("Setting up {} dataset." % str(stage or TrainerFn.FITTING)) + + if stage in (None, TrainerFn.FITTING): + self.train_data = MVTecLOCODataset( root=self.root, category=self.category, + split=SPLIT_TRAIN, pre_process=self.pre_process_train, - split="train", task=self.task, - seed=self.seed, - create_validation_set=self.create_validation_set, + imread_strategy=self.imread_strategy, ) - if self.create_validation_set: - self.val_data = MVTecDataset( + if stage == TrainerFn.VALIDATING: + self.val_data = MVTecLOCODataset( root=self.root, category=self.category, pre_process=self.pre_process_val, - split="val", + split=SPLIT_VALIDATION, task=self.task, - seed=self.seed, - create_validation_set=self.create_validation_set, + imread_strategy=self.imread_strategy, ) - self.test_data = MVTecLOCODataset( - root=self.root, - category=self.category, - pre_process=self.pre_process_val, - split="test", - task=self.task, - seed=self.seed, - create_validation_set=self.create_validation_set, - ) + if stage == TrainerFn.TESTING: + self.test_data = MVTecLOCODataset( + root=self.root, + category=self.category, + pre_process=self.pre_process_val, + split=SPLIT_TEST, + task=self.task, + imread_strategy=self.imread_strategy, + ) - if stage == "predict": + if stage == TrainerFn.PREDICTING: self.inference_data = InferenceDataset( path=self.root, image_size=self.image_size, transform_config=self.transform_config_val ) @@ -542,8 +863,7 @@ def train_dataloader(self) -> TRAIN_DATALOADERS: def val_dataloader(self) -> EVAL_DATALOADERS: """Get validation dataloader.""" - dataset = self.val_data if self.create_validation_set else self.test_data - return DataLoader(dataset=dataset, shuffle=False, batch_size=self.test_batch_size, num_workers=self.num_workers) + return DataLoader(self.val_data, shuffle=False, batch_size=self.test_batch_size, num_workers=self.num_workers) def test_dataloader(self) -> EVAL_DATALOADERS: """Get test dataloader.""" @@ -554,3 +874,54 @@ def predict_dataloader(self) -> EVAL_DATALOADERS: return DataLoader( self.inference_data, shuffle=False, batch_size=self.test_batch_size, num_workers=self.num_workers ) + + +# TODO remove me +# debug _make_dataset in main +if __name__ == "__main__": + import itertools + + for cat in CATEGORIES: + _make_dataset(Path(f"/home/jcasagrandebertoldo/Downloads/loco/{cat}")) + + for cat, split_ in itertools.product(CATEGORIES, SPLITS): + pass + # _make_dataset(Path(f"/home/jcasagrandebertoldo/Downloads/loco/{cat}"), split_) + # next + # next + # next + # next + # next + # next + # next + # next + # test instantiate of the two classes + # then test with script + # next + # next + # next + # next + # next + # next + # next + # next + # next + # next + # next + # next + # next + # next + # next + # next + # next + # next + # next + # next + # next + # next + # next + # next + # next + # next + # next + # next diff --git a/anomalib/data/utils/__init__.py b/anomalib/data/utils/__init__.py index 53eb3f8ef2..bd3c7b488e 100644 --- a/anomalib/data/utils/__init__.py +++ b/anomalib/data/utils/__init__.py @@ -10,6 +10,7 @@ get_image_filenames, get_image_height_and_width, read_image, + read_mask, ) from .split import Split, ValSplitMode, concatenate_datasets, random_split @@ -20,6 +21,7 @@ "hash_check", "random_2d_perlin", "read_image", + "read_mask", "DownloadProgressBar", "random_split", "concatenate_datasets", diff --git a/anomalib/data/utils/download.py b/anomalib/data/utils/download.py index eefe001f44..b16a56834a 100644 --- a/anomalib/data/utils/download.py +++ b/anomalib/data/utils/download.py @@ -5,6 +5,7 @@ import hashlib import io +import tarfile from pathlib import Path from typing import Dict, Iterable, Optional, Union @@ -195,3 +196,15 @@ def hash_check(file_path: Path, expected_hash: str): assert ( hashlib.md5(hash_file.read()).hexdigest() == expected_hash ), f"Downloaded file {file_path} does not match the required hash." + + +def tar_extract_all(file_path: Path, output_dir: Path): + """Extract all files from a targz archive. + + Args: + file_path (Path): Path to archive. + output_dir (Path): Output directory. + """ + with tarfile.open(name=file_path) as tar_file: + for member in tqdm(iterable=tar_file.getmembers(), total=len(tar_file.getmembers())): + tar_file.extract(member=member, path=output_dir) diff --git a/anomalib/data/utils/image.py b/anomalib/data/utils/image.py index 758124f42e..ac472b77e6 100644 --- a/anomalib/data/utils/image.py +++ b/anomalib/data/utils/image.py @@ -208,6 +208,11 @@ def read_image(path: Union[str, Path], image_size: Optional[Union[int, Tuple]] = return image +def read_mask(mask_path: Union[str, Path]) -> np.ndarray: + """Read a mask image from disk and keep the original values.""" + return cv2.imread(str(mask_path), flags=cv2.IMREAD_GRAYSCALE) + + def pad_nextpow2(batch: Tensor) -> Tensor: """Compute required padding from input size and return padded images. From 478b2b993f561127b29d3c3ba08612b22d6f76b6 Mon Sep 17 00:00:00 2001 From: jpcbertoldo <24547377+jpcbertoldo@users.noreply.github.com> Date: Sat, 3 Sep 2022 23:13:58 +0200 Subject: [PATCH 33/38] pass pre-commit hooks --- anomalib/data/mvtec_loco.py | 154 ++++++++++++++++++++++-------------- 1 file changed, 93 insertions(+), 61 deletions(-) diff --git a/anomalib/data/mvtec_loco.py b/anomalib/data/mvtec_loco.py index 639ddc2f64..25d173992b 100644 --- a/anomalib/data/mvtec_loco.py +++ b/anomalib/data/mvtec_loco.py @@ -1,4 +1,26 @@ -""" +"""MVTec LOCO AD Dataset (CC BY-NC-SA 4.0). + +Description: + This script contains PyTorch Dataset, Dataloader and PyTorch + Lightning DataModule for the MVTec LOCO AD dataset. + + If the dataset is not on the file system, the script downloads and extracts the dataset. + +License: + MVTec LOCO AD dataset is released under the Creative Commons + Attribution-NonCommercial-ShareAlike 4.0 International License + (CC BY-NC-SA 4.0)(https://creativecommons.org/licenses/by-nc-sa/4.0/). + +Reference: + + - Paul Bergmann, Kilian Batzner, Michael Fauser, David Sattlegger, Carsten Steger: + Beyond Dents and Scratches: Logical Constraints in Unsupervised Anomaly Detection + and Localization; in: International Journal of Computer Vision, 2022, + DOI: 10.1007/s11263-022-01578-9. + + - https://www.mvtec.com/company/research/datasets/mvtec-loco + +################################################## distinguishes structural and logical anomalies n_image: 3644 @@ -35,8 +57,10 @@ objects are in a fixed position (mechanical alignment) illumination is well suited the access to images with real anomalies is limited (“impossible”) -images only show a single object or logically ensemble set of objects (i.e. one-class setting although a “class” here is a composed object) -no training annotations -- although it is assumed that the images in training are indeed from the target class (i.e. no noise) +images only show a single object or logically ensemble set of objects + (i.e. one-class setting although a “class” here is a composed object) +no training annotations -- although it is assumed that the images in training + are indeed from the target class (i.e. no noise) problem 1 (image-wise anomaly detection): “is there an anomaly in the image?” problem 2 (pixel-wise anomaly detection or anomaly segmentation): “which pixels belong to the anomaly?” pixel-wise metric: Saturated Per-Region Overlap (sPRO) @@ -116,7 +140,8 @@ 2: yellow 3: blue 5: red -the cable has to terminate in the same relative position on its two ends such that the whole construction exhibits a mirror symmetry +the cable has to terminate in the same relative position on its two ends such + that the whole construction exhibits a mirror symmetry examples of logical defects (left) the two splicing connectors do not have the same number of clamps (center) the color of the cable does not match the number of clamps @@ -127,7 +152,8 @@ missing objects the area in which the object could occur the saturation threshold is chosen to be equal to the area of the missing object -the saturation threshold for an object is chosen from the lower end of the distribution of its (manually annotated) area +the saturation threshold for an object is chosen from the lower end of + the distribution of its (manually annotated) area example (image): pushpin the missing pushpin can occur anywhere inside its compartment, therefore its entire area is annotated the saturation threshold is set to the size of a pushpin @@ -150,24 +176,23 @@ other logical constraints example (image, left): juice bottle the bottle is filled with orange juice but carries the label of the cherry juice -both the orange juice and the label with the cherry are present in the training set, but the logical anomaly arises due to the erroneous combination of the two in the same image -either the area filled with juice or the cherry as could be considered anomalous, therefore the union of the two regions is annotated -the saturation threshold is set to the area of the cherry because the segmentation of the cherry is sufficient to solve the anomaly localization +both the orange juice and the label with the cherry are present in the training set, + but the logical anomaly arises due to the erroneous combination of the two in the same image +either the area filled with juice or the cherry as could be considered anomalous, + therefore the union of the two regions is annotated +the saturation threshold is set to the area of the cherry because the + segmentation of the cherry is sufficient to solve the anomaly localization """ # TODO: clear module docstring import logging -import tarfile import warnings from pathlib import Path -from posixpath import split -from typing import Dict, List, Optional, Tuple, Union -from unicodedata import category +from typing import Dict, Optional, Tuple, Union from urllib.request import urlretrieve import albumentations as A -import cv2 import numpy as np import pandas as pd from numpy import ndarray @@ -187,7 +212,9 @@ from anomalib.pre_processing import PreProcessor # TODO: open discussion about keeping pre-resized tensors in the dataset folder -# TODO: create an issue in mvtec so the dataset will retain the information abou the anomaly type (label) so one can do per-label evaluation +# obs: mvtecad's doc says "...and create PyTorch data objects." but it does not!!! +# TODO: create an issue in mvtec so the dataset will retain the information abou +# the anomaly type (label) so one can do per-label evaluation # TODO: document the notion of label and superlabel logger = logging.getLogger(__name__) @@ -342,7 +369,8 @@ ) # this is given at the paper, each anomaly type (label) has a different gtvalue in the mask -# source: Beyond Dents and Scratches: Logical Constraints in Unsupervised Anomaly Detection and Localization (Bergmann, P. et al, 2022). +# source: Beyond Dents and Scratches: Logical Constraints in +# Unsupervised Anomaly Detection and Localization (Bergmann, P. et al, 2022). _MAP_ANOTYPE_2_GTVALUE: Dict[Tuple[str, str, str], int] = { (CATEGORY_BREAKFAST_BOX, SUPER_ANOTYPE_LOGICAL, ANOTYPE_BB_COMPARTMENTS_SWAPPED): 242, (CATEGORY_BREAKFAST_BOX, SUPER_ANOTYPE_LOGICAL, ANOTYPE_BB_MISSING_ALMONDS): 255, @@ -442,7 +470,8 @@ } # expected number of images in each category split so that we can check if the dataset is complete -# source: Beyond Dents and Scratches: Logical Constraints in Unsupervised Anomaly Detection and Localization (Bergmann, P. et al, 2022). +# source: Beyond Dents and Scratches: Logical Constraints +# in Unsupervised Anomaly Detection and Localization (Bergmann, P. et al, 2022). _EXPECTED_NSAMPLES: Dict[Tuple[str, str], int] = { (CATEGORY_BREAKFAST_BOX, SPLIT_TRAIN): 351, (CATEGORY_BREAKFAST_BOX, SPLIT_VALIDATION): 62, @@ -466,10 +495,11 @@ } -def _binarize_mask_float(mask: np.ndarray) -> np.ndarray: +def _binarize_mask_float(mask: np.ndarray) -> np.ndarray: # noqa """ the masks use different gtvalue values for the different anomaly types so the > 0 is making it binary - this operation is very simple but it is in a function to make sure its standard because it is used in different places + this operation is very simple but it is in a function to make + sure its standard because it is used in different places e.g. preloading while building the dataset and on the fly while training """ return (mask > 0).astype(float) @@ -479,22 +509,11 @@ def _make_dataset( path: Path, split: Optional[str] = None, imread_strategy: str = IMREAD_STRATEGY_PRELOAD, -) -> DataFrame: - # todo create optional to get a subset of anomlies in the test - - assert split is None or split in SPLITS, f"Invalid split: {split}" - assert imread_strategy in IMREAD_STRATEGIES, f"Invalid imread strategy: {imread_strategy}" - - category = path.resolve().name - assert category in CATEGORIES, f"Invalid path '{path}'. The directory ('{category}') must be one of {CATEGORIES}" - - if split is None: - return pd.concat([_make_dataset(path, split_, imread_strategy) for split_ in SPLITS], axis=0) - - logger.info(f"Creating MVTec LOCO AD dataset for category '{category}' split '{split}'") - +) -> DataFrame: # noqa D212 """ - structure of the files in the dataset ("/" is 'path') + Find the images in the given path and create a DataFrame with all the information from each sample. + + Expected structure of the files in the dataset ("/" is 'path') images: /{split}/{super_anotype}/{image_index}.png @@ -517,6 +536,18 @@ def _make_dataset( /ground_truth/structural_anomalies/.../000.png ... """ + # todo create optional to get a subset of anomlies in the test + + assert split is None or split in SPLITS, f"Invalid split: {split}" + assert imread_strategy in IMREAD_STRATEGIES, f"Invalid imread strategy: {imread_strategy}" + + category = path.resolve().name + assert category in CATEGORIES, f"Invalid path '{path}'. The directory ('{category}') must be one of {CATEGORIES}" + + if split is None: + return pd.concat([_make_dataset(path, split_, imread_strategy) for split_ in SPLITS], axis=0) + + logger.info("Creating MVTec LOCO AD dataset for category '%s' split '%s'", category, split) # these values look like "(train|validation|test)/(good|logical_anomalies|structural_anomalies)/(000|...|n).png" # where (a|b) means either a or b @@ -525,7 +556,8 @@ def _make_dataset( if len(samples_paths) != expected_nsamples: warnings.warn( - f"Expected {expected_nsamples} samples for split '{split}' in category '{category}' but found {len(samples_paths)}." + f"Expected {expected_nsamples} samples for split '{split}' " + "in category '{category}' but found {len(samples_paths)}." "Is the dataset corrupted?" ) @@ -549,7 +581,7 @@ def build_record(sample_path: Path): ) return ret - elif super_anotype in (SUPER_ANOTYPE_LOGICAL, SUPER_ANOTYPE_STRUCTURAL): + if super_anotype in (SUPER_ANOTYPE_LOGICAL, SUPER_ANOTYPE_STRUCTURAL): mask_path: Path = path / "ground_truth" / super_anotype / sample_path.stem / "000.png" @@ -573,11 +605,8 @@ def build_record(sample_path: Path): return ret - else: - # there should only be the folders "good", "logical_anomalies" and "structural_anomalies" - raise RuntimeError( - f"Something wrong in the dataset folder. Unknown folder {super_anotype}, path={sample_path}" - ) + # there should only be the folders "good", "logical_anomalies" and "structural_anomalies" + raise RuntimeError(f"Something wrong in the dataset folder. Unknown folder {super_anotype}, path={sample_path}") samples = pd.DataFrame.from_records([build_record(sp) for sp in samples_paths]) @@ -589,10 +618,10 @@ def build_record(sample_path: Path): # stacklevel=3 # ) - logger.debug(f"Preloading images into memory") + logger.debug("Preloading images into memory") samples["image"] = samples.image_path.map(read_image) - logger.debug(f"Preloading masks into memory") + logger.debug("Preloading masks into memory") # this is used to select the rows in the dataframe has_mask = ~samples.mask_path.isnull() @@ -634,14 +663,14 @@ def __init__( self.imread_strategy = imread_strategy self.samples = _make_dataset( - path=self.dataset_path, + path=self.category_dataset_path, split=self.split, imread_strategy=self.imread_strategy, ) @property - def dataset_path(self) -> Path: - """Path to the dataset folder.""" + def category_dataset_path(self) -> Path: + """Path to the category dataset (root/category) folder.""" return self.root / self.category def __len__(self) -> int: @@ -654,11 +683,10 @@ def _get_image(self, index: int) -> ndarray: if self.imread_strategy == IMREAD_STRATEGY_PRELOAD: return self.samples.image[index] - elif self.imread_strategy == IMREAD_STRATEGY_ONTHEFLY: + if self.imread_strategy == IMREAD_STRATEGY_ONTHEFLY: return read_image(self.samples.image_path[index]) - else: - raise NotImplementedError(f"Imread strategy '{self.imread_strategy}' is not supported.") + raise NotImplementedError(f"Imread strategy '{self.imread_strategy}' is not supported.") def _get_mask(self, index: int) -> ndarray: """Get mask at index.""" @@ -666,11 +694,10 @@ def _get_mask(self, index: int) -> ndarray: if self.imread_strategy == IMREAD_STRATEGY_PRELOAD: return self.samples.mask[index] - elif self.imread_strategy == IMREAD_STRATEGY_ONTHEFLY: + if self.imread_strategy == IMREAD_STRATEGY_ONTHEFLY: return _binarize_mask_float(read_mask(self.samples.mask_path[index])) - else: - raise NotImplementedError(f"Imread strategy '{self.imread_strategy}' is not supported.") + raise NotImplementedError(f"Imread strategy '{self.imread_strategy}' is not supported.") def __getitem__(self, index: int) -> Dict[str, Union[str, Tensor]]: """Get dataset item for the index ``index``. @@ -731,7 +758,8 @@ def __getitem__(self, index: int) -> Dict[str, Union[str, Tensor]]: class MVTecLOCO(LightningDataModule): """MVTec LOCO AD Lightning Data Module.""" - # todo correct inconsistency: `transform_config_*val*` used for val and test set, but `*test*_batch_size` used for val and set + # todo correct inconsistency: `transform_config_*val*` used for val and + # test set, but `*test*_batch_size` used for val and set def __init__( self, @@ -779,35 +807,38 @@ def __init__( self.inference_data: Dataset @property - def dataset_path(self) -> Path: + def category_dataset_path(self) -> Path: + """Path to the category dataset (root/category) folder.""" return self.root / self.category def prepare_data(self) -> None: """Download the dataset if not available.""" - if self.dataset_path.is_dir(): + if self.category_dataset_path.is_dir(): logger.info("Found the dataset.") else: self.root.mkdir(parents=True, exist_ok=True) logger.info("Downloading the Mvtec LOCO AD dataset.") - URL_MVTEC_LOCO_TARGZ = "https://www.mydrive.ch/shares/48237/1b9106ccdfbb09a0c414bd49fe44a14a/download/430647091-1646842701/mvtec_loco_anomaly_detection.tar.xz" + # flake8: noqa: E501 + # pylint: disable=line-too-long + url_mvtec_loco_targz = "https://www.mydrive.ch/shares/48237/1b9106ccdfbb09a0c414bd49fe44a14a/download/430647091-1646842701/mvtec_loco_anomaly_detection.tar.xz" dataset_name = "mvtec_loco_anomaly_detection.tar.xz" zip_filename = self.root / dataset_name with DownloadProgressBar(unit="B", unit_scale=True, miniters=1, desc="MVTec LOCO download") as progress_bar: urlretrieve( - url=URL_MVTEC_LOCO_TARGZ, + url=url_mvtec_loco_targz, filename=zip_filename, reporthook=progress_bar.update_to, ) logger.info("Checking hash") - MD5HASH_MVTEC_LOCO = "d40f092ac6f88433f609583c4a05f56f" - hash_check(zip_filename, MD5HASH_MVTEC_LOCO) + md5hash_mvtec_loco = "d40f092ac6f88433f609583c4a05f56f" + hash_check(zip_filename, md5hash_mvtec_loco) - logger.info(f"Extracting the dataset.") - logger.debug(f"Extracting to {self.root}") + logger.info("Extracting the dataset.") + logger.debug("Extracting to %s", self.root) tar_extract_all(zip_filename, self.root) logger.info("Cleaning the tar file") @@ -820,7 +851,8 @@ def setup(self, stage: Optional[str] = None) -> None: stage: Optional[str]: fit/validate/test/predict stages. (Default value = None = fit) """ - logger.info("Setting up {} dataset." % str(stage or TrainerFn.FITTING)) + # pylint: disable=consider-using-f-string + logger.info("Setting up %s dataset." % stage or TrainerFn.FITTING) if stage in (None, TrainerFn.FITTING): self.train_data = MVTecLOCODataset( From 7b9c096250b2e43c1f6212b77e506023514fcec7 Mon Sep 17 00:00:00 2001 From: jpcbertoldo <24547377+jpcbertoldo@users.noreply.github.com> Date: Sat, 3 Sep 2022 23:49:48 +0200 Subject: [PATCH 34/38] remove todos and correct a const --- anomalib/data/mvtec_loco.py | 185 +----------------------------------- 1 file changed, 5 insertions(+), 180 deletions(-) diff --git a/anomalib/data/mvtec_loco.py b/anomalib/data/mvtec_loco.py index 25d173992b..98e13ef8e0 100644 --- a/anomalib/data/mvtec_loco.py +++ b/anomalib/data/mvtec_loco.py @@ -19,173 +19,8 @@ DOI: 10.1007/s11263-022-01578-9. - https://www.mvtec.com/company/research/datasets/mvtec-loco - -################################################## - -distinguishes structural and logical anomalies -n_image: 3644 -splits: -no overlap and fixed -train -normal-only -n_image_train: 1772 -validation -normal-only -n_image_validation: 304 -test -normal + anomalous (structural and logical) -n_image_test: 1568 -n_category: 5 -breakfast_box -juice_bottle -pushpins -screw_bag -splicing_connectors -n_defect_type: 89 - -################################################## - -configs -https://docs.google.com/spreadsheets/d/1qHbyTsU2At1fusQsV8KH3_qdp3SYHHLy7QtpohKJIk0/edit?usp=sharing - -stats overview -https://docs.google.com/spreadsheets/d/11GSf1SVsHFYDSwMAULEd7g5QK7P3Y21YMB10D_g0-Gk/edit?usp=sharing - -################################################## - -assumptions -objects are in a fixed position (mechanical alignment) -illumination is well suited -the access to images with real anomalies is limited (“impossible”) -images only show a single object or logically ensemble set of objects - (i.e. one-class setting although a “class” here is a composed object) -no training annotations -- although it is assumed that the images in training - are indeed from the target class (i.e. no noise) -problem 1 (image-wise anomaly detection): “is there an anomaly in the image?” -problem 2 (pixel-wise anomaly detection or anomaly segmentation): “which pixels belong to the anomaly?” -pixel-wise metric: Saturated Per-Region Overlap (sPRO) -structural anomaly pixel annotation policy -defects are confined to local regions -each pixel that introduces a visual structure that is not present in the anomaly-free images is anomalous -logical anomaly pixel annotation policy -the union of all areas of the image that could be the cause for the anomaly is anomalous -a method is not necessarily required to predict the whole ground truth area as anomalous - -################################################## - -breakfast box -n_anomaly_type (n_structural, n_logical): 22 (5, 17) -logical constraints -contains 2 tangerines -contains 1 nectarine -the tangerines and the nectarine on the left -cereals (C) and a mix of banana chips and almonds (B&A) on the right -the ratio between C and B&A is fixed -the relative position of C and B&A is fixed -examples of logical defects -too many banana chips and almonds - -################################################## - -juice bottle -n_anomaly_type (n_structural, n_logical): 18 (7, 11) -logical constraints -there is 1 bottle -the bottle is filled with a liquid and the fill level is always the same -the liquid is of 1 out of 3 colors (red, yellow, white-ish) -the bottle carries 2 labels -the first label is attached to the center of the bottle -the first label displays an icon that determines the type of liquid (cherry, orange, banana) -cherry: red -orange: yellow -banana: white-ish -the second label is attached to the lower part of the bottle -the second label contains the text “100% Juice” -examples of logical defects -(left) the icon does not match the type of juice -(middle) the icon is slightly misplaced -(right) the fill level is too high - -################################################## - -pushpins -n_anomaly_type (n_structural, n_logical): 8 (4, 4) -logical constraints -each compartment contains 1 pushpin -examples of logical defects -1 compartment has a missing pin - -################################################## - -screw bag -n_anomaly_type (n_structural, n_logical): 20 (4, 16) -logical constraints -the bag contains -2 washers -2 nuts -1 long screw -2 short screw -examples of logical defects -two long screws and lacks a short one - -################################################## - -splicing connectors -n_anomaly_type (n_structural, n_logical): 21 (8, 13) -logical constraints -there are 2 splicing connectors -they have the same number of cable clamps -they are linked by 1 cable -the number of clamps has a one-to-one correspondence to the color of the cable -2: yellow -3: blue -5: red -the cable has to terminate in the same relative position on its two ends such - that the whole construction exhibits a mirror symmetry -examples of logical defects -(left) the two splicing connectors do not have the same number of clamps -(center) the color of the cable does not match the number of clamps -(right) the cable terminates in different positions - -################################################## - -missing objects -the area in which the object could occur -the saturation threshold is chosen to be equal to the area of the missing object -the saturation threshold for an object is chosen from the lower end of - the distribution of its (manually annotated) area -example (image): pushpin -the missing pushpin can occur anywhere inside its compartment, therefore its entire area is annotated -the saturation threshold is set to the size of a pushpin - -################################################## - -additional objects -too many instances of an object: all instances of the object are annotated -the saturation threshold is set to the area of the extraneous objects -example (image): splicing connectors -an additional cable is present between the two splicing connectors -it is not clear which of the two cables represents the anomaly, therefore both are annotated -the saturation threshold is set to the area of one cable (i.e., half of the annotated region) -properties -a method can obtain a perfect score even if it only marks one of the two cables as an anomaly -a method that marks both is neither penalized nor (extra-)rewarded - -################################################## - -other logical constraints -example (image, left): juice bottle -the bottle is filled with orange juice but carries the label of the cherry juice -both the orange juice and the label with the cherry are present in the training set, - but the logical anomaly arises due to the erroneous combination of the two in the same image -either the area filled with juice or the cherry as could be considered anomalous, - therefore the union of the two regions is annotated -the saturation threshold is set to the area of the cherry because the - segmentation of the cherry is sufficient to solve the anomaly localization """ -# TODO: clear module docstring - import logging import warnings from pathlib import Path @@ -211,17 +46,12 @@ from anomalib.data.utils.download import tar_extract_all from anomalib.pre_processing import PreProcessor -# TODO: open discussion about keeping pre-resized tensors in the dataset folder -# obs: mvtecad's doc says "...and create PyTorch data objects." but it does not!!! -# TODO: create an issue in mvtec so the dataset will retain the information abou -# the anomaly type (label) so one can do per-label evaluation -# TODO: document the notion of label and superlabel - logger = logging.getLogger(__name__) +TASK_CLASSIFICATION = "classification" TASK_SEGMENTATION = "segmentation" -TASKS = TASK_SEGMENTATION +TASKS = (TASK_CLASSIFICATION, TASK_SEGMENTATION) SPLIT_TRAIN = "train" # "validation" instead of "val" is an explicit choice because this will match the name of the folder @@ -234,7 +64,6 @@ # all images are pre-loaded into memory at initialization IMREAD_STRATEGY_PRELOAD = "preload" IMREAD_STRATEGIES = (IMREAD_STRATEGY_ONTHEFLY, IMREAD_STRATEGY_PRELOAD) -# TODO make a strategy preload_tensor CATEGORY_BREAKFAST_BOX = "breakfast_box" CATEGORY_JUICE_BOTTLE = "juice_bottle" @@ -486,7 +315,6 @@ (CATEGORY_SCREW_BAG, SPLIT_VALIDATION): 60, (CATEGORY_SCREW_BAG, SPLIT_TEST): 341, # these two below were wrong in the paper - # TODO send a correction to the authors # (CATEGORY_SPLICING_CONNECTORS, SPLIT_TRAIN): 354, # (CATEGORY_SPLICING_CONNECTORS, SPLIT_VALIDATION): 59, (CATEGORY_SPLICING_CONNECTORS, SPLIT_TRAIN): 360, @@ -702,10 +530,6 @@ def _get_mask(self, index: int) -> ndarray: def __getitem__(self, index: int) -> Dict[str, Union[str, Tensor]]: """Get dataset item for the index ``index``. - TODO: include the label string - TODO: include the label anomaly type (strutural, logical) - TODO: here? probably better to separate it... return the sPRO saturation value - Args: index (int): Index to get the item. @@ -728,6 +552,8 @@ def __getitem__(self, index: int) -> Dict[str, Union[str, Tensor]]: { "label": self.samples.label[index], "image_path": self.samples.image_path[index], + "anotype": self.samples.anotype[index], + "super_anotype": self.samples.super_anotype[index], } ) @@ -742,7 +568,6 @@ def __getitem__(self, index: int) -> Dict[str, Union[str, Tensor]]: else: mask = self._get_mask(index) - # TODO: ask how this works, does the transform re-apply the last call when mask is not None? pre_processed = self.pre_process(image=image, mask=mask) item.update( { @@ -765,7 +590,7 @@ def __init__( self, root: str, category: str, - # TODO: add a parameter to specify the anomaly types and (more specifically) the anomaly classes -- "label" + # TODO: add a parameter to specify the anomaly types and (more specifically) image_size: Optional[Union[int, Tuple[int, int]]] = None, train_batch_size: int = 32, test_batch_size: int = 32, From 1ecffc57be7f1249093d4dc2b78f18da6c52af47 Mon Sep 17 00:00:00 2001 From: jpcbertoldo <24547377+jpcbertoldo@users.noreply.github.com> Date: Sat, 3 Sep 2022 23:51:33 +0200 Subject: [PATCH 35/38] remove todos and correct a const --- anomalib/data/mvtec_loco.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/anomalib/data/mvtec_loco.py b/anomalib/data/mvtec_loco.py index 98e13ef8e0..63a9fd9c89 100644 --- a/anomalib/data/mvtec_loco.py +++ b/anomalib/data/mvtec_loco.py @@ -440,11 +440,11 @@ def build_record(sample_path: Path): if imread_strategy == IMREAD_STRATEGY_PRELOAD: - # warnings.warn( - # "Preloading images into memory. " - # "If your dataset is too large, consider using another imread_strategy instead.", - # stacklevel=3 - # ) + warnings.warn( + "Preloading images into memory. " + "If your dataset is too large, consider using another imread_strategy instead.", + stacklevel=3, + ) logger.debug("Preloading images into memory") samples["image"] = samples.image_path.map(read_image) From c4c44630e749a4b9d124ee5f80a70a5c1be3f1cf Mon Sep 17 00:00:00 2001 From: jpcbertoldo <24547377+jpcbertoldo@users.noreply.github.com> Date: Sun, 4 Sep 2022 14:48:15 +0200 Subject: [PATCH 36/38] create notebook and make small corrections --- anomalib/data/__init__.py | 15 + anomalib/data/mvtec_loco.py | 287 ++++++++----- .../100_datamodules/104_mvtec_loco.ipynb | 394 ++++++++++++++++++ 3 files changed, 587 insertions(+), 109 deletions(-) create mode 100644 notebooks/100_datamodules/104_mvtec_loco.ipynb diff --git a/anomalib/data/__init__.py b/anomalib/data/__init__.py index 55cdd7aa11..2e3f6d2cef 100644 --- a/anomalib/data/__init__.py +++ b/anomalib/data/__init__.py @@ -13,6 +13,7 @@ from .folder import Folder from .inference import InferenceDataset from .mvtec import MVTec +from .mvtec_loco import MVTecLOCO logger = logging.getLogger(__name__) @@ -74,6 +75,19 @@ def get_datamodule(config: Union[DictConfig, ListConfig]) -> AnomalibDataModule: transform_config_eval=config.dataset.transform_config.eval, val_split_mode=config.dataset.val_split_mode, ) + elif config.dataset.format.lower() == "mvtec_loco": + datamodule = MVTecLOCO( + root=config.dataset.path, + category=config.dataset.category, + image_size=(config.dataset.image_size[0], config.dataset.image_size[1]), + train_batch_size=config.dataset.train_batch_size, + test_batch_size=config.dataset.test_batch_size, + num_workers=config.dataset.num_workers, + task=config.dataset.task, + transform_config_train=config.dataset.transform_config.train, + transform_config_val=config.dataset.transform_config.val, + imread_strategy=config.dataset.imread_strategy, + ) else: raise ValueError( "Unknown dataset! \n" @@ -92,4 +106,5 @@ def get_datamodule(config: Union[DictConfig, ListConfig]) -> AnomalibDataModule: "Folder", "InferenceDataset", "MVTec", + "MVTecLOCO", ] diff --git a/anomalib/data/mvtec_loco.py b/anomalib/data/mvtec_loco.py index 63a9fd9c89..5280cf470e 100644 --- a/anomalib/data/mvtec_loco.py +++ b/anomalib/data/mvtec_loco.py @@ -59,11 +59,14 @@ SPLIT_TEST = "test" SPLITS = (SPLIT_TRAIN, SPLIT_VALIDATION, SPLIT_TEST) -# each image is read upon demand IMREAD_STRATEGY_ONTHEFLY = "onthefly" -# all images are pre-loaded into memory at initialization +"""Images are read into memory upon demand (no cache).""" + IMREAD_STRATEGY_PRELOAD = "preload" +"""All images are read into memory at initialization.""" + IMREAD_STRATEGIES = (IMREAD_STRATEGY_ONTHEFLY, IMREAD_STRATEGY_PRELOAD) +"""Options of strategies to read images into memory.""" CATEGORY_BREAKFAST_BOX = "breakfast_box" CATEGORY_JUICE_BOTTLE = "juice_bottle" @@ -333,9 +336,46 @@ def _binarize_mask_float(mask: np.ndarray) -> np.ndarray: # noqa return (mask > 0).astype(float) +def download_and_extract_mvtec_loco(root: Union[str, Path]) -> None: + """Download and extract the MVTec LOCO dataset to the given root directory. + + Args: + root (Union[str, Path]): directory where the dataset will be stored. + + """ + root = Path(root) + root.mkdir(parents=True, exist_ok=True) + + logger.info("Downloading the Mvtec LOCO AD dataset.") + + # flake8: noqa: E501 + # pylint: disable=line-too-long + url_mvtec_loco_targz = "https://www.mydrive.ch/shares/48237/1b9106ccdfbb09a0c414bd49fe44a14a/download/430647091-1646842701/mvtec_loco_anomaly_detection.tar.xz" + + dataset_name = "mvtec_loco_anomaly_detection.tar.xz" + zip_filename = root / dataset_name + with DownloadProgressBar(unit="B", unit_scale=True, miniters=1, desc="MVTec LOCO download") as progress_bar: + urlretrieve( + url=url_mvtec_loco_targz, + filename=zip_filename, + reporthook=progress_bar.update_to, + ) + + logger.info("Checking hash") + md5hash_mvtec_loco = "d40f092ac6f88433f609583c4a05f56f" + hash_check(zip_filename, md5hash_mvtec_loco) + + logger.info("Extracting the dataset.") + logger.debug("Extracting to %s", root) + tar_extract_all(zip_filename, root) + + logger.info("Cleaning the tar file") + zip_filename.unlink() + + def _make_dataset( path: Path, - split: Optional[str] = None, + split: str, imread_strategy: str = IMREAD_STRATEGY_PRELOAD, ) -> DataFrame: # noqa D212 """ @@ -364,16 +404,12 @@ def _make_dataset( /ground_truth/structural_anomalies/.../000.png ... """ - # todo create optional to get a subset of anomlies in the test - assert split is None or split in SPLITS, f"Invalid split: {split}" + assert split in SPLITS, f"Invalid split: {split}" assert imread_strategy in IMREAD_STRATEGIES, f"Invalid imread strategy: {imread_strategy}" category = path.resolve().name - assert category in CATEGORIES, f"Invalid path '{path}'. The directory ('{category}') must be one of {CATEGORIES}" - - if split is None: - return pd.concat([_make_dataset(path, split_, imread_strategy) for split_ in SPLITS], axis=0) + assert category in CATEGORIES, f"Invalid path '{path}'. The category '{category}' must be one of {CATEGORIES}" logger.info("Creating MVTec LOCO AD dataset for category '%s' split '%s'", category, split) @@ -382,10 +418,12 @@ def _make_dataset( samples_paths = sorted(path.glob(f"{split}/**/*.png")) expected_nsamples = _EXPECTED_NSAMPLES[(category, split)] + assert len(samples_paths) > 0, f"No samples found in {path}" + if len(samples_paths) != expected_nsamples: warnings.warn( f"Expected {expected_nsamples} samples for split '{split}' " - "in category '{category}' but found {len(samples_paths)}." + f"in category '{category}' but found {len(samples_paths)}." "Is the dataset corrupted?" ) @@ -447,12 +485,12 @@ def build_record(sample_path: Path): ) logger.debug("Preloading images into memory") - samples["image"] = samples.image_path.map(read_image) + samples["image"] = samples["image_path"].map(read_image) logger.debug("Preloading masks into memory") # this is used to select the rows in the dataframe - has_mask = ~samples.mask_path.isnull() + has_mask = ~samples["mask_path"].isnull() samples.loc[has_mask, "mask"] = samples.loc[has_mask, "mask_path"].map( lambda x: _binarize_mask_float(read_mask(x)) @@ -469,12 +507,31 @@ def __init__( self, root: Union[Path, str], category: str, - pre_process: PreProcessor, split: str, + pre_process: PreProcessor, task: str = TASK_SEGMENTATION, - # create_validation_set: bool = False, imread_strategy: str = IMREAD_STRATEGY_PRELOAD, ) -> None: + """Mvtec LOCO AD Dataset class. + + Args: + root: Path to the MVTec LOCO AD dataset root folder. + category: Name of the MVTec LOCO AD category (there are 5). + See ``anomalib.data.mvtec_loco.CATEGORIES``. + split: 'train', 'validation' or 'test' + See anomalib.data.mvtec_loco.SPLITS. + pre_process: List of pre_processing object containing albumentation compose or config. + task: ``classification`` or ``segmentation`` + Default: ``segmentation`` + ``anomalib.data.mvtec_loco.TASKS``. + imread_strategy: When should images be read into memory? + Default: ``preload`` + See ``anomalib.data.mvtec_loco.IMREAD_STRATEGIES``. + + TODO add link + See examples in the repository ``anomalib/notebooks/100_datamodules/104_mvtec_loco.ipynb``. + """ + super().__init__(root) assert split in SPLITS, f"Split '{split}' is not supported. Supported splits are {SPLITS}" @@ -509,10 +566,10 @@ def _get_image(self, index: int) -> ndarray: """Get image at index.""" if self.imread_strategy == IMREAD_STRATEGY_PRELOAD: - return self.samples.image[index] + return self.samples.iloc[index]["image"] if self.imread_strategy == IMREAD_STRATEGY_ONTHEFLY: - return read_image(self.samples.image_path[index]) + return read_image(self.samples.iloc[index]["image_path"]) raise NotImplementedError(f"Imread strategy '{self.imread_strategy}' is not supported.") @@ -520,14 +577,14 @@ def _get_mask(self, index: int) -> ndarray: """Get mask at index.""" if self.imread_strategy == IMREAD_STRATEGY_PRELOAD: - return self.samples.mask[index] + return self.samples.iloc[index]["mask"] if self.imread_strategy == IMREAD_STRATEGY_ONTHEFLY: - return _binarize_mask_float(read_mask(self.samples.mask_path[index])) + return _binarize_mask_float(read_mask(self.samples.iloc[index]["mask_path"])) raise NotImplementedError(f"Imread strategy '{self.imread_strategy}' is not supported.") - def __getitem__(self, index: int) -> Dict[str, Union[str, Tensor]]: + def __getitem__(self, index: int) -> Union[Dict[str, Tensor], Dict[str, Union[str, Tensor, int]]]: """Get dataset item for the index ``index``. Args: @@ -535,9 +592,10 @@ def __getitem__(self, index: int) -> Dict[str, Union[str, Tensor]]: Returns: Union[Dict[str, Tensor], Dict[str, Union[str, Tensor]]]: Dict of image tensor during training. - Otherwise, Dict containing image path, target path, image tensor, label and transformed bounding box. + Otherwise, Dict containing image path, image tensor, label, anomaly type and, + if it is segmentation task, mask path and mask tensor. """ - item: Dict[str, Union[str, Tensor]] = {} + item: Dict[str, Union[str, Tensor, int]] = {} image = self._get_image(index) pre_processed = self.pre_process(image=image) @@ -550,10 +608,10 @@ def __getitem__(self, index: int) -> Dict[str, Union[str, Tensor]]: item.update( { - "label": self.samples.label[index], - "image_path": self.samples.image_path[index], - "anotype": self.samples.anotype[index], - "super_anotype": self.samples.super_anotype[index], + "label": self.samples.iloc[index]["label"], + "image_path": str(self.samples.iloc[index]["image_path"]), + "anotype": self.samples.iloc[index]["anotype"], + "super_anotype": self.samples.iloc[index]["super_anotype"], } ) @@ -562,7 +620,7 @@ def __getitem__(self, index: int) -> Dict[str, Union[str, Tensor]]: # Only Anomalous (1) images has masks in MVTec LOCO AD dataset. # Therefore, create empty mask for Normal (0) images. - if self.samples.label[index] == LABEL_NORMAL: + if self.samples.iloc[index]["label"] == LABEL_NORMAL: mask = np.zeros(shape=image.shape[:2]) # shape: (H, W, C) else: @@ -571,7 +629,7 @@ def __getitem__(self, index: int) -> Dict[str, Union[str, Tensor]]: pre_processed = self.pre_process(image=image, mask=mask) item.update( { - "mask_path": self.samples.mask_path[index], + "mask_path": str(self.samples.iloc[index]["mask_path"]), "mask": pre_processed["mask"], } ) @@ -590,20 +648,47 @@ def __init__( self, root: str, category: str, - # TODO: add a parameter to specify the anomaly types and (more specifically) + task: str = TASK_SEGMENTATION, + imread_strategy: str = IMREAD_STRATEGY_PRELOAD, image_size: Optional[Union[int, Tuple[int, int]]] = None, - train_batch_size: int = 32, - test_batch_size: int = 32, num_workers: int = 8, - task: str = TASK_SEGMENTATION, + train_batch_size: int = 32, transform_config_train: Optional[Union[str, A.Compose]] = None, + test_batch_size: int = 32, transform_config_val: Optional[Union[str, A.Compose]] = None, - seed: Optional[int] = None, - imread_strategy: str = IMREAD_STRATEGY_PRELOAD, + # TODO: add a parameter to specify the anomaly types and (more specifically) ) -> None: + """Mvtec LOCO AD Lightning Data Module. + + Args: + root: Path to the MVTec LOCO AD dataset root folder. + category: Name of the MVTec LOCO AD category (there are 5). + See ``anomalib.data.mvtec_loco.CATEGORIES``. + task: ``classification`` or ``segmentation`` + Default: ``segmentation`` + See ``anomalib.data.mvtec_loco.TASKS``. + imread_strategy: When should images be read into memory? + Default: ``preload`` + See ``anomalib.data.mvtec_loco.IMREAD_STRATEGIES``. + image_size: Images are resized to `image_size` (HEIGHT, WIDTH), or (SIZE, SIZE) if a single value is given. + num_workers: Number of workers. + train_batch_size: Training batch size. + transform_config_train: List of pre_processing object containing albumentation compose or + config applied during training. + test_batch_size: Testing batch size. + transform_config_val: List of pre_processing object containing albumentation compose or + config applied during validation. + + TODO add link + See examples in the repository ``anomalib/notebooks/100_datamodules/104_mvtec_loco.ipynb``. + """ super().__init__() - # todo? add input assertions/validations + # TODO create option to get a subset of anomalies in the test + # TODO the images are not squared here, maybe we should add warn the user if + # the ration from image_size is too different from the original image when + # the image size is given as an int + assert task in TASKS, f"Task '{task}' is not supported. Supported tasks are {TASKS}" assert ( imread_strategy in IMREAD_STRATEGIES @@ -623,7 +708,6 @@ def __init__( self.num_workers = num_workers self.task = task - self.seed = seed self.imread_strategy = imread_strategy self.train_data: Dataset @@ -643,31 +727,7 @@ def prepare_data(self) -> None: logger.info("Found the dataset.") else: - self.root.mkdir(parents=True, exist_ok=True) - - logger.info("Downloading the Mvtec LOCO AD dataset.") - # flake8: noqa: E501 - # pylint: disable=line-too-long - url_mvtec_loco_targz = "https://www.mydrive.ch/shares/48237/1b9106ccdfbb09a0c414bd49fe44a14a/download/430647091-1646842701/mvtec_loco_anomaly_detection.tar.xz" - dataset_name = "mvtec_loco_anomaly_detection.tar.xz" - zip_filename = self.root / dataset_name - with DownloadProgressBar(unit="B", unit_scale=True, miniters=1, desc="MVTec LOCO download") as progress_bar: - urlretrieve( - url=url_mvtec_loco_targz, - filename=zip_filename, - reporthook=progress_bar.update_to, - ) - - logger.info("Checking hash") - md5hash_mvtec_loco = "d40f092ac6f88433f609583c4a05f56f" - hash_check(zip_filename, md5hash_mvtec_loco) - - logger.info("Extracting the dataset.") - logger.debug("Extracting to %s", self.root) - tar_extract_all(zip_filename, self.root) - - logger.info("Cleaning the tar file") - zip_filename.unlink() + download_and_extract_mvtec_loco(self.root) def setup(self, stage: Optional[str] = None) -> None: """Setup train, validation and test data. @@ -680,6 +740,11 @@ def setup(self, stage: Optional[str] = None) -> None: logger.info("Setting up %s dataset." % stage or TrainerFn.FITTING) if stage in (None, TrainerFn.FITTING): + + if hasattr(self, "train_data"): + logger.debug("Train data already exists. Skipping setup.") + return + self.train_data = MVTecLOCODataset( root=self.root, category=self.category, @@ -690,6 +755,11 @@ def setup(self, stage: Optional[str] = None) -> None: ) if stage == TrainerFn.VALIDATING: + + if hasattr(self, "val_data"): + logger.debug("Validation data already exists. Skipping setup.") + return + self.val_data = MVTecLOCODataset( root=self.root, category=self.category, @@ -700,6 +770,11 @@ def setup(self, stage: Optional[str] = None) -> None: ) if stage == TrainerFn.TESTING: + + if hasattr(self, "test_data"): + logger.debug("Test data already exists. Skipping setup.") + return + self.test_data = MVTecLOCODataset( root=self.root, category=self.category, @@ -710,75 +785,69 @@ def setup(self, stage: Optional[str] = None) -> None: ) if stage == TrainerFn.PREDICTING: + + if hasattr(self, "inference_data"): + logger.debug("Inference data already exists. Skipping setup.") + return + self.inference_data = InferenceDataset( path=self.root, image_size=self.image_size, transform_config=self.transform_config_val ) def train_dataloader(self) -> TRAIN_DATALOADERS: """Get train dataloader.""" + if not hasattr(self, "train_data"): + raise RuntimeError("Train data not setup. Did you run `datamodule.setup('fit')`?") return DataLoader(self.train_data, shuffle=True, batch_size=self.train_batch_size, num_workers=self.num_workers) def val_dataloader(self) -> EVAL_DATALOADERS: """Get validation dataloader.""" + if not hasattr(self, "val_data"): + raise RuntimeError("Validation data not setup. Did you run `datamodule.setup('validate')`?") return DataLoader(self.val_data, shuffle=False, batch_size=self.test_batch_size, num_workers=self.num_workers) def test_dataloader(self) -> EVAL_DATALOADERS: """Get test dataloader.""" + if not hasattr(self, "test_data"): + raise RuntimeError("Test data not setup. Did you run `datamodule.setup('test')`?") return DataLoader(self.test_data, shuffle=False, batch_size=self.test_batch_size, num_workers=self.num_workers) def predict_dataloader(self) -> EVAL_DATALOADERS: """Get predict dataloader.""" + if not hasattr(self, "inference_data"): + raise RuntimeError("Inference data not setup. Did you run `datamodule.setup('predict')`?") return DataLoader( self.inference_data, shuffle=False, batch_size=self.test_batch_size, num_workers=self.num_workers ) -# TODO remove me -# debug _make_dataset in main -if __name__ == "__main__": - import itertools - - for cat in CATEGORIES: - _make_dataset(Path(f"/home/jcasagrandebertoldo/Downloads/loco/{cat}")) - - for cat, split_ in itertools.product(CATEGORIES, SPLITS): - pass - # _make_dataset(Path(f"/home/jcasagrandebertoldo/Downloads/loco/{cat}"), split_) - # next - # next - # next - # next - # next - # next - # next - # next - # test instantiate of the two classes - # then test with script - # next - # next - # next - # next - # next - # next - # next - # next - # next - # next - # next - # next - # next - # next - # next - # next - # next - # next - # next - # next - # next - # next - # next - # next - # next - # next - # next - # next +# next +# correct the multi-image ground truth +# then show it in the notebook +# then create unit tests +# next +# next +# next +# next +# next +# next +# next +# next +# next +# next +# next +# next +# next +# next +# next +# next +# next +# next +# next +# next +# next +# next +# next +# next +# next +# next diff --git a/notebooks/100_datamodules/104_mvtec_loco.ipynb b/notebooks/100_datamodules/104_mvtec_loco.ipynb new file mode 100644 index 0000000000..0f5fe9585c --- /dev/null +++ b/notebooks/100_datamodules/104_mvtec_loco.ipynb @@ -0,0 +1,394 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## MVTec LOCO AD" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from pathlib import Path\n", + "\n", + "import numpy as np\n", + "from IPython.core.interactiveshell import InteractiveShell\n", + "from PIL import Image\n", + "from torchvision.transforms import ToPILImage\n", + "\n", + "from anomalib.data.mvtec_loco import (\n", + " MVTecLOCO,\n", + " MVTecLOCODataset,\n", + " download_and_extract_mvtec_loco,\n", + ")\n", + "from anomalib.pre_processing import PreProcessor\n", + "from anomalib.pre_processing.transforms import Denormalize\n", + "\n", + "# make a cell print all the outputs instead of just the last one\n", + "InteractiveShell.ast_node_interactivity = \"all\"\n", + "\n", + "# pylint: disable=locally-disabled, pointless-statement\n", + "# the ``pointless-statement`` warning is disabled because we use them to print stuff" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Download and extract the dataset" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "root = Path(\"../../datasets/MVTecLOCO\")\n", + "if not root.exists():\n", + " download_and_extract_mvtec_loco(root)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Torch Dataset" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "MVTecLOCODataset??" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To create `MVTecDataset` we need to import `pre_process` that applies transforms to the input image." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "PreProcessor??" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pre_process = PreProcessor(image_size=(100, 170), to_tensor=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Classification Task" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# MVTec LOCO Classification Train Set\n", + "mvtec_loco_dataset_classification_train = MVTecLOCODataset(\n", + " root=\"../../datasets/MVTecLOCO\",\n", + " category=\"pushpins\",\n", + " split=\"train\",\n", + " pre_process=pre_process,\n", + " task=\"classification\",\n", + ")\n", + "mvtec_loco_dataset_classification_train.samples.head()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "sample = mvtec_loco_dataset_classification_train[0]\n", + "sample.keys()\n", + "sample[\"image\"].shape" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As can be seen above, when we choose `classification` task and `train` split, the dataset only returns `image`. This is mainly because training only requires normal images and no labels. Now let's try `test` split for the `classification` task" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# MVTec Classification Test Set\n", + "mvtec_loco_dataset_classification_test = MVTecLOCODataset(\n", + " root=\"../../datasets/MVTecLOCO\",\n", + " category=\"pushpins\",\n", + " split=\"test\",\n", + " pre_process=pre_process,\n", + " task=\"classification\",\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "sample = mvtec_loco_dataset_classification_test[0]\n", + "sample.keys()\n", + "sample[\"image\"].shape\n", + "sample[\"image_path\"]\n", + "sample[\"label\"]\n", + "sample[\"super_anotype\"], sample[\"anotype\"]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Negative indices are also enabled." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "sample = mvtec_loco_dataset_classification_test[-1]\n", + "sample.keys()\n", + "sample[\"image\"].shape\n", + "sample[\"image_path\"]\n", + "sample[\"label\"]\n", + "sample[\"super_anotype\"], sample[\"anotype\"]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Segmentation Task\n", + "\n", + "It is also possible to configure the MVTec LOCO dataset for the segmentation task, where the dataset object returns image and ground-truth mask." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# MVTec LOCO Segmentation Train Set\n", + "mvtec_loco_dataset_segmentation_train = MVTecLOCODataset(\n", + " root=\"../../datasets/MVTecLOCO\",\n", + " category=\"pushpins\",\n", + " pre_process=pre_process,\n", + " split=\"train\",\n", + " task=\"segmentation\",\n", + ")\n", + "mvtec_loco_dataset_segmentation_train.samples.head()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# MVTec LOCO Segmentation Test Set\n", + "mvtec_loco_dataset_segmentation_test = MVTecLOCODataset(\n", + " root=\"../../datasets/MVTecLOCO\",\n", + " category=\"pushpins\",\n", + " pre_process=pre_process,\n", + " split=\"test\",\n", + " task=\"segmentation\",\n", + ")\n", + "sample = mvtec_loco_dataset_segmentation_test[20]\n", + "sample.keys()\n", + "sample[\"image\"].shape\n", + "sample[\"mask\"].shape" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's visualize the image and the mask..." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "img = ToPILImage()(Denormalize()(sample[\"image\"].clone()))\n", + "msk = ToPILImage()(sample[\"mask\"]).convert(\"RGB\")\n", + "\n", + "Image.fromarray(np.vstack((np.array(img), np.array(msk))))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### DataModule\n", + "\n", + "So far, we have shown the Torch Dateset implementation of MVTec LOCO AD dataset. This is quite useful to get a sample, but we do need more than this when we train models in an end-to-end fashion.\n", + " \n", + "The [PyTorch Lightning DataModule](https://pytorch-lightning.readthedocs.io/en/latest/data/datamodule.html) for MVTec LOCO AD (shown below) is handles the the dataset download, and train/val/test/inference dataloaders instantiation." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "MVTecLOCO??" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "mvtec_datamodule = MVTecLOCO(\n", + " root=\"../../datasets/MVTecLOCO\",\n", + " category=\"pushpins\",\n", + " image_size=(200, 340), # (height, width) 5x smaller than original\n", + " train_batch_size=32,\n", + " test_batch_size=32,\n", + " num_workers=8,\n", + " task=\"segmentation\",\n", + ")\n", + "\n", + "# verify if the dataset is available and download it if not\n", + "mvtec_datamodule.prepare_data()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Train images\n", + "\n", + "# instantiate the Torch Dataset(s), loading the (meta-)data into memory\n", + "mvtec_datamodule.setup(\"fit\")\n", + "\n", + "i, data = next(enumerate(mvtec_datamodule.train_dataloader()))\n", + "data.keys()\n", + "data[\"image\"].shape" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Validation images\n", + "mvtec_datamodule.setup(\"validate\")\n", + "i, data = next(enumerate(mvtec_datamodule.val_dataloader()))\n", + "data.keys()\n", + "data[\"image\"].shape\n", + "data[\"mask\"].shape\n", + "data[\"super_anotype\"][0], data[\"anotype\"][0]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Test images\n", + "mvtec_datamodule.setup(\"test\")\n", + "# iterate a few times so we can find a sample with an anomaly\n", + "for i, data in enumerate(mvtec_datamodule.test_dataloader()):\n", + " if i == 5:\n", + " break\n", + "data.keys()\n", + "data[\"image\"].shape\n", + "data[\"mask\"].shape\n", + "data[\"super_anotype\"][0], data[\"anotype\"][0]\n", + "\n", + "img = ToPILImage()(Denormalize()(data[\"image\"][0].clone()))\n", + "msk = ToPILImage()(data[\"mask\"][0]).convert(\"RGB\")\n", + "\n", + "Image.fromarray(np.vstack((np.array(img), np.array(msk))))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "TODO: show that the ground truth is divided in multiple images\n", + "\n", + "TODO: create issue to correct docs in mvtec, e.g. it should not give example in the docstring but send the user\n", + " to the notebooks (more maintainable)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As can be seen above, creating the dataloaders are pretty straghtforward, which could be directly used for training/testing/inference." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3.8.13 ('anomalib-dev')", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.13" + }, + "orig_nbformat": 4, + "vscode": { + "interpreter": { + "hash": "8787c31053eaf11dad02e12159779e58bcbd87fee611b470525fee7090bb4db2" + } + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} From f5321acc10bc8f555cc85f2e6afff49190ace9fe Mon Sep 17 00:00:00 2001 From: jpcbertoldo <24547377+jpcbertoldo@users.noreply.github.com> Date: Fri, 9 Sep 2022 23:35:58 +0200 Subject: [PATCH 37/38] manage multiple masks --- anomalib/data/mvtec_loco.py | 132 +- .../100_datamodules/104_mvtec_loco.ipynb | 1526 ++++++++++++++++- 2 files changed, 1564 insertions(+), 94 deletions(-) diff --git a/anomalib/data/mvtec_loco.py b/anomalib/data/mvtec_loco.py index 5280cf470e..d5274ddab9 100644 --- a/anomalib/data/mvtec_loco.py +++ b/anomalib/data/mvtec_loco.py @@ -376,12 +376,12 @@ def download_and_extract_mvtec_loco(root: Union[str, Path]) -> None: def _make_dataset( path: Path, split: str, - imread_strategy: str = IMREAD_STRATEGY_PRELOAD, ) -> DataFrame: # noqa D212 """ Find the images in the given path and create a DataFrame with all the information from each sample. Expected structure of the files in the dataset ("/" is 'path') + Important: notice that the groud truth masks can be in multiple files! images: /{split}/{super_anotype}/{image_index}.png @@ -402,11 +402,11 @@ def _make_dataset( /ground_truth/logical_anomalies/079/000.png /ground_truth/structural_anomalies/.../000.png + /ground_truth/structural_anomalies/.../001.png ... """ assert split in SPLITS, f"Invalid split: {split}" - assert imread_strategy in IMREAD_STRATEGIES, f"Invalid imread strategy: {imread_strategy}" category = path.resolve().name assert category in CATEGORIES, f"Invalid path '{path}'. The category '{category}' must be one of {CATEGORIES}" @@ -429,7 +429,7 @@ def _make_dataset( def build_record(sample_path: Path): - ret: Dict[str, Union[Path, None, str, int]] = { + ret: Dict[str, Union[Path, Tuple[Path, ...], None, str, int]] = { "image_path": sample_path, **dict(zip(("split", "super_anotype", "image_filename"), sample_path.parts[-3:])), } @@ -439,7 +439,7 @@ def build_record(sample_path: Path): if super_anotype == SUPER_ANOTYPE_GOOD: ret.update( { - "mask_path": None, + "mask_paths": None, "label": LABEL_NORMAL, "super_anotype": SUPER_ANOTYPE_GOOD, "anotype": ANOTYPE_GOOD, @@ -449,23 +449,27 @@ def build_record(sample_path: Path): if super_anotype in (SUPER_ANOTYPE_LOGICAL, SUPER_ANOTYPE_STRUCTURAL): - mask_path: Path = path / "ground_truth" / super_anotype / sample_path.stem / "000.png" + mask_paths: Tuple[Path, ...] = tuple( + sorted((path / "ground_truth" / super_anotype / sample_path.stem).glob("*.png")) + ) - assert mask_path.exists(), f"Mask file '{mask_path}' does not exist. Is the dataset corrupted?" + assert len(mask_paths) > 0, f"No masks found for sample '{sample_path}'. Is the dataset corrupted?" # mask images are supposed to have only two values: 0 and GTVALUE_ANOMALY # GTVALUE_ANOMALY \in {234, ..., 255} and is given in the paper, encoded in the mapping below # TODO create an issue to cache this info so the mask is not read here - gtvalue = read_mask(mask_path).astype(int).max() + first_mask_path = mask_paths[0] + gtvalue = read_mask(first_mask_path).astype(int).max() _, anotype = _MAP_GTVALUE_2_ANOTYPE[(category, gtvalue)] ret.update( { - "mask_path": mask_path, + "mask_paths": mask_paths, "gtvalue": gtvalue, "label": LABEL_ANOMALOUS, "super_anotype": super_anotype, "anotype": anotype, + "is_multimask": len(mask_paths) > 1, } ) @@ -476,27 +480,6 @@ def build_record(sample_path: Path): samples = pd.DataFrame.from_records([build_record(sp) for sp in samples_paths]) - if imread_strategy == IMREAD_STRATEGY_PRELOAD: - - warnings.warn( - "Preloading images into memory. " - "If your dataset is too large, consider using another imread_strategy instead.", - stacklevel=3, - ) - - logger.debug("Preloading images into memory") - samples["image"] = samples["image_path"].map(read_image) - - logger.debug("Preloading masks into memory") - - # this is used to select the rows in the dataframe - has_mask = ~samples["mask_path"].isnull() - - samples.loc[has_mask, "mask"] = samples.loc[has_mask, "mask_path"].map( - lambda x: _binarize_mask_float(read_mask(x)) - ) - samples.loc[~has_mask, "mask"] = None - return samples @@ -528,7 +511,6 @@ def __init__( Default: ``preload`` See ``anomalib.data.mvtec_loco.IMREAD_STRATEGIES``. - TODO add link See examples in the repository ``anomalib/notebooks/100_datamodules/104_mvtec_loco.ipynb``. """ @@ -550,9 +532,50 @@ def __init__( self.samples = _make_dataset( path=self.category_dataset_path, split=self.split, - imread_strategy=self.imread_strategy, ) + if self.imread_strategy == IMREAD_STRATEGY_PRELOAD: + + warnings.warn( + "Preloading images into memory. " + "If your dataset is too large, consider using another imread_strategy instead.", + stacklevel=2, + ) + + logger.debug("Preloading images into memory") + self.samples["image"] = self.samples["image_path"].map(read_image) + + logger.debug("Preloading masks into memory") + # this is used to select the rows in the dataframe + has_mask = ~self.samples["mask_paths"].isnull() + + # iterate the mask paths and read the masks returning a tuple of masks + self.samples.loc[has_mask, "masks"] = self.samples.loc[has_mask, "mask_paths"].map( + lambda tupe_of_paths: tuple(_binarize_mask_float(read_mask(mask_path)) for mask_path in tupe_of_paths) + ) + + # combine the multiple masks into a single (binary) mask + self.samples.loc[has_mask, "mask"] = self.samples.loc[has_mask, "masks"].map( + lambda masks: np.stack(masks, axis=0).sum(axis=0).clip(0, 1) + ) + + # replace the tuple of masks by a single array where each anomaly has + # a different value encoding an individual anomaly region + self.samples.loc[has_mask, "masks"] = self.samples.loc[has_mask, "masks"].map(self._sum_masks) + + self.samples.loc[~has_mask, "masks"] = None + self.samples.loc[~has_mask, "mask"] = None + + @staticmethod + def _sum_masks(tupe_of_masks: Tuple[np.ndarray, ...]) -> np.ndarray: + """Combines multiple masks into a single mask by encoding each mask with a different value and summing them.""" + n_masks = len(tupe_of_masks) + # +1 is to compensate the open interval on the right + # expand_dims is to add the W and H dimensions, to make sure they are broadcasted + gtvalues = np.expand_dims(np.arange(1, n_masks + 1), (1, 2)) + stacked_masks = np.stack(tupe_of_masks, axis=0) + return (gtvalues * stacked_masks).sum(axis=0) + @property def category_dataset_path(self) -> Path: """Path to the category dataset (root/category) folder.""" @@ -573,14 +596,36 @@ def _get_image(self, index: int) -> ndarray: raise NotImplementedError(f"Imread strategy '{self.imread_strategy}' is not supported.") - def _get_mask(self, index: int) -> ndarray: + def _get_masks(self, index: int) -> Dict[str, ndarray]: """Get mask at index.""" if self.imread_strategy == IMREAD_STRATEGY_PRELOAD: - return self.samples.iloc[index]["mask"] + return { + "masks": self.samples.iloc[index]["masks"], + "mask": self.samples.iloc[index]["mask"], + } if self.imread_strategy == IMREAD_STRATEGY_ONTHEFLY: - return _binarize_mask_float(read_mask(self.samples.iloc[index]["mask_path"])) + + mask_paths = self.samples.iloc[index]["mask_paths"] + if mask_paths is None: + return { + "masks": None, + "mask": None, + } + + # iterate the mask paths and read the masks returning a tuple of masks + masks: Tuple[np.ndarray, ...] = tuple( + _binarize_mask_float(read_mask(mask_path)) for mask_path in mask_paths + ) + + return { + # replace the tuple of masks by a single array where each anomaly has + # a different value encoding an individual anomaly region + "masks": self._sum_masks(masks), + # combine the multiple masks into a single (binary) mask + "mask": np.stack(masks, axis=0).sum(axis=0).clip(0, 1), + } raise NotImplementedError(f"Imread strategy '{self.imread_strategy}' is not supported.") @@ -618,19 +663,23 @@ def __getitem__(self, index: int) -> Union[Dict[str, Tensor], Dict[str, Union[st if self.task != TASK_SEGMENTATION: return item + mask_dict: Dict[str, ndarray] + # Only Anomalous (1) images has masks in MVTec LOCO AD dataset. # Therefore, create empty mask for Normal (0) images. if self.samples.iloc[index]["label"] == LABEL_NORMAL: mask = np.zeros(shape=image.shape[:2]) # shape: (H, W, C) + mask_dict = {"mask": mask, "masks": mask} else: - mask = self._get_mask(index) + mask_dict = self._get_masks(index) - pre_processed = self.pre_process(image=image, mask=mask) item.update( { - "mask_path": str(self.samples.iloc[index]["mask_path"]), - "mask": pre_processed["mask"], + "mask_paths": str(self.samples.iloc[index]["mask_paths"]), + # TODO CHECK IF THE DOUBLE CALL TO PREPROCESS WILL WORK WITH ALBUMENTATIONS + "masks": self.pre_process(image=image, mask=mask_dict["masks"])["mask"], + "mask": self.pre_process(image=image, mask=mask_dict["mask"])["mask"], } ) @@ -656,7 +705,6 @@ def __init__( transform_config_train: Optional[Union[str, A.Compose]] = None, test_batch_size: int = 32, transform_config_val: Optional[Union[str, A.Compose]] = None, - # TODO: add a parameter to specify the anomaly types and (more specifically) ) -> None: """Mvtec LOCO AD Lightning Data Module. @@ -679,16 +727,10 @@ def __init__( transform_config_val: List of pre_processing object containing albumentation compose or config applied during validation. - TODO add link See examples in the repository ``anomalib/notebooks/100_datamodules/104_mvtec_loco.ipynb``. """ super().__init__() - # TODO create option to get a subset of anomalies in the test - # TODO the images are not squared here, maybe we should add warn the user if - # the ration from image_size is too different from the original image when - # the image size is given as an int - assert task in TASKS, f"Task '{task}' is not supported. Supported tasks are {TASKS}" assert ( imread_strategy in IMREAD_STRATEGIES diff --git a/notebooks/100_datamodules/104_mvtec_loco.ipynb b/notebooks/100_datamodules/104_mvtec_loco.ipynb index 0f5fe9585c..9dce64e7da 100644 --- a/notebooks/100_datamodules/104_mvtec_loco.ipynb +++ b/notebooks/100_datamodules/104_mvtec_loco.ipynb @@ -9,7 +9,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -44,7 +44,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ @@ -62,9 +62,221 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[0;31mInit signature:\u001b[0m \u001b[0mMVTecLOCODataset\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwds\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mSource:\u001b[0m \n", + "\u001b[0;32mclass\u001b[0m \u001b[0mMVTecLOCODataset\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mVisionDataset\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"\"\"MVTec LOCO AD PyTorch Dataset.\"\"\"\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m__init__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mroot\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mUnion\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mPath\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mstr\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mcategory\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mstr\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0msplit\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mstr\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mpre_process\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mPreProcessor\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mtask\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mstr\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mTASK_SEGMENTATION\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mimread_strategy\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mstr\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mIMREAD_STRATEGY_PRELOAD\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"\"\"Mvtec LOCO AD Dataset class.\u001b[0m\n", + "\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m Args:\u001b[0m\n", + "\u001b[0;34m root: Path to the MVTec LOCO AD dataset root folder.\u001b[0m\n", + "\u001b[0;34m category: Name of the MVTec LOCO AD category (there are 5).\u001b[0m\n", + "\u001b[0;34m See ``anomalib.data.mvtec_loco.CATEGORIES``.\u001b[0m\n", + "\u001b[0;34m split: 'train', 'validation' or 'test'\u001b[0m\n", + "\u001b[0;34m See anomalib.data.mvtec_loco.SPLITS.\u001b[0m\n", + "\u001b[0;34m pre_process: List of pre_processing object containing albumentation compose or config.\u001b[0m\n", + "\u001b[0;34m task: ``classification`` or ``segmentation``\u001b[0m\n", + "\u001b[0;34m Default: ``segmentation``\u001b[0m\n", + "\u001b[0;34m ``anomalib.data.mvtec_loco.TASKS``.\u001b[0m\n", + "\u001b[0;34m imread_strategy: When should images be read into memory?\u001b[0m\n", + "\u001b[0;34m Default: ``preload``\u001b[0m\n", + "\u001b[0;34m See ``anomalib.data.mvtec_loco.IMREAD_STRATEGIES``.\u001b[0m\n", + "\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m TODO add link\u001b[0m\n", + "\u001b[0;34m See examples in the repository ``anomalib/notebooks/100_datamodules/104_mvtec_loco.ipynb``.\u001b[0m\n", + "\u001b[0;34m \"\"\"\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0msuper\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__init__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mroot\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32massert\u001b[0m \u001b[0msplit\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mSPLITS\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34mf\"Split '{split}' is not supported. Supported splits are {SPLITS}\"\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32massert\u001b[0m \u001b[0mtask\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mTASKS\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34mf\"Task '{task}' is not supported. Supported tasks are {TASKS}\"\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32massert\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mimread_strategy\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mIMREAD_STRATEGIES\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34mf\"Imread strategy '{imread_strategy}' is not supported. Supported imread strategies are {IMREAD_STRATEGIES}\"\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mroot\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mPath\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mroot\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0misinstance\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mroot\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mstr\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32melse\u001b[0m \u001b[0mroot\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcategory\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mstr\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mcategory\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msplit\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0msplit\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtask\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mtask\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpre_process\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mpre_process\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mimread_strategy\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mimread_strategy\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msamples\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0m_make_dataset\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mpath\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcategory_dataset_path\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0msplit\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msplit\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mimread_strategy\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0mIMREAD_STRATEGY_PRELOAD\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mwarnings\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mwarn\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"Preloading images into memory. \"\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"If your dataset is too large, consider using another imread_strategy instead.\"\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mstacklevel\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m2\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mlogger\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdebug\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Preloading images into memory\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msamples\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"image\"\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msamples\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"image_path\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmap\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mread_image\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mlogger\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdebug\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Preloading masks into memory\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;31m# this is used to select the rows in the dataframe\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mhas_mask\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m~\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msamples\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"mask_paths\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0misnull\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;31m# iterate the mask paths and read the masks returning a tuple of masks\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msamples\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mloc\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mhas_mask\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"masks\"\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msamples\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mloc\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mhas_mask\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"mask_paths\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmap\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mlambda\u001b[0m \u001b[0mtupe_of_paths\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mtuple\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0m_binarize_mask_float\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mread_mask\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmask_path\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mmask_path\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mtupe_of_paths\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;31m# combine the multiple masks into a single (binary) mask\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msamples\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mloc\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mhas_mask\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"mask\"\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msamples\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mloc\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mhas_mask\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"masks\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmap\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mlambda\u001b[0m \u001b[0mmasks\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mstack\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmasks\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0maxis\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msum\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0maxis\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mclip\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;31m# replace the tuple of masks by a single array where each anomaly has\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;31m# a different value encoding an individual anomaly region\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msamples\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mloc\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mhas_mask\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"masks\"\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msamples\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mloc\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mhas_mask\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"masks\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmap\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_sum_masks\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msamples\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mloc\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m~\u001b[0m\u001b[0mhas_mask\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"masks\"\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msamples\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mloc\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m~\u001b[0m\u001b[0mhas_mask\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"mask\"\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m@\u001b[0m\u001b[0mstaticmethod\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m_sum_masks\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mtupe_of_masks\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mTuple\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mndarray\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mndarray\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"\"\"combines multiple masks into a single mask by encoding each mask with a different value and summing them.\"\"\"\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mn_masks\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mlen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mtupe_of_masks\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;31m# +1 is to compensate the open interval on the right\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;31m# expand_dims is to add the W and H dimensions, to make sure they are broadcasted\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mgtvalues\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mexpand_dims\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0marange\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn_masks\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m2\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mstacked_masks\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mstack\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mtupe_of_masks\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0maxis\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0mgtvalues\u001b[0m \u001b[0;34m*\u001b[0m \u001b[0mstacked_masks\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msum\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0maxis\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m@\u001b[0m\u001b[0mproperty\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mcategory_dataset_path\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0mPath\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"\"\"Path to the category dataset (root/category) folder.\"\"\"\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mroot\u001b[0m \u001b[0;34m/\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcategory\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m__len__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0mint\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"\"\"Get length of the dataset.\"\"\"\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mlen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msamples\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m_get_image\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mindex\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mint\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0mndarray\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"\"\"Get image at index.\"\"\"\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mimread_strategy\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0mIMREAD_STRATEGY_PRELOAD\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msamples\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0miloc\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mindex\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"image\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mimread_strategy\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0mIMREAD_STRATEGY_ONTHEFLY\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mread_image\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msamples\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0miloc\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mindex\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"image_path\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mraise\u001b[0m \u001b[0mNotImplementedError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34mf\"Imread strategy '{self.imread_strategy}' is not supported.\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m_get_masks\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mindex\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mint\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0mDict\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mstr\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mndarray\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"\"\"Get mask at index.\"\"\"\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mimread_strategy\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0mIMREAD_STRATEGY_PRELOAD\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0;34m{\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"masks\"\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msamples\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0miloc\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mindex\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"masks\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"mask\"\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msamples\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0miloc\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mindex\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"mask\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m}\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mimread_strategy\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0mIMREAD_STRATEGY_ONTHEFLY\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mmask_paths\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msamples\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0miloc\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mindex\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"mask_paths\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mmask_paths\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0;34m{\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"masks\"\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"mask\"\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m}\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;31m# iterate the mask paths and read the masks returning a tuple of masks\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mmasks\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mtuple\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0m_binarize_mask_float\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mread_mask\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmask_path\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mmask_path\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mmask_paths\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0;34m{\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;31m# replace the tuple of masks by a single array where each anomaly has\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;31m# a different value encoding an individual anomaly region \u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"masks\"\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_sum_masks\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmasks\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;31m# combine the multiple masks into a single (binary) mask\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"mask\"\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mstack\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmasks\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0maxis\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msum\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0maxis\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mclip\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m}\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mraise\u001b[0m \u001b[0mNotImplementedError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34mf\"Imread strategy '{self.imread_strategy}' is not supported.\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m__getitem__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mindex\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mint\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0mUnion\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mDict\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mstr\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mTensor\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mDict\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mstr\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mUnion\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mstr\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mTensor\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mint\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"\"\"Get dataset item for the index ``index``.\u001b[0m\n", + "\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m Args:\u001b[0m\n", + "\u001b[0;34m index (int): Index to get the item.\u001b[0m\n", + "\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m Returns:\u001b[0m\n", + "\u001b[0;34m Union[Dict[str, Tensor], Dict[str, Union[str, Tensor]]]: Dict of image tensor during training.\u001b[0m\n", + "\u001b[0;34m Otherwise, Dict containing image path, image tensor, label, anomaly type and,\u001b[0m\n", + "\u001b[0;34m if it is segmentation task, mask path and mask tensor.\u001b[0m\n", + "\u001b[0;34m \"\"\"\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mitem\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mDict\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mstr\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mUnion\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mstr\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mTensor\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mint\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m{\u001b[0m\u001b[0;34m}\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mimage\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_get_image\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mindex\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mpre_processed\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpre_process\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mimage\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mimage\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mitem\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m{\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"image\"\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mpre_processed\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"image\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m}\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msplit\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0;32min\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0mSPLIT_VALIDATION\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mSPLIT_TEST\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mitem\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mitem\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mupdate\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m{\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"label\"\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msamples\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0miloc\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mindex\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"label\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"image_path\"\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mstr\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msamples\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0miloc\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mindex\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"image_path\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"anotype\"\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msamples\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0miloc\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mindex\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"anotype\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"super_anotype\"\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msamples\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0miloc\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mindex\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"super_anotype\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m}\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtask\u001b[0m \u001b[0;34m!=\u001b[0m \u001b[0mTASK_SEGMENTATION\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mitem\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mmask_dict\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mDict\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mstr\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mndarray\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;31m# Only Anomalous (1) images has masks in MVTec LOCO AD dataset.\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;31m# Therefore, create empty mask for Normal (0) images.\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msamples\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0miloc\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mindex\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"label\"\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0mLABEL_NORMAL\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mmask\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mzeros\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mshape\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mimage\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mshape\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;36m2\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;31m# shape: (H, W, C)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mmask_dict\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m{\u001b[0m\u001b[0;34m\"mask\"\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mmask\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"masks\"\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mmask\u001b[0m\u001b[0;34m}\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mmask_dict\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_get_masks\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mindex\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mitem\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mupdate\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m{\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"mask_paths\"\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mstr\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msamples\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0miloc\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mindex\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"mask_paths\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;31m# TODO CHECK IF THE DOUBLE CALL TO PREPROCESS WILL WORK WITH ALBUMENTATIONS\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"masks\"\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpre_process\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mimage\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mimage\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mmask\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mmask_dict\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"masks\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"mask\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"mask\"\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpre_process\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mimage\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mimage\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mmask\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mmask_dict\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"mask\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"mask\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m}\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mitem\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mFile:\u001b[0m ~/repos/anomalib/anomalib/data/mvtec_loco.py\n", + "\u001b[0;31mType:\u001b[0m type\n", + "\u001b[0;31mSubclasses:\u001b[0m \n" + ] + } + ], "source": [ "MVTecLOCODataset??" ] @@ -78,16 +290,154 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[0;31mInit signature:\u001b[0m\n", + "\u001b[0mPreProcessor\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mconfig\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mUnion\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mstr\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0malbumentations\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcore\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcomposition\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mCompose\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mNoneType\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mimage_size\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mUnion\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mint\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mTuple\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mNoneType\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mto_tensor\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mbool\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mTrue\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mSource:\u001b[0m \n", + "\u001b[0;32mclass\u001b[0m \u001b[0mPreProcessor\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"\"\"Applies pre-processing and data augmentations to the input and returns the transformed output.\u001b[0m\n", + "\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m Output could be either numpy ndarray or torch tensor.\u001b[0m\n", + "\u001b[0;34m When `PreProcessor` class is used for training, the output would be `torch.Tensor`.\u001b[0m\n", + "\u001b[0;34m For the inference it returns a numpy array.\u001b[0m\n", + "\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m Args:\u001b[0m\n", + "\u001b[0;34m config (Optional[Union[str, A.Compose]], optional): Transformation configurations.\u001b[0m\n", + "\u001b[0;34m When it is ``None``, ``PreProcessor`` only applies resizing. When it is ``str``\u001b[0m\n", + "\u001b[0;34m it loads the config via ``albumentations`` deserialisation methos . Defaults to None.\u001b[0m\n", + "\u001b[0;34m image_size (Optional[Union[int, Tuple[int, int]]], optional): When there is no config,\u001b[0m\n", + "\u001b[0;34m ``image_size`` resizes the image. Defaults to None.\u001b[0m\n", + "\u001b[0;34m to_tensor (bool, optional): Boolean to check whether the augmented image is transformed\u001b[0m\n", + "\u001b[0;34m into a tensor or not. Defaults to True.\u001b[0m\n", + "\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m Examples:\u001b[0m\n", + "\u001b[0;34m >>> import skimage\u001b[0m\n", + "\u001b[0;34m >>> image = skimage.data.astronaut()\u001b[0m\n", + "\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m >>> pre_processor = PreProcessor(image_size=256, to_tensor=False)\u001b[0m\n", + "\u001b[0;34m >>> output = pre_processor(image=image)\u001b[0m\n", + "\u001b[0;34m >>> output[\"image\"].shape\u001b[0m\n", + "\u001b[0;34m (256, 256, 3)\u001b[0m\n", + "\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m >>> pre_processor = PreProcessor(image_size=256, to_tensor=True)\u001b[0m\n", + "\u001b[0;34m >>> output = pre_processor(image=image)\u001b[0m\n", + "\u001b[0;34m >>> output[\"image\"].shape\u001b[0m\n", + "\u001b[0;34m torch.Size([3, 256, 256])\u001b[0m\n", + "\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m Transforms could be read from albumentations Compose object.\u001b[0m\n", + "\u001b[0;34m >>> import albumentations as A\u001b[0m\n", + "\u001b[0;34m >>> from albumentations.pytorch import ToTensorV2\u001b[0m\n", + "\u001b[0;34m >>> config = A.Compose([A.Resize(512, 512), ToTensorV2()])\u001b[0m\n", + "\u001b[0;34m >>> pre_processor = PreProcessor(config=config, to_tensor=False)\u001b[0m\n", + "\u001b[0;34m >>> output = pre_processor(image=image)\u001b[0m\n", + "\u001b[0;34m >>> output[\"image\"].shape\u001b[0m\n", + "\u001b[0;34m (512, 512, 3)\u001b[0m\n", + "\u001b[0;34m >>> type(output[\"image\"])\u001b[0m\n", + "\u001b[0;34m numpy.ndarray\u001b[0m\n", + "\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m Transforms could be deserialized from a yaml file.\u001b[0m\n", + "\u001b[0;34m >>> transforms = A.Compose([A.Resize(1024, 1024), ToTensorV2()])\u001b[0m\n", + "\u001b[0;34m >>> A.save(transforms, \"/tmp/transforms.yaml\", data_format=\"yaml\")\u001b[0m\n", + "\u001b[0;34m >>> pre_processor = PreProcessor(config=\"/tmp/transforms.yaml\")\u001b[0m\n", + "\u001b[0;34m >>> output = pre_processor(image=image)\u001b[0m\n", + "\u001b[0;34m >>> output[\"image\"].shape\u001b[0m\n", + "\u001b[0;34m torch.Size([3, 1024, 1024])\u001b[0m\n", + "\u001b[0;34m \"\"\"\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m__init__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mconfig\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mOptional\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mUnion\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mstr\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mA\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mCompose\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mimage_size\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mOptional\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mUnion\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mint\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mTuple\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mto_tensor\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mbool\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mTrue\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mconfig\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mconfig\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mimage_size\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mimage_size\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mto_tensor\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mto_tensor\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtransforms\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget_transforms\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mget_transforms\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0mA\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mCompose\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"\"\"Get transforms from config or image size.\u001b[0m\n", + "\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m Returns:\u001b[0m\n", + "\u001b[0;34m A.Compose: List of albumentation transformations to apply to the\u001b[0m\n", + "\u001b[0;34m input image.\u001b[0m\n", + "\u001b[0;34m \"\"\"\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mconfig\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mNone\u001b[0m \u001b[0;32mand\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mimage_size\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mraise\u001b[0m \u001b[0mValueError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"Both config and image_size cannot be `None`. \"\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"Provide either config file to de-serialize transforms \"\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"or image_size to get the default transformations\"\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mtransforms\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mA\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mCompose\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mconfig\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mNone\u001b[0m \u001b[0;32mand\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mimage_size\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mheight\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mwidth\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_get_height_and_width\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mtransforms\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mA\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mCompose\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mA\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mResize\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mheight\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mheight\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mwidth\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mwidth\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0malways_apply\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mTrue\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mA\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mNormalize\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmean\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m0.485\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m0.456\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m0.406\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mstd\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m0.229\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m0.224\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m0.225\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mToTensorV2\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mconfig\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0misinstance\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mconfig\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mstr\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mtransforms\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mA\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mload\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfilepath\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mconfig\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mdata_format\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m\"yaml\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32melif\u001b[0m \u001b[0misinstance\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mconfig\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mA\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mCompose\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mtransforms\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mconfig\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mraise\u001b[0m \u001b[0mValueError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"config could be either ``str`` or ``A.Compose``\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mto_tensor\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0misinstance\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mtransforms\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m-\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mToTensorV2\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mtransforms\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mA\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mCompose\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mtransforms\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m-\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;31m# always resize to specified image size\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0many\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0misinstance\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mtransform\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mA\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mResize\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mtransform\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mtransforms\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mand\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mimage_size\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mheight\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mwidth\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_get_height_and_width\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mtransforms\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mA\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mCompose\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mA\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mResize\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mheight\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mheight\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mwidth\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mwidth\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0malways_apply\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mTrue\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtransforms\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mtransforms\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m__call__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"\"\"Return transformed arguments.\"\"\"\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtransforms\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m_get_height_and_width\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0mTuple\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mOptional\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mint\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mOptional\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mint\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"\"\"Extract height and width from image size attribute.\"\"\"\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0misinstance\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mimage_size\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mint\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mimage_size\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mimage_size\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0misinstance\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mimage_size\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtuple\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mimage_size\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mimage_size\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mimage_size\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mraise\u001b[0m \u001b[0mValueError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"``image_size`` could be either int or Tuple[int, int]\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mFile:\u001b[0m ~/repos/anomalib/anomalib/pre_processing/pre_process.py\n", + "\u001b[0;31mType:\u001b[0m type\n", + "\u001b[0;31mSubclasses:\u001b[0m \n" + ] + } + ], "source": [ "PreProcessor??" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "metadata": {}, "outputs": [], "source": [ @@ -103,9 +453,148 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/tmp/ipykernel_115879/3968494898.py:2: UserWarning: Preloading images into memory. If your dataset is too large, consider using another imread_strategy instead.\n", + " mvtec_loco_dataset_classification_train = MVTecLOCODataset(\n" + ] + }, + { + "data": { + "text/html": [ + "

\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
image_pathsplitsuper_anotypeimage_filenamemask_pathslabelanotypeimagemasksmask
0../../datasets/MVTecLOCO/pushpins/train/good/0...traingood000.pngNone0good[[[11, 11, 12], [11, 11, 12], [11, 11, 13], [1...NoneNone
1../../datasets/MVTecLOCO/pushpins/train/good/0...traingood001.pngNone0good[[[12, 11, 11], [12, 10, 11], [12, 10, 12], [1...NoneNone
2../../datasets/MVTecLOCO/pushpins/train/good/0...traingood002.pngNone0good[[[14, 12, 12], [13, 12, 13], [12, 12, 13], [1...NoneNone
3../../datasets/MVTecLOCO/pushpins/train/good/0...traingood003.pngNone0good[[[12, 11, 12], [12, 11, 12], [11, 11, 12], [1...NoneNone
4../../datasets/MVTecLOCO/pushpins/train/good/0...traingood004.pngNone0good[[[12, 12, 13], [12, 11, 14], [11, 11, 14], [1...NoneNone
\n", + "
" + ], + "text/plain": [ + " image_path split super_anotype \\\n", + "0 ../../datasets/MVTecLOCO/pushpins/train/good/0... train good \n", + "1 ../../datasets/MVTecLOCO/pushpins/train/good/0... train good \n", + "2 ../../datasets/MVTecLOCO/pushpins/train/good/0... train good \n", + "3 ../../datasets/MVTecLOCO/pushpins/train/good/0... train good \n", + "4 ../../datasets/MVTecLOCO/pushpins/train/good/0... train good \n", + "\n", + " image_filename mask_paths label anotype \\\n", + "0 000.png None 0 good \n", + "1 001.png None 0 good \n", + "2 002.png None 0 good \n", + "3 003.png None 0 good \n", + "4 004.png None 0 good \n", + "\n", + " image masks mask \n", + "0 [[[11, 11, 12], [11, 11, 12], [11, 11, 13], [1... None None \n", + "1 [[[12, 11, 11], [12, 10, 11], [12, 10, 12], [1... None None \n", + "2 [[[14, 12, 12], [13, 12, 13], [12, 12, 13], [1... None None \n", + "3 [[[12, 11, 12], [12, 11, 12], [11, 11, 12], [1... None None \n", + "4 [[[12, 12, 13], [12, 11, 14], [11, 11, 14], [1... None None " + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# MVTec LOCO Classification Train Set\n", "mvtec_loco_dataset_classification_train = MVTecLOCODataset(\n", @@ -120,9 +609,30 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "dict_keys(['image'])" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "torch.Size([3, 100, 170])" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "sample = mvtec_loco_dataset_classification_train[0]\n", "sample.keys()\n", @@ -138,9 +648,18 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/tmp/ipykernel_115879/3604180834.py:2: UserWarning: Preloading images into memory. If your dataset is too large, consider using another imread_strategy instead.\n", + " mvtec_loco_dataset_classification_test = MVTecLOCODataset(\n" + ] + } + ], "source": [ "# MVTec Classification Test Set\n", "mvtec_loco_dataset_classification_test = MVTecLOCODataset(\n", @@ -154,9 +673,60 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "dict_keys(['image', 'label', 'image_path', 'anotype', 'super_anotype'])" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "torch.Size([3, 100, 170])" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "'../../datasets/MVTecLOCO/pushpins/test/good/000.png'" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "0" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "('good', 'good')" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "sample = mvtec_loco_dataset_classification_test[0]\n", "sample.keys()\n", @@ -175,9 +745,60 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 10, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "dict_keys(['image', 'label', 'image_path', 'anotype', 'super_anotype'])" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "torch.Size([3, 100, 170])" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "'../../datasets/MVTecLOCO/pushpins/test/structural_anomalies/080.png'" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "1" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "('structural_anomalies', 'front_bent')" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "sample = mvtec_loco_dataset_classification_test[-1]\n", "sample.keys()\n", @@ -198,9 +819,148 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 11, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/tmp/ipykernel_115879/3202540915.py:2: UserWarning: Preloading images into memory. If your dataset is too large, consider using another imread_strategy instead.\n", + " mvtec_loco_dataset_segmentation_train = MVTecLOCODataset(\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
image_pathsplitsuper_anotypeimage_filenamemask_pathslabelanotypeimagemasksmask
0../../datasets/MVTecLOCO/pushpins/train/good/0...traingood000.pngNone0good[[[11, 11, 12], [11, 11, 12], [11, 11, 13], [1...NoneNone
1../../datasets/MVTecLOCO/pushpins/train/good/0...traingood001.pngNone0good[[[12, 11, 11], [12, 10, 11], [12, 10, 12], [1...NoneNone
2../../datasets/MVTecLOCO/pushpins/train/good/0...traingood002.pngNone0good[[[14, 12, 12], [13, 12, 13], [12, 12, 13], [1...NoneNone
3../../datasets/MVTecLOCO/pushpins/train/good/0...traingood003.pngNone0good[[[12, 11, 12], [12, 11, 12], [11, 11, 12], [1...NoneNone
4../../datasets/MVTecLOCO/pushpins/train/good/0...traingood004.pngNone0good[[[12, 12, 13], [12, 11, 14], [11, 11, 14], [1...NoneNone
\n", + "
" + ], + "text/plain": [ + " image_path split super_anotype \\\n", + "0 ../../datasets/MVTecLOCO/pushpins/train/good/0... train good \n", + "1 ../../datasets/MVTecLOCO/pushpins/train/good/0... train good \n", + "2 ../../datasets/MVTecLOCO/pushpins/train/good/0... train good \n", + "3 ../../datasets/MVTecLOCO/pushpins/train/good/0... train good \n", + "4 ../../datasets/MVTecLOCO/pushpins/train/good/0... train good \n", + "\n", + " image_filename mask_paths label anotype \\\n", + "0 000.png None 0 good \n", + "1 001.png None 0 good \n", + "2 002.png None 0 good \n", + "3 003.png None 0 good \n", + "4 004.png None 0 good \n", + "\n", + " image masks mask \n", + "0 [[[11, 11, 12], [11, 11, 12], [11, 11, 13], [1... None None \n", + "1 [[[12, 11, 11], [12, 10, 11], [12, 10, 12], [1... None None \n", + "2 [[[14, 12, 12], [13, 12, 13], [12, 12, 13], [1... None None \n", + "3 [[[12, 11, 12], [12, 11, 12], [11, 11, 12], [1... None None \n", + "4 [[[12, 12, 13], [12, 11, 14], [11, 11, 14], [1... None None " + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# MVTec LOCO Segmentation Train Set\n", "mvtec_loco_dataset_segmentation_train = MVTecLOCODataset(\n", @@ -215,9 +975,48 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 12, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/tmp/ipykernel_115879/3616612057.py:2: UserWarning: Preloading images into memory. If your dataset is too large, consider using another imread_strategy instead.\n", + " mvtec_loco_dataset_segmentation_test = MVTecLOCODataset(\n" + ] + }, + { + "data": { + "text/plain": [ + "dict_keys(['image', 'label', 'image_path', 'anotype', 'super_anotype', 'mask_paths', 'masks', 'mask'])" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "torch.Size([3, 100, 170])" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "torch.Size([100, 170])" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# MVTec LOCO Segmentation Test Set\n", "mvtec_loco_dataset_segmentation_test = MVTecLOCODataset(\n", @@ -242,16 +1041,290 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 13, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "img = ToPILImage()(Denormalize()(sample[\"image\"].clone()))\n", + "msk = ToPILImage()(sample[\"mask\"]).convert(\"RGB\")\n", + "\n", + "Image.fromarray(np.vstack((np.array(img), np.array(msk))))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "An example of structural anomaly" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "dict_keys(['image', 'label', 'image_path', 'anotype', 'super_anotype', 'mask_paths', 'masks', 'mask'])" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "'structural_anomalies'" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "'broken'" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "sample = mvtec_loco_dataset_segmentation_test[250]\n", + "sample.keys()\n", + "sample[\"super_anotype\"]\n", + "sample[\"anotype\"]\n", + "img = ToPILImage()(Denormalize()(sample[\"image\"].clone()))\n", + "msk = ToPILImage()(sample[\"mask\"]).convert(\"RGB\")\n", + "\n", + "Image.fromarray(np.vstack((np.array(img), np.array(msk))))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "An example of logical anomaly" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "dict_keys(['image', 'label', 'image_path', 'anotype', 'super_anotype', 'mask_paths', 'masks', 'mask'])" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "'logical_anomalies'" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "'missing_separator'" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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y/YX+3WoNQJBekDJdOceCNaDGjeY4b5y0tCyIvCA9to7ojMbBjexo5eeWGLywpd+/F+97V0v+vhP/q2/+ge6ehIdYY3HMdJxp9YNPjApK4auwZ6xqPz2X7nwM99wGCaYzLonl3d/b3neq3eWg19yaGa9Tjc7bJbVggYg21tbQGNmKOyVr/iKez965wfuvhuip06dUekIMWUEiNZgg2zMggLJZ6ErCqQh11mTUXRFO8ik01kv231/5Mjxz/7cT13WOptuvPc977vv4dNOEStQpRR201R8sL5XCCHaqdoMClH2pahiop1NrFeZFu0F08K+n5ihA2ZbW9NONyd9sS7Cd4dh98J5hEA9gqoS0RUdqhNSgApYRKggTCVCihroESq9AK2mBlO6lGKw6WxSzDY606J1MR98ubdXBbWii4hlZEHanQALYwlAXKsiIiChDIVWCIJKhBZiEDc1VHGlWpHNftbPShAmvrNfT156xQP33AXV+WIeQUuEmxC4QSopcEAqqc4ANFhRVQvqslf26mfmvjvUvaEiuCGc9JPtzY0XPfum533RN6ioe4iiNGB1JI5NijGWIBY+AFAND3jkXPaApXiXo2l8BDwhrpwWFQqPUDAwtG6HTGv25cELL3zx+Z39xdYR/8u3vuehRx9LojCJaKUOGFAbuJ87vavYmP4lfVuSDMTUwBCMYmKmitf96L9fLvah+OGf/dUH7r8PK6GQJpCRbLwQM9KzetRWqQgabSH3EM85IglzQdRw05Of/i1f/jLUxWxz899824+iYc5NggWtljN+3/jYSTfLQInIXqRG2gfRAjDT4ycuedYN137mp71kPh/K1F79Ez8L02E5ECBVJKLJpCjdiehKN1RPmoiKmoSIFUVn2lsppqWbWCkbW9su3Nreuur4sSuv2WI3Od5XoHF6ARQiRLSRt9SUEWaAhlcQHtlZuOKSgJJzIVqojRF7yvUCVIweFxn2I/daQezvPXbf/btx/wOnz59N1nDWg1sO0sicbEmAgEwdNBISPqB1ArkA9NZkagpIzh792u/88V/76e/+9p/59Qfuvw8AII2UGsgmQwm4Au6mCLikrlAiZmwJeDLpahaZPBFogPbhD7z/9353+IIv/LSv/vYfybarkcobDVzLEAgN7iYgMQQzjxxULBKAFCeR/SRIyKny0Ycfe8vZM09/0rVHTxz97h94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", + "text/plain": [ + "" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ + "sample = mvtec_loco_dataset_segmentation_test[200]\n", + "sample.keys()\n", + "sample[\"super_anotype\"]\n", + "sample[\"anotype\"]\n", "img = ToPILImage()(Denormalize()(sample[\"image\"].clone()))\n", "msk = ToPILImage()(sample[\"mask\"]).convert(\"RGB\")\n", "\n", "Image.fromarray(np.vstack((np.array(img), np.array(msk))))" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "An example of logical anomaly with multiple anomalous regions\n", + "\n", + "**Important**: the ground truth can have multiple masks (one for each logical anomalous region).\n", + "\n", + "The **union** of the (multiple) masks is conveniently returned in the field `\"mask\"`, but the individual `\"masks\"` (with **s**!) should be considered for correct evaluation!" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "dict_keys(['image', 'label', 'image_path', 'anotype', 'super_anotype', 'mask_paths', 'masks', 'mask'])" + ] + }, + "execution_count": 38, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "'logical_anomalies'" + ] + }, + "execution_count": 38, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "'additional_1_pushpin'" + ] + }, + "execution_count": 38, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "tensor([0., 1.], dtype=torch.float64)" + ] + }, + "execution_count": 38, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "array([ 0, 255], dtype=uint8)" + ] + }, + "execution_count": 38, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "tensor([0., 1., 2.], dtype=torch.float64)" + ] + }, + "execution_count": 38, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "array([ 0, 56, 156], dtype=uint8)" + ] + }, + "execution_count": 38, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "" + ] + }, + "execution_count": 38, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "" + ] + }, + "execution_count": 38, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "sample = mvtec_loco_dataset_segmentation_test[150]\n", + "sample.keys()\n", + "sample[\"super_anotype\"]\n", + "sample[\"anotype\"]\n", + "img = np.array(ToPILImage()(Denormalize()(sample[\"image\"].clone())))\n", + "msk = np.array(ToPILImage()(sample[\"mask\"]).convert(\"RGB\"))\n", + "\n", + "# !!!!!\n", + "# \"100 * \" is artifically increasing the gtvalue of the mask to make it more visible\n", + "msks = np.array(ToPILImage()(100 * sample[\"masks\"]).convert(\"RGB\"))\n", + "# !!!!!\n", + "\n", + "sample[\"mask\"].unique()\n", + "np.unique(msk)\n", + "\n", + "sample[\"masks\"].unique()\n", + "np.unique(msks)\n", + "\n", + "Image.fromarray(np.vstack((img, msk)))\n", + "Image.fromarray(np.vstack((img, msks)))" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -265,16 +1338,221 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 39, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[0;31mInit signature:\u001b[0m\n", + "\u001b[0mMVTecLOCO\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mroot\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mstr\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mcategory\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mstr\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mtask\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mstr\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m'segmentation'\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mimread_strategy\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mstr\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m'preload'\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mimage_size\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mUnion\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mint\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mTuple\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mint\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mint\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mNoneType\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mnum_workers\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mint\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;36m8\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mtrain_batch_size\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mint\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;36m32\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mtransform_config_train\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mUnion\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mstr\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0malbumentations\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcore\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcomposition\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mCompose\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mNoneType\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mtest_batch_size\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mint\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;36m32\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mtransform_config_val\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mUnion\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mstr\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0malbumentations\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcore\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcomposition\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mCompose\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mNoneType\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mSource:\u001b[0m \n", + "\u001b[0;32mclass\u001b[0m \u001b[0mMVTecLOCO\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mLightningDataModule\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"\"\"MVTec LOCO AD Lightning Data Module.\"\"\"\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;31m# todo correct inconsistency: `transform_config_*val*` used for val and\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;31m# test set, but `*test*_batch_size` used for val and set\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m__init__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mroot\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mstr\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mcategory\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mstr\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mtask\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mstr\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mTASK_SEGMENTATION\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mimread_strategy\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mstr\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mIMREAD_STRATEGY_PRELOAD\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mimage_size\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mOptional\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mUnion\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mint\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mTuple\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mint\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mint\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mnum_workers\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mint\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;36m8\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mtrain_batch_size\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mint\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;36m32\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mtransform_config_train\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mOptional\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mUnion\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mstr\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mA\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mCompose\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mtest_batch_size\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mint\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;36m32\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mtransform_config_val\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mOptional\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mUnion\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mstr\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mA\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mCompose\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;31m# TODO: add a parameter to specify the anomaly types and (more specifically)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"\"\"Mvtec LOCO AD Lightning Data Module.\u001b[0m\n", + "\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m Args:\u001b[0m\n", + "\u001b[0;34m root: Path to the MVTec LOCO AD dataset root folder.\u001b[0m\n", + "\u001b[0;34m category: Name of the MVTec LOCO AD category (there are 5).\u001b[0m\n", + "\u001b[0;34m See ``anomalib.data.mvtec_loco.CATEGORIES``.\u001b[0m\n", + "\u001b[0;34m task: ``classification`` or ``segmentation``\u001b[0m\n", + "\u001b[0;34m Default: ``segmentation``\u001b[0m\n", + "\u001b[0;34m See ``anomalib.data.mvtec_loco.TASKS``.\u001b[0m\n", + "\u001b[0;34m imread_strategy: When should images be read into memory?\u001b[0m\n", + "\u001b[0;34m Default: ``preload``\u001b[0m\n", + "\u001b[0;34m See ``anomalib.data.mvtec_loco.IMREAD_STRATEGIES``.\u001b[0m\n", + "\u001b[0;34m image_size: Images are resized to `image_size` (HEIGHT, WIDTH), or (SIZE, SIZE) if a single value is given.\u001b[0m\n", + "\u001b[0;34m num_workers: Number of workers.\u001b[0m\n", + "\u001b[0;34m train_batch_size: Training batch size.\u001b[0m\n", + "\u001b[0;34m transform_config_train: List of pre_processing object containing albumentation compose or\u001b[0m\n", + "\u001b[0;34m config applied during training.\u001b[0m\n", + "\u001b[0;34m test_batch_size: Testing batch size.\u001b[0m\n", + "\u001b[0;34m transform_config_val: List of pre_processing object containing albumentation compose or\u001b[0m\n", + "\u001b[0;34m config applied during validation.\u001b[0m\n", + "\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m TODO add link\u001b[0m\n", + "\u001b[0;34m See examples in the repository ``anomalib/notebooks/100_datamodules/104_mvtec_loco.ipynb``.\u001b[0m\n", + "\u001b[0;34m \"\"\"\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0msuper\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__init__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;31m# TODO create option to get a subset of anomalies in the test\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;31m# TODO the images are not squared here, maybe we should add warn the user if\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;31m# the ration from image_size is too different from the original image when\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;31m# the image size is given as an int\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32massert\u001b[0m \u001b[0mtask\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mTASKS\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34mf\"Task '{task}' is not supported. Supported tasks are {TASKS}\"\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32massert\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mimread_strategy\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mIMREAD_STRATEGIES\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34mf\"Imread strategy '{imread_strategy}' is not supported. Supported imread strategies are {IMREAD_STRATEGIES}\"\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mroot\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mroot\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0misinstance\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mroot\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mPath\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32melse\u001b[0m \u001b[0mPath\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mroot\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcategory\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mcategory\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtransform_config_train\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mtransform_config_train\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtransform_config_val\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mtransform_config_val\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mimage_size\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mimage_size\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpre_process_train\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mPreProcessor\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mconfig\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtransform_config_train\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mimage_size\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mimage_size\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpre_process_val\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mPreProcessor\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mconfig\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtransform_config_val\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mimage_size\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mimage_size\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtrain_batch_size\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mtrain_batch_size\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtest_batch_size\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mtest_batch_size\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mnum_workers\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mnum_workers\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtask\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mtask\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mimread_strategy\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mimread_strategy\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtrain_data\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mDataset\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtest_data\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mDataset\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mval_data\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mDataset\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0minference_data\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mDataset\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m@\u001b[0m\u001b[0mproperty\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mcategory_dataset_path\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0mPath\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"\"\"Path to the category dataset (root/category) folder.\"\"\"\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mroot\u001b[0m \u001b[0;34m/\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcategory\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mprepare_data\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"\"\"Download the dataset if not available.\"\"\"\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcategory_dataset_path\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mis_dir\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mlogger\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0minfo\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Found the dataset.\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mdownload_and_extract_mvtec_loco\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mroot\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0msetup\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mstage\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mOptional\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mstr\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"\"\"Setup train, validation and test data.\u001b[0m\n", + "\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m Args:\u001b[0m\n", + "\u001b[0;34m stage: Optional[str]: fit/validate/test/predict stages. (Default value = None = fit)\u001b[0m\n", + "\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m \"\"\"\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;31m# pylint: disable=consider-using-f-string\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mlogger\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0minfo\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Setting up %s dataset.\"\u001b[0m \u001b[0;34m%\u001b[0m \u001b[0mstage\u001b[0m \u001b[0;32mor\u001b[0m \u001b[0mTrainerFn\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mFITTING\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mstage\u001b[0m \u001b[0;32min\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mTrainerFn\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mFITTING\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mhasattr\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"train_data\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mlogger\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdebug\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Train data already exists. Skipping setup.\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mreturn\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtrain_data\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mMVTecLOCODataset\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mroot\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mroot\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mcategory\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcategory\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0msplit\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mSPLIT_TRAIN\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mpre_process\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpre_process_train\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mtask\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtask\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mimread_strategy\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mimread_strategy\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mstage\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0mTrainerFn\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mVALIDATING\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mhasattr\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"val_data\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mlogger\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdebug\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Validation data already exists. Skipping setup.\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mreturn\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mval_data\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mMVTecLOCODataset\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mroot\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mroot\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mcategory\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcategory\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mpre_process\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpre_process_val\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0msplit\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mSPLIT_VALIDATION\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mtask\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtask\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mimread_strategy\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mimread_strategy\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mstage\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0mTrainerFn\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mTESTING\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mhasattr\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"test_data\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mlogger\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdebug\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Test data already exists. Skipping setup.\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mreturn\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtest_data\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mMVTecLOCODataset\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mroot\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mroot\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mcategory\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcategory\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mpre_process\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpre_process_val\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0msplit\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mSPLIT_TEST\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mtask\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtask\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mimread_strategy\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mimread_strategy\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mstage\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0mTrainerFn\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mPREDICTING\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mhasattr\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"inference_data\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mlogger\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdebug\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Inference data already exists. Skipping setup.\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mreturn\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0minference_data\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mInferenceDataset\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mpath\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mroot\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mimage_size\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mimage_size\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtransform_config\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtransform_config_val\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mtrain_dataloader\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0mTRAIN_DATALOADERS\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"\"\"Get train dataloader.\"\"\"\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0mhasattr\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"train_data\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mraise\u001b[0m \u001b[0mRuntimeError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Train data not setup. Did you run `datamodule.setup('fit')`?\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mDataLoader\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtrain_data\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mshuffle\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mTrue\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mbatch_size\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtrain_batch_size\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mnum_workers\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mnum_workers\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mval_dataloader\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0mEVAL_DATALOADERS\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"\"\"Get validation dataloader.\"\"\"\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0mhasattr\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"val_data\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mraise\u001b[0m \u001b[0mRuntimeError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Validation data not setup. Did you run `datamodule.setup('validate')`?\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mDataLoader\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mval_data\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mshuffle\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mFalse\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mbatch_size\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtest_batch_size\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mnum_workers\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mnum_workers\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mtest_dataloader\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0mEVAL_DATALOADERS\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"\"\"Get test dataloader.\"\"\"\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0mhasattr\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"test_data\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mraise\u001b[0m \u001b[0mRuntimeError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Test data not setup. Did you run `datamodule.setup('test')`?\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mDataLoader\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtest_data\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mshuffle\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mFalse\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mbatch_size\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtest_batch_size\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mnum_workers\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mnum_workers\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mpredict_dataloader\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0mEVAL_DATALOADERS\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m\"\"\"Get predict dataloader.\"\"\"\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0mhasattr\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"inference_data\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mraise\u001b[0m \u001b[0mRuntimeError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Inference data not setup. Did you run `datamodule.setup('predict')`?\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mDataLoader\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0minference_data\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mshuffle\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mFalse\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mbatch_size\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtest_batch_size\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mnum_workers\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mnum_workers\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mFile:\u001b[0m ~/repos/anomalib/anomalib/data/mvtec_loco.py\n", + "\u001b[0;31mType:\u001b[0m type\n", + "\u001b[0;31mSubclasses:\u001b[0m \n" + ] + } + ], "source": [ "MVTecLOCO??" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 40, "metadata": {}, "outputs": [], "source": [ @@ -294,9 +1572,38 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 41, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/jcasagrandebertoldo/repos/anomalib/anomalib/data/mvtec_loco.py:794: UserWarning: Preloading images into memory. If your dataset is too large, consider using another imread_strategy instead.\n", + " self.train_data = MVTecLOCODataset(\n" + ] + }, + { + "data": { + "text/plain": [ + "dict_keys(['image'])" + ] + }, + "execution_count": 41, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "torch.Size([32, 3, 200, 340])" + ] + }, + "execution_count": 41, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# Train images\n", "\n", @@ -310,9 +1617,60 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 44, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "dict_keys(['image', 'label', 'image_path', 'anotype', 'super_anotype', 'mask_paths', 'masks', 'mask'])" + ] + }, + "execution_count": 44, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "torch.Size([32, 3, 200, 340])" + ] + }, + "execution_count": 44, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "torch.Size([32, 200, 340])" + ] + }, + "execution_count": 44, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "torch.Size([32, 200, 340])" + ] + }, + "execution_count": 44, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "('good', 'good')" + ] + }, + "execution_count": 44, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# Validation images\n", "mvtec_datamodule.setup(\"validate\")\n", @@ -320,14 +1678,98 @@ "data.keys()\n", "data[\"image\"].shape\n", "data[\"mask\"].shape\n", + "data[\"masks\"].shape\n", "data[\"super_anotype\"][0], data[\"anotype\"][0]" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 46, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "dict_keys(['image', 'label', 'image_path', 'anotype', 'super_anotype', 'mask_paths', 'masks', 'mask'])" + ] + }, + "execution_count": 46, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "torch.Size([32, 3, 200, 340])" + ] + }, + "execution_count": 46, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "torch.Size([32, 200, 340])" + ] + }, + "execution_count": 46, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "tensor([0., 1.], dtype=torch.float64)" + ] + }, + "execution_count": 46, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "torch.Size([32, 200, 340])" + ] + }, + "execution_count": 46, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "tensor([ 0., 1., 2., 3., 4., 5., 6., 7., 8., 9., 10., 11., 12., 13.,\n", + " 14., 15.], dtype=torch.float64)" + ] + }, + "execution_count": 46, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "('logical_anomalies', 'additional_1_pushpin')" + ] + }, + "execution_count": 46, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "" + ] + }, + "execution_count": 46, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# Test images\n", "mvtec_datamodule.setup(\"test\")\n", @@ -338,6 +1780,9 @@ "data.keys()\n", "data[\"image\"].shape\n", "data[\"mask\"].shape\n", + "data[\"mask\"].unique()\n", + "data[\"masks\"].shape\n", + "data[\"masks\"].unique()\n", "data[\"super_anotype\"][0], data[\"anotype\"][0]\n", "\n", "img = ToPILImage()(Denormalize()(data[\"image\"][0].clone()))\n", @@ -345,23 +1790,6 @@ "\n", "Image.fromarray(np.vstack((np.array(img), np.array(msk))))" ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "TODO: show that the ground truth is divided in multiple images\n", - "\n", - "TODO: create issue to correct docs in mvtec, e.g. it should not give example in the docstring but send the user\n", - " to the notebooks (more maintainable)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "As can be seen above, creating the dataloaders are pretty straghtforward, which could be directly used for training/testing/inference." - ] } ], "metadata": { From 13ab40f4bd014fd9f619c06230cd44078ba280e1 Mon Sep 17 00:00:00 2001 From: jpcbertoldo <24547377+jpcbertoldo@users.noreply.github.com> Date: Sun, 11 Sep 2022 14:22:13 +0200 Subject: [PATCH 38/38] add unit tests for mvtec loco --- anomalib/data/mvtec_loco.py | 32 -------------------------------- 1 file changed, 32 deletions(-) diff --git a/anomalib/data/mvtec_loco.py b/anomalib/data/mvtec_loco.py index d5274ddab9..1dd8f6cf3c 100644 --- a/anomalib/data/mvtec_loco.py +++ b/anomalib/data/mvtec_loco.py @@ -861,35 +861,3 @@ def predict_dataloader(self) -> EVAL_DATALOADERS: return DataLoader( self.inference_data, shuffle=False, batch_size=self.test_batch_size, num_workers=self.num_workers ) - - -# next -# correct the multi-image ground truth -# then show it in the notebook -# then create unit tests -# next -# next -# next -# next -# next -# next -# next -# next -# next -# next -# next -# next -# next -# next -# next -# next -# next -# next -# next -# next -# next -# next -# next -# next -# next -# next