
conda create -n anomalib_env python=3.10
conda activate anomalib_envpip install -U torchvision einops FrEIA timm open_clip_torch imgaug lightning kornia openvino git+https://github.com/openvinotoolkit/anomalib.gitpip install -U git+https://github.com/voxel51/fiftyone.gitpip install -U huggingface_hub umap-learn git+https://github.com/openai/CLIP.gitimport fiftyone as fo # base library and app
import fiftyone.brain as fob # ML methods
import fiftyone.zoo as foz # zoo datasets and models
from fiftyone import ViewField as F # helper for defining views
import fiftyone.utils.huggingface as fouh # Hugging Face integrationdataset = fouh.load_from_hub("Voxel51/mvtec-ad", persistent=True, overwrite=True)session = fo.launch_app(dataset)
model = foz.load_zoo_model("clip-vit-base32-torch") # load the CLIP model from the zoo
# Compute embeddings for the anomaly dataset
dataset.compute_embeddings(
model=model, embeddings_field="clip_embeddings", batch_size=64
)
# Dimensionality reduction using UMAP on the embeddings
fob.compute_visualization(
dataset, embeddings="clip_embeddings", method="umap", brain_key="clip_vis"
)
import numpy as np
import os
from pathlib import Path
from PIL import Image
from torchvision.transforms.v2 import Resize
from anomalib import TaskType
from anomalib.data.image.folder import Folder
from anomalib.deploy import ExportType, OpenVINOInferencer
from anomalib.engine import Engine
from anomalib.models import Padim, PatchcoreOBJECT = "bottle" ## object to train on
ROOT_DIR = Path("/tmp/mvtec_ad") ## root directory to store data for anomalib
TASK = TaskType.SEGMENTATION ## task type for the model
IMAGE_SIZE = (256, 256) ## preprocess image size for uniformitydef create_datamodule(object_type, transform=None):
## Build transform
if transform is None:
transform = Resize(IMAGE_SIZE, antialias=True)
normal_data = dataset.match(F("category.label") == object_type).match(
F("split") == "train"
)
abnormal_data = (
dataset.match(F("category.label") == object_type)
.match(F("split") == "test")
.match(F("defect.label") != "good")
)
normal_dir = Path(ROOT_DIR) / object_type / "normal"
abnormal_dir = ROOT_DIR / object_type / "abnormal"
mask_dir = ROOT_DIR / object_type / "mask"
# create directories if they do not exist
os.makedirs(normal_dir, exist_ok=True)
os.makedirs(abnormal_dir, exist_ok=True)
os.makedirs(mask_dir, exist_ok=True)
if not os.path.exists(str(normal_dir)):
normal_data.export(
export_dir=str(normal_dir),
dataset_type=fo.types.ImageDirectory,
export_media="symlink",
)
for sample in abnormal_data.iter_samples():
base_filename = sample.filename
dir_name = os.path.dirname(sample.filepath).split("/")[-1]
new_filename = f"{dir_name}_{base_filename}"
if not os.path.exists(str(abnormal_dir / new_filename)):
## symlink anomalous image into Anomalib abnormal dir
os.symlink(sample.filepath, str(abnormal_dir / new_filename)) if not os.path.exists(str(mask_dir / new_filename)):
## symlink mask into Anomalib mask dir
os.symlink(sample.defect_mask.mask_path, str(mask_dir / new_filename))
## Create a DataModule in Anomalib
datamodule = Folder(
name=object_type,
root=ROOT_DIR,
normal_dir=normal_dir,
abnormal_dir=abnormal_dir,
mask_dir=mask_dir,
task=TASK,
transform=transform
)
datamodule.setup()
return datamoduledef train_and_export_model(object_type, model, transform=None):
## Train model on our data
datamodule = create_datamodule(object_type, transform=transform)
engine = Engine(task=TASK)
engine.fit(model=model, datamodule=datamodule)
## Export model into OpenVINO format for fast inference
engine.export(
model=model,
export_type=ExportType.OPENVINO,
)
output_path = Path(engine.trainer.default_root_dir)
openvino_model_path = output_path / "weights" / "openvino" / "model.bin"
metadata = output_path / "weights" / "openvino" / "metadata.json" ## Load the inference object from export
inferencer = OpenVINOInferencer(
path=openvino_model_path,
metadata=metadata,
device="CPU",
)
return inferencerPaDiMe first. The training process should take less than a minute:model = Padim()
inferencer = train_and_export_model(OBJECT, model)## get the test split of the dataset
test_split = dataset.match(F("category.label") == OBJECT).match(F("split") == "test")
## get the first sample from the test split
test_image = Image.open(test_split.first().filepath)
output = inferencer.predict(image=test_image)
print(output)ImageResult(image=[[[255 255 255]
[255 255 255]
[255 255 255]
...
[255 255 255]
[255 255 255]
[255 255 255]]
...
[255 255 255] [255 255 255]
[255 255 255]]], pred_score=0.7751642969087686, pred_label=1, anomaly_map=[[0.32784402 0.32784402 0.32784414 ... 0.3314721 0.33147204 0.33147204]
[0.32784402 0.32784402 0.32784414 ... 0.3314721 0.33147204 0.33147204]
[0.32784408 0.32784408 0.3278442 ... 0.33147222 0.33147216 0.33147216]
...
[0.32959 0.32959 0.32959005 ... 0.3336093 0.3336093 0.3336093 ]
[0.3295899 0.3295899 0.32958996 ... 0.33360928 0.33360928 0.33360928]
[0.3295899 0.3295899 0.32958996 ... 0.33360928 0.33360928 0.33360928]], gt_mask=None, gt_boxes=None, pred_boxes=None, box_labels=None, pred_mask=[[0 0 0 ... 0 0 0]
[0 0 0 ... 0 0 0]
[0 0 0 ... 0 0 0]
...
[0 0 0 ... 0 0 0]
[0 0 0 ... 0 0 0]
[0 0 0 ... 0 0 0]], heat_map=[[[153 235 255]
[153 235 255]
[153 235 255]
...
[153 236 255]
[153 236 255]
[153 236 255]]
...
[153 238 255]
[153 238 255]
[153 238 255]]], segmentations=[[[255 255 255]
[255 255 255]
[255 255 255]
...
[255 255 255]
[255 255 255]
[255 255 255]]
...
[255 255 255]
[255 255 255]
[255 255 255]]])def run_inference(sample_collection, inferencer, key, threshold=0.5):
for sample in sample_collection.iter_samples(autosave=True, progress=True):
output = inferencer.predict(image=Image.open(sample.filepath))
conf = output.pred_score
anomaly = "normal" if conf < threshold else "anomaly"
sample[f"pred_anomaly_score_{key}"] = conf
sample[f"pred_anomaly_{key}"] = fo.Classification(label=anomaly)
sample[f"pred_anomaly_map_{key}"] = fo.Heatmap(map=output.anomaly_map)
sample[f"pred_defect_mask_{key}"] = fo.Segmentation(mask=output.pred_mask)run_inference(test_split, inferencer, "padim")
session = fo.launch_app(view=test_split)
for sample in test_split.iter_samples(autosave=True, progress=True):
if sample["defect"].label == "good":
sample["defect_mask"] = fo.Segmentation(
mask=np.zeros_like(sample["pred_defect_mask_padim"].mask)
)old_labels = test_split.distinct("defect.label")
label_map = {label:"anomaly" for label in old_labels if label != "good"}
label_map["good"] = "normal"
mapped_view = test_split.map_labels("defect", label_map)
session.view = mapped_view.view()eval_classif_padim = mapped_view.evaluate_classifications(
"pred_anomaly_padim",
gt_field="defect",
eval_key="eval_classif_padim",
method="binary",
classes=["normal", "anomaly"],
)
eval_classif_padim.print_report()
precision recall f1-score support
normal 0.95 0.90 0.92 20
anomaly 0.97 0.98 0.98 63
accuracy 0.96 83
macro avg 0.96 0.94 0.95 83
weighted avg 0.96 0.96 0.96 83
eval_seg_padim = mapped_view.evaluate_segmentations(
"pred_defect_mask_padim",
gt_field="defect_mask",
eval_key="eval_seg_padim",
)
eval_seg_padim.print_report(classes=[0, 255])
precision recall f1-score support
0 0.99 0.96 0.98 63343269.0
255 0.60 0.89 0.72 3886731.0
micro avg 0.96 0.96 0.96 67230000.0
macro avg 0.80 0.93 0.85 67230000.0
weighted avg 0.97 0.96 0.96 67230000.0
## Train Patchcore model and run inference
model = Patchcore()
## This will take a little longer to train, but should still be < 5 minutes
inferencer = train_and_export_model(OBJECT, model)
run_inference(mapped_view, inferencer, "patchcore")
## Evaluate Patchcore model on classification task
eval_classif_patchcore = mapped_view.evaluate_classifications(
"pred_anomaly_patchcore",
gt_field="defect",
eval_key="eval_classif_patchcore",
method="binary",
classes=["normal", "anomaly"],
)
eval_classif_patchcore.print_report()
precision recall f1-score support
normal 0.95 1.00 0.98 20
anomaly 1.00 0.98 0.99 63
accuracy 0.99 83
macro avg 0.98 0.99 0.98 83
weighted avg 0.99 0.99 0.99 83
eval_seg_patchcore = mapped_view.match(F("defect.label") == "anomaly").evaluate_segmentations(
"pred_defect_mask_patchcore",
gt_field="defect_mask",
eval_key="eval_seg_patchcore",
)
eval_seg_patchcore.print_report(classes=[0, 255])
session.view = mapped_view.shuffle().view() precision recall f1-score support
0 0.99 0.95 0.97 47143269.0
255 0.60 0.85 0.70 3886731.0
micro avg 0.95 0.95 0.95 51030000.0
macro avg 0.80 0.90 0.84 51030000.0
weighted avg 0.96 0.95 0.95 51030000.0

Β© 2026 Voxel51 All Rights Reserved