Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8" /> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
<title>ML Vision Playground</title> | |
<script | |
defer | |
src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js" | |
></script> | |
<link rel="stylesheet" href="assets/style.css" /> | |
</head> | |
<body x-data="app()" :class="{'dark': darkMode}"> | |
<div class="container"> | |
<header> | |
<h1>ML Vision Playground</h1> | |
</header> | |
<div class="applications-container mb-2"> | |
<!-- Object Detection Application --> | |
<!-- Object detection --> | |
<div | |
class="application-tab" | |
:class="{'active': activeApplication === 'objdet'}" | |
> | |
<div class="application-header" @click="toggleApplication('objdet')"> | |
<div class="application-title"> | |
<img src="assets/obj-detect.svg" height="20px" /> | |
Object detection | |
</div> | |
<img | |
src="assets/arrow.svg" | |
height="20px" | |
class="application-arrow" | |
/> | |
</div> | |
<div class="application-content"> | |
<!-- api creds --> | |
<div class="application-meta-input card mb-2"> | |
<div class="form-container"> | |
<div class="input-group"> | |
<label for="url" | |
>URL | |
<span class="helper-text" | |
>Enter the base URL for the API.</span | |
></label | |
> | |
<input | |
type="text" | |
id="url" | |
placeholder="https://example.com/api" | |
value="https://api.app.deeploy.ml/workspaces/5aff1650-8d8c-4d0c-b4da-87146360ba82/deployments/8df40f37-11cc-473f-aff5-2112de56f84e/predict" | |
/> | |
</div> | |
<div class="input-group"> | |
<label for="apiKey" | |
>API Key | |
<span class="helper-text" | |
>Your secret API key. | |
</span></label | |
> | |
<input | |
type="text" | |
id="apiKey" | |
placeholder="DPT23exmplg4FJfgfFREsada0lMUgJs0kcC9wxd" | |
/> | |
</div> | |
</div> | |
</div> | |
<div class="grid grid-cols-2"> | |
<!-- Input Card --> | |
<div class="card"> | |
<div class="card-header"> | |
<h2 class="card-title">Input Image</h2> | |
<p class="card-description"> | |
Upload or capture an image to analyze | |
</p> | |
</div> | |
<div class="tabs"> | |
<div | |
@click="activeTab = 'upload'" | |
:class="{'active': activeTab === 'upload'}" | |
class="tab" | |
> | |
Upload | |
</div> | |
<div | |
@click="activeTab = 'camera'" | |
:class="{'active': activeTab === 'camera'}" | |
class="tab" | |
> | |
Camera | |
</div> | |
</div> | |
<div | |
x-show="imageUrl" | |
class="preview-image-container" | |
style="max-width: 100%; height: auto" | |
> | |
<canvas | |
id="previewCanvas" | |
x-ref="previewCanvas" | |
class="preview-image" | |
style="max-width: 100%; height: auto" | |
x-effect="imageUrl && (() => { | |
const canvas = $refs.previewCanvas; | |
const img = new Image(); | |
img.onload = function() { | |
canvas.width = img.width; | |
canvas.height = img.height; | |
canvas.getContext('2d').drawImage(img, 0, 0); | |
}; | |
img.src = imageUrl; | |
})()" | |
></canvas> | |
</div> | |
<!-- <canvas id="canvas" x-ref="canvas"></canvas> --> | |
<div | |
x-show="activeTab === 'upload' && !imageUrl" | |
class="upload-tab" | |
> | |
<div | |
@click="$refs.fileInput.click()" | |
@dragover.prevent="dragOver = true" | |
@dragleave="dragOver = false" | |
@drop.prevent="handleDrop($event)" | |
:class="{'has-image': imageUrl, 'border-primary': dragOver}" | |
class="preview-area" | |
> | |
<input | |
type="file" | |
@change="handleFileSelect" | |
x-ref="fileInput" | |
accept="image/*" | |
class="hidden" | |
/> | |
<template x-if="!imageUrl"> | |
<div class="preview-placeholder"> | |
<svg | |
xmlns="http://www.w3.org/2000/svg" | |
fill="none" | |
viewBox="0 0 24 24" | |
stroke="currentColor" | |
> | |
<path | |
stroke-linecap="round" | |
stroke-linejoin="round" | |
stroke-width="2" | |
d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" | |
/> | |
</svg> | |
<p>Click to upload or drag and drop</p> | |
<p class="text-sm">Supports JPG, PNG, WEBP</p> | |
</div> | |
</template> | |
</div> | |
<!-- <div class="btn-group"> | |
<button @click="clearImage" x-show="imageUrl" class="btn btn-secondary"> | |
Clear | |
</button> | |
</div> --> | |
</div> | |
<div | |
x-show="activeTab === 'camera' && !imageUrl" | |
class="camera-tab" | |
> | |
<div class="preview-area"> | |
<video | |
x-ref="video" | |
autoplay | |
playsinline | |
class="hidden" | |
></video> | |
<canvas x-ref="canvas" class="hidden"></canvas> | |
<div class="preview-placeholder"> | |
<svg | |
xmlns="http://www.w3.org/2000/svg" | |
fill="none" | |
viewBox="0 0 24 24" | |
stroke="currentColor" | |
> | |
<path | |
stroke-linecap="round" | |
stroke-linejoin="round" | |
stroke-width="2" | |
d="M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z" | |
/> | |
<path | |
stroke-linecap="round" | |
stroke-linejoin="round" | |
stroke-width="2" | |
d="M15 13a3 3 0 11-6 0 3 3 0 016 0z" | |
/> | |
</svg> | |
<p>Camera Feed</p> | |
</div> | |
</div> | |
<div class="btn-group"> | |
<button @click="startCamera" class="btn btn-primary"> | |
Start Camera | |
</button> | |
<button | |
@click="captureImage" | |
class="btn btn-primary" | |
:disabled="!cameraActive" | |
> | |
Capture | |
</button> | |
<button | |
@click="stopCamera" | |
class="btn btn-secondary" | |
:disabled="!cameraActive" | |
> | |
Stop | |
</button> | |
</div> | |
</div> | |
<div class="btn-group"> | |
<button | |
@click="clearImage" | |
x-show="imageUrl" | |
class="btn btn-secondary" | |
> | |
Clear | |
</button> | |
</div> | |
<!-- Collapsible section to select sample images from thumbnails --> | |
<div class="sample-images-container mt-2"> | |
<div | |
class="collapsible-header" | |
@click="sampleImagesOpen = !sampleImagesOpen" | |
> | |
<span>Sample Images</span> | |
<img | |
src="assets/arrow.svg" | |
height="20px" | |
:class="{'rotate-180': sampleImagesOpen}" | |
/> | |
</div> | |
<div | |
x-show="sampleImagesOpen" | |
x-transition | |
class="sample-images-content" | |
> | |
<div class="sample-image-grid"> | |
<template x-for="(image, type) in sampleData" :key="type"> | |
<span @click="loadSample(type)" class="sample-image"> | |
<img | |
:src="image.url" | |
alt="" | |
width="80px" | |
style="max-height: 100px" | |
/> | |
</span> | |
</template> | |
</div> | |
</div> | |
</div> | |
<!-- Confidence Interval Slider --> | |
<div class="confidence-slider mt-2"> | |
<label | |
for="confidence-slider" | |
class="block text-sm font-medium text-gray-700" | |
>Confidence Interval</label | |
> | |
<input | |
id="confidence-slider" | |
type="range" | |
min="0" | |
max="100" | |
step="1" | |
x-model="confidenceThreshold" | |
/> | |
<p class="text-sm mt-1"> | |
Selected: <span x-text="confidenceThreshold"></span>% | |
</p> | |
</div> | |
<!-- Predict Button --> | |
<div class="mt-2"> | |
<button | |
@click="predictImage()" | |
class="btn btn-primary w-100" | |
:disabled="!imageUrl" | |
> | |
Predict | |
</button> | |
</div> | |
</div> | |
<!-- Processing Card --> | |
<!-- Processing Card - results --> | |
<div class="card"> | |
<div class="card-header"> | |
<h2 class="card-title">Output</h2> | |
<p class="card-description"> | |
Select an application to analyze your image | |
</p> | |
</div> | |
<div class="application-content"> | |
<!-- Preview content same as before --> | |
<span id="objdet-status-display"></span> | |
<textarea | |
id="objdet-output-display" | |
class="text-output-area" | |
></textarea> | |
<div class="btn-group mt-2"> | |
<button | |
@click="clearResults" | |
class="btn btn-secondary" | |
:disabled="!results.length" | |
> | |
Clear Results | |
</button> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<!-- end --> | |
</div> | |
<!-- OCR --> | |
<div | |
class="application-tab" | |
:class="{'active': activeApplication === 'ocr'}" | |
> | |
<div class="application-header" @click="toggleApplication('ocr')"> | |
<div class="application-title"> | |
<img src="assets/text.svg" height="20px" /> | |
Text Recognition (OCR) | |
</div> | |
<img | |
src="assets/arrow.svg" | |
height="20px" | |
class="application-arrow" | |
/> | |
</div> | |
<div class="application-content"> | |
<!-- api creds --> | |
<div class="application-meta-input card mb-2"> | |
<div class="form-container"> | |
<div class="input-group"> | |
<!-- Add the loader component here --> | |
<label for="url-ocr" | |
>URL | |
<span class="helper-text" | |
>Enter the base URL for the API.</span | |
></label | |
> | |
<input | |
type="text" | |
id="url-ocr" | |
placeholder="https://example.com/api" | |
value="https://api.app.deeploy.ml/workspaces/5aff1650-8d8c-4d0c-b4da-87146360ba82/deployments/796f79bd-bab8-495e-bc90-6654903312fe/predict" | |
/> | |
</div> | |
<div class="input-group"> | |
<label for="apiKey-ocr" | |
>API Key | |
<span class="helper-text" | |
>Your secret API key. | |
</span></label | |
> | |
<input | |
type="text" | |
id="apiKey-ocr" | |
placeholder="DPT23exmplg4FJfgfFREsada0lMUgJs0kcC9wxd" | |
/> | |
</div> | |
</div> | |
</div> | |
<!-- app --> | |
<div class="grid grid-cols-2"> | |
<!-- Input Card --> | |
<div class="card"> | |
<div class="card-header"> | |
<h2 class="card-title">Input Image</h2> | |
<p class="card-description"> | |
Upload or capture an image to analyze | |
</p> | |
</div> | |
<div class="tabs"> | |
<div | |
@click="activeTab = 'upload'" | |
:class="{'active': activeTab === 'upload'}" | |
class="tab" | |
> | |
Upload | |
</div> | |
<div | |
@click="activeTab = 'camera'" | |
:class="{'active': activeTab === 'camera'}" | |
class="tab" | |
> | |
Camera | |
</div> | |
</div> | |
<div | |
x-show="imageUrlOcr" | |
class="preview-image-container" | |
style="max-width: 100%; height: auto" | |
> | |
<canvas | |
id="previewCanvasOcr" | |
x-ref="previewCanvasOcr" | |
class="preview-image" | |
style="max-width: 100%; height: auto" | |
x-effect="imageUrlOcr && (() => { | |
const canvas = $refs.previewCanvasOcr; | |
const img = new Image(); | |
img.onload = function() { | |
canvas.width = img.width; | |
canvas.height = img.height; | |
canvas.getContext('2d').drawImage(img, 0, 0); | |
}; | |
img.src = imageUrlOcr; | |
})()" | |
></canvas> | |
</div> | |
<!-- <canvas id="canvas" x-ref="canvas"></canvas> --> | |
<div | |
x-show="activeTab === 'upload' && !imageUrlOcr" | |
class="upload-tab" | |
> | |
<div | |
@click="$refs.fileInputOcr.click()" | |
@dragover.prevent="dragOver = true" | |
@dragleave="dragOver = false" | |
@drop.prevent="handleDropOcr($event)" | |
:class="{'has-image': imageUrlOcr, 'border-primary': dragOver}" | |
class="preview-area" | |
> | |
<input | |
type="file" | |
@change="handleFileSelectOcr" | |
x-ref="fileInputOcr" | |
accept="image/*" | |
class="hidden" | |
/> | |
<template x-if="!imageUrlOcr"> | |
<div class="preview-placeholder"> | |
<svg | |
xmlns="http://www.w3.org/2000/svg" | |
fill="none" | |
viewBox="0 0 24 24" | |
stroke="currentColor" | |
> | |
<path | |
stroke-linecap="round" | |
stroke-linejoin="round" | |
stroke-width="2" | |
d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" | |
/> | |
</svg> | |
<p>Click to upload or drag and drop</p> | |
<p class="text-sm">Supports JPG, PNG, WEBP</p> | |
</div> | |
</template> | |
</div> | |
<!-- <div class="btn-group"> | |
<button @click="clearImage" x-show="imageUrl" class="btn btn-secondary"> | |
Clear | |
</button> | |
</div> --> | |
</div> | |
<div | |
x-show="activeTab === 'camera' && !imageUrlOcr" | |
class="camera-tab" | |
> | |
<div class="preview-area"> | |
<video | |
x-ref="video" | |
autoplay | |
playsinline | |
class="hidden" | |
></video> | |
<canvas x-ref="canvas" class="hidden"></canvas> | |
<div class="preview-placeholder"> | |
<svg | |
xmlns="http://www.w3.org/2000/svg" | |
fill="none" | |
viewBox="0 0 24 24" | |
stroke="currentColor" | |
> | |
<path | |
stroke-linecap="round" | |
stroke-linejoin="round" | |
stroke-width="2" | |
d="M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z" | |
/> | |
<path | |
stroke-linecap="round" | |
stroke-linejoin="round" | |
stroke-width="2" | |
d="M15 13a3 3 0 11-6 0 3 3 0 016 0z" | |
/> | |
</svg> | |
<p>Camera Feed</p> | |
</div> | |
</div> | |
<div class="btn-group"> | |
<button @click="startCamera" class="btn btn-primary"> | |
Start Camera | |
</button> | |
<button | |
@click="captureImage" | |
class="btn btn-primary" | |
:disabled="!cameraActive" | |
> | |
Capture | |
</button> | |
<button | |
@click="stopCamera" | |
class="btn btn-secondary" | |
:disabled="!cameraActive" | |
> | |
Stop | |
</button> | |
</div> | |
</div> | |
<div class="btn-group"> | |
<button | |
@click="clearImage" | |
x-show="imageUrlOcr" | |
class="btn btn-secondary" | |
> | |
Clear | |
</button> | |
</div> | |
<!-- Collapsible section to select sample images from thumbnails --> | |
<div class="sample-images-container mt-2"> | |
<div | |
class="collapsible-header" | |
@click="sampleImagesOpen = !sampleImagesOpen" | |
> | |
<span>Sample Images</span> | |
<img | |
src="assets/arrow.svg" | |
height="20px" | |
:class="{'rotate-180': sampleImagesOpen}" | |
/> | |
</div> | |
<div | |
x-show="sampleImagesOpen" | |
x-transition | |
class="sample-images-content" | |
> | |
<div class="sample-image-grid"> | |
<template | |
x-for="(image, type) in sampleDataOcr" | |
:key="type" | |
> | |
<span @click="loadSampleOcr(type)" class="sample-image"> | |
<img | |
:src="image.url" | |
alt="" | |
width="80px" | |
style="max-height: 100px" | |
/> | |
</span> | |
</template> | |
</div> | |
</div> | |
</div> | |
<!-- Confidence Interval Slider --> | |
<!-- <div class="confidence-slider mt-2"> | |
<label | |
for="confidence-slider" | |
class="block text-sm font-medium text-gray-700" | |
>Confidence Interval</label | |
> | |
<input | |
id="confidence-slider" | |
type="range" | |
min="0" | |
max="100" | |
step="1" | |
x-model="confidenceThreshold" | |
/> | |
<p class="text-sm mt-1"> | |
Selected: <span x-text="confidenceThreshold"></span>% | |
</p> | |
</div> --> | |
<!-- Predict Button --> | |
<div class="mt-2"> | |
<button | |
@click="predictOcr()" | |
class="btn btn-primary w-100" | |
:disabled="!imageUrlOcr" | |
> | |
Predict | |
</button> | |
</div> | |
</div> | |
<!-- Processing Card --> | |
<!-- Processing Card - results --> | |
<div class="card"> | |
<div class="card-header"> | |
<h2 class="card-title">Output</h2> | |
<p class="card-description"> | |
Select an application to analyze your image | |
</p> | |
</div> | |
<div class="application-content"> | |
<!-- Preview content same as before --> | |
<span id="ocr-status-display"></span> | |
<textarea | |
id="ocr-output-display" | |
class="text-output-area" | |
></textarea> | |
<div class="btn-group mt-2"> | |
<button | |
@click="clearResults" | |
class="btn btn-secondary" | |
> | |
Clear Results | |
</button> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<script> | |
// ocr | |
// detection / yolov11 | |
// whisper | |
// medical (yolov11) | |
// | |
$ = (id) => document.getElementById(id); | |
function fetchWrapper(url, statusElPrefix, options = {}) { | |
// Get or create UI elements | |
const statusSpan = $(`${statusElPrefix}-status-display`); | |
const jsonTextarea = $(`${statusElPrefix}-output-display`); | |
// Update status to loading | |
statusSpan.textContent = "Loading..."; | |
statusSpan.className = "status loading"; | |
// Clear previous results | |
jsonTextarea.value = ""; | |
// Make the fetch request | |
return fetch(url, options) | |
.then((response) => { | |
// Display status code | |
statusSpan.textContent = `Status: ${response.status} ${response.statusText}`; | |
statusSpan.className = response.ok | |
? "status success" | |
: "status error"; | |
// Check if response is JSON | |
const contentType = response.headers.get("content-type"); | |
if (contentType && contentType.includes("application/json")) { | |
return response.json().then((data) => { | |
// Format and display JSON | |
jsonTextarea.value = JSON.stringify(data, null, 2); | |
drawBoundingBoxesObjdet(data); | |
return data; // Return data for further processing | |
}); | |
} else { | |
// Handle non-JSON responses | |
return response.text().then((text) => { | |
jsonTextarea.value = text; | |
return text; // Return text for further processing | |
}); | |
} | |
}) | |
.catch((error) => { | |
// Handle network errors | |
statusSpan.textContent = `Error: ${error.message}`; | |
statusSpan.className = "status error"; | |
jsonTextarea.value = `Failed to fetch: ${error.message}`; | |
console.error("Fetch error:", error); | |
throw error; // Re-throw to allow caller to handle error | |
}); | |
} | |
// Draw bounding boxes on the image | |
function drawBoundingBoxesObjdet(data) { | |
// Check if we have predictions to display | |
if (!data || !data.detections || !data.detections.length) return; | |
// Get canvas and create context for drawing | |
const canvasPreview = $("previewCanvas"); | |
const ctx = canvasPreview.getContext("2d"); | |
// Draw each bounding box | |
for (const box of data.detections) { | |
// Set styling for the box | |
ctx.strokeStyle = "rgba(255, 0, 0, 0.8)"; | |
ctx.lineWidth = 2; | |
// Draw the rectangle | |
ctx.strokeRect( | |
box.x_min, | |
box.y_min, | |
box.x_max - box.x_min, | |
box.y_max - box.y_min | |
); | |
// Add label text with confidence | |
const label = `${box.class_name} (${Math.round( | |
box.confidence * 100 | |
)}%)`; | |
ctx.fillStyle = "rgba(255, 0, 0, 0.8)"; | |
// Scale font based on image dimensions (640px is the base) | |
const scale = Math.max(1, Math.min(2, canvasPreview.width / 640)); | |
ctx.font = `${Math.round(14 * scale)}px Arial`; | |
const textWidth = ctx.measureText(label).width; | |
ctx.fillRect(box.x_min, box.y_min - 20, textWidth + 10, 20); | |
ctx.fillStyle = "white"; | |
ctx.fillText(label, box.x_min + 5, box.y_min - 5); | |
}; | |
} | |
document.addEventListener("alpine:init", () => { | |
Alpine.data("app", () => ({ | |
darkMode: false, | |
activeTab: "upload", | |
activeModel: "object", | |
activeApplication: "object", | |
imageUrl: null, | |
imageUrlOcr: null, | |
dragOver: false, | |
cameraActive: false, | |
boundingBoxes: [], | |
results: [], | |
confidenceThreshold: 50, // Default confidence threshold, | |
sampleImagesOpen: false, | |
toggleApplication(app) { | |
this.activeApplication = | |
this.activeApplication === app ? null : app; | |
this.clearResults(); | |
}, | |
predictOcr() { | |
console.log("predictOcr called"); | |
if (!this.imageUrlOcr) { | |
console.log("no imageUrlOcr"); | |
return; | |
} | |
const baseUrl = $("url-ocr").value; | |
const apiKey = $("apiKey-ocr").value; | |
const imageUrlOcr = this.imageUrlOcr; | |
if (!baseUrl) { | |
alert("Please enter an API URL"); | |
return; | |
} | |
if (!apiKey) { | |
alert("Please enter an API Key"); | |
return; | |
} | |
// Fetch the blob first, then make the API call | |
fetch(imageUrlOcr) | |
.then((response) => response.blob()) | |
.then((blob) => { | |
// Convert blob to base64 | |
const reader = new FileReader(); | |
reader.readAsDataURL(blob); | |
reader.onloadend = () => { | |
// Extract the base64 data (remove the data:image/xyz;base64, prefix) | |
const base64data = reader.result.split(",")[1]; | |
// use fetchWrapper to make the API call using application/json body | |
fetchWrapper(`/api/req`, "ocr", { | |
method: "POST", | |
headers: { | |
"Content-Type": "application/json", | |
Authorization: `Bearer ${apiKey}`, | |
"x-req-path": `${baseUrl}`, | |
}, | |
body: JSON.stringify({ | |
instances: [], | |
image_base64: base64data, | |
}), | |
}); | |
}; | |
}) | |
.catch((error) => { | |
console.error("Error preparing image:", error); | |
alert("Error preparing image for upload"); | |
}); | |
}, | |
predictImage() { | |
if (!this.imageUrl) return; | |
const baseUrl = $("url").value; | |
const apiKey = $("apiKey").value; | |
const imageUrl = this.imageUrl; | |
if (!baseUrl) { | |
alert("Please enter an API URL"); | |
return; | |
} | |
if (!apiKey) { | |
alert("Please enter an API Key"); | |
return; | |
} | |
// Fetch the blob first, then make the API call | |
fetch(imageUrl) | |
.then((response) => response.blob()) | |
.then((blob) => { | |
// use fetchWrapper to make the API call using application/octet-stream body | |
fetchWrapper(`/api/req`, "objdet", { | |
method: "POST", | |
headers: { | |
"Content-Type": "application/octet-stream", | |
Authorization: `Bearer ${apiKey}`, | |
"x-req-path": `${baseUrl}`, | |
}, | |
body: blob, | |
}); | |
}) | |
.catch((error) => { | |
console.error("Error preparing image:", error); | |
alert("Error preparing image for upload"); | |
}); | |
}, | |
// Sample data for demo purposes | |
sampleData: { | |
catdog: { | |
url: "https://images.unsplash.com/photo-1606098216818-40939b7c98ad?w=600", | |
}, | |
document: { | |
url: "https://images.unsplash.com/photo-1546410531-bb4caa6b424d?w=600", | |
}, | |
food: { | |
url: "https://images.unsplash.com/photo-1512621776951-a57141f2eefd?w=600", | |
}, | |
people: { | |
url: "https://images.unsplash.com/photo-1527529482837-4698179dc6ce?w=600", | |
}, | |
people2: { | |
url: "assets/img/AdobeStock_84708957.jpg", | |
}, | |
confuse: { | |
url: "assets/img/confuse.jpeg", | |
}, | |
}, | |
sampleDataOcr: { | |
ausweis: { | |
url: "assets/img/Deutscher_Personalausweis_(2010_Version).jpg", | |
}, | |
textbook: { url: "assets/img/computer_vision_textbook_001.jpeg" }, | |
handwritten: { url: "assets/img/sample_handwritten.png" }, | |
nlpass: { url: "assets/img/sample_nl_passport.jpg" }, | |
}, | |
handleFileSelect(e) { | |
const file = e.target.files[0]; | |
if (file?.type.match("image.*")) { | |
this.imageUrl = URL.createObjectURL(file); | |
this.clearResults(); | |
} | |
}, | |
handleFileSelectOcr(e) { | |
const file = e.target.files[0]; | |
if (file?.type.match("image.*")) { | |
this.imageUrlOcr = URL.createObjectURL(file); | |
this.clearResults(); | |
} | |
}, | |
handleDrop(e) { | |
this.dragOver = false; | |
const file = e.dataTransfer.files[0]; | |
if (file?.type.match("image.*")) { | |
this.imageUrl = URL.createObjectURL(file); | |
this.clearResults(); | |
} | |
}, | |
handleDropOcr(e) { | |
this.dragOver = false; | |
const file = e.dataTransfer.files[0]; | |
if (file?.type.match("image.*")) { | |
this.imageUrlOcr = URL.createObjectURL(file); | |
this.clearResults(); | |
} | |
}, | |
clearImage() { | |
if (this.imageUrl) { | |
URL.revokeObjectURL(this.imageUrl); | |
this.imageUrl = null; | |
} | |
this.clearResults(); | |
}, | |
async startCamera() { | |
try { | |
const stream = await navigator.mediaDevices.getUserMedia({ | |
video: true, | |
}); | |
this.$refs.video.srcObject = stream; | |
this.$refs.video.classList.remove("hidden"); | |
this.cameraActive = true; | |
} catch (err) { | |
console.error("Error accessing camera:", err); | |
alert("Could not access camera. Please check permissions."); | |
} | |
}, | |
captureImage() { | |
const video = this.$refs.video; | |
const canvas = this.$refs.canvas; | |
canvas.width = video.videoWidth; | |
canvas.height = video.videoHeight; | |
canvas.getContext("2d").drawImage(video, 0, 0); | |
this.imageUrl = canvas.toDataURL("image/png"); | |
this.clearResults(); | |
}, | |
stopCamera() { | |
const stream = this.$refs.video.srcObject; | |
if (stream) { | |
stream.getTracks().forEach((track) => track.stop()); | |
this.$refs.video.srcObject = null; | |
this.$refs.video.classList.add("hidden"); | |
this.cameraActive = false; | |
} | |
}, | |
loadSample(type) { | |
this.imageUrl = this.sampleData[type].url; | |
this.clearResults(); | |
}, | |
loadSampleOcr(type) { | |
this.imageUrlOcr = this.sampleDataOcr[type].url; | |
// this.clearResults(); | |
}, | |
processImage(appname) { | |
if (!this.imageUrl) return; | |
this.clearResults(); | |
// API call | |
}, | |
clearResults() { | |
const jsonTextareaOcr = $(`ocr-output-display`); | |
const jsonTextareaObjdect = $(`objdet-output-display`); | |
// Clear previous results | |
jsonTextareaOcr.value = ""; | |
jsonTextareaObjdect.value = ""; | |
}, | |
})); | |
}); | |
</script> | |
</div> | |
</body> | |
</html> | |