Spaces:
Sleeping
Sleeping
| <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="canvasObjdet" x-ref="canvasObjdet" style="visibility: hidden;"></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="videoObjdet" | |
| autoplay | |
| playsinline | |
| class="hidden" | |
| ></video> | |
| <canvas x-ref="canvasOcr" 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="startCameraObjdet" class="btn btn-primary"> | |
| Start Camera | |
| </button> | |
| <button | |
| @click="captureImageObjdet" | |
| class="btn btn-primary" | |
| :disabled="!cameraActive" | |
| > | |
| Capture | |
| </button> | |
| <button | |
| @click="stopCameraObjdet" | |
| 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="videoOcr" | |
| 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="startCameraOcr" class="btn btn-primary"> | |
| Start Camera | |
| </button> | |
| <button | |
| @click="captureImageOcr" | |
| class="btn btn-primary" | |
| :disabled="!cameraActive" | |
| > | |
| Capture | |
| </button> | |
| <button | |
| @click="stopCameraOcr" | |
| 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 startCameraObjdet() { | |
| try { | |
| const stream = await navigator.mediaDevices.getUserMedia({ | |
| video: true, | |
| }); | |
| this.$refs.videoObjdet.srcObject = stream; | |
| this.$refs.videoObjdet.classList.remove("hidden"); | |
| this.cameraActive = true; | |
| } catch (err) { | |
| console.error("Error accessing camera:", err); | |
| alert("Could not access camera. Please check permissions."); | |
| } | |
| }, | |
| async startCameraOcr() { | |
| try { | |
| const stream = await navigator.mediaDevices.getUserMedia({ | |
| video: true, | |
| }); | |
| this.$refs.videoOcr.srcObject = stream; | |
| this.$refs.videoOcr.classList.remove("hidden"); | |
| this.cameraActive = true; | |
| } catch (err) { | |
| console.error("Error accessing camera:", err); | |
| alert("Could not access camera. Please check permissions."); | |
| } | |
| }, | |
| captureImageObjdet() { | |
| const video = this.$refs.videoObjdet; | |
| const canvas = this.$refs.canvasObjdet; | |
| canvas.width = video.videoWidth; | |
| canvas.height = video.videoHeight; | |
| canvas.getContext("2d").drawImage(video, 0, 0); | |
| this.imageUrl = canvas.toDataURL("image/png"); | |
| this.clearResults(); | |
| }, | |
| captureImageOcr() { | |
| const video = this.$refs.videoOcr; | |
| const canvas = this.$refs.canvasOcr; | |
| canvas.width = video.videoWidth; | |
| canvas.height = video.videoHeight; | |
| canvas.getContext("2d").drawImage(video, 0, 0); | |
| this.imageUrlOcr = canvas.toDataURL("image/png"); | |
| this.clearResults(); | |
| }, | |
| stopCameraObjdet() { | |
| const stream = this.$refs.videoObjdet.srcObject; | |
| if (stream) { | |
| stream.getTracks().forEach((track) => track.stop()); | |
| this.$refs.videoObjdet.srcObject = null; | |
| this.$refs.videoObjdet.classList.add("hidden"); | |
| this.cameraActive = false; | |
| } | |
| }, | |
| stopCameraOcr() { | |
| const stream = this.$refs.videOcr.srcObject; | |
| if (stream) { | |
| stream.getTracks().forEach((track) => track.stop()); | |
| this.$refs.videoOcr.srcObject = null; | |
| this.$refs.videoOcr.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> | |