Feature pipelines¶
A feature pipeline turns each image into an embedding with a frozen pretrained model, preprocesses it and scores it with a one-class detector fitted on normal images only:
from imgad import AnomalyDetectionPipeline, HFFeatureExtractor, build_detector
pipe = AnomalyDetectionPipeline(HFFeatureExtractor("facebook/dinov2-base", pooling="cls+mean"),
build_detector("mahalanobis"), preprocessing=["l2"])
Feature extractors¶
HFFeatureExtractor(model_name, pooling="auto", layer=None, normalize=True, ...) works with any
vision model on the Hub that loads with AutoModel:
| Family | Examples | Default pooling |
|---|---|---|
| CLIP-style dual encoders | openai/clip-*, google/siglip-*, google/siglip2-*, facebook/metaclip-*, kakaobrain/align-base, laion/CLIP-* (HF format) |
projection → get_image_features |
| Vision backbones | facebook/dinov2-*, google/vit-*, microsoft/beit-*, facebook/convnext-*, microsoft/resnet-* |
pooler |
pooling options: auto, projection, pooler, cls, mean (mean of patch tokens), cls+mean.
layer selects intermediate layers (layer=-4, or layer=[6, 9, 12] to concatenate several), which
often capture local defects better than the more semantic last layer.
For open_clip checkpoints (hf-hub:laion/..., EVA-CLIP, DataComp…):
OpenCLIPFeatureExtractor("hf-hub:laion/CLIP-ViT-B-32-laion2B-s34B-b79K").
timm models (including Hub checkpoints such as the pathology models UNI or Virchow2):
TimmFeatureExtractor("hf-hub:org/name", pooling="auto" | "cls" | "mean" | "cls+mean", timm_kwargs={...}), or
{"type": "timm", ...} in specs. Classes in timm_kwargs are given as import paths ("torch.nn.SiLU") so that
configurations stay serialisable. Ready-made medical recipes: build_extractor("zoo:uni"), see the
model zoo.
Gated or private models (DINOv3, UNI, CONCH…) need a token: see Gated Hugging Face models.
Preprocessing¶
List of steps applied before the detector (fitted on the normal data):
"l2", "standard", "center", "pca:0.95" (explained variance) or "pca:64" (components),
"pca_whiten:<n>", or any sklearn transformer.
Detectors¶
All detectors share the same API (fit, score, predict) and the convention higher score = more
anomalous.
| Name | Method | Notes |
|---|---|---|
knn |
Distance to the k nearest normal samples | Very strong baseline with CLIP/DINO; metric="cosine" |
mahalanobis / gaussian |
Mahalanobis distance (Ledoit-Wolf, OAS, MCD) | Very good with foundation-model features |
centroid |
Distance to the mean embedding | Minimal cost |
kmeans |
Distance to the nearest centroid | Multimodal normality |
lof |
Local Outlier Factor (novelty mode) | |
gmm |
Negative log-likelihood under a Gaussian mixture | |
kde |
Negative KDE log-density | Better after pca:<n> to few dimensions |
ocsvm |
One-Class SVM | |
iforest |
Isolation Forest | |
pca |
PCA reconstruction error | |
ensemble |
Mean of normalised detectors (z-score or rank) | |
sklearn |
Wraps any sklearn estimator | SklearnDetector(estimator=EllipticEnvelope()) |
pyod |
Any PyOD model | {"name": "pyod", "model": "ECOD"} |
zero_shot |
Text prompts (+ nearest normal images when there are some) | Vision–language extractors only; zero- and few-shot (guide) |
Threshold: after fit, threshold_ is set to the threshold_quantile (0.95 by default) of
out-of-sample scores of the normal data: leave-one-out for kNN/LOF and 5-fold cross-validation for
the rest. This avoids the optimistic threshold of detectors that overfit the training set (KDE, OCSVM,
PCA…). It can be disabled with det.calibration = "train".
Extending¶
Registering your own detector or extractor makes it available by name in Python, YAML and the CLI (see
examples/extending.py):