Skip to content

Commit edcbb67

Browse files
authored
Add MVTec LOCO dataset (#2607)
* Add MVTec LOCO torch dataset implementation Signed-off-by: Samet Akcay <samet.akcay@intel.com> * Add MVTec LOCO lightning datamodule implementation Signed-off-by: Samet Akcay <samet.akcay@intel.com> * Add relative imports Signed-off-by: Samet Akcay <samet.akcay@intel.com> * Add example script Signed-off-by: Samet Akcay <samet.akcay@intel.com> * Add mvtec loco config file Signed-off-by: Samet Akcay <samet.akcay@intel.com> * Setup validation set in datamodule Signed-off-by: Samet Akcay <samet.akcay@intel.com> * Update dataset implementation Signed-off-by: Samet Akcay <samet.akcay@intel.com> * Add mvtec loco tests Signed-off-by: Samet Akcay <samet.akcay@intel.com> * Remove custom read_mask method Signed-off-by: Samet Akcay <samet.akcay@intel.com> * Remove v0 to v1 upgrade tests Signed-off-by: Samet Akcay <samet.akcay@intel.com> * Remove v0 to v1 upgrade scipts Signed-off-by: Samet Akcay <samet.akcay@intel.com> --------- Signed-off-by: Samet Akcay <samet.akcay@intel.com>
1 parent b545697 commit edcbb67

File tree

12 files changed

+681
-403
lines changed

12 files changed

+681
-403
lines changed

examples/api/02_data/mvtec_loco.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
"""Example showing how to use the MVTec LOCO dataset with Anomalib.
2+
3+
MVTec LOCO is a dataset for detecting logical and structural anomalies in images.
4+
It contains 5 categories of industrial objects with various types of defects.
5+
"""
6+
7+
# Copyright (C) 2025 Intel Corporation
8+
# SPDX-License-Identifier: Apache-2.0
9+
10+
from anomalib.data import MVTecLOCO
11+
12+
# 1. Basic Usage
13+
# Load a specific category with default settings
14+
datamodule = MVTecLOCO(
15+
root="./datasets/MVTec_LOCO",
16+
category="breakfast_box",
17+
)
18+
datamodule.prepare_data()
19+
datamodule.setup()
20+
i, data = next(enumerate(datamodule.test_dataloader()))
21+
22+
23+
# 2. Advanced Configuration
24+
# Customize data loading and preprocessing
25+
datamodule = MVTecLOCO(
26+
root="./datasets/MVTec_LOCO",
27+
category="juice_bottle",
28+
train_batch_size=32,
29+
eval_batch_size=32,
30+
num_workers=8,
31+
val_split_mode="from_test", # Create validation set from test set
32+
val_split_ratio=0.5, # Use 50% of test set for validation
33+
)
34+
35+
# 3. Using Multiple Categories
36+
# Train on multiple categories (if supported by the model)
37+
for category in ["breakfast_box", "juice_bottle", "pushpins"]:
38+
category_data = MVTecLOCO(
39+
root="./datasets/MVTec_LOCO",
40+
category=category,
41+
)
42+
# Use category_data with your model...
43+
44+
# 4. Accessing Dataset Properties
45+
# Get information about the dataset
46+
print(f"Number of training samples: {len(datamodule.train_data)}")
47+
print(f"Number of validation samples: {len(datamodule.val_data)}")
48+
print(f"Number of test samples: {len(datamodule.test_data)}")
49+
50+
# 5. Working with Data Samples
51+
# Get a sample from the dataset
52+
sample = datamodule.train_data[0]
53+
print("\nSample keys:", list(sample.__dict__.keys()))
54+
print("Image shape:", sample.image.shape if sample.image is not None else None)
55+
print("Mask shape:", sample.gt_mask.shape if sample.gt_mask is not None else None)
56+
print("Label:", sample.gt_label)
57+
58+
# 6. Using with a Model
59+
# Example of using the datamodule with a model
60+
from anomalib.engine import Engine # noqa: E402
61+
from anomalib.models import Patchcore # noqa: E402
62+
63+
# Initialize model
64+
model = Patchcore(backbone="wide_resnet50_2", layers=["layer3"], coreset_sampling_ratio=0.1)
65+
66+
# Train using the Engine
67+
engine = Engine()
68+
engine.fit(model=model, datamodule=datamodule)
69+
70+
# Get predictions
71+
predictions = engine.predict(model=model, datamodule=datamodule)
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
class_path: anomalib.data.MVTecLOCO
2+
init_args:
3+
root: ./datasets/MVTec_LOCO
4+
category: breakfast_box
5+
train_batch_size: 32
6+
eval_batch_size: 32
7+
num_workers: 8
8+
test_split_mode: from_dir
9+
test_split_ratio: 0.2
10+
val_split_mode: same_as_test
11+
val_split_ratio: 0.5
12+
seed: null

src/anomalib/data/__init__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
# Datamodules
5050
from .datamodules.base import AnomalibDataModule
5151
from .datamodules.depth import DepthDataFormat, Folder3D, MVTec3D
52-
from .datamodules.image import BTech, Datumaro, Folder, ImageDataFormat, Kolektor, MVTec, MVTecAD, Visa
52+
from .datamodules.image import BTech, Datumaro, Folder, ImageDataFormat, Kolektor, MVTec, MVTecAD, MVTecLOCO, Visa
5353
from .datamodules.video import Avenue, ShanghaiTech, UCSDped, VideoDataFormat
5454

5555
# Datasets
@@ -61,6 +61,7 @@
6161
FolderDataset,
6262
KolektorDataset,
6363
MVTecADDataset,
64+
MVTecLOCODataset,
6465
VisaDataset,
6566
)
6667
from .datasets.video import AvenueDataset, ShanghaiTechDataset, UCSDpedDataset
@@ -163,6 +164,8 @@ def get_datamodule(config: DictConfig | ListConfig | dict) -> AnomalibDataModule
163164
"MVTec", # Include MVTec for backward compatibility
164165
"MVTecAD",
165166
"MVTecADDataset",
167+
"MVTecLOCO",
168+
"MVTecLOCODataset",
166169
"Visa",
167170
"VisaDataset",
168171
# Video

src/anomalib/data/datamodules/image/__init__.py

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
- ``Folder``: Custom folder structure with normal/abnormal images
99
- ``Kolektor``: Kolektor Surface-Defect Dataset
1010
- ``MVTecAD``: MVTec Anomaly Detection Dataset
11+
- ``MVTecLOCO``: MVTec LOCO Dataset with logical and structural anomalies
1112
- ``Visa``: Visual Anomaly Dataset
1213
1314
Example:
@@ -29,23 +30,29 @@
2930
from .datumaro import Datumaro
3031
from .folder import Folder
3132
from .kolektor import Kolektor
33+
from .mvtec_loco import MVTecLOCO
3234
from .mvtecad import MVTec, MVTecAD
3335
from .visa import Visa
3436

3537

3638
class ImageDataFormat(str, Enum):
3739
"""Supported Image Dataset Types.
3840
39-
The following dataset formats are supported:
41+
The following dataset formats are supported:
4042
41-
- ``BTECH``: BTech Surface Defect Dataset
42-
- ``DATUMARO``: Dataset in Datumaro format
43-
- ``FOLDER``: Custom folder structure
44-
- ``FOLDER_3D``: Custom folder structure for 3D images
45-
- ``KOLEKTOR``: Kolektor Surface-Defect Dataset
46-
- ``MVTEC_AD``: MVTec AD Dataset
47-
- ``MVTEC_3D``: MVTec 3D AD Dataset
48-
- ``VISA``: Visual Anomaly Dataset
43+
- ``BTECH``: BTech Surface Defect Dataset
44+
- ``DATUMARO``: Dataset in Datumaro format
45+
- ``FOLDER``: Custom folder structure
46+
- ``FOLDER_3D``: Custom folder structure for 3D images
47+
- ``KOLEKTOR``: Kolektor Surface-Defect Dataset
48+
- ``MVTEC_AD``: MVTec AD Dataset
49+
- ``MVTEC_3D``: MVTec 3D AD Dataset
50+
<<<<<<< HEAD
51+
- ``MVTEC_LOCO``: MVTec LOCO Dataset
52+
- ``VISA``: Visual Inspection for Steel Anomaly Dataset
53+
=======
54+
- ``VISA``: Visual Anomaly Dataset
55+
>>>>>>> b5456978dd2e513cfdd1e4c2aedce0286050ba80
4956
"""
5057

5158
BTECH = "btech"
@@ -55,6 +62,7 @@ class ImageDataFormat(str, Enum):
5562
KOLEKTOR = "kolektor"
5663
MVTEC_AD = "mvtecad"
5764
MVTEC_3D = "mvtec_3d"
65+
MVTEC_LOCO = "mvtec_loco"
5866
VISA = "visa"
5967

6068

@@ -65,5 +73,6 @@ class ImageDataFormat(str, Enum):
6573
"Kolektor",
6674
"MVTecAD",
6775
"MVTec", # Include both for backward compatibility
76+
"MVTecLOCO",
6877
"Visa",
6978
]
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
"""MVTec LOCO Data Module.
2+
3+
This module provides a PyTorch Lightning DataModule for the MVTec LOCO dataset. The
4+
dataset contains 5 categories of industrial objects with both normal and
5+
anomalous samples. Each category includes RGB images and pixel-level ground truth
6+
masks for anomaly segmentation.
7+
8+
The dataset distinguishes between structural anomalies (local defects) and
9+
logical anomalies (global defects).
10+
11+
Example:
12+
Create a MVTec LOCO datamodule::
13+
14+
>>> from anomalib.data import MVTecLOCO
15+
>>> datamodule = MVTecLOCO(
16+
... root="./datasets/MVTec_LOCO",
17+
... category="breakfast_box"
18+
... )
19+
20+
Notes:
21+
The dataset will be automatically downloaded and converted to the required
22+
format when first used. The directory structure after preparation will be::
23+
24+
datasets/
25+
└── MVTec_LOCO/
26+
├── breakfast_box/
27+
├── juice_bottle/
28+
└── ...
29+
30+
License:
31+
MVTec LOCO dataset is released under the Creative Commons
32+
Attribution-NonCommercial-ShareAlike 4.0 International License
33+
(CC BY-NC-SA 4.0).
34+
https://creativecommons.org/licenses/by-nc-sa/4.0/
35+
36+
Reference:
37+
Bergmann, P., Fauser, M., Sattlegger, D., & Steger, C. (2022).
38+
MVTec LOCO - A Dataset for Detecting Logical Anomalies in Images.
39+
In IEEE/CVF Conference on Computer Vision and Pattern Recognition (CVPR),
40+
2022.
41+
"""
42+
43+
# Copyright (C) 2025 Intel Corporation
44+
# SPDX-License-Identifier: Apache-2.0
45+
46+
import logging
47+
from pathlib import Path
48+
49+
from torchvision.transforms.v2 import Transform
50+
51+
from anomalib.data.datamodules.base.image import AnomalibDataModule
52+
from anomalib.data.datasets.image.mvtec_loco import MVTecLOCODataset
53+
from anomalib.data.utils import Split, TestSplitMode, ValSplitMode
54+
55+
logger = logging.getLogger(__name__)
56+
57+
58+
class MVTecLOCO(AnomalibDataModule):
59+
"""MVTec LOCO Datamodule.
60+
61+
Args:
62+
root (Path | str): Path to the root of the dataset.
63+
Defaults to ``"./datasets/MVTec_LOCO"``.
64+
category (str): Category of the MVTec LOCO dataset (e.g. ``"breakfast_box"`` or
65+
``"juice_bottle"``). Defaults to ``"breakfast_box"``.
66+
train_batch_size (int, optional): Training batch size.
67+
Defaults to ``32``.
68+
eval_batch_size (int, optional): Test batch size.
69+
Defaults to ``32``.
70+
num_workers (int, optional): Number of workers.
71+
Defaults to ``8``.
72+
train_augmentations (Transform | None): Augmentations to apply to the training images
73+
Defaults to ``None``.
74+
val_augmentations (Transform | None): Augmentations to apply to the validation images.
75+
Defaults to ``None``.
76+
test_augmentations (Transform | None): Augmentations to apply to the test images.
77+
Defaults to ``None``.
78+
augmentations (Transform | None): General augmentations to apply if stage-specific
79+
augmentations are not provided.
80+
test_split_mode (TestSplitMode): Method to create test set.
81+
Defaults to ``TestSplitMode.FROM_DIR``.
82+
test_split_ratio (float): Fraction of data to use for testing.
83+
Defaults to ``0.2``.
84+
val_split_mode (ValSplitMode): Method to create validation set.
85+
Defaults to ``ValSplitMode.SAME_AS_TEST``.
86+
val_split_ratio (float): Fraction of data to use for validation.
87+
Defaults to ``0.5``.
88+
seed (int | None, optional): Seed for reproducibility.
89+
Defaults to ``None``.
90+
91+
Example:
92+
Create MVTec LOCO datamodule with default settings::
93+
94+
>>> datamodule = MVTecLOCO()
95+
>>> datamodule.setup()
96+
>>> i, data = next(enumerate(datamodule.train_dataloader()))
97+
>>> data.keys()
98+
dict_keys(['image_path', 'label', 'image', 'mask_path', 'mask'])
99+
100+
>>> data["image"].shape
101+
torch.Size([32, 3, 256, 256])
102+
103+
Change the category::
104+
105+
>>> datamodule = MVTecLOCO(category="juice_bottle")
106+
107+
Create validation set from test data::
108+
109+
>>> datamodule = MVTecLOCO(
110+
... val_split_mode=ValSplitMode.FROM_TEST,
111+
... val_split_ratio=0.1
112+
... )
113+
114+
Create synthetic validation set::
115+
116+
>>> datamodule = MVTecLOCO(
117+
... val_split_mode=ValSplitMode.SYNTHETIC,
118+
... val_split_ratio=0.2
119+
... )
120+
"""
121+
122+
def __init__(
123+
self,
124+
root: Path | str = "./datasets/MVTec_LOCO",
125+
category: str = "breakfast_box",
126+
train_batch_size: int = 32,
127+
eval_batch_size: int = 32,
128+
num_workers: int = 8,
129+
train_augmentations: Transform | None = None,
130+
val_augmentations: Transform | None = None,
131+
test_augmentations: Transform | None = None,
132+
augmentations: Transform | None = None,
133+
test_split_mode: TestSplitMode | str | None = None,
134+
test_split_ratio: float | None = None,
135+
val_split_mode: ValSplitMode | str | None = None,
136+
val_split_ratio: float | None = None,
137+
seed: int | None = None,
138+
) -> None:
139+
super().__init__(
140+
train_batch_size=train_batch_size,
141+
eval_batch_size=eval_batch_size,
142+
num_workers=num_workers,
143+
train_augmentations=train_augmentations,
144+
val_augmentations=val_augmentations,
145+
test_augmentations=test_augmentations,
146+
augmentations=augmentations,
147+
test_split_mode=test_split_mode,
148+
test_split_ratio=test_split_ratio,
149+
val_split_mode=val_split_mode,
150+
val_split_ratio=val_split_ratio,
151+
seed=seed,
152+
)
153+
154+
self.root = Path(root)
155+
self.category = category
156+
157+
def _setup(self, _stage: str | None = None) -> None:
158+
"""Set up the datasets and perform dynamic subset splitting.
159+
160+
This method may be overridden in subclass for custom splitting behaviour.
161+
162+
Note:
163+
The stage argument is not used here. This is because, for a given
164+
instance of an AnomalibDataModule subclass, all three subsets are
165+
created at the first call of setup(). This is to accommodate the
166+
subset splitting behaviour of anomaly tasks, where the validation set
167+
is usually extracted from the test set, and the test set must
168+
therefore be created as early as the `fit` stage.
169+
"""
170+
# MVTec LOCO provides a training set that contains only normal images.
171+
self.train_data = MVTecLOCODataset(
172+
split=Split.TRAIN,
173+
root=self.root,
174+
category=self.category,
175+
)
176+
177+
# MVTec LOCO provides a validation set that contains only normal images.
178+
self.val_data = MVTecLOCODataset(
179+
split=Split.VAL,
180+
root=self.root,
181+
category=self.category,
182+
)
183+
184+
# MVTec LOCO provides a test set that contains both normal and anomalous images.
185+
# Anomalous images are further divided into structural and logical anomalies.
186+
self.test_data = MVTecLOCODataset(
187+
split=Split.TEST,
188+
root=self.root,
189+
category=self.category,
190+
)

src/anomalib/data/datasets/image/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
- ``FolderDataset``: Custom dataset from folder structure
99
- ``KolektorDataset``: Kolektor surface defect dataset
1010
- ``MVTecADDataset``: MVTec AD dataset with industrial objects
11+
- ``MVTecLOCODataset``: MVTec LOCO dataset with logical and structural anomalies
1112
- ``VisaDataset``: Visual Anomaly dataset
1213
1314
Example:
@@ -26,6 +27,7 @@
2627
from .datumaro import DatumaroDataset
2728
from .folder import FolderDataset
2829
from .kolektor import KolektorDataset
30+
from .mvtec_loco import MVTecLOCODataset
2931
from .mvtecad import MVTecADDataset, MVTecDataset
3032
from .visa import VisaDataset
3133

@@ -36,5 +38,6 @@
3638
"KolektorDataset",
3739
"MVTecDataset",
3840
"MVTecADDataset",
41+
"MVTecLOCODataset",
3942
"VisaDataset",
4043
]

0 commit comments

Comments
 (0)