Downloads · 30 days
0
resoajoe/depth-nano
depth-nano is a image classification model from resoajoe. Use it when you need a label for an image. The card lists the license as mit.
47,187 parameters. 189 KB of ONNX. Given one frame from a generated video, estimate how many generations it is removed from a real anchor image.
Downloads · 30 days
0
Access
Public
Updated Sep 3, 2026
Repo size
391 KB
Likes
0
Public
Click a slice to open those files.
.pt198 KB · 49%
From the Hugging Face model README
47,187 parameters. 189 KB of ONNX. Given one frame from a generated video, estimate how many generations it is removed from a real anchor image.
When you generate long video by chaining — each chunk conditioned on the last frame of the previous one — error accumulates. This model reads that accumulation off a single 64×64 frame.
Domain measured / deployment domain tested: measured on LTX-Video-2B and Wan 2.2 generated interior-room frames; deployment domain: no other generator or scene type tested; never seen a real photograph. (Fifth line of the card standard, added 2026-09-02: a number is only as good as the domain it was measured in.)
What it is for: monitoring chained video generation. Answers "how degraded is this frame, in units of generations from ground truth?" so a pipeline can decide when to re-anchor.
What it must not be used for:
Three ordinal bins over chaining depth:
| class | depth | chunks |
|---|---|---|
fresh | 0 — generated directly from a real anchor | 932 |
shallow | 1–5 generations removed | 264 |
deep | 6+ generations removed | 644 |
Ordinal bins, not regression. The depth distribution is bimodal — 932 chunks at depth 0, 547 at 12+, a thin tail between. A regressor scores well on that by learning "zero or large" and never resolving the middle, and MAE hides the failure. Binning forces the middle class to show its own recall, which is where the difficulty actually is.
Stratified split by ARM, 34 arms, ~30% held out, 3 seeds. Splitting by arm is mandatory here (frames in an arm share a seed and a chaining config), and because depth is confounded with arm identity — the heavily-chained arms are deep throughout — shuffles are retried until both sides contain all three classes.
| seed | accuracy | majority | lift | ordinal MAE | fresh | shallow | deep |
|---|---|---|---|---|---|---|---|
| 0 | 0.946 | 0.434 | +0.512 | 0.055 | 0.979 | 0.772 | 0.974 |
| 1 | 0.965 | 0.696 | +0.268 | 0.035 | 0.992 | 0.877 | 0.917 |
| 2 | 0.947 | 0.471 | +0.476 | 0.057 | 0.995 | 0.520 | 0.956 |
| mean | +0.419 ± 0.108 | 0.049 ± 0.010 | 0.989 | 0.723 | 0.949 |
Ordinal MAE of 0.049 bins means that when it is wrong it is almost always wrong by one adjacent
bin, never by two. shallow is the hard class and the one that varies (0.520–0.877); the two
extremes are close to solved.
+0.42 lift is much higher than anything else in this family, which is exactly when to suspect a
trivial cue. The obvious one: fresh chunks are generated directly from the real anchor image,
so a model could score well by learning "does this look like the anchor photo?" — which would be a
real signal, but not depth estimation.
So I removed fresh entirely and asked only shallow (1–5) vs deep (6+), where no anchor-
similarity cue exists. 908 chunks, 27 arms, 5 seeds:
| seed | accuracy | majority | lift |
|---|---|---|---|
| 0 | 0.919 | 0.555 | +0.365 |
| 1 | 0.953 | 0.560 | +0.393 |
| 2 | 0.902 | 0.624 | +0.278 |
| 3 | 0.957 | 0.574 | +0.383 |
| 4 | 0.952 | 0.733 | +0.218 |
| mean | +0.328 ± 0.068 |
It still works. The model is reading accumulated generation damage, not recognising the anchor.
Label-shuffle control: with depth labels permuted across chunks, the same pipeline scores −0.097 and −0.042 lift. The harness does not manufacture signal.
Trained on full frames resized to 64×64, no crop. Chaining damage is a global property of the frame, so unlike most nano models in this family there is no object-to-frame ratio to preserve and no clutter wall. The corollary: do not feed it a crop — the degradation statistics of a region are not those of the frame. Whole frames, resized, letterboxed if the aspect differs.
shallow recall swings from 0.52 to 0.88 depending on which arms are held out. The middle
of the depth range is genuinely under-sampled (264 chunks vs 932 and 644).import cv2, numpy as np, onnxruntime as ort
CLASSES = ["fresh", "shallow", "deep"]
sess = ort.InferenceSession("depth.onnx", providers=["CPUExecutionProvider"])
img = cv2.imread("frame.jpg") # whole frame, BGR, as cv2.imread returns it
x = cv2.resize(img, (64, 64), interpolation=cv2.INTER_AREA)
x = x.astype(np.float32).transpose(2, 0, 1)[None] / 255.0
logits = sess.run(None, {"image": x})[0][0]
print(CLASSES[int(logits.argmax())])
Input is BGR, 0–1, NCHW — the channel order cv2.imread returns, not RGB. Training used BGR
throughout; feeding RGB swaps two channels silently and degrades output without erroring.
ONNX vs PyTorch, identical weights, both on CPU, 512 real frames:
Tolerance is relative, not absolute. An absolute 1e-3 check flags a bit-accurate export of this model as broken — it did, the first time, on the sibling model.
The original plan was a model predicting chunks until collapse. It was killed before training: collapse onset turns out not to be a well-defined scalar. Only 12 of 34 arms end in an absorbing failure run, and on 8 of those, two reasonable definitions of onset disagree by more than 2 chunks — in one arm by 44, because a single chunk flickered past the quality checker. The label's variance was dominated by the checker, not by the process. Chaining depth needs no changepoint and no quality signal, so it replaced it.
Trained on an NVIDIA Jetson AGX Orin. Both depth.onnx and depth.pt included.
resoajoe/quality-gate-nano — is this chunk bad?resoajoe/failure-type-nano — why is it bad?Measured on a Jetson AGX Orin. ONNX Runtime sizes its intra-op thread pool to the CPU core count, and those workers spin-wait between inferences. Running two 47K-parameter models this way left ~18 threads busy-waiting at ~10.6% of a core each — about 1.9 cores burned continuously to run inferences that take 0.28 ms. A model this small cannot use intra-op parallelism at all.
so = ort.SessionOptions()
so.intra_op_num_threads = 1
so.inter_op_num_threads = 1
so.execution_mode = ort.ExecutionMode.ORT_SEQUENTIAL
so.add_session_config_entry("session.intra_op.allow_spinning", "0")
sess = ort.InferenceSession("depth.onnx", sess_options=so, providers=["CPUExecutionProvider"])
Measured effect on the same workload: idle CPU 192% → 16.5% of one core, active 231% → 88%, thread count 45 → 22, throughput unchanged. On edge hardware this is the difference between "runs alongside everything else" and "saturates the machine".
Every margin quoted here is against a stated baseline, because a margin without one is not a measurement. The baseline is the best single-threshold classifier over ten cheap statistics, fitted optimistically:
mean · std · lapvar · hf (high-frequency energy ratio) · grad (Sobel magnitude) ·
entropy · centre_edge · radial_slope · row_fft_peak · col_fft_peak
The last four are spatially aware, added after an earlier six-statistic baseline — all global aggregates — was found to systematically overstate model value on spatially structured tasks. A baseline that cannot see where anything is loses to a CNN by default. On one test task that flaw inflated an apparent margin from +0.060 to +0.261.
Two questions are asked with it, and they disagree:
Where this card quotes a single scalar figure without qualification, it is the in-sample one.
Training frames are model-generated video of a synthetic interior scene. No real patients, no real footage, and no personal data are involved anywhere in this work.