FDM 3D Printing Defect Detection Model Prototyping
By Eldar · Published July 11, 2026
Phase 1 data pipeline for FDM defect detection: sources ~280 frames from HuggingFace and Kaggle, applies zero-shot Grounding DINO pre-labeling across four defect classes (stringing, spaghetti, layer shift, warping).
- computer-vision
- object-detection
- 3d-printing
- defect-detection
- grounding-dino
- data-preparation
Inside this notebook
# FDM 3D-Printing Defect Detection — Phase 1: Data Sourcing & Pre-labeling This notebook builds the data foundation for an FDM (Fused Deposition Modeling) 3D-printing defect detector across four classes with a **fixed class order**: `stringing=0, spaghetti=1, layer_shift=2, warping=3`. **Phase 1 pipeline:** 1. **GPU + pinned stack** — verify GPU, install pinned dependency versions. 2. **Data sourcing** — stage ~280 real frames from two sources: HuggingFace `DasKunststoffZentrumSKZ/Errors_Additive_Manufacturing_Plattform_Cam` (stringing/spaghetti/warping) and Kaggle `wengmhu/fdm-3d-printing-defect-dataset` (layer_shift), producing `frames/` + `frames/manifest.json`. 3. **Zero-shot pre-labeling** — Grounding DINO tiny detects the four defect classes on every frame, recall-first thresholds, raw detections saved to `frames/predictions.json`. 4. **Auto-accept / uncertain split** — confident detections (≥0.45) become YOLO-format labels in `auto_labels/`; low-confidence frames go to a capped (≤30) human-review queue at `labeling/review_queue.json` + `labeling/review_frames/`. All artifacts are persisted to the outputs bucket under `labeling/`.
import subprocess
print(subprocess.run(["nvidia-smi"], capture_output=True, text=True).stdout)
import torch
print("torch version:", torch.__version__)
print("CUDA available:", torch.cuda.is_available())
if torch.cuda.is_available():
print("Device:", torch.cuda.get_device_name(0))
else:
print("⚠️ WARNING: No GPU detected — proceeding on CPU. This will be much slower for Grounding DINO inference.")Sat Jul 11 16:57:41 2026 +-----------------------------------------------------------------------------------------+ | NVIDIA-SMI 580.95.05 Driver Version: 580.95.05 CUDA Version: 13.0 | +-----------------------------------------+------------------------+----------------------+ | GPU Name Persistence-M | Bus-Id Disp.A | Volatile Uncorr. ECC | | Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. | |…
%pip install -q "transformers>=4.44" "ultralytics==8.4.90" opencv-python-headless pillow kagglehub datasets huggingface_hub
import importlib
pkgs = ["torch", "transformers", "ultralytics", "cv2", "PIL", "kagglehub", "datasets", "huggingface_hub"]
name_map = {"cv2": "opencv-python-headless", "PIL": "pillow"}
for p in pkgs:
try:
mod = importlib.import_module(p)
ver = getattr(mod, "__version__", "unknown")
if p == "PIL":
import PIL
ver = PIL.__version__
print(f"{name_map.get(p, p)}: {ver}")
except Exception as e:
print(f"{p}: FAILED to import -> {e}")Note: you may need to restart the kernel to use updated packages. torch: 2.3.1 transformers: 4.44.2 Creating new Ultralytics Settings v0.0.6 file ✅ View Ultralytics Settings with 'yolo settings' or at '/root/.config/Ultralytics/settings.json' Update Settings with 'yolo settings key=value', i.e. 'yolo settings runs_dir=path/to/dir'. For help see https://docs.ultralytics.com/quickstart/#ultralytics-settings. ultralytics: 8.4.90 opencv-python-headless: 5.0.0 pillow: 10.3.0 kagglehub: 1.0.2 dataset…
from huggingface_hub import hf_hub_download
yaml_path = hf_hub_download(
repo_id="DasKunststoffZentrumSKZ/Errors_Additive_Manufacturing_Plattform_Cam",
filename="dataset.yaml",
repo_type="dataset",
)
with open(yaml_path) as f:
yaml_content = f.read()
print(yaml_content)path: coco_images train: images/train val: images/val test: images/test nc: 9 names: ['Nozzle', 'Object', 'Purge Line', 'Spaghetti', 'Stringing', 'Unterextrusion', 'Warping', 'schlechte erste Schicht', 'Double Print']
from huggingface_hub import snapshot_download
import os
# Confirmed class mapping from dataset.yaml (authoritative)
hf_class_names = ['Nozzle', 'Object', 'Purge Line', 'Spaghetti', 'Stringing', 'Unterextrusion', 'Warping', 'schlechte erste Schicht', 'Double Print']
hf_id_to_name = {i: n for i, n in enumerate(hf_class_names)}
print("HF class id -> name mapping:", hf_id_to_name)
# Classes of interest for this dataset (test split, smallest = 1428 rows)
HF_TARGET = {3: "spaghetti", 4: "stringing", 6: "warping"}
print("Target HF class ids of interest:", HF_TARGET)
snap_dir = snapshot_download(
repo_id="DasKunststoffZentrumSKZ/Errors_Additive_Manufacturing_Plattform_Cam",
repo_type="dataset",
allow_patterns=["images/test/*", "labels/test/*", "dataset.yaml"],
local_dir="/home/user/hf_snapshot",
)
…HF class id -> name mapping: {0: 'Nozzle', 1: 'Object', 2: 'Purge Line', 3: 'Spaghetti', 4: 'Stringing', 5: 'Unterextrusion', 6: 'Warping', 7: 'schlechte erste Schicht', 8: 'Double Print'}
Target HF class ids of interest: {3: 'spaghetti', 4: 'stringing', 6: 'warping'}
Snapshot dir: /home/user/hf_snapshot
images/test count: 334
labels/test count: 334import glob
from collections import defaultdict
labels_dir = os.path.join(snap_dir, "labels", "test")
images_dir = os.path.join(snap_dir, "images", "test")
label_files = sorted(glob.glob(os.path.join(labels_dir, "*.txt")))
print("Total label files found:", len(label_files))
# Parse each label file -> set of HF class ids present
frame_classes = {}
for lf in label_files:
stem = os.path.splitext(os.path.basename(lf))[0]
classes_present = set()
with open(lf) as f:
for line in f:
parts = line.strip().split()
if not parts:
…Total label files found: 334 Frames available per target class (HF dataset, test split): spaghetti: 154 stringing: 3
from huggingface_hub import HfApi
api = HfApi()
all_files = api.list_repo_files("DasKunststoffZentrumSKZ/Errors_Additive_Manufacturing_Plattform_Cam", repo_type="dataset")
test_images = [f for f in all_files if f.startswith("images/test/")]
test_labels = [f for f in all_files if f.startswith("labels/test/")]
train_images = [f for f in all_files if f.startswith("images/train/")]
val_images = [f for f in all_files if f.startswith("images/val/")]
print("Total files in repo:", len(all_files))
print("images/test:", len(test_images))
print("labels/test:", len(test_labels))
print("images/train:", len(train_images))
print("images/val:", len(val_images))This is a preview. Open the live notebook to see all 73 cells with their charts and full outputs, or fork it into your own Clusy workspace.