Download app.js from Varun95/COCO: direct link, hf CLI and curl.
- Browser
- Download file 18 kB
-
https://huggingface.co/spaces/Varun95/COCO/resolve/main/app.js
- Command line
-
hf download hf://spaces/Varun95/COCO/app.js
-
curl -L -o app.js https://huggingface.co/spaces/Varun95/COCO/resolve/main/app.js
18 kB
| /* | |
| YOLO VISION | |
| Browser controller | |
| Responsibilities: | |
| 1. User selects ONNX model | |
| 2. Read model locally | |
| 3. Send model to Web Worker | |
| 4. Access webcam | |
| 5. Send camera frames to Worker | |
| 6. Receive detections | |
| 7. Draw boxes + segmentation masks | |
| */ | |
| const INPUT_SIZE = 640; | |
| /* ========================================================= | |
| STATE | |
| ========================================================= */ | |
| let worker = null; | |
| let stream = null; | |
| let running = false; | |
| let frameBusy = false; | |
| let confidenceThreshold = 0.25; | |
| let fps = 0; | |
| let lastFrameTime = 0; | |
| /* ========================================================= | |
| DOM | |
| ========================================================= */ | |
| const modelFile = | |
| document.getElementById( | |
| "modelFile" | |
| ); | |
| const modelStatus = | |
| document.getElementById( | |
| "modelStatus" | |
| ); | |
| const modelFileName = | |
| document.getElementById( | |
| "modelFileName" | |
| ); | |
| const loadingStatus = | |
| document.getElementById( | |
| "loadingStatus" | |
| ); | |
| const progressBar = | |
| document.getElementById( | |
| "progressBar" | |
| ); | |
| const progressPercent = | |
| document.getElementById( | |
| "progressPercent" | |
| ); | |
| const status = | |
| document.getElementById( | |
| "status" | |
| ); | |
| const backend = | |
| document.getElementById( | |
| "backend" | |
| ); | |
| const camera = | |
| document.getElementById( | |
| "camera" | |
| ); | |
| const output = | |
| document.getElementById( | |
| "output" | |
| ); | |
| const outputCtx = | |
| output.getContext( | |
| "2d" | |
| ); | |
| const cameraPlaceholder = | |
| document.getElementById( | |
| "cameraPlaceholder" | |
| ); | |
| const outputPlaceholder = | |
| document.getElementById( | |
| "outputPlaceholder" | |
| ); | |
| const startButton = | |
| document.getElementById( | |
| "start" | |
| ); | |
| const stopButton = | |
| document.getElementById( | |
| "stop" | |
| ); | |
| const confidence = | |
| document.getElementById( | |
| "confidence" | |
| ); | |
| const confidenceText = | |
| document.getElementById( | |
| "confidenceText" | |
| ); | |
| const objectsText = | |
| document.getElementById( | |
| "objects" | |
| ); | |
| /* ========================================================= | |
| PROGRESS | |
| ========================================================= */ | |
| function updateProgress( | |
| percent, | |
| message | |
| ) { | |
| percent = | |
| Math.max( | |
| 0, | |
| Math.min( | |
| 100, | |
| percent | |
| ) | |
| ); | |
| progressBar.style.width = | |
| `${percent}%`; | |
| progressPercent.textContent = | |
| `${Math.round(percent)}%`; | |
| loadingStatus.textContent = | |
| message; | |
| } | |
| /* ========================================================= | |
| FORMAT FILE SIZE | |
| ========================================================= */ | |
| function formatBytes(bytes) { | |
| if (bytes < 1024) { | |
| return `${bytes} B`; | |
| } | |
| if ( | |
| bytes < | |
| 1024 * 1024 | |
| ) { | |
| return ( | |
| bytes / | |
| 1024 | |
| ).toFixed(1) | |
| + " KB"; | |
| } | |
| return ( | |
| bytes / | |
| 1024 / | |
| 1024 | |
| ).toFixed(2) | |
| + " MB"; | |
| } | |
| /* ========================================================= | |
| CREATE WORKER | |
| ========================================================= */ | |
| function createWorker() { | |
| if (worker) { | |
| worker.terminate(); | |
| } | |
| worker = | |
| new Worker( | |
| "worker.js", | |
| { | |
| type: "module" | |
| } | |
| ); | |
| worker.onmessage = | |
| handleWorkerMessage; | |
| worker.onerror = | |
| function(event) { | |
| console.error( | |
| "Worker error:", | |
| event | |
| ); | |
| modelStatus.textContent = | |
| "π΄ Worker error"; | |
| status.textContent = | |
| "π΄ Worker error"; | |
| updateProgress( | |
| 0, | |
| "Worker error. Check browser console." | |
| ); | |
| }; | |
| } | |
| /* ========================================================= | |
| MODEL FILE SELECTION | |
| ========================================================= */ | |
| modelFile.addEventListener( | |
| "change", | |
| async function(event) { | |
| const file = | |
| event.target.files[0]; | |
| if (!file) { | |
| return; | |
| } | |
| console.log( | |
| "Selected:", | |
| file.name | |
| ); | |
| /* | |
| Basic validation | |
| */ | |
| if ( | |
| !file.name | |
| .toLowerCase() | |
| .endsWith(".onnx") | |
| ) { | |
| alert( | |
| "Please select an ONNX (.onnx) model." | |
| ); | |
| return; | |
| } | |
| modelFileName.textContent = | |
| `${file.name} β ${formatBytes(file.size)}`; | |
| modelStatus.textContent = | |
| "π‘ Reading model..."; | |
| status.textContent = | |
| "π‘ Loading YOLO model"; | |
| updateProgress( | |
| 10, | |
| "Reading selected ONNX model..." | |
| ); | |
| try { | |
| /* | |
| Read model into memory | |
| */ | |
| const buffer = | |
| await file.arrayBuffer(); | |
| updateProgress( | |
| 45, | |
| "Model loaded into browser memory..." | |
| ); | |
| /* | |
| Create worker | |
| */ | |
| createWorker(); | |
| updateProgress( | |
| 65, | |
| "Sending model to AI worker..." | |
| ); | |
| /* | |
| Transfer ArrayBuffer. | |
| Transfer means the browser does not | |
| make another copy of the model. | |
| */ | |
| worker.postMessage( | |
| { | |
| type: "init", | |
| modelBytes: | |
| buffer, | |
| inputSize: | |
| INPUT_SIZE | |
| }, | |
| [ | |
| buffer | |
| ] | |
| ); | |
| } | |
| catch(error) { | |
| console.error( | |
| error | |
| ); | |
| modelStatus.textContent = | |
| "π΄ Model loading failed"; | |
| status.textContent = | |
| "π΄ Model Error"; | |
| updateProgress( | |
| 0, | |
| "β " + error.message | |
| ); | |
| } | |
| } | |
| ); | |
| /* ========================================================= | |
| WORKER MESSAGE HANDLER | |
| ========================================================= */ | |
| function handleWorkerMessage(event) { | |
| const data = | |
| event.data; | |
| /* | |
| Worker setup progress | |
| */ | |
| if ( | |
| data.type === | |
| "loading" | |
| ) { | |
| updateProgress( | |
| data.progress, | |
| data.message | |
| ); | |
| return; | |
| } | |
| /* | |
| Model ready | |
| */ | |
| if ( | |
| data.type === | |
| "ready" | |
| ) { | |
| updateProgress( | |
| 100, | |
| "YOLO model ready!" | |
| ); | |
| modelStatus.textContent = | |
| "π’ YOLO11n-Seg Ready"; | |
| status.textContent = | |
| "π’ YOLO Ready"; | |
| backend.textContent = | |
| data.backend; | |
| startButton.disabled = | |
| false; | |
| return; | |
| } | |
| /* | |
| Error | |
| */ | |
| if ( | |
| data.type === | |
| "error" | |
| ) { | |
| console.error( | |
| data.message | |
| ); | |
| modelStatus.textContent = | |
| "π΄ Model error"; | |
| status.textContent = | |
| "π΄ AI Error"; | |
| updateProgress( | |
| 0, | |
| "β " + data.message | |
| ); | |
| return; | |
| } | |
| /* | |
| Detection result | |
| */ | |
| if ( | |
| data.type === | |
| "result" | |
| ) { | |
| frameBusy = | |
| false; | |
| drawResult( | |
| data | |
| ); | |
| } | |
| } | |
| /* ========================================================= | |
| START CAMERA | |
| ========================================================= */ | |
| async function startCamera() { | |
| if (!worker) { | |
| alert( | |
| "Please select the YOLO ONNX model first." | |
| ); | |
| return; | |
| } | |
| try { | |
| stream = | |
| await navigator | |
| .mediaDevices | |
| .getUserMedia( | |
| { | |
| video: { | |
| facingMode: | |
| "environment" | |
| }, | |
| audio: false | |
| } | |
| ); | |
| camera.srcObject = | |
| stream; | |
| await camera.play(); | |
| running = | |
| true; | |
| startButton.disabled = | |
| true; | |
| stopButton.disabled = | |
| false; | |
| cameraPlaceholder.style.display = | |
| "none"; | |
| outputPlaceholder.style.display = | |
| "none"; | |
| status.textContent = | |
| "π’ Camera Running"; | |
| processFrame(); | |
| } | |
| catch(error) { | |
| console.error( | |
| error | |
| ); | |
| status.textContent = | |
| "π΄ Camera Error"; | |
| alert( | |
| "Camera access failed:\n" + | |
| error.message | |
| ); | |
| } | |
| } | |
| /* ========================================================= | |
| STOP CAMERA | |
| ========================================================= */ | |
| function stopCamera() { | |
| running = | |
| false; | |
| if (stream) { | |
| stream | |
| .getTracks() | |
| .forEach( | |
| track => | |
| track.stop() | |
| ); | |
| stream = | |
| null; | |
| } | |
| camera.srcObject = | |
| null; | |
| startButton.disabled = | |
| false; | |
| stopButton.disabled = | |
| true; | |
| cameraPlaceholder.style.display = | |
| "block"; | |
| status.textContent = | |
| "π‘ Camera Stopped"; | |
| } | |
| /* ========================================================= | |
| PROCESS CAMERA FRAME | |
| ========================================================= */ | |
| function processFrame() { | |
| if (!running) { | |
| return; | |
| } | |
| /* | |
| Don't send another frame while | |
| the previous one is being processed. | |
| */ | |
| if (!frameBusy) { | |
| if ( | |
| camera.readyState >= 2 && | |
| camera.videoWidth > 0 | |
| ) { | |
| sendFrame(); | |
| } | |
| } | |
| requestAnimationFrame( | |
| processFrame | |
| ); | |
| } | |
| /* ========================================================= | |
| SEND FRAME | |
| ========================================================= */ | |
| function sendFrame() { | |
| frameBusy = | |
| true; | |
| const width = | |
| camera.videoWidth; | |
| const height = | |
| camera.videoHeight; | |
| /* | |
| Temporary canvas | |
| Using the camera's actual resolution. | |
| */ | |
| const canvas = | |
| document.createElement( | |
| "canvas" | |
| ); | |
| canvas.width = | |
| width; | |
| canvas.height = | |
| height; | |
| const ctx = | |
| canvas.getContext( | |
| "2d", | |
| { | |
| willReadFrequently: | |
| true | |
| } | |
| ); | |
| ctx.drawImage( | |
| camera, | |
| 0, | |
| 0, | |
| width, | |
| height | |
| ); | |
| const imageData = | |
| ctx.getImageData( | |
| 0, | |
| 0, | |
| width, | |
| height | |
| ); | |
| /* | |
| Copy RGBA data into an ArrayBuffer. | |
| */ | |
| const rgba = | |
| new Uint8ClampedArray( | |
| imageData.data | |
| ); | |
| worker.postMessage( | |
| { | |
| type: | |
| "frame", | |
| rgba: | |
| rgba.buffer, | |
| width: | |
| width, | |
| height: | |
| height, | |
| confidence: | |
| confidenceThreshold | |
| }, | |
| [ | |
| rgba.buffer | |
| ] | |
| ); | |
| } | |
| /* ========================================================= | |
| DRAW RESULT | |
| ========================================================= */ | |
| function drawResult(data) { | |
| const width = | |
| data.width; | |
| const height = | |
| data.height; | |
| if ( | |
| !width || | |
| !height | |
| ) { | |
| return; | |
| } | |
| output.width = | |
| width; | |
| output.height = | |
| height; | |
| /* | |
| Draw original frame | |
| */ | |
| if (data.frame) { | |
| const frameBytes = | |
| new Uint8ClampedArray( | |
| data.frame | |
| ); | |
| const frameImage = | |
| new ImageData( | |
| frameBytes, | |
| width, | |
| height | |
| ); | |
| outputCtx.putImageData( | |
| frameImage, | |
| 0, | |
| 0 | |
| ); | |
| } | |
| /* | |
| Segmentation overlays | |
| */ | |
| if ( | |
| data.detections && | |
| data.detections.length | |
| ) { | |
| for ( | |
| const detection | |
| of data.detections | |
| ) { | |
| drawMask( | |
| detection, | |
| width, | |
| height | |
| ); | |
| } | |
| } | |
| /* | |
| Draw bounding boxes + labels | |
| */ | |
| if ( | |
| data.detections | |
| ) { | |
| for ( | |
| const detection | |
| of data.detections | |
| ) { | |
| drawDetection( | |
| detection | |
| ); | |
| } | |
| } | |
| objectsText.textContent = | |
| data.detections | |
| ? data.detections.length | |
| : "0"; | |
| /* | |
| FPS | |
| */ | |
| const now = | |
| performance.now(); | |
| if (lastFrameTime > 0) { | |
| const delta = | |
| now - | |
| lastFrameTime; | |
| const currentFPS = | |
| 1000 / | |
| delta; | |
| fps = | |
| fps * 0.8 + | |
| currentFPS * 0.2; | |
| const fpsElement = | |
| document.getElementById( | |
| "fps" | |
| ); | |
| if (fpsElement) { | |
| fpsElement.textContent = | |
| fps.toFixed(1); | |
| } | |
| } | |
| lastFrameTime = | |
| now; | |
| } | |
| /* ========================================================= | |
| DRAW SEGMENTATION MASK | |
| ========================================================= */ | |
| function drawMask( | |
| detection, | |
| width, | |
| height | |
| ) { | |
| if ( | |
| !detection.mask | |
| ) { | |
| return; | |
| } | |
| const mask = | |
| new Uint8Array( | |
| detection.mask | |
| ); | |
| const maskWidth = | |
| detection.maskWidth; | |
| const maskHeight = | |
| detection.maskHeight; | |
| if ( | |
| !maskWidth || | |
| !maskHeight | |
| ) { | |
| return; | |
| } | |
| /* | |
| Draw mask directly onto output. | |
| The worker returns a mask already | |
| mapped to the original camera image. | |
| */ | |
| const image = | |
| outputCtx.getImageData( | |
| 0, | |
| 0, | |
| width, | |
| height | |
| ); | |
| const pixels = | |
| image.data; | |
| const limit = | |
| Math.min( | |
| mask.length, | |
| width * height | |
| ); | |
| for ( | |
| let i = 0; | |
| i < limit; | |
| i++ | |
| ) { | |
| if ( | |
| mask[i] > 0 | |
| ) { | |
| const p = | |
| i * 4; | |
| /* | |
| Semi-transparent cyan/green | |
| segmentation overlay. | |
| */ | |
| pixels[p] = | |
| Math.min( | |
| 255, | |
| pixels[p] * 0.45 + | |
| 40 * 0.55 | |
| ); | |
| pixels[p + 1] = | |
| Math.min( | |
| 255, | |
| pixels[p + 1] * 0.45 + | |
| 220 * 0.55 | |
| ); | |
| pixels[p + 2] = | |
| Math.min( | |
| 255, | |
| pixels[p + 2] * 0.45 + | |
| 120 * 0.55 | |
| ); | |
| } | |
| } | |
| outputCtx.putImageData( | |
| image, | |
| 0, | |
| 0 | |
| ); | |
| } | |
| /* ========================================================= | |
| DRAW BOX + LABEL | |
| ========================================================= */ | |
| function drawDetection( | |
| detection | |
| ) { | |
| const x1 = | |
| detection.x1; | |
| const y1 = | |
| detection.y1; | |
| const x2 = | |
| detection.x2; | |
| const y2 = | |
| detection.y2; | |
| const width = | |
| x2 - x1; | |
| const height = | |
| y2 - y1; | |
| outputCtx.strokeStyle = | |
| "#22c55e"; | |
| outputCtx.lineWidth = | |
| 3; | |
| outputCtx.strokeRect( | |
| x1, | |
| y1, | |
| width, | |
| height | |
| ); | |
| const confidence = | |
| Number( | |
| detection.confidence | |
| ).toFixed(2); | |
| const label = | |
| `${detection.name} ${confidence}`; | |
| outputCtx.font = | |
| "bold 15px Arial"; | |
| const textWidth = | |
| outputCtx.measureText( | |
| label | |
| ).width; | |
| const labelHeight = | |
| 24; | |
| outputCtx.fillStyle = | |
| "#22c55e"; | |
| outputCtx.fillRect( | |
| x1, | |
| Math.max( | |
| 0, | |
| y1 - labelHeight | |
| ), | |
| textWidth + 12, | |
| labelHeight | |
| ); | |
| outputCtx.fillStyle = | |
| "#020617"; | |
| outputCtx.fillText( | |
| label, | |
| x1 + 6, | |
| Math.max( | |
| 17, | |
| y1 - 7 | |
| ) | |
| ); | |
| } | |
| /* ========================================================= | |
| CONFIDENCE SLIDER | |
| ========================================================= */ | |
| confidence.addEventListener( | |
| "input", | |
| function() { | |
| confidenceThreshold = | |
| Number( | |
| confidence.value | |
| ); | |
| confidenceText.textContent = | |
| confidenceThreshold.toFixed( | |
| 2 | |
| ); | |
| } | |
| ); | |
| /* ========================================================= | |
| BUTTONS | |
| ========================================================= */ | |
| startButton.addEventListener( | |
| "click", | |
| startCamera | |
| ); | |
| stopButton.addEventListener( | |
| "click", | |
| stopCamera | |
| ); | |
| /* ========================================================= | |
| INITIAL STATE | |
| ========================================================= */ | |
| startButton.disabled = | |
| true; | |
| stopButton.disabled = | |
| true; | |
| updateProgress( | |
| 0, | |
| "Download the model, then select the .onnx file." | |
| ); |