# How to Implement Anomaly Detection with PatchCore or PaDiM in ailia-models

> Learn how to implement anomaly detection using PatchCore or PaDiM in ailia-models. Discover feature extraction, compression, and nearest-neighbor distance for powerful anomaly identification.

- Repository: [axinc-ai/ailia-models](https://github.com/axinc-ai/ailia-models)
- Tags: tutorial
- Published: 2026-02-26

---

**Both PatchCore and PaDiM extract multi-layer features from a pre-trained ResNet, compress them via random projection, build a compact core-set using K-Center Greedy sampling, and detect anomalies by measuring nearest-neighbor distances in a FAISS index.**

The ailia-models repository by axinc-ai provides production-ready implementations of these unsupervised visual anomaly detection methods. This guide explains how to implement PatchCore and PaDiM using the repository's modular Python API, covering feature extraction, core-set construction, and inference pipelines.

## Understanding the PatchCore and PaDiM Pipeline

Both methods follow the identical unsupervised pipeline implemented in [`anomaly_detection/patchcore/patchcore_utils.py`](https://github.com/axinc-ai/ailia-models/blob/main/anomaly_detection/patchcore/patchcore_utils.py) and [`anomaly_detection/padim/padim_utils.py`](https://github.com/axinc-ai/ailia-models/blob/main/anomaly_detection/padim/padim_utils.py).

### Feature Extraction Backbone

The implementation uses ONNX models of pre-trained **ResNet-18** or **Wide-ResNet-50-2** backbones. Features are extracted from the outputs of intermediate layers `layer2` and `layer3`, then concatenated per spatial location to create high-dimensional embedding vectors.

### Dimensionality Reduction and Core-Set Sampling

Embeddings are compressed using `sklearn.random_projection.SparseRandomProjection` to preserve distances via the Johnson-Lindenstrauss lemma. The **K-Center Greedy** algorithm (`k_center_greedy.KCenterGreedy`) then selects a representative subset—defaulting to 0.1% of training patches—and stores them in a FAISS index.

### Nearest-Neighbor Anomaly Scoring

During inference, each test patch queries the FAISS index (`faiss.IndexFlatL2`) to retrieve k-nearest neighbors. The distance to the closest neighbor becomes the patch-level anomaly score, which is normalized, blurred with a Gaussian filter, and up-sampled to the original image size.

## Project Structure and Key Files

| Path | Description |
|------|-------------|
| [`anomaly_detection/patchcore/patchcore.py`](https://github.com/axinc-ai/ailia-models/blob/main/anomaly_detection/patchcore/patchcore.py) | CLI driver for PatchCore |
| [`anomaly_detection/patchcore/patchcore_utils.py`](https://github.com/axinc-ai/ailia-models/blob/main/anomaly_detection/patchcore/patchcore_utils.py) | Core implementation: preprocessing, embedding concatenation, K-Center Greedy, FAISS indexing, inference, and visualization |
| [`anomaly_detection/patchcore/README.md`](https://github.com/axinc-ai/ailia-models/blob/main/anomaly_detection/patchcore/README.md) | Quick-start guide and command-line examples |
| [`anomaly_detection/padim/padim.py`](https://github.com/axinc-ai/ailia-models/blob/main/anomaly_detection/padim/padim.py) | CLI driver for PaDiM |
| [`anomaly_detection/padim/padim_utils.py`](https://github.com/axinc-ai/ailia-models/blob/main/anomaly_detection/padim/padim_utils.py) | PaDiM utility functions (identical logic to PatchCore) |
| [`anomaly_detection/padim/README.md`](https://github.com/axinc-ai/ailia-models/blob/main/anomaly_detection/padim/README.md) | Usage instructions for PaDiM |
| [`util/model_utils.py`](https://github.com/axinc-ai/ailia-models/blob/main/util/model_utils.py) | `check_and_download_models()` for ONNX weights |
| [`util/detector_utils.py`](https://github.com/axinc-ai/ailia-models/blob/main/util/detector_utils.py) | `load_image()` helper |
| [`util/arg_utils.py`](https://github.com/axinc-ai/ailia-models/blob/main/util/arg_utils.py) | Shared argument parsing |

## Implementing PatchCore

The following example demonstrates the complete workflow using [`anomaly_detection/patchcore/patchcore_utils.py`](https://github.com/axinc-ai/ailia-models/blob/main/anomaly_detection/patchcore/patchcore_utils.py):

```python
import ailia
from anomaly_detection.patchcore.patchcore_utils import (
    training, infer, normalize_score_maps, calculate_anomal_scores,
    visualize, get_params,
)
from util.model_utils import check_and_download_models
from util.detector_utils import load_image
import cv2
import pickle

# ── 0. Choose backbone and parameters ──────────────────────────────────────

arch = "wide_resnet50_2"          # or "resnet18"

n_neighbors = 9
weight_path, model_path, params = get_params(arch, n_neighbors)

# ── 1. Download model files if missing ───────────────────────────────────────

REMOTE_PATH = "https://storage.googleapis.com/ailia-models/patchcore/"
check_and_download_models(weight_path, model_path, REMOTE_PATH)

# ── 2. Create the Ailia net ---------------------------------------------------

net = ailia.Net(model_path, weight_path, env_id=0)

# ── 3. Train on normal images (no anomalies) -------------------------------

train_dir = "./anomaly_detection/patchcore/train"   # folder of only “good” samples

embedding_coreset = training(
    net, params,
    size=256, keep_aspect=True,
    batch_size=32,
    train_dir=train_dir,
    aug=False, aug_num=1,
    coreset_sampling_ratio=0.001,
    logger=ailia.utils.logging.getLogger(__name__),
)

# ── 4. (Optional) Save the core‑set for later reuse ───────────────────────────

with open("patchcore_train.pkl", "wb") as f:
    pickle.dump(embedding_coreset, f)

# ── 5. Load a test image ----------------------------------------------------

test_path = "./anomaly_detection/patchcore/bottle_000.png"
img = load_image(test_path)                      # BGR‑A format

img = cv2.cvtColor(img, cv2.COLOR_BGRA2RGB)      # → RGB

# Preprocess must match training

from anomaly_detection.patchcore.patchcore_utils import preprocess
img_pre = preprocess(img, size=256, keep_aspect=True)

# ── 6. Inference ------------------------------------------------------------

score_map = infer(net, params, embedding_coreset, img_pre)

# ── 7. Normalise & visualise -----------------------------------------------

scores = normalize_score_maps([score_map])[0]      # (H, W) in [0, 1]

anomaly_score = calculate_anomal_scores([score_map])[0]
heat, mask, overlay = visualize(
    img,                     # original (H,W,3) uint8

    scores,
    threshold=0.5,           # set manually or compute from GT

)

cv2.imwrite("patchcore_heat.png", heat.astype("uint8"))
cv2.imwrite("patchcore_mask.png", mask.astype("uint8"))
cv2.imwrite("patchcore_overlay.png", overlay)
print(f"Image‑level anomaly score = {anomaly_score:.4f}")

```

## Implementing PaDiM

PaDiM follows the identical API, importing from [`anomaly_detection/padim/padim_utils.py`](https://github.com/axinc-ai/ailia-models/blob/main/anomaly_detection/padim/padim_utils.py) instead:

```python
import ailia
from anomaly_detection.padim.padim_utils import (
    training, infer, normalize_score_maps,
    calculate_anomal_scores, visualize, get_params,
)
from util.model_utils import check_and_download_models
from util.detector_utils import load_image
import cv2, pickle

# 0. Choose backbone

arch = "resnet18"
n_neighbors = 9
weight_path, model_path, params = get_params(arch, n_neighbors)

# 1. Download model files

REMOTE_PATH = "https://storage.googleapis.com/ailia-models/padim/"
check_and_download_models(weight_path, model_path, REMOTE_PATH)

# 2. Net

net = ailia.Net(model_path, weight_path, env_id=0)

# 3. Training

train_dir = "./anomaly_detection/padim/train"
embedding_coreset = training(
    net, params,
    size=256, keep_aspect=True,
    batch_size=32,
    train_dir=train_dir,
    aug=False, aug_num=1,
    coreset_sampling_ratio=0.001,
    logger=ailia.utils.logging.getLogger(__name__),
)

# 4. Save core‑set (optional)

with open("padim_train.pkl", "wb") as f:
    pickle.dump(embedding_coreset, f)

# 5. Infer on a test image

test_path = "./anomaly_detection/padim/bottle_000.png"
img = load_image(test_path)
img = cv2.cvtColor(img, cv2.COLOR_BGRA2RGB)

from anomaly_detection.padim.padim_utils import preprocess
img_pre = preprocess(img, size=256, keep_aspect=True)

score_map = infer(net, params, embedding_coreset, img_pre)

# 6. Visualise

scores = normalize_score_maps([score_map])[0]
anomaly_score = calculate_anomal_scores([score_map])[0]
heat, mask, overlay = visualize(img, scores, threshold=0.5)

cv2.imwrite("padim_heat.png", heat.astype("uint8"))
cv2.imwrite("padim_mask.png", mask.astype("uint8"))
cv2.imwrite("padim_overlay.png", overlay)
print(f"PaDiM image‑level score = {anomaly_score:.4f}")

```

## Automatic Threshold Selection

When ground-truth masks are available, compute the optimal threshold by maximizing the F1-score using `decide_threshold_from_gt_image`:

```python
from anomaly_detection.patchcore.patchcore_utils import decide_threshold_from_gt_image

gt_dir = "./anomaly_detection/patchcore/gt_masks"
threshold = decide_threshold_from_gt_image(net, params, embedding_coreset, gt_dir)
print("Optimal threshold =", threshold)

```

This function uses `sklearn.metrics.precision_recall_curve` to select the threshold that yields the highest F1-score on the validation set.

## Summary

- **PatchCore** and **PaDiM** in `ailia-models` share the same unsupervised pipeline: feature extraction from ResNet layers `layer2` and `layer3`, random projection, K-Center Greedy core-set sampling, and FAISS nearest-neighbor search.
- Entry points are [`anomaly_detection/patchcore/patchcore.py`](https://github.com/axinc-ai/ailia-models/blob/main/anomaly_detection/patchcore/patchcore.py) and [`anomaly_detection/padim/padim.py`](https://github.com/axinc-ai/ailia-models/blob/main/anomaly_detection/padim/padim.py), with reusable logic in [`patchcore_utils.py`](https://github.com/axinc-ai/ailia-models/blob/main/patchcore_utils.py) and [`padim_utils.py`](https://github.com/axinc-ai/ailia-models/blob/main/padim_utils.py).
- The API supports both CLI usage and programmatic embedding via `training()`, `infer()`, and `visualize()` functions.
- Automatic threshold selection is available when ground-truth masks are provided, using F1-score optimization.

## Frequently Asked Questions

### What is the difference between PatchCore and PaDiM in the ailia-models implementation?

Both algorithms follow the identical pipeline in this repository: multi-layer feature extraction, random projection, core-set sampling, and nearest-neighbor distance scoring. The primary distinction lies in the original research focus—PatchCore emphasizes memory-efficient core-set subsampling, while PaDiM focuses on multivariate Gaussian modeling—but the `ailia-models` implementation unifies both under the same K-Center Greedy and FAISS architecture for practical deployment.

### Which backbone architectures are supported for feature extraction?

The implementation supports **ResNet-18** and **Wide-ResNet-50-2** backbones, provided as ONNX models. Features are extracted from the outputs of intermediate layers `layer2` and `layer3`, then concatenated per spatial location to form the embedding vectors used for core-set construction.

### How does the K-Center Greedy algorithm reduce memory usage?

Instead of storing every training patch embedding, the `KCenterGreedy` class in [`patchcore_utils.py`](https://github.com/axinc-ai/ailia-models/blob/main/patchcore_utils.py) iteratively selects a subset of patches (default 0.1% of the training set) that maximally cover the embedding space. This core-set is stored in a FAISS index, reducing memory from millions of vectors to thousands while preserving detection accuracy through the Johnson-Lindenstrauss distance-preserving property of random projection.

### Can I use custom datasets without ground-truth masks for training?

Yes. Both `training()` functions require only a directory of normal (non-defective) images. Ground-truth masks are optional and used solely for automatic threshold selection via `decide_threshold_from_gt_image()`. Without masks, you must manually specify a threshold (typically 0.5) in the `visualize()` function or determine it empirically on a validation set.