Files
2026-07-13 12:45:29 +08:00

180 lines
5.5 KiB
HTML

<!-- This demo is part of Burn project: https://github.com/tracel-ai/burn
Released under a dual license:
https://github.com/tracel-ai/burn/blob/main/LICENSE-MIT
https://github.com/tracel-ai/burn/blob/main/LICENSE-APACHE
-->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Burn Remote MNIST Inference (WebAssembly + Iroh)</title>
<script
src="https://cdn.jsdelivr.net/npm/fabric@5.3.0/dist/fabric.min.js"
integrity="sha256-SPjwkVvrUS/H/htIwO6wdd0IA8eQ79/XXNAH+cPuoso="
crossorigin="anonymous"
></script>
<script
src="https://cdn.jsdelivr.net/npm/chart.js@4.2.1/dist/chart.umd.min.js"
integrity="sha256-tgiW1vJqfIKxE0F2uVvsXbgUlTyrhPMY/sm30hh/Sxc="
crossorigin="anonymous"
></script>
<script
src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels@2.2.0/dist/chartjs-plugin-datalabels.min.js"
integrity="sha256-IMCPPZxtLvdt9tam8RJ8ABMzn+Mq3SQiInbDmMYwjDg="
crossorigin="anonymous"
></script>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/normalize.min.css@8.0.1/normalize.min.css"
integrity="sha256-oeib74n7OcB5VoyaI+aGxJKkNEdyxYjd2m3fi/3gKls="
crossorigin="anonymous"
/>
<style>
h1 {
padding: 15px;
}
th,
td {
padding: 5px;
text-align: center;
vertical-align: middle;
}
#connect-bar {
padding: 0 15px 10px;
}
#status {
margin-left: 10px;
font-family: monospace;
}
</style>
</head>
<body>
<h1>Burn Remote MNIST Inference</h1>
<div id="connect-bar">
Compute peer topic:
<input id="topic" value="burn-web" size="24" />
<button id="connect">Connect</button>
<span id="status">not connected</span>
</div>
<table>
<tr>
<th>Draw a digit here</th>
<th>Cropped and scaled</th>
<th>Probability result</th>
</tr>
<tr>
<td>
<canvas id="main-canvas" width="300" height="300" style="border: 1px solid #aaa"></canvas>
</td>
<td>
<canvas
id="scaled-canvas"
width="28"
height="28"
style="border: 1px solid #aaa; width: 100px; height: 100px"
></canvas>
<canvas id="crop-canvas" width="28" height="28" style="display: none"></canvas>
</td>
<td>
<canvas id="chart" style="border: 1px solid #aaa; width: 600px; height: 300px"></canvas>
</td>
</tr>
<tr>
<td><button id="clear">Clear</button></td>
<td></td>
<td></td>
</tr>
</table>
<script type="module">
import { $, cropScaleGetImageData, chartConfigBuilder } from "./index.js";
import { default as wasm, RemoteMnist } from "./pkg/remote_inference_web.js";
const chart = chartConfigBuilder($("chart"));
const mainCanvasEl = $("main-canvas");
const scaledCanvasEl = $("scaled-canvas");
const cropEl = $("crop-canvas");
const mainContext = mainCanvasEl.getContext("2d", { willReadFrequently: true });
const cropContext = cropEl.getContext("2d", { willReadFrequently: true });
const scaledContext = scaledCanvasEl.getContext("2d", { willReadFrequently: true });
const fabricCanvas = new fabric.Canvas(mainCanvasEl, {
isDrawingMode: true,
});
const backgroundColor = "rgba(255, 255, 255, 255)"; // White with solid alpha
fabricCanvas.freeDrawingBrush.width = 25;
fabricCanvas.backgroundColor = backgroundColor;
$("clear").onclick = function () {
fabricCanvas.clear();
fabricCanvas.backgroundColor = backgroundColor;
fabricCanvas.renderAll();
mainContext.clearRect(0, 0, mainCanvasEl.width, mainCanvasEl.height);
scaledContext.clearRect(0, 0, scaledCanvasEl.width, scaledCanvasEl.height);
chart.data.datasets[0].data = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0];
chart.update();
};
let timeoutId;
let isDrawing = false;
let isTimeOutSet = false;
let mnist = null;
await wasm();
$("connect").onclick = async function () {
const topic = $("topic").value.trim();
$("status").textContent = "connecting...";
$("connect").disabled = true;
try {
mnist = await RemoteMnist.connect(topic);
$("status").textContent = "connected, draw a digit";
} catch (err) {
$("status").textContent = "failed: " + err;
$("connect").disabled = false;
}
};
async function fireOffInference() {
if (mnist === null) return;
clearTimeout(timeoutId);
timeoutId = setTimeout(async () => {
isTimeOutSet = true;
fabricCanvas.freeDrawingBrush._finalizeAndAddPath();
const data = cropScaleGetImageData(mainContext, cropContext, scaledContext);
const output = await mnist.inference(data);
chart.data.datasets[0].data = output;
chart.update();
isTimeOutSet = false;
}, 50);
isTimeOutSet = true;
}
fabricCanvas.on("mouse:down", function () {
isDrawing = true;
});
fabricCanvas.on("mouse:up", async function () {
isDrawing = false;
await fireOffInference();
});
fabricCanvas.on("mouse:move", async function () {
if (isDrawing && isTimeOutSet === false) {
await fireOffInference();
}
});
</script>
</body>
</html>