akashch1512's picture
UI bettement
b7ed242
Raw History Blame Contribute Delete
12 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>DepthWizard — DSM viewer & 3D flythrough</title>
<style>
:root{
color-scheme: dark;
--bg:#0d1013; --panel:#151a20; --line:#252c34; --ink:#e8ecf1; --dim:#8a949e;
--accent:#2d6cdf; --warn:#e5793a;
}
*{box-sizing:border-box}
html,body{height:100%}
body{margin:0;background:var(--bg);color:var(--ink);overflow:hidden;
font:13px/1.6 system-ui,-apple-system,"Segoe UI",sans-serif;
display:grid;grid-template-columns:280px 1fr;}
body.collapsed{grid-template-columns:0 1fr}
body.collapsed aside{display:none}
/* ---- sidebar ---- */
aside{background:var(--panel);border-right:1px solid var(--line);
overflow-y:auto;padding:16px 16px 40px;position:relative}
#collapseAside{position:absolute;top:10px;right:10px;flex:none;
width:auto;padding:3px 7px}
h1{font-size:16px;margin:0 0 2px;letter-spacing:.2px}
.tag{color:var(--dim);font-size:12px;margin:0 0 16px}
section{border-top:1px solid var(--line);padding:13px 0}
section:first-of-type{border-top:0;padding-top:0}
h2{font-size:12px;text-transform:uppercase;letter-spacing:.07em;color:var(--dim);
margin:0 0 9px;font-weight:600}
label{display:block;margin:9px 0 3px;color:#b8c2cc;font-size:13px;cursor:pointer}
input[type=range]{width:100%;accent-color:var(--accent)}
select,input[type=file]{width:100%;background:#0f1318;color:var(--ink);
border:1px solid var(--line);border-radius:6px;padding:6px 8px;font:inherit;font-size:13px}
.row{display:flex;gap:8px;align-items:center;margin-top:8px}
.row.chk{color:#b8c2cc;font-size:13px}
.row.chk label{display:inline;margin:0;cursor:pointer}
.btns{display:flex;gap:6px;margin-top:10px;flex-wrap:wrap}
button{flex:1 1 auto;padding:7px 9px;background:#242c35;color:var(--ink);
border:1px solid var(--line);border-radius:6px;cursor:pointer;font:inherit;font-size:13px}
button:hover{background:#2c3540}
button.on{background:var(--accent);border-color:var(--accent);color:#fff;font-weight:600}
button[aria-pressed=true]{background:var(--accent);border-color:var(--accent);color:#fff;font-weight:600}
#info,#probe,#metrics{color:#b8c2cc;font-size:13px}
#probe{min-height:44px}
#metrics table{width:100%;border-collapse:collapse}
#metrics td{padding:2px 0;font-variant-numeric:tabular-nums}
#metrics td:last-child{text-align:right}
.dim{color:var(--dim)}
code{background:#0f1318;padding:1px 4px;border-radius:3px;font-size:12px}
#warn{display:none;background:#2a1f14;border:1px solid #5a3d1e;border-left:3px solid var(--warn);
border-radius:6px;padding:8px 10px;margin-top:10px;font-size:12.5px;color:#f0c9a0}
#warn div{padding:2px 0}
#profile{width:100%;height:76px;background:#0f1318;border:1px solid var(--line);
border-radius:6px;margin-top:8px;display:block}
/* ---- panes ---- */
main{display:grid;grid-template-columns:minmax(0,38%) minmax(0,1fr);min-width:0}
body.only3d main{grid-template-columns:0 1fr}
body.only3d #pane2d{display:none}
.pane{position:relative;min-width:0;overflow:hidden}
#pane2d{border-right:1px solid var(--line);display:flex;flex-direction:column;background:#0a0d10}
.paneBar{height:34px;display:flex;align-items:center;gap:8px;padding:0 10px;
border-bottom:1px solid var(--line);color:var(--dim);font-size:12px;flex:none}
.paneBar select{width:auto;padding:3px 6px;font-size:12px}
.paneBar label{display:flex;margin:0;font-size:12px}
#map{flex:1;width:100%;min-height:0;touch-action:none}
#pane3d canvas{display:block}
#hud{position:absolute;top:10px;right:12px;display:flex;gap:8px;
font-size:12px;color:var(--dim);background:rgba(13,16,19,.72);
padding:5px 9px;border-radius:6px;border:1px solid var(--line);
font-variant-numeric:tabular-nums}
#hint{position:absolute;left:12px;bottom:10px;color:var(--dim);font-size:12px;
background:rgba(13,16,19,.72);padding:5px 9px;border-radius:6px;
border:1px solid var(--line);max-width:calc(100% - 24px)}
/* embedded in the Space: the model has already run, so there is nothing to
pick — drop the loader and promote the section that follows it. The host
page already names the product, so the viewer's own header is redundant
inside the frame (and previously disagreed with it). Validation and the
reference-surface options are dead weight too: there is no file picker to
load gt_ndsm_m.npy from. */
body.embedded h1, body.embedded .tag{display:none}
body.embedded #loadSection{display:none}
body.embedded #validationSection{display:none}
body.embedded section:nth-of-type(2){border-top:0;padding-top:0}
body.embedded #layer option[value=reference], body.embedded #layer option[value=error],
body.embedded #layer2d option[value=reference], body.embedded #layer2d option[value=error]{
display:none}
/* This query is evaluated against the *iframe's* own width, which in a 3:1
host column split is often well under 900px on an ordinary laptop even
though the page itself is nowhere near mobile-sized — that reads as a
bug, not a responsive layout. Lowered so only a genuinely narrow frame
(phone-embed width, or the standalone page on a small window) triggers
it; `app.py` also sets a `min_width` on the host column so the frame
itself stays wide on desktop. */
@media (max-width:560px){
body{grid-template-columns:1fr;grid-template-rows:auto 1fr}
aside{max-height:44vh}
main{grid-template-columns:1fr}
#pane2d{display:none}
}
button.on{position:relative}
#warn{max-height:160px;overflow-y:auto}
#warnDismiss{display:none;margin-top:6px;padding:3px 8px;font-size:11px;flex:none}
#warn.show + #warnDismiss{display:inline-block}
.colorbar-wrap{position:absolute;display:flex;flex-direction:column;
align-items:center;font-size:10px;color:var(--dim);
background:rgba(13,16,19,.72);padding:6px 4px;border-radius:6px;
border:1px solid var(--line);pointer-events:none}
#colorbar3d{right:12px;bottom:44px}
#colorbar2d{right:8px;top:40px}
#scalebar{position:absolute;left:10px;bottom:10px;pointer-events:none}
#meshSpinner{display:none;position:absolute;inset:0;align-items:center;
justify-content:center;flex-direction:column;gap:10px;font-size:13px;
color:var(--ink);background:rgba(10,13,16,.55)}
#meshSpinner.show{display:flex}
.spin{width:26px;height:26px;border-radius:50%;border:3px solid var(--line);
border-top-color:var(--accent);animation:spin .8s linear infinite}
@keyframes spin{to{transform:rotate(360deg)}}
</style>
</head>
<body>
<aside>
<button id="collapseAside" title="Hide sidebar" aria-label="Hide sidebar">◂</button>
<h1>DepthWizard</h1>
<p class="tag">single-view DSM · 3D flythrough · SIH 2026 PS 26175</p>
<section id="loadSection">
<h2>Load a prediction</h2>
<label for="files">Prediction folder</label>
<input type="file" id="files" multiple
accept=".png,.jpg,.json,.npy"
title="select ndsm_m.npy, rgb.png, meta.json (and gt_ndsm_m.npy to validate)" />
<p class="dim" style="margin:8px 0 0;font-size:12px">
Pick the whole output folder of<br><code>python -m infer.predict …</code><br>
<code>ndsm_m.npy</code> + <code>meta.json</code> is the minimum;
add <code>rgb.png</code> to drape the optical image and
<code>gt_ndsm_m.npy</code> to score it live.
</p>
<div id="warn" aria-live="polite"></div>
<button id="warnDismiss">Dismiss</button>
<div id="info" style="margin-top:10px">nothing loaded yet</div>
</section>
<section>
<h2>Camera</h2>
<div class="btns" role="group" aria-label="Camera mode">
<button data-mode="orbit" class="on" aria-pressed="true">Orbit</button>
<button data-mode="fp" aria-pressed="false">First person</button>
<button data-mode="fly" aria-pressed="false">Drone</button>
</div>
<div class="btns">
<button id="reset">Reset view</button>
<button id="split">2D pane</button>
<button id="fullscreen">Fullscreen</button>
</div>
</section>
<section>
<h2>Surface</h2>
<label for="layer">3D drape</label>
<select id="layer">
<option value="rgb">Optical RGB (projected)</option>
<option value="height">Height colormap</option>
<option value="hillshade">Hillshade</option>
<option value="reference" disabled>Reference height</option>
<option value="error" disabled>Error vs reference</option>
</select>
<label for="vex">Vertical exaggeration <span id="vexLabel">1.0×</span></label>
<input type="range" id="vex" min="0.2" max="8" step="0.1" value="1" />
<label for="quality">Mesh detail</label>
<select id="quality">
<option value="150000">Fast</option>
<option value="500000" selected>Balanced</option>
<option value="1500000">Full</option>
</select>
<div class="row chk"><input type="checkbox" id="wire" /> <label for="wire">wireframe</label></div>
</section>
<section>
<h2>Sun</h2>
<label for="sunAz">Azimuth <span class="dim">°</span></label>
<input type="range" id="sunAz" min="0" max="360" step="1" value="315" />
<label for="sunEl">Elevation <span class="dim">°</span></label>
<input type="range" id="sunEl" min="5" max="88" step="1" value="45" />
<div class="row chk"><input type="checkbox" id="shadows" checked /> <label for="shadows">cast shadows</label></div>
</section>
<section>
<h2>Measure</h2>
<div id="probe" aria-live="polite">click the terrain to probe a height; click again for slope and a profile</div>
<canvas id="profile"></canvas>
<div class="btns"><button id="clearProbe">Clear points</button></div>
</section>
<section id="validationSection">
<h2>Validation</h2>
<div id="metrics"><span class="dim">load <code>gt_ndsm_m.npy</code> alongside to
validate against a reference surface</span></div>
</section>
</aside>
<main>
<div class="pane" id="pane2d">
<div class="paneBar">
<label for="layer2d" style="margin:0">Map</label>
<select id="layer2d">
<option value="height">Height</option>
<option value="hillshade">Hillshade</option>
<option value="rgb">Optical</option>
<option value="reference" disabled>Reference</option>
<option value="error" disabled>Error</option>
</select>
<label style="margin:0;display:flex;align-items:center;gap:5px;color:var(--dim)">
<input type="checkbox" id="contours" /> contours</label>
</div>
<canvas id="map"></canvas>
<canvas id="scalebar"></canvas>
<div class="colorbar-wrap" id="colorbar2d"><canvas width="14" height="120"></canvas></div>
</div>
<div class="pane" id="pane3d">
<div id="hud"><span id="fps">— fps</span><span id="vertCount">—</span></div>
<div id="hint">click the scene to focus it, then: drag = orbit · scroll = zoom ·
click = probe · F = first person · R = reset<br>
<span class="dim">touch: one finger = orbit · pinch = zoom · two-finger drag = pan</span></div>
<div class="colorbar-wrap" id="colorbar3d"><canvas width="14" height="120"></canvas></div>
<div id="meshSpinner"><div class="spin"></div>building mesh…</div>
</div>
</main>
<script>
/* Surface anything that dies before app.js can install its own reporting —
inside an iframe there is no console the viewer's user will ever look at. */
window.addEventListener('error', (e) => {
const el = document.getElementById('warn');
if (!el) return;
el.style.display = 'block';
el.innerHTML += (el.innerHTML ? '<br>' : '') + 'viewer error: ' +
String(e.message || e.type).replace(/[<>&]/g, '');
});
</script>
<script src="vendor/three.min.js"></script>
<script src="vendor/OrbitControls.js"></script>
<script src="vendor/PointerLockControls.js"></script>
<script src="app.js"></script>
</body>
</html>