Skip to main content
Anomaly scoring is the final stage of PatchCore where test image patches are compared against the memory bank to compute both image-level anomaly scores and pixel-level segmentation masks. This is achieved through efficient nearest neighbor search using FAISS.

Overview

The scoring process converts feature distances into interpretable anomaly measures:
1

Feature Extraction

Extract patch embeddings from the test image using the same pipeline as training
2

Nearest Neighbor Search

Find the k-nearest neighbors in the memory bank for each test patch
3

Distance Computation

Compute Euclidean distances to nearest neighbors
4

Score Aggregation

Aggregate patch scores into image-level scores and pixel-level masks

NearestNeighbourScorer

The core scoring class that manages the memory bank and computes anomaly scores:
common.py

Key Parameters

int
default:"1"
Number of nearest neighbors to consider for scoring. Typical values:
  • 1 (default): Uses distance to single nearest neighbor - most common
  • 3-5: Averages distances to multiple neighbors - more robust but slower
FaissNN
The nearest neighbor search backend. Options:
  • FaissNN(on_gpu=False): CPU-based exact search
  • FaissNN(on_gpu=True): GPU-accelerated exact search (recommended)
  • ApproximateFaissNN(): Approximate search for very large memory banks

FAISS Integration

PatchCore uses Facebook’s FAISS library for efficient similarity search:
common.py

Index Creation

common.py
IndexFlatL2 performs exact nearest neighbor search using L2 (Euclidean) distance. This ensures optimal detection quality.

Fitting the Memory Bank

common.py
common.py
FAISS’s search() method returns both distances and indices of nearest neighbors. Distances are used for anomaly scores, indices for interpretability.

Prediction Pipeline

The predict() method of NearestNeighbourScorer computes anomaly scores:
common.py

Score Computation

For each test patch, the anomaly score is simply the distance to its nearest neighbor:
Interpretation: Higher distance = more anomalous (further from normal training patches)
When using k>1, scores are averaged:
Benefit: More robust to noise, but slightly slower

From Patches to Images

PatchCore computes both patch-level and image-level scores:
patchcore.py

Image-Level Scoring

The PatchMaker.score() method aggregates patch scores using max pooling:
patchcore.py
Max pooling is used instead of average pooling because anomalies are often localized. A single highly anomalous patch should flag the entire image.

Segmentation Mask Generation

The RescaleSegmentor converts patch scores into smooth segmentation masks:
common.py

Segmentation Pipeline

1

Spatial Reshaping

Convert flat patch scores back to 2D grid (e.g., 56×56)
2

Bilinear Upsampling

Interpolate to original image resolution (e.g., 224×224)
3

Gaussian Smoothing

Apply Gaussian filter with σ=4 for smooth boundaries
The smoothing step is crucial for visualization and removes blocky artifacts from patch-based scoring.

Feature Merging

Before scoring, features from multiple layers are concatenated:
common.py
When using multiple backbone layers (e.g., layer2 + layer3), their features are flattened and concatenated before distance computation.

ApproximateFaissNN

For very large memory banks (>1M patches), approximate search can be used:
common.py

IndexIVFPQ Parameters

int
default:"512"
Number of Voronoi cells for inverted index. More centroids = better accuracy but slower.
int
default:"64"
Number of sub-quantizers for product quantization. Higher = better approximation.
int
default:"8"
Bits per sub-quantizer code. Typically 8 bits (256 values).
IndexIVFPQ requires a training step and provides approximate results. Only use when exact search is too slow.

Complete Scoring Example

Here’s a complete example of the scoring process:

Scoring Metrics

PatchCore is evaluated using several metrics:
Area under ROC curve for binary classification (normal vs. anomalous)Target: >99% for MVTec AD
Area under ROC curve for pixel-wise anomaly localizationTarget: >98% for MVTec AD
Per-Region-Overlap score measuring localization qualityTarget: >95% for MVTec AD

Optimization Tips

GPU Acceleration: Always use --faiss_on_gpu for datasets with >10k memory bank patches:
This can provide 10-50× speedup for nearest neighbor search.
Batch Inference: Process multiple images together to maximize GPU utilization:

Saving and Loading

The scorer can be saved and loaded for deployment:
common.py
Saved files:
  • nnscorer_search_index.faiss: FAISS index containing memory bank
  • nnscorer_features.pkl (optional): Raw feature vectors
  • patchcore_params.pkl: Model configuration

Visualization Example

Next Steps

PatchCore Algorithm

Review the complete algorithm pipeline

Quick Start

Start building your own anomaly detector