Spaces:
Running
A newer version of the Gradio SDK is available: 6.20.0
Annotated-Photo Fallback: Image Sizing and Annotation Accuracy
Review date: 2026-06-14. Scope: one user report β when the annotated-photo fallback triggers (instead of the 3D cutaway), the uploaded image is "too large to display," and the annotation placement should be confirmed accurate.
This document is findings + next steps for Codex to implement. It does not
authorize any deployment or Hugging Face changes on its own; follow the normal
GitHub β HF sync and verification flow in AGENTS.md. All file/line references
are to index.html unless noted.
TL;DR
- The image-sizing bug is real and confirmed in a browser. The
annotated-photo image renders at its intrinsic aspect height at full panel
width, ignoring the viewport height, so tall photos overflow the viewport
by ~2x.
object-fit: containnever engages because the box it should fit inside is itself oversized. - Root cause: nested
display: grid; place-items: centercontainers (.annotated-stageβ.annotation-frame) with auto-sized rows. A child'sheight: 100%resolves against an auto (content-sized) grid row, not the container's pixel height, so it collapses back to the image's natural size. - The annotation accuracy is mostly broken as a side effect of the sizing
bug β labels are positioned against the oversized, overflowing image box,
so any callout in the lower half of a tall photo lands below the visible
viewport and is clipped. Fixing the sizing fixes most of the misplacement.
A few annotation-accuracy improvements remain that are independent of sizing
(no marker dot at the point, edge-clamp detaches labels, unused
box).
How this was verified (not just code inspection)
I reproduced the exact buildAnnotatedPhoto DOM (index.html:996-1076) and the
exact CSS rules (.fallback-stage/.annotated-stage/.annotation-frame/
.annotation-image, index.html:466-511) in a standalone page, loaded a
3000 Γ 4000 portrait test image into a 900 Γ 600 viewport pane, and
measured the rendered geometry in a real browser (Chromium via Playwright):
| Scenario | Image rendered (WΓH) | Fits 600px-tall pane? |
|---|---|---|
| Current code (baseline) | 864 Γ 1151 | β overflows (image bottom at 1209px) |
| Fix: pin image absolutely to padded box | 864 Γ 498 | β contained (top 58 β bottom 556) |
Fix: definite minmax(0,1fr) tracks at both grid levels |
864 Γ 498 | β contained |
864 = 900 β 18pxΓ2 (panel width minus horizontal padding); 1151 β 864 Γ (4000/3000) β i.e. the image is laid out at panel width and natural aspect,
with zero vertical constraint. That is the "too large to display" symptom.
(The repro page was temporary and has been removed; re-create it if you want to
re-measure.)
Root cause (precise)
The fallback markup is three nested boxes:
#viewport (absolute, inset:0) <- definite size, good
ββ .fallback-stage.annotated-stage index.html:1011-1012
position:absolute; inset:0 (definite height β)
display:grid; place-items:center; padding:58/18/44 index.html:490-495
ββ .annotation-frame index.html:1013-1014
width:100%; height:100% index.html:497-503
display:grid; place-items:center <-- second nested grid
ββ img.annotation-image index.html:1015-1018
β width:100%; height:100%; object-fit:contain index.html:505-511
ββ .label-layer (absolute, inset:0)
Two compounding CSS facts:
place-items: centersetsalign-items: center, notstretch. Combined with the defaultgrid-auto-rows: auto, the single grid row is content sized, not stretched to the container's definite height.- A percentage
heightresolves against the grid area (the track), and anautotrack is indefinite. Soheight: 100%on a child of aplace-items: centergrid computes asauto.
This happens twice:
.annotation-frame { height: 100% }inside.annotated-stageβ the frame's track is auto β frame height becomes auto.img.annotation-image { height: 100% }inside.annotation-frameβ the image's track is auto β image height becomes auto β the<img>uses its intrinsic aspect ratio at the available width (864px) β 1151px tall.
Measured proof that the bug is the inner grid too: even after forcing
.annotation-frame to a correct definite 498px box, the image still rendered
1151px, because its height:100% resolves against .annotation-frame's inner
auto row, not the frame's element box. Both grid levels must be fixed (or the
nested-grid sizing dropped entirely).
Note:
.source-imagein the side panel (index.html:384-390) does not have this bug because it has a fixedheight: 180pxβ a definite height, soobject-fit: containworks there. The viewport fallback wants to fill available space rather than a fixed height, which is why it hit the trap.
Fix options (both verified to contain the image at 864 Γ 498)
Pick one. Option A is recommended β fewest moving parts, no dependence on fragile nested-grid percentage resolution.
Option A (recommended): pin the image to the padded content box
Stop relying on height: 100% chaining through two grids. Make the image fill a
single absolutely-positioned box and let object-fit: contain do the framing.
/* .annotated-stage can keep its background; drop the grid centering */
.annotation-frame {
position: absolute;
inset: 58px 18px 44px; /* same as the old .annotated-stage padding */
/* remove width/height:100% + display:grid + place-items:center */
}
.annotation-image {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: contain; /* now fits a definite box -> letterboxed, contained */
}
.label-layer already is position:absolute; inset:0 (index.html:466-471),
so it keeps lining up with the frame. The 58/18/44 padding currently lives on
.annotated-stage (index.html:493); move that inset onto .annotation-frame
(or keep the padding on the stage and set .annotation-frame { position:absolute; inset:0 }). Either way the frame becomes a definite box and the image is
contained.
Option B: keep the grids but make every track definite
If you prefer to keep display:grid centering, the breakage is the auto rows β
give both grids definite tracks and let the items stretch:
.annotated-stage {
display: grid;
grid-template-columns: minmax(0, 1fr);
grid-template-rows: minmax(0, 1fr);
place-items: stretch; /* not center: stretch the frame to fill */
padding: 58px 18px 44px;
}
.annotation-frame {
display: grid;
grid-template-columns: minmax(0, 1fr);
grid-template-rows: minmax(0, 1fr);
min-width: 0;
min-height: 0;
width: auto; /* track sizing handles it; 100% no longer needed */
height: auto;
}
.annotation-image { width: 100%; height: 100%; object-fit: contain; }
This also measured 864 Γ 498, contained. It is more brittle (relies on readers
understanding the minmax(0,1fr) + min-height:0 idiom), hence Option A is
preferred.
Whichever option: this is CSS-only. No change to
buildAnnotatedPhoto's JS, the schema, the backend, orapp.pyis required for the sizing fix.
Annotation accuracy
A) Most misplacement is a symptom of the sizing bug β fixing sizing fixes it
updatePhotoLabels (index.html:1031-1062) positions each callout from:
containedImageRect(image)(index.html:1096-1118) β computes the letterboxed sub-rect of the image fromimage.naturalWidth/Heightand the<img>bounding box, thenlabel.left = imageRect.left β layerRect.left + point[0] * imageRect.width(and thepoint[1]equivalent for top), clamped to the layer.
This math is correct only when the <img> box matches the visible image
area. With the current sizing bug the <img> box is 864 Γ 1151 and overflows
the viewport, so:
imageRect.width/heightcome from the oversized box, so apoint[1] = 0.8callout is placed at ~0.8 Γ 1151 β 920px down β far below the 600px viewport, which isoverflow: hidden(.viewport-pane,index.html:105), so the label is clipped and the user never sees it.- The clamp uses
layerRect.height(also 1151), so the "keep on screen" clamp (index.html:1044-1047) clamps to the wrong, oversized bounds.
Once the image is contained (Option A/B), containedImageRect returns the
real letterboxed rect and the existing placement math is accurate. So the
sizing fix is also the primary annotation-accuracy fix. Re-verify after fixing.
B) Independent annotation-accuracy improvements (do after the sizing fix)
User decision (2026-06-14): in scope β implement sizing fix and this annotation-accuracy polish. Do items #1β#3 below; #4 (1px border) and #5 (coercion clamp) are optional robustness items, implement if cheap.
No marker at the actual point. The callout box is centered on the point via
.scene-label { transform: translate(-50%, -50%) }(index.html:476), so the text occludes the component instead of pointing at it, and an edge-clamped label has nothing tying it back to the real location. Recommend drawing a small dot/crosshair at the exactpoint(a 1β2px element, not transformed) and offsetting the text label, optionally with a short leader line. Numbered prefixes already exist ((index + 1) + ". ",index.html:1051) but there's no matching number on the photo, so the number is currently meaningless spatially.Edge clamp silently detaches the label from its point.
Math.min(width β 72, Math.max(72, β¦))(index.html:1040-1047) keeps the label on screen but, with no leader/marker (see #1), a clamped label no longer indicates which component it describes. The marker dot from #1 is the fix β keep the dot at the true (clamped-to-image) point and only clamp the text.annotation.boxis parsed and validated but never drawn. The schema and coercion carry an optional normalizedbox: [x, y, w, h](snap2sim/schema.py:197-202,snap2sim/model_io.py:325-327), butbuildAnnotatedPhotoignores it. When present, drawing the bounding box over the contained image would be a much stronger, more accurate annotation than a single point. Optional enhancement.1px border offset.
.annotation-imagehas a1pxborder (index.html:509) andbox-sizing: border-boxis global (index.html:38-40), soobject-fit: containfits the content box (inside the border) whilecontainedImageRectmeasures the border box β a 1px placement error. Negligible; mention only. If you want it exact, account for the border or useoutlineinstead ofborder.Coercion rejects (drops) out-of-range points instead of clamping.
_unit_number_listreturnsNoneif any coordinate is outside[0,1](snap2sim/model_io.py:311-315), so a point like[1.02, 0.5]discards the entire annotation and the part may then fall through to "unavailable". The browser alreadyclamp01s at render (index.html:1120-1122), so consider clamping (not rejecting) server-side for resilience. Minor robustness item; the prompt already asks for[0,1](snap2sim/prompts.py:31-34).
The point-coordinate contract itself is consistent end-to-end: normalized
[0,1], origin top-left, asserted in the prompt (snap2sim/prompts.py:31-34)
and consumed as point[0]βx*width, point[1]βy*height from the image's
top-left (index.html:1040-1047). No change needed there.
Security / invariants to preserve
- This is a client-side CSS/JS change to an existing render path; keep the
"no model-authored HTML" rule from
AGENTS.md/SECURITY.md.label/notemust staytextContent, neverinnerHTML(already correct,index.html:1051-1058). A new marker-dot/box element must be built withdocument.createElement+ style, not injected markup. - Keep using the local
currentPreviewUrlobject URL for the photo (index.html:1018,index.html:1134-1139); do not round-trip the image back from the server for display. - No change to rate limiting, upload caps, or decompression-bomb guards in
app.pyis required.
Suggested implementation order for Codex
- Sizing fix first (Option A). CSS-only in
index.html. This is the bug the user reported and it also corrects most annotation placement. - Re-verify in a real browser with both a tall (portrait) and a wide
(landscape) photo that triggers the annotate fallback: the whole image is
visible and contained, no viewport overflow/scroll, and callouts land on the
correct components within the image (especially lower-half points that were
previously clipped). Check the β€860px stacked layout (
index.html:555-581). - Then the annotation-accuracy items (in scope per the user): marker dot at
the true point + offset/leader label (#1, #2), and render
annotation.boxwhen present (#3). Items #4/#5 optional. - Run the standard local checks per
AGENTS.md(schema/parser + FastAPITestClientfor/,/analyze_image,/generate_scene), then the normal PR -> GitHub Actions HF sync -> Space verification.
Resolved scope
- Sizing fix + annotation-accuracy polish are both in scope
(user decision, 2026-06-14): the CSS sizing fix, marker dots at the true
point, offset/leader labels, and rendering
annotation.boxwhen the model provides it. - Final submission note, 2026-06-15: this work shipped before the public
build-small-hackathon/Snap2Simsubmission.