Official configurable implementation of:
- GRD-Net (2023): Generative-Reconstructive-Discriminative anomaly detection with ROI-aware attention.
- DeepIndustrial-SN (2026): runtime-optimized configuration focused on the generative branch for fast patch-level decisions.
This repository now provides one YAML-configurable pipeline with two paper profiles, a full PyTorch backend, and a TensorFlow/Keras scaffold API for future parity.
Source documents in this repository:
docs/International_Journal_of_Intelligent_Systems_-_2023_-_Ferrari_-_GRD‐Net.pdfdocs/DeepIndustrial_SN.pdf
Core design:
- Generator
Gwith encoder-decoder-encoder topology. - Discriminator
Cfor adversarial feature matching. - Discriminative U-Net
Δfor anomaly map prediction. - ROI attention through intersection in discriminative loss.
Paper-level equations implemented as profile defaults:
Synthetic perturbation:
Adversarial feature loss:
Discriminator objective (AMP-safe logits form used in code):
Contextual loss:
Encoder consistency:
Generator objective:
ROI intersection for discriminative supervision:
Total full-profile objective:
where FL is focal loss.
Default implementation policy for this profile:
- contextual base term uses
L1 - noise regularization term
L_{nse}is disabled - discriminator branch uses
BCEWithLogitsLoss(equivalent to sigmoid + BCE, but AMP-safe)
This profile keeps the generative adversarial core, with deployment-oriented constraints and score computation.
Perturbation mechanism:
Noise regularization term:
Contextual update (Huber + SSIM):
Anomaly score:
Heatmap:
with per-sample min-max normalization.
Default implementation policy for this profile:
- contextual base term uses
Huber - noise regularization term
L_{nse}is enabled - discriminator branch uses
BCEWithLogitsLosson logits
Top-level package (grdnet/) is layered and modular:
grdnet/config: strict typed schema + YAML loader.grdnet/core: deterministic logging, reproducibility, exceptions.grdnet/data: MVTec-like adapter, custom MVTec-like extension point, patch extraction.grdnet/models/pytorch: residual generator/discriminator and U-Net segmentator.grdnet/models/tensorflow: scaffold placeholders for API parity planning.grdnet/losses: centralized objective definitions.grdnet/backends: strategy/factory backend abstraction.grdnet/training: loop + checkpoints.grdnet/inference: calibrate/eval/infer workflows.grdnet/metrics: thresholding and score metrics.grdnet/reporting: structured console + CSV outputs.grdnet/pipeline: command runners.
flowchart LR
X[Input patch X] --> P[Perturbation Pq]
P --> XN[Noisy patch Xn]
XN --> G[Generator G EDE]
G --> XR[Reconstruction X hat]
G --> ZR[Latent z hat]
XN --> E1[Latent z]
X --> D[Discriminator C]
XR --> D
D --> FR[Feature on X]
D --> FF[Feature on X hat]
D --> PR[Real fake logit]
X --> LC[Context loss L con]
XR --> LC
E1 --> LE[Encoder loss L enc]
ZR --> LE
FR --> LA[Adversarial loss L adv]
FF --> LA
N[Noise branch N M beta] --> LN[Noise loss L nse optional]
XR --> LN
XN --> LN
LA --> LG[Generator loss L gen]
LC --> LG
LE --> LG
LN --> LG
flowchart LR
X[Input patch X] --> P[Perturbation Pq]
P --> XN[Noisy patch Xn]
XN --> G[Generator G]
G --> XR[Reconstruction X hat]
G --> ZR[Latent z hat]
XN --> Z[Latent z]
X --> D[Discriminator C]
XR --> D
D --> FCX[Feature C on X]
D --> FCXR[Feature C on X hat]
XN --> CAT[Concat Xn and X hat]
XR --> CAT
CAT --> U[Discriminative U Net Delta]
U --> AD[Anomaly map A discr]
ROI[ROI mask] --> I[Intersection I]
AD --> I
I --> FL[Focal loss FL]
X --> LCON[Context loss L con]
XR --> LCON
Z --> LENC[Encoder loss L enc]
ZR --> LENC
FCX --> LADV[Adversarial loss L adv]
FCXR --> LADV
LADV --> LGEN[Generator loss L gen]
LCON --> LGEN
LENC --> LGEN
LGEN --> LTOT[Total loss L tot]
FL --> LTOT
Current official adapter supports:
mvtec/
bottle/
train/
good/
*.png
test/
good/
*.png
defect_type/
*.png
ground_truth/
defect_type/
*_mask.png
capsule/
...
Configured split roots remain explicit in YAML (data.train_dir, data.test_dir, data.mask_dir, ...). The
official profile defaults now target the benchmark root symlink directly:
train_dir: ./mvtec
test_dir: ./mvtec
calibration_dir: ./mvtec
mask_dir: null
When a split root points to MVTec benchmark root (mvtec/<category>/... layout), commands now run
per-category automatically: one model/checkpoint/report set per category.
Per-category configuration is also supported:
train_dir: mvtec/<category>/train
test_dir: mvtec/<category>/test
mask_dir: mvtec/<category>/ground_truth
Notes:
- Training can be
nominal_train_only=true. - Official profile defaults use automatic device selection (
backend.device: "auto"). roi_rootmissing defaults ROI to all ones.- Missing ROI mask falls back to full-image ROI (all ones) with warning logs.
- Missing ground-truth mask falls back to all-zero mask and image is treated as good (label 0), with warning logs.
patch_sizeandpatch_strideaccept either scalar values or explicit pairs[h, w].inference.run_acceptance_ratio=0.0disables image-level acceptance voting and keeps patch-level thresholding only.
Provided configs:
configs/profiles/grdnet_2023_full.yamlconfigs/profiles/deepindustrial_sn_2026.yaml
DeepIndustrial-SN profile defaults keep the paper-aligned architecture
(base_features: 128, stages: [3,3,3,3]) while using a conservative
data.batch_size: 4 default for commodity 5-6 GB GPUs and enabling
backend.mixed_precision: true on CUDA. For paper-scale runs on larger GPUs,
set data.batch_size: 32.
Stage-resampling policy is now explicit and configurable:
model.encoder_downsample_position:"last"(paper-canonical) or"first"(ablation).model.decoder_upsample_position:"last"(paper-canonical) or"first"(ablation).
Backend stubs:
configs/backends/pytorch.yamlconfigs/backends/tensorflow_scaffold.yaml
Runtime minimums:
- Python
>= 3.11 - Torch
>= 2.7.0
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
pip install -e .Validate configuration:
grdnet validate-config -c configs/profiles/grdnet_2023_full.yamlTrain:
grdnet train -c configs/profiles/grdnet_2023_full.yamlCalibrate threshold:
grdnet calibrate -c configs/profiles/deepindustrial_sn_2026.yaml --checkpoint artifacts/checkpoints/deepindustrial_sn_2026Evaluate:
grdnet eval -c configs/profiles/deepindustrial_sn_2026.yaml --checkpoint artifacts/checkpoints/deepindustrial_sn_2026Infer:
grdnet infer -c configs/profiles/deepindustrial_sn_2026.yaml --checkpoint artifacts/checkpoints/deepindustrial_sn_2026Run from repository root:
python main.py validate-config -c configs/profiles/deepindustrial_sn_2026.yaml
python main.py train -c configs/profiles/deepindustrial_sn_2026.yaml
python main.py calibrate -c configs/profiles/deepindustrial_sn_2026.yaml --checkpoint artifacts/checkpoints/deepindustrial_sn_2026
python main.py eval -c configs/profiles/deepindustrial_sn_2026.yaml --checkpoint artifacts/checkpoints/deepindustrial_sn_2026
python main.py infer -c configs/profiles/deepindustrial_sn_2026.yaml --checkpoint artifacts/checkpoints/deepindustrial_sn_2026Use this mode when you want one model for one MVTec class (for example hazelnut).
- Copy the profile YAML and rename it (example):
cp configs/profiles/deepindustrial_sn_2026.yaml configs/profiles/deepindustrial_sn_2026_hazelnut.yaml- In
configs/profiles/deepindustrial_sn_2026_hazelnut.yaml, set category-specific roots and artifacts:
data:
train_dir: "./mvtec/hazelnut"
val_dir: null
test_dir: "./mvtec/hazelnut"
calibration_dir: "./mvtec/hazelnut"
mask_dir: null
training:
checkpoint_dir: "./artifacts/checkpoints/deepindustrial_sn_2026/hazelnut"
output_dir: "./artifacts/reports/deepindustrial_sn_2026/hazelnut"- Run full pipeline:
CFG=configs/profiles/deepindustrial_sn_2026_hazelnut.yaml
python main.py validate-config -c "$CFG"
python main.py train -c "$CFG"
CKPT=$(ls -1 artifacts/checkpoints/deepindustrial_sn_2026/hazelnut/epoch_*.pt | sort | tail -n 1)
echo "$CKPT"
python main.py calibrate -c "$CFG" --checkpoint "$CKPT"
python main.py eval -c "$CFG" --checkpoint "$CKPT"
python main.py infer -c "$CFG" --checkpoint "$CKPT"- Data usage in this mode:
- Training reads
./mvtec/hazelnut/train/good/*only (nominal_train_only=true). - Calibration reads
./mvtec/hazelnut/test/*and masks from./mvtec/hazelnut/ground_truth/*. - Evaluation and inference use the same
testandground_truthroots.
- Output locations in this mode:
- Checkpoints:
artifacts/checkpoints/deepindustrial_sn_2026/hazelnut/epoch_XXXX.pt - Metrics/predictions:
artifacts/reports/deepindustrial_sn_2026/hazelnut/*.csv
Use this mode for benchmark protocol: one independent model per category across the full dataset.
- Keep benchmark-root paths in YAML:
data:
train_dir: "./mvtec"
test_dir: "./mvtec"
calibration_dir: "./mvtec"
mask_dir: null- Run commands once; runner auto-discovers categories and loops:
CFG=configs/profiles/deepindustrial_sn_2026.yaml
python main.py validate-config -c "$CFG"
python main.py train -c "$CFG"
python main.py calibrate -c "$CFG" --checkpoint artifacts/checkpoints/deepindustrial_sn_2026
python main.py eval -c "$CFG" --checkpoint artifacts/checkpoints/deepindustrial_sn_2026
python main.py infer -c "$CFG" --checkpoint artifacts/checkpoints/deepindustrial_sn_2026- Category discovery rule:
- A folder is treated as one category only if it contains all three subfolders:
train/,test/, andground_truth/. - Example discovered categories:
bottle,capsule,hazelnut,zipper, etc.
- Per-category data mapping in benchmark mode:
- Train on
mvtec/<category>/train/good/*(with defaultnominal_train_only=true). - Calibrate on
mvtec/<category>/test/*with masks resolved frommvtec/<category>/ground_truth/*. - Evaluate/infer on the same category
testsplit.
- Checkpoint argument behavior in benchmark mode:
- Directory mode (recommended):
--checkpoint artifacts/checkpoints/deepindustrial_sn_2026The runner loads latestepoch_*.ptfrom each category subfolder. - Template mode (explicit):
--checkpoint "artifacts/checkpoints/deepindustrial_sn_2026/{category}/epoch_0010.pt"
- Artifact structure in benchmark mode:
artifacts/
checkpoints/
deepindustrial_sn_2026/
bottle/epoch_XXXX.pt
capsule/epoch_XXXX.pt
...
reports/
deepindustrial_sn_2026/
bottle/metrics.csv
bottle/predictions.csv
capsule/metrics.csv
capsule/predictions.csv
...
- Expected benchmark log signals:
benchmark_mode command=train categories=...benchmark_category_start command=<train|calibrate|eval|infer> category=<name>benchmark_category_done ...
Troubleshooting:
backend.device: "auto"probes CUDA. If CUDA initialization fails, the backend now reports a deterministic error message and falls back to CPU.- On 5-6 GB GPUs, start with
backend.mixed_precision: trueanddata.batch_size: 4. - If training is OOM or cuBLAS init fails, lower
data.batch_size(for example4 -> 2 -> 1) and setPYTORCH_CUDA_ALLOC_CONF=expandable_segments:True.
Artifacts are written under training.output_dir and training.checkpoint_dir from YAML:
-
In benchmark-root mode, artifacts are nested by category (for example
.../bottle,.../zipper). -
metrics.csv: epoch and evaluation metrics. -
predictions.csv: patch-wise scores/predictions plus image-level aggregation fields (image_prediction,anomalous_patch_ratio). -
train_batch_previews/epoch_XXXX_step_YYYY.png: 4-row train preview composite (X,X*, Perlin maskM,X_hat) captured from configured train steps. -
epoch_XXXX.pt: full training checkpoints.
Train preview controls are configurable from YAML:
reporting:
train_batch_preview:
enabled: true
every_n_epochs: 1
step_index: 1
max_images: 8
subdir: "train_batch_previews"Evaluation output metrics include:
f1,accuracy,precision,recalltp,tn,fp,fnauroc,ap(average_precision)balanced_accuracy,tpr,tnr
pytorch: full v1 implementation.tensorflow_scaffold: API placeholder only. Commands requiring compute raise deterministicNotImplementedErrorwith explicit guidance.
Determinism controls are first-class config fields:
system.seedsystem.deterministic
The implementation seeds Python, NumPy, and PyTorch RNGs and configures deterministic backend behavior when requested.
Primary references implemented in this repo:
- Ferrari et al. (2023), GRD-Net: Generative-Reconstructive-Discriminative Anomaly Detection with Region of Interest Attention Module, International Journal of Intelligent Systems. PDF in
docs/International_Journal_of_Intelligent_Systems_-_2023_-_Ferrari_-_GRD‐Net.pdf. - Ferrari et al. (2026), DeepIndustrial_SN (runtime-focused GRD-Net variant). PDF in
docs/DeepIndustrial_SN.pdf.
Secondary conceptual references used by both papers and reflected in this codebase:
- GANomaly-style E-D-E reconstruction consistency.
- DRÆM-inspired synthetic perturbation via Perlin-mask anomaly simulation.
- SSIM-guided reconstruction quality and heatmap-based anomaly localization.
The rebuild is structured for strict gates:
- lint (
ruff) - type checks (
mypy) - tests (
pytest) - dependency vulnerability audit (
pip-audit) - unsafe-code scan (
bandit) - coverage gate (
pytest --cov ... --cov-fail-under=90)
- Full scientific/engineering implementation is currently PyTorch-first.
- TensorFlow/Keras is scaffolded for interface stability and future parity work.
- Public dataset support currently targets MVTec-style structures.