Spaces:
Running on Zero
Running on Zero
|
Download UIPLAN.md from akashch1512/SingleViewHeigthEstimation: direct link, hf CLI and curl.
- Browser
- Download file 10.5 kB
-
https://huggingface.co/spaces/akashch1512/SingleViewHeigthEstimation/resolve/main/UIPLAN.md
- Command line
-
hf download hf://spaces/akashch1512/SingleViewHeigthEstimation/UIPLAN.md
-
curl -L -o UIPLAN.md https://huggingface.co/spaces/akashch1512/SingleViewHeigthEstimation/resolve/main/UIPLAN.md
10.5 kB
| # UI review — issues and betterments | |
| Scope: the Gradio shell (`app.py`) and the embedded dual-pane viewer | |
| (`viewer/index.html` + `viewer/app.js`) as it renders inside the Space. | |
| Ordered by impact. Each item names the file and the concrete change. | |
| --- | |
| ## P0 — broken or misleading behaviour | |
| ### 1. `Running…` is never cleared when the run fails | |
| `app.py` (`run.click(...).then(...)`) — the chain writes `"Running…"` into | |
| `status`, and `predict` raises `gr.Error` on every failure path. Gradio shows | |
| the error toast, but `status` is an *output of the raising function*, so it is | |
| never overwritten: the page keeps claiming the job is in flight, permanently. | |
| Fix: return errors as values instead of raising for the recoverable cases | |
| (quota exhausted, no image, non-finite output), or add a trailing cleanup step | |
| that resets `status` regardless of outcome. | |
| ### 2. Stale results stay on screen during and after a new run | |
| Uploading a second image leaves the previous run's viewer iframe, gallery, | |
| 16-bit image and download bundle visible. A user who changes the GSD and | |
| re-runs sees the *old* 3D scene for the whole run and has no way to tell which | |
| result the downloads belong to. | |
| Fix: in the first `.then` step, clear every output (`gr.update(value=None)` | |
| for gallery/height/downloads, `""` for the viewer) alongside `"Running…"`. | |
| ### 3. The run button is not disabled while a run is in flight | |
| Two clicks = two ZeroGPU reservations = double the quota burn, which is the | |
| single most common failure mode for this Space (see the quota error text in | |
| `app.py`). Nothing in the UI prevents it. | |
| Fix: `run.click(lambda: (gr.update(interactive=False), "Running…"), ...)` and | |
| re-enable in a trailing `.then`. Setting `concurrency_limit=1` on the event is | |
| worth doing too. | |
| ### 4. No empty state — the viewer is a 560 px void on first load | |
| `_CSS` reserves the height so the layout does not collapse, but nothing is | |
| drawn there. A first-time visitor sees a large blank panel and no signal that | |
| it will become a 3D scene. | |
| Fix: render a placeholder into `gr.HTML` at startup (a dashed frame with | |
| "Your 3D terrain will appear here" plus the one-line workflow), replaced on | |
| the first result. | |
| ### 5. No examples | |
| There is no `gr.Examples`. A visitor with no aerial imagery to hand — the | |
| majority — cannot try the demo at all. `viewer/sample/` already exists. | |
| Fix: `gr.Examples([[sample_png, 0.5, False]], inputs=[image, gsd, tta])` with | |
| two or three scenes at different GSDs. | |
| ### 6. Silent downsampling is never surfaced | |
| `read_scene(..., max_side=1600)` resamples any large scene, and the viewer | |
| payload is capped again at `_VIEWER_MAX_SIDE = 1024`. `meta.json` carries the | |
| note; the UI says nothing. A user who uploads an 8000 px tile gets a | |
| quarter-resolution product and no warning. | |
| Fix: when the input was resampled, add a line to `status` — "Scene resampled | |
| 8000 → 1600 px (GSD adjusted 0.15 → 0.75 m/px)." | |
| --- | |
| ## P1 — comprehension: the result cannot be read | |
| ### 7. No colour legend anywhere | |
| The turbo height colormap is the primary output — in the gallery, the 2D map | |
| pane and the 3D drape — and there is no colorbar in any of the three. Colour | |
| cannot be converted back to metres by eye. `viewer/app.js` has `turbo()`; | |
| nothing draws a scale from it. | |
| Fix: draw a vertical colorbar with min/mid/max metre ticks into the 2D pane | |
| and the 3D pane corner, and bake one into `height_colormap.png` in | |
| `_write_artifacts`. | |
| ### 8. No scale bar or north arrow on the 2D map | |
| The pane reports nothing about ground extent. `S.gsd` is known, so a | |
| 100 m / 500 m bar is a few lines of canvas. | |
| ### 9. The elevation profile is unlabelled | |
| `drawProfile` in `viewer/app.js` prints only the max height. No y-axis | |
| minimum, no x-axis ground distance, no units on the axis. The numbers exist in | |
| `reportProbe` but are not tied to the plot. | |
| Fix: label both ends of the y range and the x extent in metres. | |
| ### 10. The low-ground warning is buried | |
| The `frac_below_1m < 0.15` warning is appended to the bottom of a four-line | |
| markdown blob in the sidebar. It is the most important correctness signal the | |
| app produces — it usually means the declared GSD is wrong. | |
| Fix: give it its own `gr.Markdown` with a visible warning style, above the | |
| stats, hidden when not triggered. | |
| ### 11. Two product names in one page | |
| The Gradio header says "Single View Height Estimator"; the iframe sidebar says | |
| "DepthWizard · single-view DSM · SIH 2026 PS 26175". Inside one viewport that | |
| reads as an embedded third-party tool. | |
| Fix: pick one name; hide or rewrite the viewer's `<h1>`/`.tag` under | |
| `body.embedded`. | |
| ### 12. Dead UI in embedded mode | |
| The viewer hides `#loadSection` when embedded, but leaves: | |
| - the **Validation** section telling the user to "load `gt_ndsm_m.npy` | |
| alongside" — impossible, the file picker is gone; | |
| - permanently-disabled `Reference height` / `Error vs reference` options in | |
| both layer dropdowns. | |
| Fix: `body.embedded` hides the Validation section and both reference options; | |
| keep them for the standalone `viewer/index.html`. | |
| ### 13. "Mesh detail" options are misleading in the Space | |
| The embedded payload is capped at 1024 px (~1.05 M pixels), so "Full (1.5M)" | |
| and "Balanced (500k)" differ little and "Full" can never be reached. | |
| `_VIEWER_MAX_SIDE` and the `#quality` options are set independently and | |
| disagree. | |
| Fix: derive the option list from the actual grid size, or relabel to Fast / | |
| Balanced / Full without vertex counts. | |
| ### 14. Downloads are an unlabelled pile of temp paths | |
| `gr.File(file_count="multiple")` shows `dw_a1b2c3_result.zip`, `ndsm_m.npy`, | |
| `terrain.glb`… with no explanation of what to do with each. The zip name is a | |
| temp directory id. | |
| Fix: rename the archive after the uploaded file, and put a one-line legend | |
| under the component — "`.npy` = raw metres · `.glb` = textured mesh · | |
| `meta.json` = encoding + scene metadata". | |
| --- | |
| ## P2 — input ergonomics | |
| ### 15. The GSD field is the app's main footgun and is under-supported | |
| `minimum=0.01`, no maximum, and `0` silently means "guess from the | |
| checkpoint's canonical GSD" (`float(gsd or 0)` → `read_scene` falls back to | |
| `spec.canonical_gsd_m`). Nothing tells the user that, before or after. | |
| Fix: add a preset radio (0.3 / 0.5 / 1.0 m/px + Custom + "I don't know"), make | |
| the "don't know" path explicit, and echo the *effective* GSD and its source in | |
| the status line (`scene_meta.gsd_source` is already computed). | |
| ### 16. TTA does not disclose its cost | |
| "Higher quality (slower)" hides the fact that it asks for a 120 s ZeroGPU | |
| window instead of 45 s — and that this is the usual cause of the quota error. | |
| The user only learns it from the failure message. | |
| Fix: `info="~4× the compute; reserves 120 s of GPU quota instead of 45 s"`. | |
| ### 17. No expected runtime anywhere | |
| Nothing tells the user whether to wait 10 s or 3 minutes before the first run. | |
| ### 18. No reset / clear | |
| Once a result is on screen there is no way back to the empty state. | |
| --- | |
| ## P3 — layout, theme, accessibility | |
| ### 19. The Gradio shell is light, the viewer is hard-coded dark | |
| `viewer/index.html` sets `color-scheme: dark` and `--bg:#0d1013`. In default | |
| (light) Gradio that is a 760 px black slab in a white page. | |
| Fix: set a dark `gr.themes` on the Blocks, or make the viewer's palette follow | |
| `prefers-color-scheme` and pass the host theme in through the payload. | |
| ### 20. Fixed 760 px iframe height | |
| Hard-coded in `_viewer()`. On a 768 px-tall laptop the viewer needs page | |
| scrolling, while the viewer itself is `overflow:hidden` — so its sidebar | |
| scrolls independently inside a frame that is already clipped. Two nested | |
| scroll contexts. | |
| Fix: size the iframe in viewport units (`height:min(760px, 78vh)`) with a | |
| floor, and add a fullscreen toggle (the frame already has `allowfullscreen`). | |
| ### 21. The viewer's mobile breakpoint fires on desktop | |
| `@media (max-width:900px)` is evaluated against the *iframe* width. In the 3:1 | |
| column split the frame is often under 900 px on a normal laptop, so the 2D | |
| pane disappears and the sidebar stacks — the mobile layout on a desktop, which | |
| reads as a bug. | |
| Fix: switch to a container-driven breakpoint (or lower it and set a | |
| `min_width` on the Gradio column so the frame stays wide). | |
| ### 22. The 322 px fixed sidebar squeezes the 2D pane | |
| Inside an already-narrow frame, `322px | 38% | 1fr` leaves the map pane a | |
| sliver. Consider a collapsible sidebar. | |
| ### 23. No `<label for=...>` anywhere in the viewer | |
| Every control in `viewer/index.html` uses a bare `<label>` followed by a | |
| sibling input — exaggeration, sun azimuth/elevation, drape, mesh detail. | |
| Labels are not clickable and screen readers announce nothing. The checkbox | |
| rows wrap their text in a `<span>`, not a label. | |
| Fix: add `for=`/`id=` pairs, or wrap the inputs. | |
| ### 24. Mode buttons carry no pressed state for assistive tech | |
| `button.on` is visual only — add `aria-pressed`. `#hud`, `#probe` and `#info` | |
| update silently — add `aria-live="polite"` to `#probe` and `#warn`. | |
| ### 25. `gr.HTML(label="3D view")` renders no label | |
| `gr.HTML` ignores `label`. The main output panel is unheaded. | |
| ### 26. Keyboard shortcuts need a click first | |
| `F` and `R` are bound on the iframe's `window`; until the user clicks inside | |
| the frame the host page owns the keyboard. The hint advertises them | |
| unconditionally. | |
| Fix: mention "click the scene first", or focus the canvas on load. | |
| ### 27. Desktop-only interaction hints | |
| `drag = orbit · scroll = zoom` with no touch equivalents, and the 2D pane | |
| probe is pointer-only with no zoom/pan. | |
| ### 28. `warn()` appends without bound or dedupe | |
| It concatenates into `#warn.innerHTML` forever. A repeating error grows the | |
| sidebar indefinitely and there is no dismiss. | |
| ### 29. No progress feedback while the mesh builds | |
| `buildMesh()` on a ~1 M-vertex grid blocks the frame for seconds *after* the | |
| Gradio progress bar has already finished. The user sees a frozen black pane | |
| and no spinner. | |
| ### 30. Small type throughout the viewer | |
| 11–12 px for controls, 11 px for `.tag`, `#hud` and `#hint`. Fine for a dense | |
| desktop tool, tight inside an embedded frame. | |
| --- | |
| ## Suggested order of work | |
| 1. **P0 1–3** — state bugs; small diffs in the `run.click` chain. | |
| 2. **P0 4–5** — empty state + examples; the biggest first-impression win. | |
| 3. **P1 7–9** — colorbar, scale bar, profile axes; makes the output readable. | |
| 4. **P1 10–14** + **P2 15–16** — messaging and input clarity. | |
| 5. **P3** — theme unification (19) and the layout/breakpoint pair (20–22) | |
| together, then accessibility (23–25). | |