---
title: "How Do You Ship Industrial Defect Detection With Only 'Good' Samples? An Anomalib Field Guide"
date: 2026-08-30
description: "anomalib on a two-GPU workstation: all 15 MVTecAD scenes trained and measured (mean image-AUROC 0.981), plus the three real bugs the sweep had to fix first."
category: "Industrial AI"
tags:
  - "anomalib"
  - "anomaly detection"
  - "industrial inspection"
  - "MVTecAD"
  - "PatchCore"
  - "OpenVINO"
verifiedHardware: "Workstation 2-GPU rig (RTX 4090D 24GB + RTX A4000 16GB) · conda anomalib_env, Python 3.10 · anomalib 2.1.0.dev0 · MVTecAD 15 scenes"
keyTakeaways:
  - "Anomalib is the OpenVINO team's open-source industrial anomaly-detection library (now under the open-edge-platform GitHub organization, v2.x): PaDiM, PatchCore, EfficientAd and other models train on normal samples only — we counted 209–391 'good' images per scene in the standard benchmark — because the paradigm learns what healthy looks like and scores everything that deviates."
  - "What we actually stood up on the workstation (2025-11-19): a conda environment, a 5-option quick_start.sh that checks GPU/env/mirror/dataset then runs train or predict, a train_all_scenes.py batch runner covering MVTecAD's 15 scene categories with per-scene timing and error logs, and three working docs (deployment guide, SOP, results interpretation)."
  - "The ledger, completed 2026-08-30: all 15 MVTec categories now have trained checkpoints and measured metrics — mean image-AUROC 0.981 (range 0.911-1.000) at one-epoch defaults, per-scene durations 29-169 s on a 16GB A4000, with three texture classes showing brittle pixel-level AUROC under untuned defaults. Completing it took fixing three real bugs first: the shipped batch script never worked (v2 CLI rejects its batch flags), the HF mirror variable is mandatory or runs stall on backbone checks, and CUDA's fastest-first ordering lands single-GPU jobs on the busiest card unless you pin by UUID."
measuredOn: 2025-11-19
faq:
  - q: "What is anomalib and who maintains it?"
    a: "An open-source deep-learning library for visual anomaly and defect detection, released by Intel's OpenVINO team and now hosted at the open-edge-platform GitHub organization (current line is v2.x). It collects state-of-the-art algorithms — PaDiM, PatchCore, EfficientAd, FastFlow, STFPM, CFlow and more — under one CLI and Python API, with export paths to OpenVINO (and TensorRT) for deployment. It grew out of the MVTec AD benchmark ecosystem and wins recurrently at industrial-inspection challenges."
  - q: "How can it train with only normal samples?"
    a: "The paradigm shift: instead of learning what defects look like (which requires labeled defect images you rarely have), the models learn a dense statistical or memory-based description of 'healthy' from good-only images, then score deviations at inference. In the standard MVTecAD benchmark, a scene's entire training set is a few hundred good images — we counted 209 for bottle, 224 for cable, 320 for screw, 391 for hazelnut. Defect images appear only at test time, for evaluation."
  - q: "How do I train all scenes in one go?"
    a: "Not with the shipped batch script — as delivered, train_all_scenes.py has never worked: the v2 CLI rejects its top-level batch flags, which is exactly why the November run stopped at 3 of 15 scenes. The working invocation is the quick_start-style command per category: `anomalib train --model Patchcore --data anomalib.data.MVTecAD --data.category <name> --data.root ./data --trainer.max_epochs 1 --trainer.accelerator gpu --trainer.devices 1` — with `HF_ENDPOINT=https://hf-mirror.com` exported and the GPU pinned by UUID (see the ledger section for why each of those is mandatory)."
  - q: "How do I use my own production-line data?"
    a: "Match the folder convention the SOP documents: train/good (normals only), test/good plus test/<defect_type>, optional ground_truth/<defect_type> masks — then point the Folder datamodule at it (`--data anomalib.data.datamodules.image.folder.FolderDataModule --data.init_args.normal_dir=\"train/good\" ...`). A CSV annotation format also exists. Swapping a real line's data in is a folder-layout exercise, not a code exercise."
  - q: "Does it work from networks that can't reach HuggingFace?"
    a: "Yes, with one environment variable: `export HF_ENDPOINT=https://hf-mirror.com` — the pretrained backbones download through the mirror. Both our scripts set it automatically; without it, first-run backbone downloads stall in mainland-China network conditions."
  - q: "What are the real pain points?"
    a: "Version churn first: our checkout is 2.1.0.dev0, and between the tutorials we followed the CLI's data-module syntax changed (`--data anomalib.data.MVTecAD --data.category X` versus `--data.init_args.category X`) — our own two docs codify both syntaxes, which is exactly what 'documentation lags the library' looks like in practice. Also expect: results directories versioning as v0/v1 with a `latest` symlink you must understand before deployment; progress invisible if a wrapper captures output (our runner logs after completion, not streaming); and docs that reference renamed modules until the next release catches up."
---
Industrial quality inspection has a data paradox: defects are rare, defect labels are rarer, and yet most supervised vision needs both. Anomalib inverts the problem — learn only what *healthy* product looks like, then flag whatever deviates. This dispatch is the field-guide version of standing that up on a real workstation: what the library is, what it does, the exact path we ran, and where it bit us — and it opens this site's Industrial AI line, where the rubric stays the same for every tool: what it is → what it does → how to run it → what went wrong. Written for engineers meeting it for the first time; every command below is the one we actually used.

## What it is

[Anomalib](https://github.com/open-edge-platform/anomalib) is the OpenVINO team's open-source library for visual anomaly and defect detection — currently v2.x under the `open-edge-platform` GitHub organization — bundling the mainstream algorithms (PaDiM, PatchCore, EfficientAd, plus FastFlow, STFpm, CFlow) behind one CLI. The unifying idea: **train on normal samples only**. No defect taxonomy, no balanced datasets, no labeling campaign. [PatchCore](https://arxiv.org/abs/2106.08265), the default workhorse, memorizes patch-level features of good product into a coreset memory and scores test images by nearest-memory distance — which is why it runs one-epoch training and still performs.

The library grew up around [MVTec AD](https://www.mvtec.com/company/research/datasets/mvtec-ad), the industry-standard benchmark of 15 industrial scene categories (bottle, cable, capsule, carpet, grid, hazelnut, leather, metal_nut, pill, screw, tile, toothbrush, transistor, wood, zipper) — textures and objects photographed as a production camera would see them.

## What that means in sample counts

The "few normal samples" claim, measured on our own disk: bottle trains from **209** good images, cable **224**, screw **320**, hazelnut **391**. That is the whole training set per scene — defect images exist only in the test split, for scoring. If your line can photograph a few hundred good parts, you have a training set.

## How we ran it

The rig: a two-GPU workstation (RTX 4090D 24GB — the card recorded in our environment snapshot, 24564 MiB as nvidia-smi reports it — plus an A4000 16GB), Ubuntu, conda environment `anomalib_env` on Python 3.10, an anomalib checkout at 2.1.0.dev0. Training runs were single-GPU (`--trainer.devices 1`).

**One script to start.** `quick_start.sh` checks the GPU, activates the conda env, sets `HF_ENDPOINT=https://hf-mirror.com` (mainland-network reality: pretrained backbones download through the mirror or not at all), verifies the dataset tree, then offers a 5-option menu — 1-epoch smoke train on bottle, full 5-epoch train, inference test against the saved checkpoint, batch-train everything, or exit.

**One script to batch.** `train_all_scenes.py` loops all 15 categories with Patchcore, defaulting to 1 epoch and batch 32 per scene, timing each run, writing per-scene success/error logs, and finishing with a summary that verifies each `model.ckpt` path under `results/Patchcore/MVTecAD/<category>/v0/weights/lightning/`.

**Three docs to hand over.** The work distilled into a deployment guide (environment from zero through training and troubleshooting), an SOP (the universal one-line template, custom-dataset wiring for both Folder and CSV formats, per-model and multi-GPU DDP variants), and a results-interpretation note (the v0/v1 versioning and the `latest` symlink — deploy from `latest/weights/lightning/model.ckpt` unless you are pinning a known-good version).

The core command, the one line everything else wraps:

```bash
anomalib train \
    --model Patchcore \
    --data anomalib.data.MVTecAD \
    --data.category bottle \
    --data.root ./data \
    --trainer.max_epochs 1 \
    --trainer.accelerator gpu \
    --trainer.devices 1
```

## The ledger — completed (2026-08-30 sweep)

The original honest gap — 3 of 15 scenes trained, durations and metrics unretained — is now closed. On 2026-08-30 a sweep on the idle A4000 trained the remaining 12 categories and read-only-evaluated the three November checkpoints (the same byte-identical files the [Mac mini deployment](/posts/2026-08-30-mac-mini-cpu-edge-deployment/) carries — untouched, evaluated in place). All 15 categories now have checkpoints and measured metrics:

| Category | image AUROC | image F1 | pixel AUROC | Source |
|---|---|---|---|---|
| bottle | 1.000 | 1.000 | 0.986 | Nov ckpt, read-only eval |
| cable | 0.986 | 0.967 | 0.985 | Nov ckpt, read-only eval |
| screw | 0.968 | 0.947 | 0.989 | Nov ckpt, read-only eval |
| capsule | 0.993 | 0.982 | 0.990 | 2026-08-30 train |
| carpet | 0.986 | 0.972 | 0.991 | 2026-08-30 train |
| grid | 0.986 | 0.957 | 0.982 | 2026-08-30 train |
| hazelnut | 0.993 | 0.988 | 0.632 | retry at eval_batch 8 |
| leather | 0.995 | 0.992 | 0.439 | 2026-08-30 train |
| metal_nut | 0.998 | 0.984 | 0.987 | 2026-08-30 train |
| pill | 0.945 | 0.950 | 0.981 | 2026-08-30 train |
| tile | 0.994 | 0.956 | 0.619 | 2026-08-30 train |
| toothbrush | 0.911 | 0.935 | 0.989 | 2026-08-30 train |
| transistor | 0.993 | 0.950 | 0.973 | 2026-08-30 train |
| wood | 0.987 | 0.959 | 0.932 | 2026-08-30 train |
| zipper | 0.976 | 0.979 | 0.981 | 2026-08-30 train |

Mean image-AUROC **0.981** (range 0.911–1.000) at one-epoch defaults — no coreset tuning, no threshold calibration. Per-scene training time on the A4000: 29 s (toothbrush) to 169 s (carpet), plus 28–38 s for each read-only evaluation. The November checkpoints remain the per-scene coreset artifacts first published — 231,212,587 bytes (bottle), 240,649,771 (cable) — unchanged by the sweep and now joined by twelve siblings of their own kind. The honest asterisks: three texture classes (leather 0.439, tile 0.619, hazelnut 0.632) show brittle **pixel-level** AUROC under these defaults — image-level detection stays healthy while localization degrades; and hazelnut's row exists only because its first attempt died of CUDA OOM at the default evaluation batch on 16 GB (it passed at `eval_batch_size 8`). For tuned reference numbers, the PatchCore paper's 99.1% image AUROC remains the authors' figure, not ours.

**What the sweep cost to actually run — three bugs, each verified by its own failure first.** One: the shipped `train_all_scenes.py` has never successfully trained anything — the v2 CLI rejects its top-level `--train_batch_size/--eval_batch_size` flags, which is why November's run stopped at 3 scenes (those came from `quick_start.sh`'s flag-free commands); the working invocation drops the batch flags. Two: without `HF_ENDPOINT=https://hf-mirror.com`, training completes and then the run stalls to death on backbone HEAD checks to huggingface.co — the mirror is mandatory even for cached weights. Three: CUDA enumerates devices fastest-first, so `--trainer.devices 1` lands on the 24 GB card that was already running a live service, not the idle 16 GB card beside it — pin with `CUDA_VISIBLE_DEVICES=<UUID>`. And the disk footprint stands as first published: 4.8 GB of MVTecAD under `datasets/` (5.0 GB working copy under `data/`), 804 MB of training artifacts — now grown by twelve scenes.

## The pitfalls (lived, not hypothetical)

**Version churn is the tax.** Our checkout is 2.1.0.dev0; between the tutorials we followed, the CLI's data-module syntax changed — older style `--data anomalib.data.MVTecAD --data.category bottle` (what our runner uses) versus v2 style `--data anomalib.data.datamodules.image.mvtecad.MVTecAD --data.init_args.category bottle` (what our SOP's universal template uses). Our two documents codify both syntaxes because both worked at different moments of the checkout's life. That *is* the "fast iteration, lagging docs" experience: search-engine answers from six months ago quietly stop applying. Pin your version; read the CLI help of the version you pinned.

**Results directories version silently.** Each retrain creates v0, v1, v2… with a `latest` symlink. Convenient — until a script grabs `latest` after an accidental retrain and your deployed model changes underneath you. Pin explicit version paths in production.

**Wrapped runs hide progress.** Our batch runner captures subprocess output to log after completion — during a long sweep the terminal sits silent. For interactive use, run the raw command; for sweeps, trust the logs.

**The mirror is not optional (here).** Without `HF_ENDPOINT=https://hf-mirror.com`, first-run backbone downloads stall. One environment variable; both our scripts set it; worth knowing it's the failure mode when someone reports "it hangs at startup."

**What we'd tell a first-timer:** expect the friction in *versions and paths*, not in GPUs or math — the recorded effort went into reconciling tutorial syntax with the installed version and understanding the results tree, not into wrangling compute.

## Who this is for

Engineers doing industrial visual QC with few or zero labeled defect samples; teams evaluating whether open-source detection is credible before buying a vision system; anyone who wants anomaly detection running locally this afternoon rather than after a labeling campaign. The pros, honestly earned: out-of-the-box models that train in one epoch on a few hundred normals, one CLI across paradigms, heatmap visualizations per test image (under `results/.../v0/images/`), and an OpenVINO export path when it's time to deploy on Intel edge hardware. The cons, equally honest: a fast-moving 2.x API surface, docs that trail it, and an ecosystem where the benchmark answers your search before your version does.

## Replication appendix

```bash
# Environment
conda create -n anomalib_env python=3.10 -y && conda activate anomalib_env
# (install anomalib per the repo's current instructions; ours was a 2.1.0.dev0 checkout)

# Dataset: MVTec AD, 15 categories, extracted so that ./data/<category>/train/good exists

# Mirror for mainland networks
export HF_ENDPOINT=https://hf-mirror.com

# Smoke train (one scene, one epoch)
anomalib train --model Patchcore --data anomalib.data.MVTecAD \
    --data.category bottle --data.root ./data \
    --trainer.max_epochs 1 --trainer.accelerator gpu --trainer.devices 1

# Inference against the checkpoint
anomalib predict --model Patchcore --data anomalib.data.MVTecAD \
    --data.category bottle --data.root ./data \
    --ckpt_path results/Patchcore/MVTecAD/bottle/v0/weights/lightning/model.ckpt \
    --return_predictions true

# Batch: python train_all_scenes.py   (15 scenes; or --category screw for one)
# ⚠ as shipped the batch script fails on v2 CLIs — use the direct command above per scene

# Read-only evaluation of an existing checkpoint (no retraining, weights untouched):
anomalib test --model Patchcore --data anomalib.data.MVTecAD \
    --data.category bottle --data.root ./data \
    --ckpt_path results/Patchcore/MVTecAD/bottle/v0/weights/lightning/model.ckpt

# 16GB-card scenes that OOM at default eval batch (hazelnut here):
#   add  --data.init_args.eval_batch_size 8
```

Artifacts produced and kept on the workstation: `quick_start.sh`, `train_all_scenes.py`, `ANOMALIB_DEPLOYMENT_GUIDE.md`, `sop.md`, `results_explanation.md` (plus a `qa.md`), checkpoints for 3 categories, per-run `config.yaml` and visualization folders. **The scripts, docs and the 15-category metrics table are now open-sourced** at [github.com/xiong1984/anomalib-mvtec-field-kit](https://github.com/xiong1984/anomalib-mvtec-field-kit) — including the v2-CLI-fixed batch trainer and the three gotchas above as its README. Numbers from this deployment are in the [/data/ ledger](/data/); this article is the narrative around them.

## Sources and method

First-party: the workstation's scripts, docs, dataset tree and result artifacts (dated 2025-11-19), inspected directly for this dispatch. Third-party, checked: the [anomalib repository](https://github.com/open-edge-platform/anomalib) (organization move, model roster), [Intel's anomalib defect-detection tutorial](https://www.intel.com/content/www/us/en/developer/articles/training/defect-detection-with-anomalib.html), the [PatchCore paper (arXiv:2106.08265)](https://arxiv.org/abs/2106.08265), and the [MVTec AD dataset page](https://www.mvtec.com/company/research/datasets/mvtec-ad). Drafted with AI assistance under human editorial direction; the AUROC figure cited is the paper authors' claim, explicitly not our measurement. Provenance note for the provenance-minded: the deployment work itself was originally set up with AI assistance on the workstation — this site discloses that where it matters.
