akashch1512's picture
UI bettement
b7ed242
|
Raw History Blame Contribute Delete
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).