Binary visual defect detector (good / bad) for industrial quality control, built on the MVTec AD transistor subset.
| Phase | Method | Mean F1 | Mean AUROC | Notes |
|---|---|---|---|---|
| A | DINOv2-base linear probe (logReg, Optuna-tuned) | ~0.76 | ~0.98 | Production backbone; cut 0.046 = mean of FAR@5% fold cuts |
| B | PatchCore + WideResNet-50 | ~0.60 | ~0.89 | Beats ResNet-18 bank; FAR-calibrated cuts |
| C | EfficientAD (em size) |
~0.77 | ~0.97 | Best single model; fold 3 reaches F1 1.0 |
| D | Fusion: DINOv2 + EfficientAD (stacked LR) | ~0.81 | ~0.99 | Best overall operating point |
| F | Frozen fusion D-alpha + rescue head | ~0.82 | ~0.99 | Rescue hits recall 1.0 on all 5 folds |
| G | Repeated 5×5 CV + inference-only stress | ~0.80 | ~0.99 | Stress: brightness/shift/zoom/crop; honest report at n=40 |
| H | Geometric normalization + TTA | recall 1.0 | — | Centroid-aligned data/interim/transistor_norm/; TTA holds recall, FAR varies |
| E | Threshold sweep + per-defect autopsy | F1 0.87 | — | global-max cut: recall 0.925 @ FAR 0.029 |
DINOv3 variants were evaluated as gated backbones but DINOv2-base (public) won on stability and stayed the production pick.
notebooks/industrial_sam31.py (SAM3.1 sam3.1_multiplex.pt + DINOv2-masked + EfficientAD-medium 5000 steps + alpha fusion, models/transistor_binary/prod_sam31/):
| Method | AUROC | Recall (BAD) | FAR | Notes |
|---|---|---|---|---|
| DINO-masked + LogReg | 0.9977 | 1.000 | 0.109 | Best single arm; cut q95 train-GOOD |
| EfficientAD (original) | 0.9773 | 0.875 | 0.091 | Lowest FAR |
| Fusion (alpha=0.05) | 0.9955 | 1.000 | 0.127 | EffAD-dominated; McNemar dino-vs-fused p=1.0 |
SAM runs lazy with unload_sam() after viz (7.2 GB → 0.01 GB VRAM) so DINO/EffAD train uncontended; §8 reloads it transparently for live inference. Demo: GOOD→GOOD, BAD→BAD.
data/processed/transistor_binary/ (tracked with Git LFS) is derived from MVTec AD transistor:
good/— 273 images,bad/— 40 images, pluslabels.csv(filename,label,defect,source,original_path)- Defect types:
bent_lead,cut_lead,damaged_case,misplaced - The original MVTec archive (~5 GB) is not versioned; re-derive with your own download (research-only license, see below)
Requires Python 3.12 and uv. Torch ships with CUDA 12.8 via the pinned pytorch-cu128 index in pyproject.toml — do not remove it.
uv sync
cp .env.example .env # add HF_TOKEN only if you need gated HF models (DINOv3)
uv run marimo edit --no-token --port 2718 notebooks/fase_a.py # always from repo root (or `make notebooks`)
uv run mlflow ui # sqlite:///mlflow.db, experiment "transistor-binary"Verify GPU: uv run python -c "import torch; print(torch.__version__, torch.cuda.is_available())" → +cu128 True.
Each notebooks/fase_*.py is a marimo notebook (run, don't import).
Metrics/CSVs land in reports/, figures in reports/figures/, model files in models/:
| Notebook | What it does | Key outputs |
|---|---|---|
eda.py |
Exploratory analysis of the binary split | reports/figures/mlfigs/ |
fase_a.py |
DINOv2/DINOv3 embeddings + Optuna + logistic probe | reports/fase_a_fold_metrics*.csv, reports/fase_a_optuna*.csv, models/thresholds_dinov2_folds.json |
fase_b.py |
PatchCore memory banks (R18 / WRN-50) | reports/fase_b_fold_metrics_*.csv, models/thresholds_patchcore_*.json |
fase_c.py |
EfficientAD via anomalib (em / es) |
reports/fase_c_fold_metrics_*.csv, models/thresholds_efficientad_*.json |
fase_d.py |
Score fusion: alpha-blend + stacked LR | reports/fase_d_fold_metrics.csv |
fase_e.py |
Operating-point sweep + per-defect autopsy | reports/fase_e_sweep.csv, reports/fase_e_autopsia.csv |
fase_f.py |
Frozen D-alpha fusion + rescue head | reports/fase_f_fold_metrics.csv, models/thresholds.json |
fase_g.py |
Robustness: repeated 5×5 CV + inference stress | reports/fase_g_repeated.csv, reports/fase_g_stress.csv |
fase_h.py |
Geometric norm (data/interim/transistor_norm/) + TTA eval |
reports/fase_h_repeated.csv, reports/fase_h_tta.csv |
industrial_master.py |
Reusable GOOD/BAD template consolidating the A–H winner (DINOv2 + LogReg + EfficientAD-medium + D-alpha + local q99 rescue). Also exported as industrial_master.ipynb |
models/<project>/ (cache, OOF, onnx/, config_freeze.json), reports/figures/<project>/ |
industrial_sam.py |
SAM3 + DINO-masked + EffAD-original + fusion, ONNX export | models/transistor_binary/prod/ |
industrial_sam31.py |
SAM3.1 multiplex (ultralytics) variant of the above, lazy VRAM. Also exported as industrial_sam31.ipynb |
models/transistor_binary/prod_sam31/, reports/figures/transistor_binary_sam31/ |
Reuse with a new part: point PROJECT_NAME/DATASET_DIR (§1) at dataset/{labels.csv,good/,bad/} — everything else (splits, train-only calibration, namespaced caches, MLflow-by-name) is generic. Current verdict on transistor (repeated 5×5): rescue BAD recall 1.000±0.000 (25/25 folds), FAR 0.097 → below the default pilot bar, see summary cell.
Pilot inference without notebooks: uv run python -m betterclasificator.modeling.train --logreg --bank
(materializes models/), then uv run python -m betterclasificator.modeling.predict IMG....
Metrics and thresholds are committed; bulky artifacts (models/*.pt, *.ckpt, results*/, mlruns/, mlflow.db) are git-ignored and regenerated by re-running the notebooks.
uv.lockis committed;uv syncrestores the exact env.data/processed/transistor_binary/+labels.csvare in LFS so folds/splits rebuild identically.- Notebook convention: one marimo cell per step; re-running cascades downstream (see
AGENTS.md).
- Code: MIT (see
LICENSE). - Data: MVTec AD is released under CC BY-NC-SA 4.0 — research / non-commercial use only.
data/processed/transistor_binary/inherits that restriction.