122 lines
3.5 KiB
TypeScript
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,
|
|
}
|
|
}
|