Files
mediaproc/ui/detection-app/src/cv/wasmBridge.ts
2026-03-30 07:22:14 -03:00

122 lines
3.5 KiB
TypeScript

/**
* WASM Worker Bridge — runs OpenCV operations in a Web Worker.
*
* The worker loads opencv.js (~10MB) in its own thread.
* Main thread stays responsive during WASM compilation.
*
* Lazy-creates the worker on first call. Sends ImageData +
* params, gets back results via transferable buffers.
*/
import type { EdgeDetectionParams, EdgeDetectionResult, EdgeDetectionDebugResult } from './edges'
import type { SegmentationParams, SegmentationDebugResult } from './segmentation'
let worker: Worker | null = null
let messageId = 0
const pending = new Map<number, { resolve: (v: any) => void; reject: (e: Error) => void }>()
let initPromise: Promise<boolean> | null = null
let ready = false
let failed = false
function getWorker(): Worker {
if (!worker) {
worker = new Worker(new URL('./worker.ts', import.meta.url), { type: 'module' })
worker.onmessage = (event) => {
const { id, type, error: errMsg, ...data } = event.data
const handler = pending.get(id)
if (!handler) return
pending.delete(id)
if (type === 'error') {
handler.reject(new Error(errMsg ?? 'Worker error'))
} else {
handler.resolve(data)
}
}
worker.onerror = (event) => {
// Reject all pending
for (const [, handler] of pending) {
handler.reject(new Error(event.message ?? 'Worker crashed'))
}
pending.clear()
}
}
return worker
}
function postMessage(type: string, imageData: ImageData, params: Record<string, unknown>): Promise<any> {
return new Promise((resolve, reject) => {
const id = ++messageId
pending.set(id, { resolve, reject })
getWorker().postMessage({ id, type, imageData, params }, [imageData.data.buffer])
})
}
/** Initialize the worker + load WASM. Returns true if ready. */
export async function initWasm(): Promise<boolean> {
if (ready) return true
if (failed) return false
if (initPromise) return initPromise
initPromise = (async () => {
try {
// Send a ping — the worker will load opencv.js on first message
const result = await postMessage('ping', new ImageData(1, 1), {})
ready = true
return true
} catch {
failed = true
return false
}
})()
return initPromise
}
export function isWasmReady(): boolean {
return ready
}
export function isWasmFailed(): boolean {
return failed
}
// --- Edge detection ---
export async function detectEdgesWasm(
imageData: ImageData,
params: Partial<EdgeDetectionParams>,
): Promise<EdgeDetectionResult> {
const data = await postMessage('detect_edges', imageData, params as Record<string, unknown>)
return { regions: data.regions }
}
export async function detectEdgesWasmDebug(
imageData: ImageData,
params: Partial<EdgeDetectionParams>,
): Promise<EdgeDetectionDebugResult> {
const data = await postMessage('detect_edges_debug', imageData, params as Record<string, unknown>)
return {
regions: data.regions,
edgeImageData: data.edgeImageData,
linesImageData: data.linesImageData,
horizontalCount: data.horizontalCount,
pairCount: data.pairCount,
}
}
// --- Segmentation ---
export async function segmentFieldWasmDebug(
imageData: ImageData,
params: Partial<SegmentationParams>,
): Promise<SegmentationDebugResult> {
const data = await postMessage('segment_field_debug', imageData, params as Record<string, unknown>)
return {
boundary: data.boundary,
coverage: data.coverage,
maskImageData: data.maskImageData,
overlayImageData: data.overlayImageData,
}
}