Thaki-FabVision-PCB-Anomaly
🇰🇷 Korean card: MODEL_CARD.ko.md
Research Preview · unsupervised visual anomaly detection for printed circuit boards · trained and tested on VisA PCB1–PCB4 only.
Four small normalizing-flow density heads (one per VisA PCB category) on top of a frozen DINOv2-L backbone. Each head is trained on normal images only and outputs a 256×256 anomaly map plus an image score. On the official VisA 1cls test split, evaluated once after the model and threshold were fixed, mean image AUROC is 0.978 and mean pixel AUROC is 0.991. These numbers describe VisA photographs. Nothing here was measured on a real SMT line or factory camera, and this card makes no claim about that setting.
0. How to use
The backbone is not redistributed. It is downloaded from facebook/dinov2-large at the pinned revision 47b73eefe95e8d44ec3623f8890bd894b6ea2d6c.
pip install torch "transformers>=4.45" safetensors pillow numpy
from huggingface_hub import snapshot_download
import sys
path = snapshot_download("ThakiCloud/Thaki-FabVision-PCB-Anomaly")
sys.path.insert(0, path)
from PIL import Image
import inference
backbone, head, threshold, device = inference.load(path, "pcb1") # pcb1 | pcb2 | pcb3 | pcb4
amap, score = inference.score(backbone, head, Image.open("board.jpg"), device)
print(score, score > threshold) # image score and the fixed decision threshold
Or from the shell: python inference.py <model_dir> pcb1 board.jpg.
Use the head that matches the board type. A head trained on one PCB model does not know what a normal board of another model looks like. The threshold in config.json is the 95th percentile of scores on held-out normal images (Normal Dev), so on data like the training data roughly 5% of normal boards are expected to be flagged.
1. What it is
| Part | Setting |
|---|---|
| Backbone | facebook/dinov2-large, frozen, input 672×672 (bicubic) |
| Features | mean of block-12 and block-18 patch tokens, 1024-d on a 48×48 grid |
| Adapter | per-channel standardization from training features (fixed, stored in the head) |
| Head | FastFlow-style 2D flow, 8 blocks (ActNorm → fixed permutation → affine coupling, 3×3 / 1×1 kernels alternating, hidden 512) |
| Output | per-position NLL → bilinear upsample to 256×256 → Gaussian blur σ=4; image score = map maximum |
| Weights | 4 × heads/pcb{1..4}.safetensors, 126 MB each |
The flow head was implemented from scratch for this backbone. It is not an Anomalib FastFlow checkpoint.
2. Results (VisA official 1cls test, evaluated once)
Model selection and threshold used only normal images: checkpoint = lowest mean NLL on Normal Dev (evaluated every 5 epochs), threshold = Normal Dev q95. Test defects and masks were not used for any choice.
| Category | Image AUROC | Image AP | Pixel AUROC | AUPRO (FPR≤0.3) | Normal FPR @thr | Defect TPR @thr |
|---|---|---|---|---|---|---|
| pcb1 | 0.973 | 0.972 | 0.997 | 0.976 | 0.080 | 0.91 |
| pcb2 | 0.970 | 0.966 | 0.988 | 0.954 | 0.040 | 0.90 |
| pcb3 | 0.985 | 0.985 | 0.988 | 0.941 | 0.079 | 0.93 |
| pcb4 | 0.985 | 0.983 | 0.992 | 0.951 | 0.050 | 0.93 |
| Mean | 0.978 | 0.976 | 0.991 | 0.956 | 0.062 | — |
Test sizes: 100 defective and 100–101 normal images per category.
Baseline on the same features. PatchCore (3×3 neighbourhood pooling, 10% greedy coreset, 1-NN) built on the same frozen DINOv2 features reached mean image AUROC 0.871, pixel AUROC 0.981, AUPRO 0.817. This is not the usual WideResNet-50 PatchCore, so the comparison only says the flow head uses these features better than nearest-neighbour scoring does. It does not say this model beats PatchCore in general.
Runtime (one NVIDIA B200, batch 1, fresh process). Median latency about 27 ms per image including the backbone, peak inference memory 1.67 GiB. Reloading the published weights reproduces the evaluation scores exactly (max relative error 0.0).
All pre-registered gates passed: image AUROC mean ≥ 0.95 and each category ≥ 0.90; pixel AUROC mean ≥ 0.95; AUPRO mean ≥ 0.80; normal FPR mean ≤ 0.10; image AUROC ≥ same-feature PatchCore − 0.03; reload error ≤ 1e-5. The criteria were committed before any data was downloaded. Aggregate numbers are in eval_summary.json.
3. Limitations
- VisA only. Four board types, one acquisition setup. A different board, camera, lens, lighting or resolution needs a new head trained on that line's normal images, and its threshold has to be set again on that line.
- Near-duplicate photos. VisA photographs the same board design repeatedly with the same camera, so train and test images are very similar (thousands of perceptual-hash near-duplicate pairs per category; no exact duplicates). Scores on a real line with drift will likely be lower.
- Training stability. The pcb4 head's validation loss jumped late in training; the selection rule picked epoch 30. Retraining with other seeds was not done.
- Threshold. About 5% normal false alarms by design. Pick an operating point for your own cost of misses versus false alarms.
- Not a defect classifier. It reports where an image looks unlike normal training boards. It does not name the defect type.
4. Data and licenses
- Training and evaluation data: VisA (Zou et al., ECCV 2022), PCB1–PCB4, CC BY 4.0. No images, masks, features, memory banks or evaluation samples are included in this repository.
- Backbone: DINOv2 (Oquab et al., 2023), Apache-2.0, downloaded from its own repository at runtime.
- Heads, inference code and card: Apache-2.0, ThakiCloud.
@inproceedings{zou2022spot,
title={SPot-the-Difference Self-Supervised Pre-training for Anomaly Detection and Segmentation},
author={Zou, Yang and Jeong, Jongheon and Pemula, Latha and Zhang, Dongqing and Dabeer, Onkar},
booktitle={ECCV}, year={2022}
}
5. Reproduce
One NVIDIA B200, about 36 minutes for all four categories (feature extraction, 60 epochs per head, PatchCore baseline, test, benchmark). Adam lr 1e-3, weight decay 1e-5, batch 16, gradient-norm clip 10, seed 0. Split: 90/10 normal train/Normal Dev, seed 0. The model code is fabvision_pcb.py.
Head SHA256: pcb1 82379ec5…5f4a16, pcb2 58568e80…a7324e, pcb3 d624df55…cf52f0bd, pcb4 5cc24871…c0778e (full values in config.json).