xray-anything / frontend /index.html
Barath's picture
Upload folder using huggingface_hub
843fc22 verified
Raw
History Blame Contribute Delete
19.7 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>X-RAY ANYTHING Β· RADIOLOGY UNIT 07</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link href="https://fonts.googleapis.com/css2?family=Share+Tech+Mono&display=swap" rel="stylesheet" />
<style>
:root {
--bg: #04070d; --panel: #070d18; --line: #11304a;
--cyan: #4fd6ff; --green: #3dff9e; --text: #bfe9ff; --dim: #2f7ea8;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html, body { height: 100%; }
body {
background: radial-gradient(ellipse at 50% -20%, #0a1c33 0%, var(--bg) 60%) fixed;
color: var(--text); font-family: 'Share Tech Mono', monospace; overflow-x: hidden;
}
body::before {
content: ''; position: fixed; inset: 0; pointer-events: none; z-index: 0;
background-image:
linear-gradient(rgba(79,214,255,.04) 1px, transparent 1px),
linear-gradient(90deg, rgba(79,214,255,.04) 1px, transparent 1px);
background-size: 28px 28px;
}
#root { position: relative; z-index: 2; max-width: 1180px; margin: 0 auto; padding: 20px 16px 36px; }
.header {
text-align: center; border: 1px solid var(--line); border-radius: 12px;
padding: 14px 10px 10px;
background: linear-gradient(180deg, #08121f 0%, #060b14 100%);
box-shadow: 0 0 30px rgba(31,167,255,.08), inset 0 0 18px rgba(0,0,0,.6);
}
.header h1 {
letter-spacing: .35em; color: #e6f8ff; font-size: 1.55em; font-weight: 400;
text-shadow: 0 0 14px rgba(79,214,255,.7);
}
.header .sub { color: var(--cyan); letter-spacing: .18em; font-size: .7em; opacity: .85; margin-top: 3px; }
.online { color: var(--green); font-size: .68em; letter-spacing: .25em; }
.online::before { content: '●'; margin-right: .5em; animation: blink 1.6s infinite; }
@keyframes blink { 0%,55% {opacity:1} 60%,100% {opacity:.15} }
/* 3D stage */
#stage-wrap {
margin-top: 14px; border: 1px solid var(--line); border-radius: 12px;
background: radial-gradient(ellipse at 50% 30%, #081526 0%, #03060c 75%);
box-shadow: inset 0 0 40px rgba(0,0,0,.8), 0 0 24px rgba(31,167,255,.07);
position: relative; overflow: hidden; height: 480px;
}
#stage { width: 100%; height: 100%; display: block; }
.scanning-label {
position: absolute; top: 14px; right: 18px; z-index: 7; display: none;
color: #6fe3ff; font-size: 11px; letter-spacing: .4em;
text-shadow: 0 0 10px rgba(79,214,255,.9);
animation: blink .9s infinite; pointer-events: none;
}
#stage-hint {
position: absolute; left: 0; right: 0; bottom: 14px; z-index: 7;
text-align: center; color: var(--dim); font-size: .68em; letter-spacing: .3em;
pointer-events: none;
}
.controls { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; margin-top: 14px; }
@media (max-width: 880px) { .controls { grid-template-columns: 1fr; } }
.panel {
border: 1px solid var(--line); border-radius: 12px; background: var(--panel);
box-shadow: inset 0 0 25px rgba(0,0,0,.7); padding: 12px;
}
.panel .tag { text-align: center; color: var(--dim); font-size: .64em; letter-spacing: .35em; margin-bottom: 8px; }
.btn {
width: 100%; padding: 13px; border-radius: 10px; margin-top: 8px;
font-family: inherit; font-size: .95em; letter-spacing: .3em; cursor: pointer;
color: #aee9ff; background: linear-gradient(180deg, #0e2c47 0%, #0a1d31 100%);
border: 1px solid #1fa7ff; text-shadow: 0 0 10px rgba(79,214,255,.8);
box-shadow: 0 0 22px rgba(31,167,255,.25), inset 0 0 12px rgba(31,167,255,.12);
transition: all .25s;
}
.btn:hover:not(:disabled) { box-shadow: 0 0 36px rgba(31,167,255,.55); color: #fff; }
.btn:disabled { opacity: .4; cursor: not-allowed; }
.btn.secondary { border-color: #15425f; color: #7fc4e4; letter-spacing: .2em; font-size: .78em; padding: 9px; box-shadow: none; margin-top: 8px; }
.samples { display: flex; gap: 10px; margin-top: 8px; }
.samples .chip {
flex: 1; border: 1px solid var(--line); border-radius: 8px; overflow: hidden;
cursor: pointer; background: #050a12; transition: border-color .2s; position: relative;
}
.samples .chip:hover { border-color: var(--cyan); }
.samples .chip img { width: 100%; height: 72px; object-fit: cover; display: block; opacity: .8; }
.samples .chip span {
position: absolute; bottom: 0; left: 0; right: 0; font-size: .56em; letter-spacing: .22em;
text-align: center; color: var(--cyan); background: rgba(4,9,16,.78); padding: 3px 0;
}
.readout {
padding: 9px 11px; border: 1px solid var(--line); border-radius: 8px;
background: #050b14; min-height: 86px; font-size: .74em; line-height: 1.7;
color: var(--green); white-space: pre-wrap;
}
.readout .dim { color: var(--dim); }
.footer { text-align: center; color: var(--dim); font-size: .62em; letter-spacing: .3em; margin-top: 18px; line-height: 2; }
.footer a { color: var(--cyan); text-decoration: none; }
#boot {
position: fixed; inset: 0; z-index: 50; background: var(--bg);
display: flex; align-items: center; justify-content: center;
transition: opacity .8s; font-size: .85em;
}
#boot.done { opacity: 0; pointer-events: none; }
#boot pre { color: var(--green); letter-spacing: .15em; line-height: 2; }
/* fullscreen radiograph viewer */
#viewer {
position: fixed; inset: 0; z-index: 60; background: rgba(2,5,10,.94);
display: none; align-items: center; justify-content: center; cursor: zoom-out;
}
#viewer img { max-width: 92vw; max-height: 92vh; box-shadow: 0 0 60px rgba(79,214,255,.25); border-radius: 8px; }
</style>
</head>
<body>
<div id="boot"><pre id="bootlog"></pre></div>
<div id="viewer"><img id="viewer-img" alt="radiograph"/></div>
<div id="root">
<div class="header">
<div class="online">SCANNER ONLINE</div>
<h1>X-RAY ANYTHING</h1>
<div class="sub">RADIOLOGY UNIT 07 Β· PHOTOGRAPH ANY OBJECT β€” SEE INSIDE IT</div>
</div>
<div id="stage-wrap">
<canvas id="stage"></canvas>
<div class="scanning-label" id="scan-label">SCANNING</div>
<div id="stage-hint">LOAD A SPECIMEN β€” IT RIDES THE TABLE INTO THE BORE AND RETURNS AS A RADIOGRAPH</div>
</div>
<div class="controls">
<div class="panel">
<div class="tag">β–Ό SPECIMEN INTAKE β–Ό</div>
<button class="btn" id="load-btn">⬆ LOAD SPECIMEN</button>
<input type="file" id="file" accept="image/*" style="display:none"/>
<div class="samples">
<div class="chip" data-src="/samples/gpu.jpg"><img src="/samples/gpu.jpg" alt=""/><span>SPECIMEN 001 Β· GPU</span></div>
<div class="chip" data-src="/samples/mouse.jpg"><img src="/samples/mouse.jpg" alt=""/><span>SPECIMEN 002 Β· MOUSE</span></div>
</div>
<button class="btn" id="scan-btn" disabled>β–Ά BEGIN SCAN</button>
</div>
<div class="panel">
<div class="tag">β–Ό CONSOLE β–Ό</div>
<div class="readout" id="readout"><span class="dim">// system idle β€” load a specimen to begin</span></div>
<button class="btn secondary" id="view-btn" style="display:none">β›Ά VIEW RADIOGRAPH FULLSCREEN</button>
<button class="btn secondary" id="dl-btn" style="display:none">⬇ DOWNLOAD RADIOGRAPH</button>
</div>
</div>
<div class="footer">
BUILD SMALL HACKATHON 2026 Β· ALL MODELS ≀ 32B PARAMETERS<br/>
FLUX.2-KLEIN-9B + QWEN2.5-VL-3B Β· BUILT WITH GRADIO gr.Server Β·
<a href="https://huggingface.co/spaces/build-small-hackathon/xray-anything" target="_blank">SOURCE</a>
</div>
</div>
<script type="module">
import * as THREE from "https://cdn.jsdelivr.net/npm/three@0.169.0/build/three.module.js";
import { Client } from "https://cdn.jsdelivr.net/npm/@gradio/client/+esm";
const $ = (s) => document.querySelector(s);
/* ============ boot ============ */
const BOOT_LINES = [
"RADIOLOGY UNIT 07 Β· POWER ON",
"LOADING EMITTER ......... FLUX.2-KLEIN-9B",
"LOADING OPTICS .......... QWEN2.5-VL-3B",
"PARAMETER BUDGET ........ 12B / 32B CAP",
"SPINNING UP GANTRY ...... OK",
"SCANNER ONLINE.",
];
(async () => {
const log = $("#bootlog");
for (const line of BOOT_LINES) {
log.textContent += line + "\n";
await new Promise(r => setTimeout(r, 240));
}
await new Promise(r => setTimeout(r, 350));
$("#boot").classList.add("done");
})();
/* ============ console readout ============ */
const readout = $("#readout");
function log(line, color) {
const div = document.createElement("div");
if (color) div.style.color = color;
div.textContent = line;
readout.appendChild(div);
while (readout.childNodes.length > 5) readout.removeChild(readout.firstChild);
}
function clearLog() { readout.innerHTML = ""; }
/* ============ three.js scene ============ */
const canvas = $("#stage");
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true, alpha: true });
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
const scene = new THREE.Scene();
scene.fog = new THREE.Fog(0x03060c, 8, 18);
const camera = new THREE.PerspectiveCamera(42, 2, 0.1, 50);
camera.position.set(0.0, 1.1, 7.2);
function resize() {
const w = canvas.clientWidth, h = canvas.clientHeight;
renderer.setSize(w, h, false);
camera.aspect = w / h;
camera.updateProjectionMatrix();
}
new ResizeObserver(resize).observe(canvas);
scene.add(new THREE.AmbientLight(0x2a4a66, 1.2));
const key = new THREE.PointLight(0x9fd9ff, 60, 30); key.position.set(4, 5, 6); scene.add(key);
const rim = new THREE.PointLight(0x1fa7ff, 40, 20); rim.position.set(-5, 2, -2); scene.add(rim);
/* --- the machine --- */
const machine = new THREE.Group();
scene.add(machine);
const shellMat = new THREE.MeshStandardMaterial({ color: 0xdfe9f2, roughness: 0.35, metalness: 0.15 });
const darkMat = new THREE.MeshStandardMaterial({ color: 0x0a1929, roughness: 0.6, metalness: 0.3 });
// main gantry donut
const gantry = new THREE.Mesh(new THREE.TorusGeometry(1.95, 0.85, 32, 64), shellMat);
machine.add(gantry);
// housing block behind
const housing = new THREE.Mesh(new THREE.CylinderGeometry(2.6, 2.6, 1.6, 48), shellMat);
housing.rotation.x = Math.PI / 2;
housing.position.z = -1.3;
machine.add(housing);
// bore tube
const bore = new THREE.Mesh(
new THREE.CylinderGeometry(1.12, 1.12, 3.4, 48, 1, true),
new THREE.MeshStandardMaterial({ color: 0x060d16, roughness: .9, side: THREE.BackSide })
);
bore.rotation.x = Math.PI / 2;
bore.position.z = -0.6;
machine.add(bore);
// glowing ring inside the bore mouth
const glowMat = new THREE.MeshBasicMaterial({ color: 0x4fd6ff, transparent: true, opacity: 0.85 });
const ringLight = new THREE.Mesh(new THREE.TorusGeometry(1.13, 0.035, 12, 64), glowMat);
ringLight.position.z = 0.95;
machine.add(ringLight);
const ringLight2 = ringLight.clone(); ringLight2.position.z = -0.2; ringLight2.material = glowMat.clone();
ringLight2.material.opacity = 0.35; machine.add(ringLight2);
// patient table: pedestal + sliding tray
const pedestal = new THREE.Mesh(new THREE.BoxGeometry(1.5, 0.9, 4.6), darkMat);
pedestal.position.set(0, -1.62, 2.6);
scene.add(pedestal);
const tray = new THREE.Group();
const trayTop = new THREE.Mesh(new THREE.BoxGeometry(1.35, 0.09, 3.6), shellMat);
tray.add(trayTop);
tray.position.set(0, -1.12, 2.8);
scene.add(tray);
// floor glow disc
const floor = new THREE.Mesh(
new THREE.CircleGeometry(7, 48),
new THREE.MeshBasicMaterial({ color: 0x06121f, transparent: true, opacity: 0.85 })
);
floor.rotation.x = -Math.PI / 2; floor.position.y = -2.08; scene.add(floor);
/* --- the specimen plate (your image) --- */
const texLoader = new THREE.TextureLoader();
let specimenMesh = null;
function makeSpecimen(dataUrl, cb) {
texLoader.load(dataUrl, (tex) => {
tex.colorSpace = THREE.SRGBColorSpace;
const aspect = tex.image.width / tex.image.height;
// fit inside 1.5 wide x 1.3 tall, standing upright on the tray
let w = 1.5, h = w / aspect;
if (h > 1.3) { h = 1.3; w = h * aspect; }
if (specimenMesh) {
tray.remove(specimenMesh);
specimenMesh.geometry.dispose();
specimenMesh.material.dispose();
}
specimenMesh = new THREE.Mesh(
new THREE.PlaneGeometry(w, h),
new THREE.MeshBasicMaterial({ map: tex, side: THREE.DoubleSide, transparent: true })
);
specimenMesh.position.set(0, h / 2 + 0.05, 0.4);
tray.add(specimenMesh);
cb && cb();
});
}
function swapSpecimenTexture(dataUrl, cb) {
texLoader.load(dataUrl, (tex) => {
tex.colorSpace = THREE.SRGBColorSpace;
specimenMesh.material.map = tex;
specimenMesh.material.needsUpdate = true;
cb && cb();
});
}
// scan beam: glowing bar sweeping the bore mouth
const beam = new THREE.Mesh(
new THREE.PlaneGeometry(2.1, 0.045),
new THREE.MeshBasicMaterial({ color: 0x9ff0ff, transparent: true, opacity: 0, blending: THREE.AdditiveBlending })
);
beam.position.z = 1.0;
scene.add(beam);
// flash quad for the reveal moment
const flash = new THREE.Mesh(
new THREE.PlaneGeometry(30, 30),
new THREE.MeshBasicMaterial({ color: 0xcdf6ff, transparent: true, opacity: 0, blending: THREE.AdditiveBlending })
);
flash.position.z = 3.5;
scene.add(flash);
/* --- animation state machine --- */
const ease = (t) => t < .5 ? 2*t*t : -1 + (4 - 2*t) * t; // easeInOut
let mode = "idle"; // idle | in | scanning | out
let t0 = 0;
const TRAY_HOME = 2.8, TRAY_IN = -0.25;
let scanDone = false, revealData = null;
function setMode(m) { mode = m; t0 = performance.now(); }
let mouse = { x: 0, y: 0 };
window.addEventListener("pointermove", (e) => {
mouse.x = (e.clientX / window.innerWidth - 0.5) * 2;
mouse.y = (e.clientY / window.innerHeight - 0.5) * 2;
});
const clock = new THREE.Clock();
function animate() {
requestAnimationFrame(animate);
const t = clock.getElapsedTime();
const since = (performance.now() - t0) / 1000;
// idle ring pulse + slow spin
ringLight.rotation.z = t * 0.6;
ringLight2.rotation.z = -t * 0.4;
const basePulse = 0.75 + Math.sin(t * 2.2) * 0.15;
if (mode === "idle") {
glowMat.opacity = basePulse * 0.6;
beam.material.opacity = 0;
}
if (mode === "in") {
const p = ease(Math.min(since / 1.8, 1));
tray.position.z = TRAY_HOME + (TRAY_IN - TRAY_HOME) * p;
glowMat.opacity = 0.5 + p * 0.5;
if (p >= 1) setMode("scanning");
}
if (mode === "scanning") {
glowMat.opacity = 0.9 + Math.sin(t * 14) * 0.1;
ringLight.rotation.z = t * 5;
ringLight2.rotation.z = -t * 3.4;
// beam sweeps up and down across the bore mouth
const sweep = Math.sin(t * 2.6);
beam.position.y = sweep * 0.95;
beam.material.opacity = 0.55 + Math.sin(t * 9) * 0.2;
// when the scan result is in, flash and come out
if (scanDone && since > 4.2) {
swapSpecimenTexture(revealData.image, () => {
flash.material.opacity = 0.9;
setMode("out");
});
scanDone = false;
}
}
if (mode === "out") {
const p = ease(Math.min(since / 1.9, 1));
tray.position.z = TRAY_IN + (TRAY_HOME - TRAY_IN) * p;
beam.material.opacity = Math.max(0, 0.5 - p);
glowMat.opacity = 1 - p * 0.4;
ringLight.rotation.z = t * (5 - 4.4 * p);
if (p >= 1) { setMode("idle"); onScanFinished(); }
}
// flash decay
flash.material.opacity = Math.max(0, flash.material.opacity - 0.03);
// cinematic parallax
camera.position.x += ((mouse.x * 0.55) - camera.position.x) * 0.04;
camera.position.y += ((1.1 - mouse.y * 0.3) - camera.position.y) * 0.04;
camera.lookAt(0, -0.15, 0.6);
renderer.render(scene, camera);
}
resize();
animate();
/* ============ app logic ============ */
let specimenDataUrl = null;
let scanning = false;
let client = null;
async function fileToDataUrl(file) {
return new Promise((res) => {
const fr = new FileReader();
fr.onload = () => res(fr.result);
fr.readAsDataURL(file);
});
}
function setSpecimen(dataUrl) {
if (scanning) return;
specimenDataUrl = dataUrl;
makeSpecimen(dataUrl, () => {
$("#scan-btn").disabled = false;
$("#stage-hint").style.display = "none";
clearLog();
log("// specimen on table β€” gantry ready", "#4fd6ff");
});
}
$("#load-btn").addEventListener("click", () => $("#file").click());
$("#file").addEventListener("change", async (e) => {
if (e.target.files[0]) setSpecimen(await fileToDataUrl(e.target.files[0]));
});
document.body.addEventListener("dragover", (e) => e.preventDefault());
document.body.addEventListener("drop", async (e) => {
e.preventDefault();
if (e.dataTransfer.files[0]) setSpecimen(await fileToDataUrl(e.dataTransfer.files[0]));
});
document.querySelectorAll(".chip").forEach((chip) => {
chip.addEventListener("click", async () => {
const blob = await (await fetch(chip.dataset.src)).blob();
setSpecimen(await fileToDataUrl(blob));
});
});
/* scanner hum */
let audioCtx = null, hum = null;
function startHum() {
try {
audioCtx = audioCtx || new AudioContext();
const osc = audioCtx.createOscillator(), osc2 = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.type = "sawtooth"; osc.frequency.value = 48;
osc2.type = "sine"; osc2.frequency.value = 97;
gain.gain.value = 0.022;
osc.connect(gain); osc2.connect(gain); gain.connect(audioCtx.destination);
osc.start(); osc2.start();
hum = { osc, osc2, gain };
} catch {}
}
function stopHum() {
if (!hum) return;
hum.gain.gain.linearRampToValueAtTime(0, audioCtx.currentTime + .4);
setTimeout(() => { hum.osc.stop(); hum.osc2.stop(); hum = null; }, 500);
}
const STAGES = [
"β—Œ TABLE ADVANCING INTO BORE",
"β—Œ IDENTIFYING SPECIMEN",
"β—Œ EXPOSING β€” 4-STEP RECTIFIED FLOW",
"β—Œ RECONSTRUCTING SLICES",
];
let stageTimer = null;
$("#scan-btn").addEventListener("click", async () => {
if (!specimenDataUrl || scanning || mode !== "idle") return;
scanning = true;
scanDone = false; revealData = null;
$("#scan-btn").disabled = true;
$("#view-btn").style.display = "none";
$("#dl-btn").style.display = "none";
$("#scan-label").style.display = "block";
clearLog();
log(STAGES[0] + " ...");
let stage = 1;
stageTimer = setInterval(() => {
log(STAGES[Math.min(stage, STAGES.length - 1)] + " ...");
stage++;
}, 2100);
startHum();
setMode("in");
try {
client = client || await Client.connect(location.origin);
const out = await client.predict("/xray", { image_b64: specimenDataUrl });
revealData = out.data[0];
scanDone = true; // the animation loop picks this up and plays the reveal
} catch (err) {
clearInterval(stageTimer);
stopHum();
$("#scan-label").style.display = "none";
setMode("out");
clearLog();
log("// SCAN FAULT β€” " + (err?.message || err), "#ff6b6b");
scanning = false;
$("#scan-btn").disabled = false;
}
});
function onScanFinished() {
if (!revealData) return;
clearInterval(stageTimer);
stopHum();
$("#scan-label").style.display = "none";
clearLog();
log(`// SCAN COMPLETE β€” SPECIMEN: ${String(revealData.object).toUpperCase()}`, "#3dff9e");
log("// radiograph is an AI reconstruction, not engineering documentation", "#2f7ea8");
$("#view-btn").style.display = "block";
$("#dl-btn").style.display = "block";
const img = revealData.image;
$("#view-btn").onclick = () => {
$("#viewer-img").src = img;
$("#viewer").style.display = "flex";
};
$("#dl-btn").onclick = () => {
const a = document.createElement("a");
a.href = img; a.download = "radiograph.webp"; a.click();
};
scanning = false;
$("#scan-btn").disabled = false;
}
$("#viewer").addEventListener("click", () => { $("#viewer").style.display = "none"; });
</script>
</body>
</html>