"""Before/after slider and point-cloud viewer for the demo, rendered as plain HTML.
Gradio does not run scripts inside ``gr.HTML``, so the elements carry their data
inline and ``HEAD_JS`` (passed to ``gr.Blocks(head=...)``) wires up every
``.iris-ba`` / ``.iris-cloud`` element as it appears.
"""
import base64
import io
import numpy as np
from PIL import Image
HFOV_DEG = 60 # assumed horizontal field of view for back-projection
MAX_POINTS = 80_000
def _data_uri(image: Image.Image, quality: int = 92) -> str:
buf = io.BytesIO()
image.convert("RGB").save(buf, "WEBP", quality=quality)
return "data:image/webp;base64," + base64.b64encode(buf.getvalue()).decode()
def slider_html(before: Image.Image, after: Image.Image, before_label: str, after_label: str,
download: str | None = None) -> str:
"""``download`` (a file name) adds a button in the top-right corner that saves
the *after* image, the same affordance the Generate tab's gr.Image has."""
before = before.convert("RGB").resize(after.size, Image.Resampling.BICUBIC)
button = ""
if download:
button = (
f''
)
return (
f'
'
f''
f''
f'{button}
↔
'
f'{before_label}'
f'{after_label}
'
)
def cloud_html(depth: np.ndarray, image: Image.Image) -> str:
"""Back-project relative log depth to a coloured point cloud.
The prediction is log depth up to scale and shift, so ``exp`` of it gives
depth up to scale (shift in log space is a global scale); it is rescaled
to a median of 3 for viewing.
"""
h, w = depth.shape
rgb = np.asarray(image.convert("RGB").resize((w, h), Image.Resampling.BILINEAR))
stride = max(1, int(np.ceil(np.sqrt(h * w / MAX_POINTS))))
v, u = np.mgrid[0:h:stride, 0:w:stride]
z = np.exp(depth[v, u].astype(np.float64))
z = 3 * z / np.median(z)
f = (w / 2) / np.tan(np.radians(HFOV_DEG) / 2)
xyz = np.stack([(u + 0.5 - w / 2) * z / f, (v + 0.5 - h / 2) * z / f, z], -1).reshape(-1, 3)
payload = xyz.astype("')
CSS = """
.iris-ba { position: relative; width: 100%; max-height: 70vh; margin: 0 auto; cursor: ew-resize;
touch-action: none; user-select: none; overflow: hidden; background: #0c0c0c; }
.iris-ba img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; display: block; }
.iris-ba-line { position: absolute; top: 0; bottom: 0; left: 50%; width: 1px; background: #fff; z-index: 3; }
.iris-ba-line span { position: absolute; top: 50%; left: 50%; width: 40px; height: 40px; transform: translate(-50%, -50%);
display: flex; align-items: center; justify-content: center; border-radius: 999px; background: #fff; color: #000;
font: 12px 'IBM Plex Mono', monospace; box-shadow: 0 4px 14px rgba(0,0,0,.35); }
.iris-ba-label { position: absolute; bottom: 16px; z-index: 2; pointer-events: none; border-radius: 4px;
background: rgba(0,0,0,.6); padding: 6px 10px; color: #fff; backdrop-filter: blur(4px);
font: 10px/1 'IBM Plex Mono', monospace; text-transform: uppercase; letter-spacing: .12em; }
/* a copy of the image toolbar download button gradio draws on the Generate tab's result
(gradio Image .top-panel / .icon-button): same box, same CSS variables, so it follows
light/dark exactly like the native button — white with a hairline border in light,
black in dark */
.iris-ba-dl { position: absolute; top: 0; right: 0; z-index: 20; display: flex; align-items: center;
justify-content: center; box-sizing: border-box; width: 22px; height: 23px; padding: 1px;
border: 0; border-left: 1px solid var(--border-color-primary);
border-bottom: 1px solid var(--border-color-primary); border-radius: 0;
background: var(--block-background-fill); color: var(--block-label-text-color); cursor: pointer; }
.iris-ba-dl:hover { background: var(--background-fill-secondary); }
.iris-ba-dl svg { display: block; width: 14px; height: 14px; }
.iris-cloud { position: relative; width: 100%; aspect-ratio: 4 / 3; cursor: grab; touch-action: none; overflow: hidden;
border: 1px solid var(--sl-rule); margin-top: 12px; }
.iris-cloud:active { cursor: grabbing; }
.iris-cloud canvas { display: block; width: 100%; height: 100%; }
"""
HEAD_JS = """
"""