- garment-crop-gate-nano
garment-crop-gate-nano
A 47,122-parameter pre-flight gate for resoa/garment-attributes. 188 KB.
Answers one question before the 93.1M model runs: is this crop tight enough to trust the output?
Why this exists
garment-attributes states that it needs single-garment crops and that full-scene inputs "degrade
accuracy sharply". That warning is correct and qualitative β it tells you a gate is needed without
telling you where to put it. Fed a whole photograph, the model returns confident sigmoid scores
across all 218 attributes with no signal that anything is wrong.
So the degradation was measured. Fashionpedia val2020, 3,711 instances, 212 evaluable labels β the model's own published protocol. Only the crop padding varies; preprocessing, threshold and metric are identical throughout.
| padding | micro-F1 | macro-mAP | mAP retained |
|---|---|---|---|
| 0.08 (as trained) | 0.6995 | 0.4229 | 100% |
| 0.25 | 0.7006 | 0.4355 | 103% |
| 0.50 | 0.6522 | 0.3842 | 91% |
| 1.00 | 0.5441 | 0.2403 | 57% |
| 2.00 | 0.4307 | 0.1260 | 30% |
| full scene | 0.3550 | 0.0583 | 14% |
"Sharply" is β86%. A full scene keeps 14% of trained-crop mAP.
A second, free finding: 0.25 padding beats the recommended 0.08 by +3% mAP (0.4355 vs 0.4229). Slightly more context helps. That is an improvement to the existing model at no cost and no retraining β just widen the crop.
Reproduction, before any of the above is believable
Published: micro-F1 0.7100, macro-mAP 0.4417, 3,711 instances, 212 labels. This harness: micro-F1 0.6995, macro-mAP 0.4229, 3,711 instances, 212 labels.
Instance and label counts match exactly once the card's 48-pixel minimum-crop filter is applied. The residual ~1β2% gap is unexplained and most likely crop-rounding or resize detail. The curve above is relative to this harness's own 0.08 baseline, so that offset cancels; the absolute numbers should be read as ~2% low.
Does the gate actually help? Measured end-to-end
The gate is only worth its inference cost if it improves the system. Simulating a user feeding variably-cropped inputs (each held-out instance assigned a random padding from the six above):
| condition | n | micro-F1 | macro-mAP |
|---|---|---|---|
| ungated | 922 | 0.5510 | 0.3124 |
| gated β nano accepts | 443 | 0.6549 | 0.5012 |
| what the gate rejected | 479 | 0.4665 | 0.2302 |
| oracle β perfect gate | 470 | 0.6776 | 0.5086 |
+0.1888 macro-mAP, a 60% relative gain, against an oracle ceiling of +0.1962 β the gate captures 96% of what a perfect gate would deliver. The rejected pile scores 0.2302, so it is discarding the right things. Coverage 48%, gate precision 86%, recall 81%.
This is a refusal, not a repair. It does not fix a bad crop; it tells you not to trust the output and to re-crop. Half the inputs in this simulation get declined.
Gate accuracy on its own task
Held out by source image, so crops of the same garment never straddle the split:
| accuracy | |
|---|---|
| nano gate (47K params) | 0.8155 Β± 0.0096 |
| cheap baseline | 0.6240 |
| majority | 0.500 |
Margin +0.19, winning 3/3 seeds. The cheap baseline is the stronger of a transferred single threshold and a ~66-parameter logistic regression over ten cheap image statistics, both fitted on train and evaluated on test β a single threshold alone is too weak a bar to be honest about.
Labels are free and exact: the crop padding is applied by the harness, so the label is the operation performed, not an annotation.
Usage
import numpy as np, onnxruntime as ort
from PIL import Image
so = ort.SessionOptions(); so.intra_op_num_threads = 1
so.add_session_config_entry("session.intra_op.allow_spinning", "0")
gate = ort.InferenceSession("crop_gate.onnx", sess_options=so,
providers=["CPUExecutionProvider"])
def usable(pil):
a = np.asarray(pil.convert("RGB").resize((64, 64), Image.BICUBIC), np.float32) / 255.0
a = (a - a.mean()) / (a.std() + 1e-6) # per-image standardisation, as trained
x = a.transpose(2, 0, 1)[None]
return gate.run(None, {"image": x})[0].argmax() == 1
if usable(crop):
attrs = garment_attributes(crop) # trust the 93.1M model
else:
... # re-crop tighter; do not trust the output
Input is RGB, per-image standardised β not the SigLIP mean/std normalisation that
garment-attributes itself uses. Feeding it SigLIP-normalised tensors will degrade it silently.
Also free: a minimum native-size rule
The gate above judges crop tightness. Crop size is a separate, cheaper check that needs no model
at all. Measured on garment-attributes-v2, Fashionpedia val2020, with the padding held at 0.25 and
only the crop's native pixel size varying (the model resizes everything to 224x224, so anything
below that is upscaled from nothing):
| native min-side | n | macro-mAP |
|---|---|---|
| 0β24 px | 564 | 0.2316 |
| 24β36 px | 460 | 0.4496 |
| 36β48 px | 523 | 0.5785 |
| 48β64 px | 582 | 0.6223 |
| 64β96 px | 716 | 0.6274 |
| 96β160 px | 771 | 0.5860 |
| 160+ px | 1642 | 0.5069 |
Below ~24 native pixels the model collapses to a third of its peak. A min(w, h) >= 24 px check
keeps 89% of instances while the rejected pile scores 0.2316; >= 48 px keeps 71% and is what the
parent's evaluation protocol uses.
There is no upper limit worth imposing. The decline above 96 px is not a resolution effect β it is the part-versus-garment split, since large crops are whole garments carrying many attributes and small ones are pockets and collars carrying few. Controlling for instance category, native size does not predict accuracy at all (mean within-category delta +0.0033, positive in 3 of 7 categories).
Nor would higher-resolution input help. 74% of evaluation crops are already upscaled to reach 224Γ224 and score no worse than crops with more native detail than the model consumes. The model is not starved of pixels; its measured deficit is in rare attributes with few training examples.
Pixels-on-target: which attributes are answerable at all
Crop tightness and crop size both matter, but the quantity that actually governs an attribute is how many pixels its diagnostic feature occupies β and that is computable in advance from physical sizes, without asking the model anything.
A button is ~12 mm, a zipper tooth ~5 mm, a pocket opening ~140 mm, a hemline ~1200 mm. Every crop
is resized to 224Γ224, so a feature occupying fraction f of the instance lands on f Γ 224 pixels
no matter what the source resolution was.
| pixels-on-target | attributes | mean AP |
|---|---|---|
| 0β2 px | 13 | 0.340 |
| 2β4 px | 23 | 0.359 |
| 4β8 px | 10 | 0.530 |
| 20β60 px | 80 | 0.463 |
| 60+ px | 62 | 0.584 |
This is not just a proxy for training data. Support is the stronger driver (correlation +0.526 against +0.302), so the test is whether pixels-on-target still predicts accuracy within support bands. It does, and the effect grows with support:
| training support | low-pixel AP | high-pixel AP | delta |
|---|---|---|---|
| 100β400 | 0.334 | 0.408 | +0.074 |
| 400β1,500 | 0.387 | 0.499 | +0.112 |
| 1,500+ | 0.570 | 0.714 | +0.144 |
The clearest single case: bead(a) has 2,030 training instances and scores AP 0.133, because a
5 mm bead on a whole garment lands on ~1 pixel. metal has 1,135 instances and scores 0.072. No
quantity of training data fixes a feature rendered at one pixel.
The zoom calculation
To put T pixels on a feature of F mm at a 224 px input, crop to a region of F Γ 224 / T mm.
For a 5 mm feature at 8 px, that is a 140 mm crop β roughly 8Γ tighter than a whole-garment crop.
So small-feature attributes are not a resolution problem to be solved by larger inputs; they are a cropping problem. Classify what is visible at garment scale, then crop to sub-regions for the rest. 150 of 202 attributes already exceed 8 px and need no zoom at all.
Prior art: this is Johnson's criteria, rediscovered
The ~8 px threshold above was derived empirically from average-precision curves. It then turned out to match Johnson's criteria (John Johnson, 1958), the standard used in surveillance and thermal optics for over half a century:
| task | Johnson (1958) | measured here |
|---|---|---|
| detection β something is there | 2 px | β |
| recognition β what kind of thing | 8 px | ~8 px |
| identification β which specific one | 12.8 px | β |
The empirical threshold for "can a CNN classify this attribute" lands on Johnson's recognition value. That convergence is worth stating plainly: the principle is not new, and credit belongs to Johnson.
What is offered here is the application rather than the principle β Johnson's criteria describe a human observer detecting or recognising a whole object, whereas the numbers above are a CNN classifying fine-grained attributes of an object it is already looking at, validated per-attribute against training support as a confound. The zoom calculation and the a-priori camera spec follow from that, and the fact that a 1958 optics rule predicts modern CNN behaviour this well is arguably the more useful finding.
Caveat: this fails for absence and gestalt attributes
plain (pattern), no non-textile material and symmetrical are judged over a large area, not by
resolving a small part, so a "feature size" is the wrong model for them and they score well despite
tiny nominal features. They are excluded from the numbers above and listed in
pixels_on_target.json. Identify them by form β negations and whole-garment properties β not by
their results.
Why not just use the model's own confidence?
Because on this task it is worse than rejecting at random.
Selective prediction usually rejects inputs the model is unconfident about. Simulating variably cropped inputs and sweeping coverage, measuring macro-mAP on whatever each method accepts:
| method | riskβcoverage AUC (0.3β1.0) | mAP @50% coverage |
|---|---|---|
| oracle (true crop padding) | 0.2549 | 0.4312 |
| crop-gate (this model) | 0.2540 | 0.4275 |
| negative entropy | 0.2376 | 0.3715 |
| random rejection | 0.2030 | 0.2995 |
| max-confidence | 0.1853 | 0.2637 |
| top-5 mean confidence | 0.1699 | 0.2377 |
The gate reaches 99.6% of the oracle. Max-confidence and top-5 confidence both fall below random.
The mechanism is straightforward once seen: fed a full scene, the model finds many things to be confident about and fires attributes across the board. High confidence correlates with a bad crop, not a good one, so confidence-based rejection actively discards the inputs it should keep. This is the miscalibration the selective-prediction literature warns about, in a concrete instance.
The gate avoids it by never consulting the model. It measures a property of the input β independently known to predict failure β which cannot be corrupted by the model being wrong.
Limitations
- Binary, and the boundary is a cliff edge chosen from the curve above (between 0.50 and 1.00 padding). Inputs near that boundary are genuinely ambiguous and the gate is least reliable there.
- 48% coverage in the mixed simulation. If your inputs are already well cropped the gate mostly says yes and buys little; its value scales with how bad your crops are.
- Trained on Fashionpedia val2020 crops only β 3,711 instances from 1,158 images, since only the val split ships bounding boxes. Broader photography is unmeasured.
- Tightness is not the only failure mode. The
garment-attributescard also warns that flat-lay/factory photos are out of domain; this gate does not detect that at all and will happily accept a well-cropped flat-lay. - Not a garment detector. It scores a crop you already have; it will not find garments for you.
- 0.8155 accuracy means roughly one in five decisions is wrong. Use it to triage, not to guarantee.
Relation to the parent model
This does not modify, distil or replace garment-attributes β it runs in front of it and costs
about 1/2000th as many parameters. The architecture is an ordinary 4-layer conv net and nothing
about it is novel. What is offered is the measurement: a published degradation curve, a reproduction
of the parent's protocol, and an end-to-end number showing the gate recovers 96% of an oracle's
benefit.