Spaces:
Running
Running
| import { FireDetector, drawBoxes, className } from "./detector.js"; | |
| const fileInput = document.getElementById("file-input"); | |
| const detectBtn = document.getElementById("detect-btn"); | |
| const clearBtn = document.getElementById("clear-btn"); | |
| const sampleBtn = document.getElementById("sample-btn"); | |
| const statusEl = document.getElementById("status"); | |
| const statusDot = document.getElementById("status-dot"); | |
| const inputCanvas = document.getElementById("input-canvas"); | |
| const outputCanvas = document.getElementById("output-canvas"); | |
| const inputFrame = inputCanvas.closest(".canvas-frame"); | |
| const outputFrame = outputCanvas.closest(".canvas-frame"); | |
| const jsonOutput = document.getElementById("json-output"); | |
| const dropzone = document.getElementById("dropzone"); | |
| const countValue = document.getElementById("count-value"); | |
| const latencyValue = document.getElementById("latency-value"); | |
| const outputBadge = document.getElementById("output-badge"); | |
| const detector = new FireDetector(); | |
| let currentImage = null; | |
| function setStatus(message, state = "ready") { | |
| statusEl.textContent = message; | |
| statusDot.className = "status-dot"; | |
| if (state === "loading") statusDot.classList.add("status-dot--loading"); | |
| else if (state === "error") statusDot.classList.add("status-dot--error"); | |
| else statusDot.classList.add("status-dot--ready"); | |
| } | |
| function setCanvasState(frame, hasImage) { | |
| frame.classList.toggle("has-image", hasImage); | |
| } | |
| function drawImageToCanvas(canvas, image) { | |
| canvas.width = image.width; | |
| canvas.height = image.height; | |
| const ctx = canvas.getContext("2d"); | |
| ctx.clearRect(0, 0, canvas.width, canvas.height); | |
| ctx.drawImage(image, 0, 0); | |
| } | |
| async function loadImageFromUrl(url) { | |
| const response = await fetch(url); | |
| if (!response.ok) throw new Error("Failed to load image"); | |
| const blob = await response.blob(); | |
| return loadImageFromFile(blob); | |
| } | |
| function loadImageFromFile(file) { | |
| return new Promise((resolve, reject) => { | |
| const url = URL.createObjectURL(file); | |
| const image = new Image(); | |
| image.onload = () => { | |
| URL.revokeObjectURL(url); | |
| resolve(image); | |
| }; | |
| image.onerror = () => { | |
| URL.revokeObjectURL(url); | |
| reject(new Error("Failed to load image")); | |
| }; | |
| image.src = url; | |
| }); | |
| } | |
| function summarizeDetections(boxes) { | |
| const counts = {}; | |
| for (const box of boxes) { | |
| const name = className(box.cls_id); | |
| counts[name] = (counts[name] ?? 0) + 1; | |
| } | |
| return Object.entries(counts) | |
| .map(([name, count]) => `${count} ${name}${count === 1 ? "" : "s"}`) | |
| .join(", "); | |
| } | |
| function updateMetrics(count, latencyMs, boxes = null) { | |
| countValue.textContent = count ?? "—"; | |
| latencyValue.textContent = latencyMs != null ? `${latencyMs} ms` : "—"; | |
| if (count == null) { | |
| outputBadge.classList.add("hidden"); | |
| return; | |
| } | |
| const summary = boxes?.length ? summarizeDetections(boxes) : `${count} detections`; | |
| outputBadge.textContent = summary; | |
| outputBadge.classList.remove("hidden"); | |
| } | |
| function clearDemo() { | |
| currentImage = null; | |
| fileInput.value = ""; | |
| jsonOutput.textContent = "[]"; | |
| setCanvasState(inputFrame, false); | |
| setCanvasState(outputFrame, false); | |
| detectBtn.disabled = true; | |
| clearBtn.disabled = true; | |
| updateMetrics(null, null); | |
| setStatus("Model ready. Upload a scene or load the example.", "ready"); | |
| } | |
| async function setCurrentImage(image) { | |
| currentImage = image; | |
| drawImageToCanvas(inputCanvas, currentImage); | |
| drawImageToCanvas(outputCanvas, currentImage); | |
| setCanvasState(inputFrame, true); | |
| setCanvasState(outputFrame, true); | |
| jsonOutput.textContent = "[]"; | |
| detectBtn.disabled = false; | |
| clearBtn.disabled = false; | |
| updateMetrics(null, null); | |
| } | |
| async function loadImage(file) { | |
| setStatus("Loading image...", "loading"); | |
| const image = await loadImageFromFile(file); | |
| await setCurrentImage(image); | |
| setStatus("Image loaded. Running Ember...", "loading"); | |
| await runDetection(); | |
| } | |
| async function loadExample() { | |
| try { | |
| setStatus("Loading example scene...", "loading"); | |
| const image = await loadImageFromUrl("./example_input.png"); | |
| await setCurrentImage(image); | |
| setStatus("Example loaded. Running Ember...", "loading"); | |
| await runDetection(); | |
| } catch (error) { | |
| setStatus(error.message, "error"); | |
| } | |
| } | |
| async function runDetection() { | |
| if (!currentImage) return; | |
| detectBtn.disabled = true; | |
| setStatus("Analyzing scene...", "loading"); | |
| const started = performance.now(); | |
| try { | |
| const boxes = await detector.predictImage(currentImage); | |
| const latencyMs = Math.round(performance.now() - started); | |
| drawImageToCanvas(outputCanvas, currentImage); | |
| drawBoxes(outputCanvas.getContext("2d"), boxes); | |
| jsonOutput.textContent = JSON.stringify(boxes, null, 2); | |
| updateMetrics(boxes.length, latencyMs, boxes); | |
| const headline = | |
| boxes.length === 0 | |
| ? "No fire hazards detected in this frame." | |
| : `Ember found ${boxes.length} detection${boxes.length === 1 ? "" : "s"} in ${latencyMs} ms.`; | |
| setStatus(headline, "ready"); | |
| } catch (error) { | |
| console.error(error); | |
| setStatus(`Detection failed: ${error.message}`, "error"); | |
| } finally { | |
| detectBtn.disabled = false; | |
| } | |
| } | |
| fileInput.addEventListener("change", async (event) => { | |
| const file = event.target.files?.[0]; | |
| if (!file) return; | |
| try { | |
| await loadImage(file); | |
| } catch (error) { | |
| setStatus(error.message, "error"); | |
| } | |
| }); | |
| detectBtn.addEventListener("click", runDetection); | |
| clearBtn.addEventListener("click", clearDemo); | |
| sampleBtn.addEventListener("click", loadExample); | |
| dropzone.addEventListener("dragover", (event) => { | |
| event.preventDefault(); | |
| dropzone.classList.add("is-dragover"); | |
| }); | |
| dropzone.addEventListener("dragleave", () => dropzone.classList.remove("is-dragover")); | |
| dropzone.addEventListener("drop", async (event) => { | |
| event.preventDefault(); | |
| dropzone.classList.remove("is-dragover"); | |
| const file = event.dataTransfer?.files?.[0]; | |
| if (!file || !file.type.startsWith("image/")) { | |
| setStatus("Please drop an image file.", "error"); | |
| return; | |
| } | |
| try { | |
| await loadImage(file); | |
| } catch (error) { | |
| setStatus(error.message, "error"); | |
| } | |
| }); | |
| try { | |
| setStatus("Loading Ember model...", "loading"); | |
| await detector.load("./weights.onnx"); | |
| setStatus("Ember ready. Upload a scene or load the example.", "ready"); | |
| } catch (error) { | |
| console.error(error); | |
| setStatus("Failed to load model. Check that weights.onnx is available.", "error"); | |
| } | |