attr-eomt 0.1.2__tar.gz → 0.1.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. {attr_eomt-0.1.2 → attr_eomt-0.1.3}/.gitignore +1 -0
  2. {attr_eomt-0.1.2 → attr_eomt-0.1.3}/PKG-INFO +60 -37
  3. {attr_eomt-0.1.2 → attr_eomt-0.1.3}/README.md +58 -36
  4. attr_eomt-0.1.3/docs/assets/01-architecture.png +0 -0
  5. {attr_eomt-0.1.2 → attr_eomt-0.1.3}/docs/assets/01-architecture.svg +1 -1
  6. attr_eomt-0.1.3/docs/assets/02-factorized-labels.png +0 -0
  7. attr_eomt-0.1.3/docs/assets/02-factorized-labels.svg +1 -0
  8. attr_eomt-0.1.3/docs/examples/billie_eilish.jpg +0 -0
  9. attr_eomt-0.1.3/docs/examples/rosalia.jpg +0 -0
  10. attr_eomt-0.1.3/docs/examples/sample.jpg +0 -0
  11. attr_eomt-0.1.3/docs/examples/taylor_swift.jpg +0 -0
  12. {attr_eomt-0.1.2 → attr_eomt-0.1.3}/eomt/api.py +150 -5
  13. attr_eomt-0.1.3/eomt/compress.py +95 -0
  14. {attr_eomt-0.1.2 → attr_eomt-0.1.3}/eomt/engine/predict.py +8 -3
  15. {attr_eomt-0.1.2 → attr_eomt-0.1.3}/eomt/engine/train.py +77 -13
  16. {attr_eomt-0.1.2 → attr_eomt-0.1.3}/eomt/model.py +54 -19
  17. {attr_eomt-0.1.2 → attr_eomt-0.1.3}/eomt/serialization.py +26 -1
  18. attr_eomt-0.1.3/eomt/visualize.py +159 -0
  19. {attr_eomt-0.1.2 → attr_eomt-0.1.3}/pyproject.toml +2 -1
  20. {attr_eomt-0.1.2 → attr_eomt-0.1.3}/sample_data/README.md +7 -7
  21. {attr_eomt-0.1.2 → attr_eomt-0.1.3}/sample_data/annotations/instances_train.json +18 -16
  22. attr_eomt-0.1.3/sample_data/annotations/instances_val.json +39 -0
  23. {attr_eomt-0.1.2 → attr_eomt-0.1.3}/sample_data/data.yaml +3 -3
  24. attr_eomt-0.1.3/scripts/push_to_hub.py +79 -0
  25. {attr_eomt-0.1.2 → attr_eomt-0.1.3}/site/index.html +34 -24
  26. attr_eomt-0.1.3/tests/test_compress.py +61 -0
  27. attr_eomt-0.1.3/tools/attr_tagger/README.md +69 -0
  28. attr_eomt-0.1.3/tools/attr_tagger/tag.py +405 -0
  29. {attr_eomt-0.1.2 → attr_eomt-0.1.3}/uv.lock +12 -1
  30. attr_eomt-0.1.2/docs/assets/01-architecture.png +0 -0
  31. attr_eomt-0.1.2/docs/assets/02-factorized-labels.png +0 -0
  32. attr_eomt-0.1.2/docs/assets/02-factorized-labels.svg +0 -1
  33. attr_eomt-0.1.2/docs/examples/example_1.png +0 -0
  34. attr_eomt-0.1.2/eomt/visualize.py +0 -98
  35. attr_eomt-0.1.2/sample_data/annotations/instances_val.json +0 -37
  36. {attr_eomt-0.1.2 → attr_eomt-0.1.3}/.github/workflows/pages.yml +0 -0
  37. {attr_eomt-0.1.2 → attr_eomt-0.1.3}/.github/workflows/publish.yml +0 -0
  38. {attr_eomt-0.1.2 → attr_eomt-0.1.3}/.github/workflows/release.yml +0 -0
  39. {attr_eomt-0.1.2 → attr_eomt-0.1.3}/.github/workflows/tests.yml +0 -0
  40. {attr_eomt-0.1.2 → attr_eomt-0.1.3}/LICENSE +0 -0
  41. {attr_eomt-0.1.2 → attr_eomt-0.1.3}/configs/coco.yaml +0 -0
  42. {attr_eomt-0.1.2 → attr_eomt-0.1.3}/docs/assets/00-hero-banner.png +0 -0
  43. {attr_eomt-0.1.2 → attr_eomt-0.1.3}/docs/assets/00-hero-banner.svg +0 -0
  44. {attr_eomt-0.1.2 → attr_eomt-0.1.3}/docs/assets/03-training-flow.png +0 -0
  45. {attr_eomt-0.1.2 → attr_eomt-0.1.3}/docs/assets/03-training-flow.svg +0 -0
  46. {attr_eomt-0.1.2 → attr_eomt-0.1.3}/eomt/__init__.py +0 -0
  47. {attr_eomt-0.1.2 → attr_eomt-0.1.3}/eomt/aux_cls.py +0 -0
  48. {attr_eomt-0.1.2 → attr_eomt-0.1.3}/eomt/box_loss.py +0 -0
  49. {attr_eomt-0.1.2 → attr_eomt-0.1.3}/eomt/config.py +0 -0
  50. {attr_eomt-0.1.2 → attr_eomt-0.1.3}/eomt/data/__init__.py +0 -0
  51. {attr_eomt-0.1.2 → attr_eomt-0.1.3}/eomt/data/coco.py +0 -0
  52. {attr_eomt-0.1.2 → attr_eomt-0.1.3}/eomt/data/config.py +0 -0
  53. {attr_eomt-0.1.2 → attr_eomt-0.1.3}/eomt/data/download.py +0 -0
  54. {attr_eomt-0.1.2 → attr_eomt-0.1.3}/eomt/data/transforms.py +0 -0
  55. {attr_eomt-0.1.2 → attr_eomt-0.1.3}/eomt/device.py +0 -0
  56. {attr_eomt-0.1.2 → attr_eomt-0.1.3}/eomt/ema.py +0 -0
  57. {attr_eomt-0.1.2 → attr_eomt-0.1.3}/eomt/engine/__init__.py +0 -0
  58. {attr_eomt-0.1.2 → attr_eomt-0.1.3}/eomt/engine/validate.py +0 -0
  59. {attr_eomt-0.1.2 → attr_eomt-0.1.3}/eomt/loss.py +0 -0
  60. {attr_eomt-0.1.2 → attr_eomt-0.1.3}/eomt/plotting.py +0 -0
  61. {attr_eomt-0.1.2 → attr_eomt-0.1.3}/eomt/postprocess.py +0 -0
  62. {attr_eomt-0.1.2 → attr_eomt-0.1.3}/eomt/preprocess.py +0 -0
  63. {attr_eomt-0.1.2 → attr_eomt-0.1.3}/sample_data/images/train/.gitkeep +0 -0
  64. {attr_eomt-0.1.2 → attr_eomt-0.1.3}/sample_data/images/val/.gitkeep +0 -0
  65. {attr_eomt-0.1.2 → attr_eomt-0.1.3}/scripts/info.py +0 -0
  66. {attr_eomt-0.1.2 → attr_eomt-0.1.3}/scripts/predict.py +0 -0
  67. {attr_eomt-0.1.2 → attr_eomt-0.1.3}/scripts/train.py +0 -0
  68. {attr_eomt-0.1.2 → attr_eomt-0.1.3}/scripts/train_detect_coco.sh +0 -0
  69. {attr_eomt-0.1.2 → attr_eomt-0.1.3}/scripts/val.py +0 -0
  70. {attr_eomt-0.1.2 → attr_eomt-0.1.3}/tests/test_api.py +0 -0
  71. {attr_eomt-0.1.2 → attr_eomt-0.1.3}/tests/test_parity.py +0 -0
  72. {attr_eomt-0.1.2 → attr_eomt-0.1.3}/tests/test_smoke.py +0 -0
  73. {attr_eomt-0.1.2 → attr_eomt-0.1.3}/tests/test_sweep.py +0 -0
@@ -29,3 +29,4 @@ scripts/sample_images/
29
29
  .idea/
30
30
  .vscode/
31
31
  .DS_Store
32
+ .env
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: attr-eomt
3
- Version: 0.1.2
3
+ Version: 0.1.3
4
4
  Summary: Standalone EoMT (Encoder-only Mask Transformer) for instance segmentation, with DINOv2 init, COCO training/validation and inference.
5
5
  Project-URL: Homepage, https://github.com/imagra93/attr-eomt
6
6
  Project-URL: Documentation, https://imagra93.github.io/attr-eomt
@@ -23,6 +23,7 @@ Requires-Dist: pyyaml>=6.0
23
23
  Requires-Dist: requests>=2.25.0
24
24
  Requires-Dist: scipy>=1.7.0
25
25
  Requires-Dist: torch>=2.4.0
26
+ Requires-Dist: torchao>=0.7.0
26
27
  Requires-Dist: torchvision>=0.19.0
27
28
  Requires-Dist: tqdm>=4.65.0
28
29
  Requires-Dist: transformers>=5.1.0
@@ -92,9 +93,9 @@ with a fixed set of **learnable queries** (the Mask2Former idea) — each query
92
93
  "slot" that latches onto one object instance. After the encoder runs, every query emits
93
94
  a single vector, the **per-query embedding** of shape `[B, Q, hidden]`. The whole model
94
95
  is then just "turn that embedding into predictions": a **class head** for the primary
95
- label and a **mask/box head** for geometry. It is **NMS-free**, so two overlapping cars
96
- stay two distinct queries instead of being merged — the property that lets attributes
97
- stay attached to the right instance.
96
+ label and a **mask/box head** for geometry. It is **NMS-free**, so two overlapping
97
+ garments stay two distinct queries instead of being merged — the property that lets
98
+ attributes stay attached to the right instance.
98
99
 
99
100
  The attribute heads add nothing to this picture except themselves: they tap the **exact
100
101
  same embedding** (captured non-invasively with a forward hook), each a small classifier
@@ -164,9 +165,9 @@ attribute heads add a thin linear/MLP per head and are negligible by design.
164
165
  ## ⭐ Method — factorizing the label space
165
166
 
166
167
  This is the contribution. Conventional detectors fold every distinction into a single
167
- flat label space. When an object has both a *type* and several *attributes* — a car's
168
- make **and** its colour **and** whether a door is open — the only way to express that is
169
- the Cartesian product `type × colour × state × …`. That space explodes combinatorially,
168
+ flat label space. When an object has both a *type* and several *attributes* — a garment's
169
+ class **and** its viewpoint **and** whether it's occluded — the only way to express that is
170
+ the Cartesian product `type × viewpoint × occlusion × …`. That space explodes combinatorially,
170
171
  starves each leaf class of training examples, and breaks Hungarian matching by
171
172
  multiplying the query targets.
172
173
 
@@ -181,11 +182,11 @@ per-instance query embedding the detector already computes — they **add, not m
181
182
  Because the heads are independent:
182
183
 
183
184
  - **Categories stay collapsed and general.** Keep a compact, well-populated primary
184
- taxonomy (`car`, `person`, `fruit`) and push fine-grained or orthogonal distinctions
185
- into attributes — every primary class keeps its full sample count instead of being
186
- shattered into rare leaves.
187
- - **Unseen combinations generalize.** A `colour` head trained across many object types
188
- predicts `red` on a class it *never co-occurred with in red*, because colour is
185
+ taxonomy (`short_sleeve_top`, `dress`, `trousers`) and push fine-grained or orthogonal
186
+ distinctions into attributes — every primary class keeps its full sample count instead
187
+ of being shattered into rare leaves.
188
+ - **Unseen combinations generalize.** A `viewpoint` head trained across many garment types
189
+ predicts `side` on a class it *never co-occurred with sideways*, because viewpoint is
189
190
  learned independently of type. The model composes `attribute × class` combinations
190
191
  that **never appear in the training data** — combinations a flat label space cannot
191
192
  even represent.
@@ -196,25 +197,25 @@ Because the heads are independent:
196
197
  matched queries — they add only a thin linear/MLP head and a cross-entropy term, not
197
198
  a second model or a second pass.
198
199
 
199
- ### Example: a bowl of fruit
200
+ ### Example: clothing with per-instance attributes
200
201
 
201
- One model segments each fruit (primary classes `apple` / `banana` / `orange` /
202
- `pear`) and, for **every** detection, reads off two **independent** attribute heads —
203
- `ripeness` (`unripe` / `turning` / `ripe`) and a quality `grade` (`A` / `B`). The
204
- renderer prints the primary class + score on the first row and each attribute + its
205
- confidence on the row beneath it.
202
+ One model segments each garment (primary classes like `vest_dress` / `short_sleeve_top`
203
+ / `long_sleeve_dress` / `skirt` / `trousers` …) and, for **every** detection, reads off
204
+ four **independent** attribute heads — `scale` (`small` / `modest` / `large`),
205
+ `occlusion` (`no` / `slight` / `medium`), `zoom_in` (`no` / `medium` / `large`) and
206
+ `viewpoint` (`frontal` / `side` / `back`). The renderer prints the primary class + score
207
+ on the first row and each attribute + its confidence on the rows beneath it.
206
208
 
207
- ![A bowl of fruit; each box labelled with its fruit class plus a ripeness and a grade](https://raw.githubusercontent.com/imagra93/attr-eomt/main/docs/examples/example_1.png)
209
+ ![Two people in dresses; each instance labelled with its garment class plus scale, occlusion, zoom and viewpoint attributes](https://raw.githubusercontent.com/imagra93/attr-eomt/main/docs/examples/sample.jpg)
208
210
 
209
- `ripeness` and `grade` are *orthogonal* — they vary independently — which is exactly
210
- the case that's awkward to fold into the primary class space. The same pattern fits
211
- any "class **plus** per-instance sub-labels" task: **retail shelves → product +
212
- facing**, **vehicles → type + colour + damage state**, **cells / leaves → type +
213
- health**, **apparel → garment + pattern**.
211
+ The four attributes are *orthogonal* to the garment class — they vary independently —
212
+ which is exactly the case that's awkward to fold into the primary class space. The same
213
+ pattern fits any "class **plus** per-instance sub-labels" task: **retail shelves →
214
+ product + facing**, **documents → element + role**, **cells → type + health**.
214
215
 
215
- > The image above is a **simulation**: a stock photo with hand-placed detections fed
216
- > through the package's own renderer ([`eomt.visualize.draw_instances`](eomt/visualize.py))
217
- > to show the output format — not a trained model's predictions.
216
+ > Trained on the public **[DeepFashion2](https://github.com/switchablenorms/DeepFashion2)**
217
+ > dataset (13 garment classes + 4 attribute heads) and rendered with the package's own
218
+ > renderer ([`eomt.visualize.draw_instances`](eomt/visualize.py)).
218
219
 
219
220
  ---
220
221
 
@@ -259,18 +260,19 @@ names) are discovered from the JSON, the same as `nc`.
259
260
  ```jsonc
260
261
  "attributes": [
261
262
  {
262
- "name": "ripeness",
263
+ "name": "scale",
263
264
  "categories": [
264
- {"id": 0, "name": "unripe"},
265
- {"id": 1, "name": "turning"},
266
- {"id": 2, "name": "ripe"}
265
+ {"id": 1, "name": "small"},
266
+ {"id": 2, "name": "modest"},
267
+ {"id": 3, "name": "large"}
267
268
  ]
268
269
  },
269
270
  {
270
- "name": "grade",
271
+ "name": "viewpoint",
271
272
  "categories": [
272
- {"id": 10, "name": "A"},
273
- {"id": 20, "name": "B"}
273
+ {"id": 0, "name": "frontal"},
274
+ {"id": 1, "name": "side"},
275
+ {"id": 2, "name": "back"}
274
276
  ]
275
277
  }
276
278
  ]
@@ -282,14 +284,14 @@ names) are discovered from the JSON, the same as `nc`.
282
284
  {
283
285
  "id": 1, "image_id": 42, "category_id": 1,
284
286
  "segmentation": [...], "bbox": [...], "area": 1234, "iscrowd": 0,
285
- "attributes": {"ripeness": 1, "grade": 20}
287
+ "attributes": {"scale": 3, "viewpoint": 0}
286
288
  }
287
289
  ```
288
290
 
289
291
  Notes:
290
292
 
291
- - Raw ids are remapped to a contiguous `0..n-1` per head (so `grade`'s `10`/`20` become
292
- `0`/`1`); `categories` may be omitted, in which case the id set is inferred.
293
+ - Raw ids are remapped to a contiguous `0..n-1` per head (so `scale`'s `1`/`2`/`3` become
294
+ `0`/`1`/`2`); `categories` may be omitted, in which case the id set is inferred.
293
295
  - A missing per-annotation value defaults to `0`; a JSON with **no** `attributes` ⇒
294
296
  detection-only, exactly as before.
295
297
 
@@ -326,6 +328,27 @@ EoMT("runs/train/eomt-l").val(data="coco")
326
328
  EoMT("runs/train/eomt-l").predict("images/", plot=True) # writes annotated images
327
329
  ```
328
330
 
331
+ ### Compress to int8
332
+
333
+ `compress()` applies **int8 weight-only quantization** (via [torchao](https://github.com/pytorch/ao))
334
+ to the ViT transformer blocks — the bulk of the parameters — while keeping the
335
+ prediction heads in full precision. It's **data-free** (no calibration) and shrinks the
336
+ checkpoint ≈**3.4×** on `l` with no measurable mAP loss.
337
+
338
+ ```python
339
+ m = EoMT("runs/train/eomt-l")
340
+ m.compress("int8", data="coco", save="runs/train/eomt-int8/weights/best.pt") # quantize, validate, save
341
+ # size/mAP/latency deltas are returned (and written to compression_metrics.json in the
342
+ # run folder); omit `data` to skip the before/after validation
343
+
344
+ # Reload like any run — the recipe is recorded in the checkpoint, so the int8 layout
345
+ # is rebuilt automatically:
346
+ EoMT("runs/train/eomt-int8").predict("images/")
347
+ ```
348
+
349
+ The compressed model is **GPU-only** (torchao's int8 kernels have no CPU path), and
350
+ `best.pt` exported from an EMA run quantizes exactly those EMA weights.
351
+
329
352
  ### Useful `train()` options
330
353
 
331
354
  Passed as keyword arguments to `model.train(...)`:
@@ -55,9 +55,9 @@ with a fixed set of **learnable queries** (the Mask2Former idea) — each query
55
55
  "slot" that latches onto one object instance. After the encoder runs, every query emits
56
56
  a single vector, the **per-query embedding** of shape `[B, Q, hidden]`. The whole model
57
57
  is then just "turn that embedding into predictions": a **class head** for the primary
58
- label and a **mask/box head** for geometry. It is **NMS-free**, so two overlapping cars
59
- stay two distinct queries instead of being merged — the property that lets attributes
60
- stay attached to the right instance.
58
+ label and a **mask/box head** for geometry. It is **NMS-free**, so two overlapping
59
+ garments stay two distinct queries instead of being merged — the property that lets
60
+ attributes stay attached to the right instance.
61
61
 
62
62
  The attribute heads add nothing to this picture except themselves: they tap the **exact
63
63
  same embedding** (captured non-invasively with a forward hook), each a small classifier
@@ -127,9 +127,9 @@ attribute heads add a thin linear/MLP per head and are negligible by design.
127
127
  ## ⭐ Method — factorizing the label space
128
128
 
129
129
  This is the contribution. Conventional detectors fold every distinction into a single
130
- flat label space. When an object has both a *type* and several *attributes* — a car's
131
- make **and** its colour **and** whether a door is open — the only way to express that is
132
- the Cartesian product `type × colour × state × …`. That space explodes combinatorially,
130
+ flat label space. When an object has both a *type* and several *attributes* — a garment's
131
+ class **and** its viewpoint **and** whether it's occluded — the only way to express that is
132
+ the Cartesian product `type × viewpoint × occlusion × …`. That space explodes combinatorially,
133
133
  starves each leaf class of training examples, and breaks Hungarian matching by
134
134
  multiplying the query targets.
135
135
 
@@ -144,11 +144,11 @@ per-instance query embedding the detector already computes — they **add, not m
144
144
  Because the heads are independent:
145
145
 
146
146
  - **Categories stay collapsed and general.** Keep a compact, well-populated primary
147
- taxonomy (`car`, `person`, `fruit`) and push fine-grained or orthogonal distinctions
148
- into attributes — every primary class keeps its full sample count instead of being
149
- shattered into rare leaves.
150
- - **Unseen combinations generalize.** A `colour` head trained across many object types
151
- predicts `red` on a class it *never co-occurred with in red*, because colour is
147
+ taxonomy (`short_sleeve_top`, `dress`, `trousers`) and push fine-grained or orthogonal
148
+ distinctions into attributes — every primary class keeps its full sample count instead
149
+ of being shattered into rare leaves.
150
+ - **Unseen combinations generalize.** A `viewpoint` head trained across many garment types
151
+ predicts `side` on a class it *never co-occurred with sideways*, because viewpoint is
152
152
  learned independently of type. The model composes `attribute × class` combinations
153
153
  that **never appear in the training data** — combinations a flat label space cannot
154
154
  even represent.
@@ -159,25 +159,25 @@ Because the heads are independent:
159
159
  matched queries — they add only a thin linear/MLP head and a cross-entropy term, not
160
160
  a second model or a second pass.
161
161
 
162
- ### Example: a bowl of fruit
162
+ ### Example: clothing with per-instance attributes
163
163
 
164
- One model segments each fruit (primary classes `apple` / `banana` / `orange` /
165
- `pear`) and, for **every** detection, reads off two **independent** attribute heads —
166
- `ripeness` (`unripe` / `turning` / `ripe`) and a quality `grade` (`A` / `B`). The
167
- renderer prints the primary class + score on the first row and each attribute + its
168
- confidence on the row beneath it.
164
+ One model segments each garment (primary classes like `vest_dress` / `short_sleeve_top`
165
+ / `long_sleeve_dress` / `skirt` / `trousers` …) and, for **every** detection, reads off
166
+ four **independent** attribute heads — `scale` (`small` / `modest` / `large`),
167
+ `occlusion` (`no` / `slight` / `medium`), `zoom_in` (`no` / `medium` / `large`) and
168
+ `viewpoint` (`frontal` / `side` / `back`). The renderer prints the primary class + score
169
+ on the first row and each attribute + its confidence on the rows beneath it.
169
170
 
170
- ![A bowl of fruit; each box labelled with its fruit class plus a ripeness and a grade](https://raw.githubusercontent.com/imagra93/attr-eomt/main/docs/examples/example_1.png)
171
+ ![Two people in dresses; each instance labelled with its garment class plus scale, occlusion, zoom and viewpoint attributes](https://raw.githubusercontent.com/imagra93/attr-eomt/main/docs/examples/sample.jpg)
171
172
 
172
- `ripeness` and `grade` are *orthogonal* — they vary independently — which is exactly
173
- the case that's awkward to fold into the primary class space. The same pattern fits
174
- any "class **plus** per-instance sub-labels" task: **retail shelves → product +
175
- facing**, **vehicles → type + colour + damage state**, **cells / leaves → type +
176
- health**, **apparel → garment + pattern**.
173
+ The four attributes are *orthogonal* to the garment class — they vary independently —
174
+ which is exactly the case that's awkward to fold into the primary class space. The same
175
+ pattern fits any "class **plus** per-instance sub-labels" task: **retail shelves →
176
+ product + facing**, **documents → element + role**, **cells → type + health**.
177
177
 
178
- > The image above is a **simulation**: a stock photo with hand-placed detections fed
179
- > through the package's own renderer ([`eomt.visualize.draw_instances`](eomt/visualize.py))
180
- > to show the output format — not a trained model's predictions.
178
+ > Trained on the public **[DeepFashion2](https://github.com/switchablenorms/DeepFashion2)**
179
+ > dataset (13 garment classes + 4 attribute heads) and rendered with the package's own
180
+ > renderer ([`eomt.visualize.draw_instances`](eomt/visualize.py)).
181
181
 
182
182
  ---
183
183
 
@@ -222,18 +222,19 @@ names) are discovered from the JSON, the same as `nc`.
222
222
  ```jsonc
223
223
  "attributes": [
224
224
  {
225
- "name": "ripeness",
225
+ "name": "scale",
226
226
  "categories": [
227
- {"id": 0, "name": "unripe"},
228
- {"id": 1, "name": "turning"},
229
- {"id": 2, "name": "ripe"}
227
+ {"id": 1, "name": "small"},
228
+ {"id": 2, "name": "modest"},
229
+ {"id": 3, "name": "large"}
230
230
  ]
231
231
  },
232
232
  {
233
- "name": "grade",
233
+ "name": "viewpoint",
234
234
  "categories": [
235
- {"id": 10, "name": "A"},
236
- {"id": 20, "name": "B"}
235
+ {"id": 0, "name": "frontal"},
236
+ {"id": 1, "name": "side"},
237
+ {"id": 2, "name": "back"}
237
238
  ]
238
239
  }
239
240
  ]
@@ -245,14 +246,14 @@ names) are discovered from the JSON, the same as `nc`.
245
246
  {
246
247
  "id": 1, "image_id": 42, "category_id": 1,
247
248
  "segmentation": [...], "bbox": [...], "area": 1234, "iscrowd": 0,
248
- "attributes": {"ripeness": 1, "grade": 20}
249
+ "attributes": {"scale": 3, "viewpoint": 0}
249
250
  }
250
251
  ```
251
252
 
252
253
  Notes:
253
254
 
254
- - Raw ids are remapped to a contiguous `0..n-1` per head (so `grade`'s `10`/`20` become
255
- `0`/`1`); `categories` may be omitted, in which case the id set is inferred.
255
+ - Raw ids are remapped to a contiguous `0..n-1` per head (so `scale`'s `1`/`2`/`3` become
256
+ `0`/`1`/`2`); `categories` may be omitted, in which case the id set is inferred.
256
257
  - A missing per-annotation value defaults to `0`; a JSON with **no** `attributes` ⇒
257
258
  detection-only, exactly as before.
258
259
 
@@ -289,6 +290,27 @@ EoMT("runs/train/eomt-l").val(data="coco")
289
290
  EoMT("runs/train/eomt-l").predict("images/", plot=True) # writes annotated images
290
291
  ```
291
292
 
293
+ ### Compress to int8
294
+
295
+ `compress()` applies **int8 weight-only quantization** (via [torchao](https://github.com/pytorch/ao))
296
+ to the ViT transformer blocks — the bulk of the parameters — while keeping the
297
+ prediction heads in full precision. It's **data-free** (no calibration) and shrinks the
298
+ checkpoint ≈**3.4×** on `l` with no measurable mAP loss.
299
+
300
+ ```python
301
+ m = EoMT("runs/train/eomt-l")
302
+ m.compress("int8", data="coco", save="runs/train/eomt-int8/weights/best.pt") # quantize, validate, save
303
+ # size/mAP/latency deltas are returned (and written to compression_metrics.json in the
304
+ # run folder); omit `data` to skip the before/after validation
305
+
306
+ # Reload like any run — the recipe is recorded in the checkpoint, so the int8 layout
307
+ # is rebuilt automatically:
308
+ EoMT("runs/train/eomt-int8").predict("images/")
309
+ ```
310
+
311
+ The compressed model is **GPU-only** (torchao's int8 kernels have no CPU path), and
312
+ `best.pt` exported from an EMA run quantizes exactly those EMA weights.
313
+
292
314
  ### Useful `train()` options
293
315
 
294
316
  Passed as keyword arguments to `model.train(...)`:
@@ -1 +1 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 940 470" width="940" height="470"><rect x="1" y="1" width="938" height="468" rx="16" fill="#0e1318" stroke="#2a3744" stroke-width="1.5"/><text x="32" y="46" font-family="DejaVu Sans, sans-serif" font-size="23" fill="#d7e0e8" text-anchor="start" font-weight="bold">attr-eomt</text><text x="33" y="66" font-family="DejaVu Sans Mono, monospace" font-size="12" fill="#8c9aa8" text-anchor="start" font-weight="normal">architecture · the heads tap one shared embedding</text><circle cx="525" cy="42" r="4.5" fill="#4ec9b0"/><text x="536" y="46" font-family="DejaVu Sans Mono, monospace" font-size="11" fill="#8c9aa8">shared embedding</text><circle cx="659.6" cy="42" r="4.5" fill="#5aa6f0"/><text x="670.6" y="46" font-family="DejaVu Sans Mono, monospace" font-size="11" fill="#8c9aa8">primary head</text><circle cx="767.8" cy="42" r="4.5" fill="#e2b341"/><text x="778.8" y="46" font-family="DejaVu Sans Mono, monospace" font-size="11" fill="#8c9aa8">attribute head</text><defs><marker id="a" markerWidth="9" markerHeight="9" refX="7" refY="4.5" orient="auto"><path d="M0,0 L9,4.5 L0,9 z" fill="#5e6b78"/></marker><marker id="aq" markerWidth="9" markerHeight="9" refX="7" refY="4.5" orient="auto"><path d="M0,0 L9,4.5 L0,9 z" fill="#4ec9b0"/></marker></defs><rect x="34" y="220" width="104" height="60" rx="9" fill="#1b232c" stroke="#2a3744" stroke-width="1.4" /><text x="86" y="248" font-family="DejaVu Sans Mono, monospace" font-size="13" fill="#d7e0e8" text-anchor="middle" font-weight="normal">image</text><text x="86" y="266" font-family="DejaVu Sans Mono, monospace" font-size="10.5" fill="#5e6b78" text-anchor="middle" font-weight="normal">644²</text><path d="M138,250 H176" stroke="#5e6b78" stroke-width="1.6" fill="none" marker-end="url(#a)"/><rect x="180" y="188" width="178" height="124" rx="9" fill="#1b232c" stroke="#5aa6f0" stroke-width="1.4" /><text x="269" y="224" font-family="DejaVu Sans, sans-serif" font-size="14.5" fill="#5aa6f0" text-anchor="middle" font-weight="bold">DINOv2 ViT</text><text x="269" y="244" font-family="DejaVu Sans Mono, monospace" font-size="11" fill="#5e6b78" text-anchor="middle" font-weight="normal">last blocks +</text><text x="269" y="260" font-family="DejaVu Sans Mono, monospace" font-size="11" fill="#5e6b78" text-anchor="middle" font-weight="normal">learnable queries</text><text x="269" y="286" font-family="DejaVu Sans Mono, monospace" font-size="11" fill="#8c9aa8" text-anchor="middle" font-weight="normal">NMS-free</text><path d="M358,250 H426" stroke="#5e6b78" stroke-width="1.6" fill="none" marker-end="url(#a)"/><rect x="430" y="221" width="150" height="58" rx="9" fill="#10201c" stroke="#4ec9b0" stroke-width="1.8" /><text x="505" y="246" font-family="DejaVu Sans Mono, monospace" font-size="13.5" fill="#4ec9b0" text-anchor="middle" font-weight="bold">query_embed</text><text x="505" y="265" font-family="DejaVu Sans Mono, monospace" font-size="10.5" fill="#5e6b78" text-anchor="middle" font-weight="normal">[B, Q, hidden]</text><text x="505" y="204" font-family="DejaVu Sans Mono, monospace" font-size="11" fill="#8c9aa8" text-anchor="middle" font-weight="normal">▲ forward hook on class_predictor</text><path d="M580,205 C638,205 638,94 704,94" stroke="#4ec9b0" stroke-width="2.1" fill="none" marker-end="url(#aq)" /><rect x="708" y="70" width="200" height="48" rx="9" fill="#1b232c" stroke="#5aa6f0" stroke-width="1.4" /><text x="808.0" y="91" font-family="DejaVu Sans Mono, monospace" font-size="12.5" fill="#5aa6f0" text-anchor="middle" font-weight="normal">class_predictor</text><text x="808.0" y="107" font-family="DejaVu Sans Mono, monospace" font-size="10.5" fill="#5e6b78" text-anchor="middle" font-weight="normal">primary class · nc+1</text><path d="M580,218 C638,218 638,164 704,164" stroke="#4ec9b0" stroke-width="2.1" fill="none" marker-end="url(#aq)" /><rect x="708" y="140" width="200" height="48" rx="9" fill="#1b232c" stroke="#5aa6f0" stroke-width="1.4" /><text x="808.0" y="161" font-family="DejaVu Sans Mono, monospace" font-size="12.5" fill="#5aa6f0" text-anchor="middle" font-weight="normal">mask / box head</text><text x="808.0" y="177" font-family="DejaVu Sans Mono, monospace" font-size="10.5" fill="#5e6b78" text-anchor="middle" font-weight="normal">geometry</text><path d="M580,228 C638,228 638,254 704,254" stroke="#4ec9b0" stroke-width="2.1" fill="none" marker-end="url(#aq)" /><rect x="708" y="230" width="200" height="48" rx="9" fill="#1b232c" stroke="#e2b341" stroke-width="1.4" /><text x="808.0" y="251" font-family="DejaVu Sans Mono, monospace" font-size="12.5" fill="#e2b341" text-anchor="middle" font-weight="normal">aux · "ripeness"</text><text x="808.0" y="267" font-family="DejaVu Sans Mono, monospace" font-size="10.5" fill="#5e6b78" text-anchor="middle" font-weight="normal">3 classes</text><path d="M580,236 C638,236 638,324 704,324" stroke="#4ec9b0" stroke-width="2.1" fill="none" marker-end="url(#aq)" /><rect x="708" y="300" width="200" height="48" rx="9" fill="#1b232c" stroke="#e2b341" stroke-width="1.4" /><text x="808.0" y="321" font-family="DejaVu Sans Mono, monospace" font-size="12.5" fill="#e2b341" text-anchor="middle" font-weight="normal">aux · "grade"</text><text x="808.0" y="337" font-family="DejaVu Sans Mono, monospace" font-size="10.5" fill="#5e6b78" text-anchor="middle" font-weight="normal">2 classes</text><path d="M580,243 C638,243 638,396 704,396" stroke="#4ec9b0" stroke-width="2.1" fill="none" marker-end="url(#aq)" stroke-dasharray="3 4"/><rect x="708" y="372" width="200" height="48" rx="9" fill="#1b232c" stroke="#5e6b78" stroke-width="1.4" stroke-dasharray="4 4"/><text x="808.0" y="393" font-family="DejaVu Sans Mono, monospace" font-size="12.5" fill="#8c9aa8" text-anchor="middle" font-weight="normal">… as many as data defines</text><text x="470.0" y="444" font-family="DejaVu Sans Mono, monospace" font-size="11.5" fill="#5e6b78" text-anchor="middle" font-weight="normal">teal = the single per-query embedding · every head is a small classifier on top of it</text></svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 940 470" width="940" height="470"><rect x="1" y="1" width="938" height="468" rx="16" fill="#0e1318" stroke="#2a3744" stroke-width="1.5"/><text x="32" y="46" font-family="DejaVu Sans, sans-serif" font-size="23" fill="#d7e0e8" text-anchor="start" font-weight="bold">attr-eomt</text><text x="33" y="66" font-family="DejaVu Sans Mono, monospace" font-size="12" fill="#8c9aa8" text-anchor="start" font-weight="normal">architecture · the heads tap one shared embedding</text><circle cx="525" cy="42" r="4.5" fill="#4ec9b0"/><text x="536" y="46" font-family="DejaVu Sans Mono, monospace" font-size="11" fill="#8c9aa8">shared embedding</text><circle cx="659.6" cy="42" r="4.5" fill="#5aa6f0"/><text x="670.6" y="46" font-family="DejaVu Sans Mono, monospace" font-size="11" fill="#8c9aa8">primary head</text><circle cx="767.8" cy="42" r="4.5" fill="#e2b341"/><text x="778.8" y="46" font-family="DejaVu Sans Mono, monospace" font-size="11" fill="#8c9aa8">attribute head</text><defs><marker id="a" markerWidth="9" markerHeight="9" refX="7" refY="4.5" orient="auto"><path d="M0,0 L9,4.5 L0,9 z" fill="#5e6b78"/></marker><marker id="aq" markerWidth="9" markerHeight="9" refX="7" refY="4.5" orient="auto"><path d="M0,0 L9,4.5 L0,9 z" fill="#4ec9b0"/></marker></defs><rect x="34" y="220" width="104" height="60" rx="9" fill="#1b232c" stroke="#2a3744" stroke-width="1.4" /><text x="86" y="248" font-family="DejaVu Sans Mono, monospace" font-size="13" fill="#d7e0e8" text-anchor="middle" font-weight="normal">image</text><text x="86" y="266" font-family="DejaVu Sans Mono, monospace" font-size="10.5" fill="#5e6b78" text-anchor="middle" font-weight="normal">644²</text><path d="M138,250 H176" stroke="#5e6b78" stroke-width="1.6" fill="none" marker-end="url(#a)"/><rect x="180" y="188" width="178" height="124" rx="9" fill="#1b232c" stroke="#5aa6f0" stroke-width="1.4" /><text x="269" y="224" font-family="DejaVu Sans, sans-serif" font-size="14.5" fill="#5aa6f0" text-anchor="middle" font-weight="bold">DINOv2 ViT</text><text x="269" y="244" font-family="DejaVu Sans Mono, monospace" font-size="11" fill="#5e6b78" text-anchor="middle" font-weight="normal">last blocks +</text><text x="269" y="260" font-family="DejaVu Sans Mono, monospace" font-size="11" fill="#5e6b78" text-anchor="middle" font-weight="normal">learnable queries</text><text x="269" y="286" font-family="DejaVu Sans Mono, monospace" font-size="11" fill="#8c9aa8" text-anchor="middle" font-weight="normal">NMS-free</text><path d="M358,250 H426" stroke="#5e6b78" stroke-width="1.6" fill="none" marker-end="url(#a)"/><rect x="430" y="221" width="150" height="58" rx="9" fill="#10201c" stroke="#4ec9b0" stroke-width="1.8" /><text x="505" y="246" font-family="DejaVu Sans Mono, monospace" font-size="13.5" fill="#4ec9b0" text-anchor="middle" font-weight="bold">query_embed</text><text x="505" y="265" font-family="DejaVu Sans Mono, monospace" font-size="10.5" fill="#5e6b78" text-anchor="middle" font-weight="normal">[B, Q, hidden]</text><text x="505" y="204" font-family="DejaVu Sans Mono, monospace" font-size="11" fill="#8c9aa8" text-anchor="middle" font-weight="normal">▲ forward hook on class_predictor</text><path d="M580,205 C638,205 638,94 704,94" stroke="#4ec9b0" stroke-width="2.1" fill="none" marker-end="url(#aq)" /><rect x="708" y="70" width="200" height="48" rx="9" fill="#1b232c" stroke="#5aa6f0" stroke-width="1.4" /><text x="808.0" y="91" font-family="DejaVu Sans Mono, monospace" font-size="12.5" fill="#5aa6f0" text-anchor="middle" font-weight="normal">class_predictor</text><text x="808.0" y="107" font-family="DejaVu Sans Mono, monospace" font-size="10.5" fill="#5e6b78" text-anchor="middle" font-weight="normal">primary class · nc+1</text><path d="M580,218 C638,218 638,164 704,164" stroke="#4ec9b0" stroke-width="2.1" fill="none" marker-end="url(#aq)" /><rect x="708" y="140" width="200" height="48" rx="9" fill="#1b232c" stroke="#5aa6f0" stroke-width="1.4" /><text x="808.0" y="161" font-family="DejaVu Sans Mono, monospace" font-size="12.5" fill="#5aa6f0" text-anchor="middle" font-weight="normal">mask / box head</text><text x="808.0" y="177" font-family="DejaVu Sans Mono, monospace" font-size="10.5" fill="#5e6b78" text-anchor="middle" font-weight="normal">geometry</text><path d="M580,228 C638,228 638,254 704,254" stroke="#4ec9b0" stroke-width="2.1" fill="none" marker-end="url(#aq)" /><rect x="708" y="230" width="200" height="48" rx="9" fill="#1b232c" stroke="#e2b341" stroke-width="1.4" /><text x="808.0" y="251" font-family="DejaVu Sans Mono, monospace" font-size="12.5" fill="#e2b341" text-anchor="middle" font-weight="normal">aux · "scale"</text><text x="808.0" y="267" font-family="DejaVu Sans Mono, monospace" font-size="10.5" fill="#5e6b78" text-anchor="middle" font-weight="normal">3 classes</text><path d="M580,236 C638,236 638,324 704,324" stroke="#4ec9b0" stroke-width="2.1" fill="none" marker-end="url(#aq)" /><rect x="708" y="300" width="200" height="48" rx="9" fill="#1b232c" stroke="#e2b341" stroke-width="1.4" /><text x="808.0" y="321" font-family="DejaVu Sans Mono, monospace" font-size="12.5" fill="#e2b341" text-anchor="middle" font-weight="normal">aux · "viewpoint"</text><text x="808.0" y="337" font-family="DejaVu Sans Mono, monospace" font-size="10.5" fill="#5e6b78" text-anchor="middle" font-weight="normal">3 classes</text><path d="M580,243 C638,243 638,396 704,396" stroke="#4ec9b0" stroke-width="2.1" fill="none" marker-end="url(#aq)" stroke-dasharray="3 4"/><rect x="708" y="372" width="200" height="48" rx="9" fill="#1b232c" stroke="#5e6b78" stroke-width="1.4" stroke-dasharray="4 4"/><text x="808.0" y="393" font-family="DejaVu Sans Mono, monospace" font-size="12.5" fill="#8c9aa8" text-anchor="middle" font-weight="normal">… as many as data defines</text><text x="470.0" y="444" font-family="DejaVu Sans Mono, monospace" font-size="11.5" fill="#5e6b78" text-anchor="middle" font-weight="normal">teal = the single per-query embedding · every head is a small classifier on top of it</text></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 940 500" width="940" height="500"><rect x="1" y="1" width="938" height="498" rx="16" fill="#0e1318" stroke="#2a3744" stroke-width="1.5"/><text x="32" y="48" font-family="DejaVu Sans, sans-serif" font-size="23" fill="#d7e0e8" text-anchor="start" font-weight="bold">Factorize the label space</text><text x="33" y="69" font-family="DejaVu Sans Mono, monospace" font-size="12" fill="#8c9aa8" text-anchor="start" font-weight="normal">a garment's type, viewpoint and occlusion are orthogonal — don't multiply them</text><line x1="470.0" y1="110" x2="470.0" y2="430" stroke="#2a3744" stroke-width="1.2" stroke-dasharray="3 5"/><text x="470.0" y="254.0" font-family="DejaVu Sans, sans-serif" font-size="13" fill="#5e6b78" text-anchor="middle" font-weight="bold">vs</text><circle cx="470.0" cy="248.0" r="18" fill="#0e1318" stroke="#2a3744"/><text x="470.0" y="254.0" font-family="DejaVu Sans, sans-serif" font-size="13" fill="#8c9aa8" text-anchor="middle" font-weight="bold">vs</text><text x="245" y="128" font-family="DejaVu Sans Mono, monospace" font-size="12" fill="#e07a5f" text-anchor="middle" font-weight="bold">FLAT · ONE HEAD</text><text x="108" y="171.0" font-family="DejaVu Sans Mono, monospace" font-size="10.5" fill="#8c9aa8" text-anchor="end" font-weight="normal">top</text><rect x="120" y="150" width="72" height="34" rx="5" fill="#1b232c" stroke="rgba(224,122,95,.45)" stroke-width="1"/><text x="156.0" y="171.0" font-family="DejaVu Sans Mono, monospace" font-size="9.5" fill="#5e6b78" text-anchor="middle" font-weight="normal">top·frnt</text><rect x="200" y="150" width="72" height="34" rx="5" fill="#1b232c" stroke="rgba(224,122,95,.45)" stroke-width="1"/><text x="236.0" y="171.0" font-family="DejaVu Sans Mono, monospace" font-size="9.5" fill="#5e6b78" text-anchor="middle" font-weight="normal">top·side</text><rect x="280" y="150" width="72" height="34" rx="5" fill="#1b232c" stroke="rgba(224,122,95,.45)" stroke-width="1"/><text x="316.0" y="171.0" font-family="DejaVu Sans Mono, monospace" font-size="9.5" fill="#5e6b78" text-anchor="middle" font-weight="normal">top·back</text><text x="108" y="213.0" font-family="DejaVu Sans Mono, monospace" font-size="10.5" fill="#8c9aa8" text-anchor="end" font-weight="normal">dress</text><rect x="120" y="192" width="72" height="34" rx="5" fill="#1b232c" stroke="rgba(224,122,95,.45)" stroke-width="1"/><text x="156.0" y="213.0" font-family="DejaVu Sans Mono, monospace" font-size="9.5" fill="#5e6b78" text-anchor="middle" font-weight="normal">drs·frnt</text><rect x="200" y="192" width="72" height="34" rx="5" fill="#1b232c" stroke="rgba(224,122,95,.45)" stroke-width="1"/><text x="236.0" y="213.0" font-family="DejaVu Sans Mono, monospace" font-size="9.5" fill="#5e6b78" text-anchor="middle" font-weight="normal">drs·side</text><rect x="280" y="192" width="72" height="34" rx="5" fill="#1b232c" stroke="rgba(224,122,95,.45)" stroke-width="1"/><text x="316.0" y="213.0" font-family="DejaVu Sans Mono, monospace" font-size="9.5" fill="#5e6b78" text-anchor="middle" font-weight="normal">drs·back</text><text x="108" y="255.0" font-family="DejaVu Sans Mono, monospace" font-size="10.5" fill="#8c9aa8" text-anchor="end" font-weight="normal">skirt</text><rect x="120" y="234" width="72" height="34" rx="5" fill="#1b232c" stroke="rgba(224,122,95,.45)" stroke-width="1"/><text x="156.0" y="255.0" font-family="DejaVu Sans Mono, monospace" font-size="9.5" fill="#5e6b78" text-anchor="middle" font-weight="normal">skt·frnt</text><rect x="200" y="234" width="72" height="34" rx="5" fill="#1b232c" stroke="rgba(224,122,95,.45)" stroke-width="1"/><text x="236.0" y="255.0" font-family="DejaVu Sans Mono, monospace" font-size="9.5" fill="#5e6b78" text-anchor="middle" font-weight="normal">skt·side</text><rect x="280" y="234" width="72" height="34" rx="5" fill="#1b232c" stroke="rgba(224,122,95,.45)" stroke-width="1"/><text x="316.0" y="255.0" font-family="DejaVu Sans Mono, monospace" font-size="9.5" fill="#5e6b78" text-anchor="middle" font-weight="normal">skt·back</text><text x="108" y="297.0" font-family="DejaVu Sans Mono, monospace" font-size="10.5" fill="#8c9aa8" text-anchor="end" font-weight="normal">trousers</text><rect x="120" y="276" width="72" height="34" rx="5" fill="#1b232c" stroke="rgba(224,122,95,.45)" stroke-width="1"/><text x="156.0" y="297.0" font-family="DejaVu Sans Mono, monospace" font-size="9.5" fill="#5e6b78" text-anchor="middle" font-weight="normal">trs·frnt</text><rect x="200" y="276" width="72" height="34" rx="5" fill="#1b232c" stroke="rgba(224,122,95,.45)" stroke-width="1"/><text x="236.0" y="297.0" font-family="DejaVu Sans Mono, monospace" font-size="9.5" fill="#5e6b78" text-anchor="middle" font-weight="normal">trs·side</text><rect x="280" y="276" width="72" height="34" rx="5" fill="#1b232c" stroke="rgba(224,122,95,.45)" stroke-width="1"/><text x="316.0" y="297.0" font-family="DejaVu Sans Mono, monospace" font-size="9.5" fill="#5e6b78" text-anchor="middle" font-weight="normal">trs·back</text><text x="245" y="338" font-family="DejaVu Sans Mono, monospace" font-size="11" fill="#8c9aa8" text-anchor="middle" font-weight="normal">× {visible, occluded} states</text><text x="245" y="376" font-family="DejaVu Sans Mono, monospace" font-size="15" fill="#e07a5f" text-anchor="middle" font-weight="bold">4 × 3 × 2</text><text x="245" y="402" font-family="DejaVu Sans Mono, monospace" font-size="11" fill="#5e6b78" text-anchor="middle" font-weight="normal">= 24 leaf classes</text><text x="700" y="128" font-family="DejaVu Sans Mono, monospace" font-size="12" fill="#e2b341" text-anchor="middle" font-weight="bold">FACTORIZED · INDEPENDENT HEADS</text><rect x="560" y="165" width="320" height="58" rx="9" fill="#1b232c" stroke="#e2b341" stroke-width="1.3"/><text x="576" y="190" font-family="DejaVu Sans Mono, monospace" font-size="13" fill="#e2b341" text-anchor="start" font-weight="bold">type head</text><text x="576" y="208" font-family="DejaVu Sans Mono, monospace" font-size="10.5" fill="#5e6b78" text-anchor="start" font-weight="normal">top · dress · skirt · trousers</text><circle cx="852" cy="194" r="15" fill="#10201c" stroke="#e2b341"/><text x="852" y="199" font-family="DejaVu Sans Mono, monospace" font-size="14" fill="#4ec9b0" text-anchor="middle" font-weight="bold">4</text><rect x="560" y="237" width="320" height="58" rx="9" fill="#1b232c" stroke="#e2b341" stroke-width="1.3"/><text x="576" y="262" font-family="DejaVu Sans Mono, monospace" font-size="13" fill="#e2b341" text-anchor="start" font-weight="bold">viewpoint head</text><text x="576" y="280" font-family="DejaVu Sans Mono, monospace" font-size="10.5" fill="#5e6b78" text-anchor="start" font-weight="normal">frontal · side · back</text><circle cx="852" cy="266" r="15" fill="#10201c" stroke="#e2b341"/><text x="852" y="271" font-family="DejaVu Sans Mono, monospace" font-size="14" fill="#4ec9b0" text-anchor="middle" font-weight="bold">3</text><rect x="560" y="309" width="320" height="58" rx="9" fill="#1b232c" stroke="#e2b341" stroke-width="1.3"/><text x="576" y="334" font-family="DejaVu Sans Mono, monospace" font-size="13" fill="#e2b341" text-anchor="start" font-weight="bold">occlusion head</text><text x="576" y="352" font-family="DejaVu Sans Mono, monospace" font-size="10.5" fill="#5e6b78" text-anchor="start" font-weight="normal">visible · occluded</text><circle cx="852" cy="338" r="15" fill="#10201c" stroke="#e2b341"/><text x="852" y="343" font-family="DejaVu Sans Mono, monospace" font-size="14" fill="#4ec9b0" text-anchor="middle" font-weight="bold">2</text><text x="700" y="399" font-family="DejaVu Sans Mono, monospace" font-size="15" fill="#4ec9b0" text-anchor="middle" font-weight="bold">4 + 3 + 2</text><text x="700" y="425" font-family="DejaVu Sans Mono, monospace" font-size="11" fill="#5e6b78" text-anchor="middle" font-weight="normal">= 9 outputs · each head sees every instance</text><text x="470.0" y="472" font-family="DejaVu Sans Mono, monospace" font-size="11" fill="#5e6b78" text-anchor="middle" font-weight="normal">compact primary taxonomy keeps its sample counts · unseen attribute × class combinations still generalize</text></svg>
Binary file