Unofficial ONNX conversion of EdgeCrafter ECDet-S
Single-file FP32 ONNX (opset 17) conversion of the official EdgeCrafter ECDet-S
object-detection checkpoint, exporting the raw model outputs (no NMS, no
sigmoid, no top-k, no resizing — all pre/postprocessing stays outside the graph).
This is an unofficial conversion. It is a modification of the original
EdgeCrafter release (PyTorch → ONNX, plus embedded metadata and validation
artifacts) and is not endorsed by the EdgeCrafter authors.
Provenance
| |
|---|
| Upstream project | EdgeCrafter — Compact ViTs for Edge Dense Prediction via Task-Specialized Distillation |
| Source code commit | b17f0f340af687e7adf2dff42a49e2eb8250ee20 |
| Model config | ecdetseg/configs/ecdet/ecdet_s.yml |
| Checkpoint | ecdet_s.pth (39,938,420 bytes) |
| Checkpoint SHA-256 | 35340a45101d24a094becf8776aeaa327c9e6b37453e5e81dfd2923c134c4619 |
| model.onnx SHA-256 | e0919a2c617682d049617f97c67ea986d7c9d908220647eb3e26a55c4c24bf87 (40,492,596 bytes) |
| Upstream-reported metric | COCO val2017 AP 51.7 (as published in the EdgeCrafter README; not re-measured for this conversion) |
The checkpoint was loaded on CPU with strict=True (0 missing / 0 unexpected
keys) following the official export flow, switched to deploy/eval mode, and
traced with the TorchScript ONNX exporter. See export/export_raw_onnx.py for
the exact, reproducible procedure (repeated runs produce a byte-identical file).
Input contract
| |
|---|
| name | images |
| dtype / layout | float32, NCHW |
| shape | [batch, 3, 640, 640] — batch is dynamic, height/width fixed |
Preprocessing (must be done outside the model):
- Decode to RGB.
- Resize (stretch) directly to 640×640 — no letterbox, no aspect preservation.
float32, divide by 255.
- Normalize:
x = (x - mean) / std with mean = [0.485, 0.456, 0.406],
std = [0.229, 0.224, 0.225] (per channel, RGB order).
- HWC → CHW, add batch dimension.
This mirrors the official evaluation pipeline
(configs/ecdet/ecdet.yml val transforms: Resize [640,640] →
ConvertPILImage(float32, scale=True) → Normalize(mean, std)).
Output contract
| output | shape | meaning |
|---|
pred_logits | [batch, 300, 80] | raw class logits (sigmoid/focal scheme; sigmoid is not applied inside the graph) |
pred_boxes | [batch, 300, 4] | normalized cxcywh boxes, relative to the 640×640 input |
300 object queries, 80 contiguous COCO2017 classes. There is no
orig_target_sizes input and no labels/scores postprocessing — apply your own.
Required external postprocessing:
1probs = 1 / (1 + np.exp(-pred_logits)) # sigmoid
2scores = probs.max(-1); labels = probs.argmax(-1)
3keep = scores >= threshold # e.g. 0.4 (or use top-k)
4cx, cy, w, h = boxes[keep].T # normalized cxcywh
5xyxy = np.stack([cx-w/2, cy-h/2, cx+w/2, cy+h/2], -1).clip(0, 1)
6xyxy *= [orig_w, orig_h, orig_w, orig_h] # scale to the ORIGINAL image size
DETR-style set prediction — NMS is not required.
Class mapping
Contiguous COCO2017 ids
0..79 (
0=person,
1=bicycle,
2=car, …,
79=toothbrush), taken from the official EdgeCrafter dataset code
(
engine/data/dataset/coco_dataset.py,
mscoco_category2name). The full
mapping is embedded in the ONNX metadata key
names and duplicated in
config.json. Identical for ECDet-S and ECDet-M.
ONNX Runtime example
1import json, numpy as np, onnxruntime as ort
2from PIL import Image
3
4sess = ort.InferenceSession("model.onnx", providers=["CPUExecutionProvider"])
5names = json.loads(sess.get_modelmeta().custom_metadata_map["names"])
6
7img = Image.open("photo.jpg").convert("RGB")
8ow, oh = img.size
9x = np.asarray(img.resize((640, 640), Image.BILINEAR), np.float32) / 255.0
10x = (x - [0.485, 0.456, 0.406]) / [0.229, 0.224, 0.225]
11x = x.transpose(2, 0, 1)[None].astype(np.float32)
12
13logits, boxes = sess.run(None, {"images": x})
14probs = 1 / (1 + np.exp(-logits[0]))
15scores, labels = probs.max(-1), probs.argmax(-1)
16for i in np.where(scores >= 0.4)[0]:
17 cx, cy, w, h = boxes[0, i]
18 box = [(cx-w/2)*ow, (cy-h/2)*oh, (cx+w/2)*ow, (cy+h/2)*oh]
19 print(names[str(labels[i])], round(float(scores[i]), 3), [round(v, 1) for v in box])
Validation summary
Full machine-readable results:
validation-report.json
(status:
passed). Highlights:
onnx.checker (full) + strict shape inference pass; single file, no external
data, no custom operator domains; opset 17.
- ONNX Runtime CPU: batch 1 and batch 2 verified; batched rows are
bit-identical to single-image runs (dynamic batch works).
- PyTorch vs ONNX Runtime parity (
rtol=1e-3, atol=1e-4,
np.testing.assert_allclose): all 7 cases pass strictly (random batch 1
and 2, four real images, real-image batch 2).
- Semantic parity on 12 real COCO val2017 images with identical external
postprocessing: detections match 1:1 (12/12 images, max box diff < 0.1 px).
- Re-running the export produces a byte-identical model.onnx.
Limitations
- Some low-confidence raw queries may produce box coordinates outside
[0,1].
Apply confidence filtering and clip coordinates before scaling to the original image.
- Fixed 640×640 input; only the batch dimension is dynamic.
- FP32 only (no FP16/INT8 variants in this release).
pred_logits/pred_boxes are raw — you must apply sigmoid, thresholding and
coordinate scaling yourself (see above).
- The internal encoder TopK orders the 300 queries by score; queries whose
scores tie within float precision may appear in a different order across
runtimes. Treat the output as an unordered set.
- COCO AP was not re-measured for the ONNX model; the AP figure above is
the upstream-reported PyTorch number.
- The trace fixes the ViT spatial resolution; use exactly 3×640×640 inputs.
License and attribution
Released under the
Apache License 2.0 (see
LICENSE), the same
license as the upstream project. Original work: © 2026 The EdgeCrafter Authors
(Intellindust AI Lab), with components derived from D-FINE and RT-DETR — see
NOTICE. The ONNX conversion is a
modification of the original
model; this repository is
not endorsed by the EdgeCrafter developers.