open-plant 1.4.10 → 1.4.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/assets/point-hit-index-worker-CNFA6pZm.js.map +1 -1
- package/dist/assets/roi-clip-worker-CHxxL_lK.js.map +1 -1
- package/dist/index.cjs +25 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2046 -2002
- package/dist/index.js.map +1 -1
- package/dist/types/react/point-layer.d.ts +2 -0
- package/dist/types/react/point-layer.d.ts.map +1 -1
- package/dist/types/react/region-layer.d.ts +3 -1
- package/dist/types/react/region-layer.d.ts.map +1 -1
- package/dist/types/wsi/utils.d.ts +1 -0
- package/dist/types/wsi/utils.d.ts.map +1 -1
- package/dist/types/wsi/wsi-normalize.d.ts +2 -0
- package/dist/types/wsi/wsi-normalize.d.ts.map +1 -1
- package/dist/types/wsi/wsi-render-pass.d.ts +2 -0
- package/dist/types/wsi/wsi-render-pass.d.ts.map +1 -1
- package/dist/types/wsi/wsi-renderer-types.d.ts +3 -0
- package/dist/types/wsi/wsi-renderer-types.d.ts.map +1 -1
- package/dist/types/wsi/wsi-shaders.d.ts.map +1 -1
- package/dist/types/wsi/wsi-tile-renderer.d.ts +4 -0
- package/dist/types/wsi/wsi-tile-renderer.d.ts.map +1 -1
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -261,6 +261,7 @@ src/
|
|
|
261
261
|
| `viewTransition` | `WsiViewTransitionOptions` | `setViewState`/`fitToImage` 등 전환 |
|
|
262
262
|
| `zoomSnaps` | `number[]` | 배율 배열 → `mpp` 기준 내부 zoom으로 정규화 |
|
|
263
263
|
| `zoomSnapFitAsMin` | `boolean` | 스냅 아웃 시 fit을 하한으로 |
|
|
264
|
+
| `panExtent` | `number \| { x: number; y: number }` | 이미지 바깥으로 허용할 추가 drag 여유. 기본 축별 `0.2` |
|
|
264
265
|
| `onStats` | `(WsiRenderStats) => void` | 프레임 통계 |
|
|
265
266
|
| `onTileError` | `(WsiTileErrorEvent) => void` | 타일 로드 실패 |
|
|
266
267
|
| `onContextLost` / `onContextRestored` | `() => void` | WebGL 컨텍스트 |
|
|
@@ -336,7 +337,7 @@ import {
|
|
|
336
337
|
| `zoomThreshold` | `number` | heatmap이 반응하는 유효 줌을 뒤로 미루는 continuous zoom offset. 양수일수록 더 높은 실제 줌까지 heatmap이 크게 유지됨 |
|
|
337
338
|
| `densityContrast` | `number` | sparse 영역은 더 눌러두고 dense 영역은 더 빨리 강조하는 대비 계수 |
|
|
338
339
|
| `clipToRegions` | `WsiRegion[]` | ROI 내부로 mask + point filter |
|
|
339
|
-
| `maxRenderedPoints` | `number` | visible bin budget. 기본 `
|
|
340
|
+
| `maxRenderedPoints` | `number` | visible bin budget. 기본 `52000` |
|
|
340
341
|
| `zIndex` | `number` | 오버레이 draw priority |
|
|
341
342
|
| `onStats` | `(stats) => void` | point count / render time 등 |
|
|
342
343
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"point-hit-index-worker-CNFA6pZm.js","sources":["../src/wsi/point-hit-index-shared.ts","../src/wsi/utils.ts","../src/workers/point-hit-index-worker.ts"],"sourcesContent":["export const MIN_POINT_HIT_GRID_SIZE = 24;\nexport const MAX_POINT_HIT_GRID_SIZE = 1024;\nexport const POINT_HIT_GRID_DENSITY_SCALE = 4;\nexport const HASH_EMPTY = -1;\n\nexport interface PointHitIndexBuildInput {\n count: number;\n positions: Float32Array;\n drawIndices?: Uint32Array | null;\n sourceWidth: number;\n sourceHeight: number;\n}\n\nexport interface PointHitIndexBuildResult {\n cellSize: number;\n safeCount: number;\n cellCount: number;\n hashCapacity: number;\n hashTable: Int32Array;\n cellKeys: Int32Array;\n cellOffsets: Uint32Array;\n cellLengths: Uint32Array;\n pointIndices: Uint32Array;\n}\n\nexport function cellHash(cellX: number, cellY: number, mask: number): number {\n return (((cellX * 73856093) ^ (cellY * 19349663)) >>> 0) & mask;\n}\n\nfunction resolveGridSize(sourceWidth: number, sourceHeight: number, visibleCount: number): number {\n if (sourceWidth <= 0 || sourceHeight <= 0 || visibleCount <= 0) return 256;\n const area = Math.max(1, sourceWidth * sourceHeight);\n const avgSpacing = Math.sqrt(area / Math.max(1, visibleCount));\n const raw = avgSpacing * POINT_HIT_GRID_DENSITY_SCALE;\n return Math.max(MIN_POINT_HIT_GRID_SIZE, Math.min(MAX_POINT_HIT_GRID_SIZE, raw));\n}\n\nfunction sanitizeDrawIndices(raw: Uint32Array | null | undefined, safeCount: number): Uint32Array | null {\n if (!(raw instanceof Uint32Array) || raw.length === 0) {\n return null;\n }\n\n let allValid = true;\n for (let i = 0; i < raw.length; i += 1) {\n if (raw[i] < safeCount) continue;\n allValid = false;\n break;\n }\n if (allValid) {\n return raw;\n }\n\n const filtered = new Uint32Array(raw.length);\n let cursor = 0;\n for (let i = 0; i < raw.length; i += 1) {\n if (raw[i] >= safeCount) continue;\n filtered[cursor] = raw[i];\n cursor += 1;\n }\n return cursor > 0 ? filtered.subarray(0, cursor) : null;\n}\n\nexport function buildPointHitIndex(input: PointHitIndexBuildInput): PointHitIndexBuildResult | null {\n const count = Math.max(0, Math.floor(input.count));\n const maxCountByPositions = Math.floor(input.positions.length / 2);\n const safeCount = Math.max(0, Math.min(count, maxCountByPositions));\n if (safeCount <= 0) {\n return null;\n }\n\n const drawIndices = sanitizeDrawIndices(input.drawIndices ?? null, safeCount);\n const visibleCount = drawIndices ? drawIndices.length : safeCount;\n if (visibleCount === 0) {\n return null;\n }\n\n const cellSize = resolveGridSize(input.sourceWidth, input.sourceHeight, visibleCount);\n const invCellSize = 1.0 / cellSize;\n\n const pointCellX = new Int32Array(visibleCount);\n const pointCellY = new Int32Array(visibleCount);\n let validCount = 0;\n\n if (drawIndices) {\n for (let i = 0; i < visibleCount; i += 1) {\n const pi = drawIndices[i];\n const px = input.positions[pi * 2];\n const py = input.positions[pi * 2 + 1];\n if (!Number.isFinite(px) || !Number.isFinite(py)) continue;\n pointCellX[validCount] = Math.floor(px * invCellSize);\n pointCellY[validCount] = Math.floor(py * invCellSize);\n validCount += 1;\n }\n } else {\n for (let i = 0; i < safeCount; i += 1) {\n const px = input.positions[i * 2];\n const py = input.positions[i * 2 + 1];\n if (!Number.isFinite(px) || !Number.isFinite(py)) continue;\n pointCellX[validCount] = Math.floor(px * invCellSize);\n pointCellY[validCount] = Math.floor(py * invCellSize);\n validCount += 1;\n }\n }\n\n if (validCount === 0) {\n return null;\n }\n\n let estimatedCells = Math.min(validCount, Math.max(64, validCount >>> 3));\n if (!Number.isFinite(estimatedCells) || estimatedCells <= 0) {\n estimatedCells = validCount;\n }\n\n let hashCapacity = 1;\n while (hashCapacity < estimatedCells * 2) hashCapacity <<= 1;\n let hashMask = hashCapacity - 1;\n\n let tempHashKeys = new Int32Array(hashCapacity * 2);\n let tempHashCounts = new Int32Array(hashCapacity);\n tempHashKeys.fill(0x7fffffff);\n let cellCount = 0;\n\n const pointCellSlot = new Int32Array(validCount);\n\n for (let i = 0; i < validCount; i += 1) {\n const cx = pointCellX[i];\n const cy = pointCellY[i];\n let slot = cellHash(cx, cy, hashMask);\n\n while (true) {\n const kx = tempHashKeys[slot * 2];\n if (kx === 0x7fffffff) {\n tempHashKeys[slot * 2] = cx;\n tempHashKeys[slot * 2 + 1] = cy;\n tempHashCounts[slot] = 1;\n pointCellSlot[i] = slot;\n cellCount += 1;\n\n if (cellCount * 4 > hashCapacity * 3) {\n const oldCap = hashCapacity;\n hashCapacity <<= 1;\n hashMask = hashCapacity - 1;\n\n const newKeys = new Int32Array(hashCapacity * 2);\n const newCounts = new Int32Array(hashCapacity);\n newKeys.fill(0x7fffffff);\n\n for (let s = 0; s < oldCap; s += 1) {\n if (tempHashKeys[s * 2] === 0x7fffffff) continue;\n const ocx = tempHashKeys[s * 2];\n const ocy = tempHashKeys[s * 2 + 1];\n let ns = cellHash(ocx, ocy, hashMask);\n while (newKeys[ns * 2] !== 0x7fffffff) ns = (ns + 1) & hashMask;\n newKeys[ns * 2] = ocx;\n newKeys[ns * 2 + 1] = ocy;\n newCounts[ns] = tempHashCounts[s];\n }\n\n tempHashKeys = newKeys;\n tempHashCounts = newCounts;\n\n slot = cellHash(cx, cy, hashMask);\n while (\n tempHashKeys[slot * 2] !== cx ||\n tempHashKeys[slot * 2 + 1] !== cy\n ) {\n slot = (slot + 1) & hashMask;\n }\n pointCellSlot[i] = slot;\n }\n break;\n }\n\n if (kx === cx && tempHashKeys[slot * 2 + 1] === cy) {\n tempHashCounts[slot] += 1;\n pointCellSlot[i] = slot;\n break;\n }\n\n slot = (slot + 1) & hashMask;\n }\n }\n\n const cellKeys = new Int32Array(cellCount * 2);\n const cellOffsets = new Uint32Array(cellCount);\n const cellLengths = new Uint32Array(cellCount);\n const slotToCellIndex = new Int32Array(hashCapacity);\n slotToCellIndex.fill(HASH_EMPTY);\n\n let cellIdx = 0;\n let offset = 0;\n for (let s = 0; s < hashCapacity; s += 1) {\n if (tempHashKeys[s * 2] === 0x7fffffff) continue;\n cellKeys[cellIdx * 2] = tempHashKeys[s * 2];\n cellKeys[cellIdx * 2 + 1] = tempHashKeys[s * 2 + 1];\n cellOffsets[cellIdx] = offset;\n cellLengths[cellIdx] = tempHashCounts[s];\n slotToCellIndex[s] = cellIdx;\n offset += tempHashCounts[s];\n cellIdx += 1;\n }\n\n const pointIndices = new Uint32Array(validCount);\n const fillCursor = new Uint32Array(cellCount);\n fillCursor.set(cellOffsets);\n\n if (drawIndices) {\n for (let i = 0; i < validCount; i += 1) {\n const ci = slotToCellIndex[pointCellSlot[i]];\n pointIndices[fillCursor[ci]] = drawIndices[i];\n fillCursor[ci] += 1;\n }\n } else {\n let srcIdx = 0;\n for (let i = 0; i < safeCount; i += 1) {\n const px = input.positions[i * 2];\n const py = input.positions[i * 2 + 1];\n if (!Number.isFinite(px) || !Number.isFinite(py)) continue;\n const ci = slotToCellIndex[pointCellSlot[srcIdx]];\n pointIndices[fillCursor[ci]] = i;\n fillCursor[ci] += 1;\n srcIdx += 1;\n }\n }\n\n let finalCap = 1;\n while (finalCap < cellCount * 2) finalCap <<= 1;\n const finalMask = finalCap - 1;\n const hashTable = new Int32Array(finalCap);\n hashTable.fill(HASH_EMPTY);\n\n for (let i = 0; i < cellCount; i += 1) {\n const cx = cellKeys[i * 2];\n const cy = cellKeys[i * 2 + 1];\n let slot = cellHash(cx, cy, finalMask);\n while (hashTable[slot] !== HASH_EMPTY) slot = (slot + 1) & finalMask;\n hashTable[slot] = i;\n }\n\n return {\n cellSize,\n safeCount,\n cellCount,\n hashCapacity: finalCap,\n hashTable,\n cellKeys,\n cellOffsets,\n cellLengths,\n pointIndices,\n };\n}\n","import { DEFAULT_POINT_COLOR } from \"./constants\";\nimport type { ClassPalette, WsiPointData, WsiViewState } from \"./types\";\n\nexport function clamp(value: number, min: number, max: number): number {\n\treturn Math.max(min, Math.min(max, value));\n}\n\nexport function calcScaleResolution(\n\timageMpp: number,\n\timageZoom: number,\n\tcurrentZoom: number,\n): number {\n\tconst mpp = Number(imageMpp);\n\tconst z0 = Number(imageZoom);\n\tconst z1 = Number(currentZoom);\n\tif (!Number.isFinite(mpp) || mpp <= 0) return 1;\n\tif (!Number.isFinite(z0) || !Number.isFinite(z1)) return mpp;\n\treturn Math.pow(2, z0 - z1) * mpp;\n}\n\nexport function calcScaleLength(\n\timageMpp: number,\n\timageZoom: number,\n\tcurrentZoom: number,\n): string {\n\tconst resolution = calcScaleResolution(imageMpp, imageZoom, currentZoom);\n\tlet length = 100 * resolution;\n\tif (Number(imageMpp)) {\n\t\tlet unit = \"μm\";\n\t\tif (length > 1000) {\n\t\t\tlength /= 1000;\n\t\t\tunit = \"mm\";\n\t\t}\n\t\treturn `${length.toPrecision(3)} ${unit}`;\n\t}\n\treturn `${Math.round(length * 1000) / 1000} pixels`;\n}\n\nexport function nowMs(): number {\n\tif (typeof performance !== \"undefined\" && typeof performance.now === \"function\") {\n\t\treturn performance.now();\n\t}\n\treturn Date.now();\n}\n\nexport function sanitizePointCount(pointData: WsiPointData): number {\n\tconst fillModesLength =\n\t\tpointData.fillModes instanceof Uint8Array\n\t\t\t? pointData.fillModes.length\n\t\t\t: Number.MAX_SAFE_INTEGER;\n\treturn Math.max(\n\t\t0,\n\t\tMath.min(\n\t\t\tMath.floor(pointData.count ?? 0),\n\t\t\tMath.floor((pointData.positions?.length ?? 0) / 2),\n\t\t\tpointData.paletteIndices?.length ?? 0,\n\t\t\tfillModesLength,\n\t\t),\n\t);\n}\n\nexport function isSameViewState(\n\ta: Partial<WsiViewState> | null | undefined,\n\tb: Partial<WsiViewState> | null | undefined,\n): boolean {\n\tif (!a && !b) return true;\n\tif (!a || !b) return false;\n\treturn (\n\t\tMath.abs((a.zoom ?? 0) - (b.zoom ?? 0)) < 1e-6 &&\n\t\tMath.abs((a.offsetX ?? 0) - (b.offsetX ?? 0)) < 1e-6 &&\n\t\tMath.abs((a.offsetY ?? 0) - (b.offsetY ?? 0)) < 1e-6 &&\n\t\tMath.abs((a.rotationDeg ?? 0) - (b.rotationDeg ?? 0)) < 1e-6\n\t);\n}\n\nexport function toBearerToken(value: string | null | undefined): string {\n\tconst trimmed = String(value ?? \"\").trim();\n\tif (!trimmed) return \"\";\n\tif (/^bearer\\s+/i.test(trimmed)) {\n\t\tconst token = trimmed.replace(/^bearer\\s+/i, \"\").trim();\n\t\treturn token ? `Bearer ${token}` : \"\";\n\t}\n\treturn `Bearer ${trimmed}`;\n}\n\nexport function hexToRgba(\n\thex: string | null | undefined,\n): [number, number, number, number] {\n\tconst value = String(hex ?? \"\").trim();\n\tconst match = value.match(/^#?([0-9a-fA-F]{6})$/);\n\tif (!match) return [...DEFAULT_POINT_COLOR];\n\n\tconst n = Number.parseInt(match[1], 16);\n\treturn [(n >> 16) & 255, (n >> 8) & 255, n & 255, 255];\n}\n\nexport function buildClassPalette(\n\tclasses:\n\t\t| Array<{ classId?: string | null; classColor?: string | null }>\n\t\t| null\n\t\t| undefined,\n): ClassPalette {\n\tconst palette: Array<[number, number, number, number]> = [\n\t\t[...DEFAULT_POINT_COLOR],\n\t];\n\tconst classToPaletteIndex = new Map<string, number>();\n\n\tfor (const item of classes ?? []) {\n\t\tconst classId = String(item?.classId ?? \"\");\n\t\tif (!classId || classToPaletteIndex.has(classId)) continue;\n\n\t\tclassToPaletteIndex.set(classId, palette.length);\n\t\tpalette.push(hexToRgba(item?.classColor));\n\t}\n\n\tconst colors = new Uint8Array(palette.length * 4);\n\tfor (let i = 0; i < palette.length; i += 1) {\n\t\tcolors[i * 4] = palette[i][0];\n\t\tcolors[i * 4 + 1] = palette[i][1];\n\t\tcolors[i * 4 + 2] = palette[i][2];\n\t\tcolors[i * 4 + 3] = palette[i][3];\n\t}\n\n\treturn { colors, classToPaletteIndex };\n}\n","import { buildPointHitIndex } from \"../wsi/point-hit-index-shared\";\nimport type {\n PointHitIndexWorkerRequest,\n PointHitIndexWorkerResponse,\n PointHitIndexWorkerSuccess,\n} from \"../wsi/point-hit-index-worker-protocol\";\nimport { nowMs } from \"../wsi/utils\";\n\nfunction toErrorMessage(error: unknown): string {\n if (error instanceof Error) return error.message;\n try {\n return String(error);\n } catch {\n return \"unknown worker error\";\n }\n}\n\nfunction handleRequest(msg: PointHitIndexWorkerRequest): PointHitIndexWorkerSuccess | null {\n const start = nowMs();\n const result = buildPointHitIndex({\n count: msg.count,\n positions: new Float32Array(msg.positions),\n drawIndices: msg.drawIndices ? new Uint32Array(msg.drawIndices) : null,\n sourceWidth: msg.sourceWidth,\n sourceHeight: msg.sourceHeight,\n });\n\n if (!result) {\n return null;\n }\n\n return {\n type: \"point-hit-index-success\",\n id: msg.id,\n cellSize: result.cellSize,\n safeCount: result.safeCount,\n cellCount: result.cellCount,\n hashCapacity: result.hashCapacity,\n hashTable: result.hashTable.buffer as ArrayBuffer,\n cellKeys: result.cellKeys.buffer as ArrayBuffer,\n cellOffsets: result.cellOffsets.buffer as ArrayBuffer,\n cellLengths: result.cellLengths.buffer as ArrayBuffer,\n pointIndices: result.pointIndices.buffer as ArrayBuffer,\n durationMs: nowMs() - start,\n };\n}\n\ninterface WorkerScope {\n postMessage(message: unknown, transfer?: Transferable[]): void;\n addEventListener(type: \"message\", listener: (event: MessageEvent<PointHitIndexWorkerRequest>) => void): void;\n}\n\nconst workerScope = self as unknown as WorkerScope;\n\nworkerScope.addEventListener(\"message\", (event: MessageEvent<PointHitIndexWorkerRequest>) => {\n const data = event.data;\n if (!data || data.type !== \"point-hit-index-request\") return;\n\n try {\n const result = handleRequest(data);\n if (!result) {\n const empty: PointHitIndexWorkerSuccess = {\n type: \"point-hit-index-success\",\n id: data.id,\n cellSize: 0,\n safeCount: 0,\n cellCount: 0,\n hashCapacity: 0,\n hashTable: new Int32Array(0).buffer,\n cellKeys: new Int32Array(0).buffer,\n cellOffsets: new Uint32Array(0).buffer,\n cellLengths: new Uint32Array(0).buffer,\n pointIndices: new Uint32Array(0).buffer,\n durationMs: 0,\n };\n workerScope.postMessage(empty, [\n empty.hashTable,\n empty.cellKeys,\n empty.cellOffsets,\n empty.cellLengths,\n empty.pointIndices,\n ]);\n return;\n }\n\n workerScope.postMessage(result, [\n result.hashTable,\n result.cellKeys,\n result.cellOffsets,\n result.cellLengths,\n result.pointIndices,\n ]);\n } catch (error) {\n const fail: PointHitIndexWorkerResponse = {\n type: \"point-hit-index-failure\",\n id: data.id,\n error: toErrorMessage(error),\n };\n workerScope.postMessage(fail);\n }\n});\n"],"names":["cellHash","cellX","cellY","mask","resolveGridSize","sourceWidth","sourceHeight","visibleCount","area","raw","sanitizeDrawIndices","safeCount","allValid","i","filtered","cursor","buildPointHitIndex","input","count","maxCountByPositions","drawIndices","cellSize","invCellSize","pointCellX","pointCellY","validCount","pi","px","py","estimatedCells","hashCapacity","hashMask","tempHashKeys","tempHashCounts","cellCount","pointCellSlot","cx","cy","slot","kx","oldCap","newKeys","newCounts","s","ocx","ocy","ns","cellKeys","cellOffsets","cellLengths","slotToCellIndex","cellIdx","offset","pointIndices","fillCursor","ci","srcIdx","finalCap","finalMask","hashTable","nowMs","toErrorMessage","error","handleRequest","msg","start","result","workerScope","event","data","empty","fail"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"point-hit-index-worker-CNFA6pZm.js","sources":["../src/wsi/point-hit-index-shared.ts","../src/wsi/utils.ts","../src/workers/point-hit-index-worker.ts"],"sourcesContent":["export const MIN_POINT_HIT_GRID_SIZE = 24;\nexport const MAX_POINT_HIT_GRID_SIZE = 1024;\nexport const POINT_HIT_GRID_DENSITY_SCALE = 4;\nexport const HASH_EMPTY = -1;\n\nexport interface PointHitIndexBuildInput {\n count: number;\n positions: Float32Array;\n drawIndices?: Uint32Array | null;\n sourceWidth: number;\n sourceHeight: number;\n}\n\nexport interface PointHitIndexBuildResult {\n cellSize: number;\n safeCount: number;\n cellCount: number;\n hashCapacity: number;\n hashTable: Int32Array;\n cellKeys: Int32Array;\n cellOffsets: Uint32Array;\n cellLengths: Uint32Array;\n pointIndices: Uint32Array;\n}\n\nexport function cellHash(cellX: number, cellY: number, mask: number): number {\n return (((cellX * 73856093) ^ (cellY * 19349663)) >>> 0) & mask;\n}\n\nfunction resolveGridSize(sourceWidth: number, sourceHeight: number, visibleCount: number): number {\n if (sourceWidth <= 0 || sourceHeight <= 0 || visibleCount <= 0) return 256;\n const area = Math.max(1, sourceWidth * sourceHeight);\n const avgSpacing = Math.sqrt(area / Math.max(1, visibleCount));\n const raw = avgSpacing * POINT_HIT_GRID_DENSITY_SCALE;\n return Math.max(MIN_POINT_HIT_GRID_SIZE, Math.min(MAX_POINT_HIT_GRID_SIZE, raw));\n}\n\nfunction sanitizeDrawIndices(raw: Uint32Array | null | undefined, safeCount: number): Uint32Array | null {\n if (!(raw instanceof Uint32Array) || raw.length === 0) {\n return null;\n }\n\n let allValid = true;\n for (let i = 0; i < raw.length; i += 1) {\n if (raw[i] < safeCount) continue;\n allValid = false;\n break;\n }\n if (allValid) {\n return raw;\n }\n\n const filtered = new Uint32Array(raw.length);\n let cursor = 0;\n for (let i = 0; i < raw.length; i += 1) {\n if (raw[i] >= safeCount) continue;\n filtered[cursor] = raw[i];\n cursor += 1;\n }\n return cursor > 0 ? filtered.subarray(0, cursor) : null;\n}\n\nexport function buildPointHitIndex(input: PointHitIndexBuildInput): PointHitIndexBuildResult | null {\n const count = Math.max(0, Math.floor(input.count));\n const maxCountByPositions = Math.floor(input.positions.length / 2);\n const safeCount = Math.max(0, Math.min(count, maxCountByPositions));\n if (safeCount <= 0) {\n return null;\n }\n\n const drawIndices = sanitizeDrawIndices(input.drawIndices ?? null, safeCount);\n const visibleCount = drawIndices ? drawIndices.length : safeCount;\n if (visibleCount === 0) {\n return null;\n }\n\n const cellSize = resolveGridSize(input.sourceWidth, input.sourceHeight, visibleCount);\n const invCellSize = 1.0 / cellSize;\n\n const pointCellX = new Int32Array(visibleCount);\n const pointCellY = new Int32Array(visibleCount);\n let validCount = 0;\n\n if (drawIndices) {\n for (let i = 0; i < visibleCount; i += 1) {\n const pi = drawIndices[i];\n const px = input.positions[pi * 2];\n const py = input.positions[pi * 2 + 1];\n if (!Number.isFinite(px) || !Number.isFinite(py)) continue;\n pointCellX[validCount] = Math.floor(px * invCellSize);\n pointCellY[validCount] = Math.floor(py * invCellSize);\n validCount += 1;\n }\n } else {\n for (let i = 0; i < safeCount; i += 1) {\n const px = input.positions[i * 2];\n const py = input.positions[i * 2 + 1];\n if (!Number.isFinite(px) || !Number.isFinite(py)) continue;\n pointCellX[validCount] = Math.floor(px * invCellSize);\n pointCellY[validCount] = Math.floor(py * invCellSize);\n validCount += 1;\n }\n }\n\n if (validCount === 0) {\n return null;\n }\n\n let estimatedCells = Math.min(validCount, Math.max(64, validCount >>> 3));\n if (!Number.isFinite(estimatedCells) || estimatedCells <= 0) {\n estimatedCells = validCount;\n }\n\n let hashCapacity = 1;\n while (hashCapacity < estimatedCells * 2) hashCapacity <<= 1;\n let hashMask = hashCapacity - 1;\n\n let tempHashKeys = new Int32Array(hashCapacity * 2);\n let tempHashCounts = new Int32Array(hashCapacity);\n tempHashKeys.fill(0x7fffffff);\n let cellCount = 0;\n\n const pointCellSlot = new Int32Array(validCount);\n\n for (let i = 0; i < validCount; i += 1) {\n const cx = pointCellX[i];\n const cy = pointCellY[i];\n let slot = cellHash(cx, cy, hashMask);\n\n while (true) {\n const kx = tempHashKeys[slot * 2];\n if (kx === 0x7fffffff) {\n tempHashKeys[slot * 2] = cx;\n tempHashKeys[slot * 2 + 1] = cy;\n tempHashCounts[slot] = 1;\n pointCellSlot[i] = slot;\n cellCount += 1;\n\n if (cellCount * 4 > hashCapacity * 3) {\n const oldCap = hashCapacity;\n hashCapacity <<= 1;\n hashMask = hashCapacity - 1;\n\n const newKeys = new Int32Array(hashCapacity * 2);\n const newCounts = new Int32Array(hashCapacity);\n newKeys.fill(0x7fffffff);\n\n for (let s = 0; s < oldCap; s += 1) {\n if (tempHashKeys[s * 2] === 0x7fffffff) continue;\n const ocx = tempHashKeys[s * 2];\n const ocy = tempHashKeys[s * 2 + 1];\n let ns = cellHash(ocx, ocy, hashMask);\n while (newKeys[ns * 2] !== 0x7fffffff) ns = (ns + 1) & hashMask;\n newKeys[ns * 2] = ocx;\n newKeys[ns * 2 + 1] = ocy;\n newCounts[ns] = tempHashCounts[s];\n }\n\n tempHashKeys = newKeys;\n tempHashCounts = newCounts;\n\n slot = cellHash(cx, cy, hashMask);\n while (\n tempHashKeys[slot * 2] !== cx ||\n tempHashKeys[slot * 2 + 1] !== cy\n ) {\n slot = (slot + 1) & hashMask;\n }\n pointCellSlot[i] = slot;\n }\n break;\n }\n\n if (kx === cx && tempHashKeys[slot * 2 + 1] === cy) {\n tempHashCounts[slot] += 1;\n pointCellSlot[i] = slot;\n break;\n }\n\n slot = (slot + 1) & hashMask;\n }\n }\n\n const cellKeys = new Int32Array(cellCount * 2);\n const cellOffsets = new Uint32Array(cellCount);\n const cellLengths = new Uint32Array(cellCount);\n const slotToCellIndex = new Int32Array(hashCapacity);\n slotToCellIndex.fill(HASH_EMPTY);\n\n let cellIdx = 0;\n let offset = 0;\n for (let s = 0; s < hashCapacity; s += 1) {\n if (tempHashKeys[s * 2] === 0x7fffffff) continue;\n cellKeys[cellIdx * 2] = tempHashKeys[s * 2];\n cellKeys[cellIdx * 2 + 1] = tempHashKeys[s * 2 + 1];\n cellOffsets[cellIdx] = offset;\n cellLengths[cellIdx] = tempHashCounts[s];\n slotToCellIndex[s] = cellIdx;\n offset += tempHashCounts[s];\n cellIdx += 1;\n }\n\n const pointIndices = new Uint32Array(validCount);\n const fillCursor = new Uint32Array(cellCount);\n fillCursor.set(cellOffsets);\n\n if (drawIndices) {\n for (let i = 0; i < validCount; i += 1) {\n const ci = slotToCellIndex[pointCellSlot[i]];\n pointIndices[fillCursor[ci]] = drawIndices[i];\n fillCursor[ci] += 1;\n }\n } else {\n let srcIdx = 0;\n for (let i = 0; i < safeCount; i += 1) {\n const px = input.positions[i * 2];\n const py = input.positions[i * 2 + 1];\n if (!Number.isFinite(px) || !Number.isFinite(py)) continue;\n const ci = slotToCellIndex[pointCellSlot[srcIdx]];\n pointIndices[fillCursor[ci]] = i;\n fillCursor[ci] += 1;\n srcIdx += 1;\n }\n }\n\n let finalCap = 1;\n while (finalCap < cellCount * 2) finalCap <<= 1;\n const finalMask = finalCap - 1;\n const hashTable = new Int32Array(finalCap);\n hashTable.fill(HASH_EMPTY);\n\n for (let i = 0; i < cellCount; i += 1) {\n const cx = cellKeys[i * 2];\n const cy = cellKeys[i * 2 + 1];\n let slot = cellHash(cx, cy, finalMask);\n while (hashTable[slot] !== HASH_EMPTY) slot = (slot + 1) & finalMask;\n hashTable[slot] = i;\n }\n\n return {\n cellSize,\n safeCount,\n cellCount,\n hashCapacity: finalCap,\n hashTable,\n cellKeys,\n cellOffsets,\n cellLengths,\n pointIndices,\n };\n}\n","import { DEFAULT_POINT_COLOR } from \"./constants\";\nimport type { ClassPalette, WsiPointData, WsiViewState } from \"./types\";\n\nexport function clamp(value: number, min: number, max: number): number {\n\treturn Math.max(min, Math.min(max, value));\n}\n\nexport function calcScaleResolution(\n\timageMpp: number,\n\timageZoom: number,\n\tcurrentZoom: number,\n): number {\n\tconst mpp = Number(imageMpp);\n\tconst z0 = Number(imageZoom);\n\tconst z1 = Number(currentZoom);\n\tif (!Number.isFinite(mpp) || mpp <= 0) return 1;\n\tif (!Number.isFinite(z0) || !Number.isFinite(z1)) return mpp;\n\treturn Math.pow(2, z0 - z1) * mpp;\n}\n\nexport function calcScaleLength(\n\timageMpp: number,\n\timageZoom: number,\n\tcurrentZoom: number,\n): string {\n\tconst resolution = calcScaleResolution(imageMpp, imageZoom, currentZoom);\n\tlet length = 100 * resolution;\n\tif (Number(imageMpp)) {\n\t\tlet unit = \"μm\";\n\t\tif (length > 1000) {\n\t\t\tlength /= 1000;\n\t\t\tunit = \"mm\";\n\t\t}\n\t\treturn `${length.toPrecision(3)} ${unit}`;\n\t}\n\treturn `${Math.round(length * 1000) / 1000} pixels`;\n}\n\nexport function nowMs(): number {\n\tif (typeof performance !== \"undefined\" && typeof performance.now === \"function\") {\n\t\treturn performance.now();\n\t}\n\treturn Date.now();\n}\n\nexport function sanitizePointCount(pointData: WsiPointData): number {\n\tconst fillModesLength =\n\t\tpointData.fillModes instanceof Uint8Array\n\t\t\t? pointData.fillModes.length\n\t\t\t: Number.MAX_SAFE_INTEGER;\n\treturn Math.max(\n\t\t0,\n\t\tMath.min(\n\t\t\tMath.floor(pointData.count ?? 0),\n\t\t\tMath.floor((pointData.positions?.length ?? 0) / 2),\n\t\t\tpointData.paletteIndices?.length ?? 0,\n\t\t\tfillModesLength,\n\t\t),\n\t);\n}\n\nexport function isSameViewState(\n\ta: Partial<WsiViewState> | null | undefined,\n\tb: Partial<WsiViewState> | null | undefined,\n): boolean {\n\tif (!a && !b) return true;\n\tif (!a || !b) return false;\n\treturn (\n\t\tMath.abs((a.zoom ?? 0) - (b.zoom ?? 0)) < 1e-6 &&\n\t\tMath.abs((a.offsetX ?? 0) - (b.offsetX ?? 0)) < 1e-6 &&\n\t\tMath.abs((a.offsetY ?? 0) - (b.offsetY ?? 0)) < 1e-6 &&\n\t\tMath.abs((a.rotationDeg ?? 0) - (b.rotationDeg ?? 0)) < 1e-6\n\t);\n}\n\nexport function toBearerToken(value: string | null | undefined): string {\n\tconst trimmed = String(value ?? \"\").trim();\n\tif (!trimmed) return \"\";\n\tif (/^bearer\\s+/i.test(trimmed)) {\n\t\tconst token = trimmed.replace(/^bearer\\s+/i, \"\").trim();\n\t\treturn token ? `Bearer ${token}` : \"\";\n\t}\n\treturn `Bearer ${trimmed}`;\n}\n\nexport function hexToRgba(\n\thex: string | null | undefined,\n): [number, number, number, number] {\n\tconst value = String(hex ?? \"\").trim();\n\tconst match = value.match(/^#?([0-9a-fA-F]{6})$/);\n\tif (!match) return [...DEFAULT_POINT_COLOR];\n\n\tconst n = Number.parseInt(match[1], 16);\n\treturn [(n >> 16) & 255, (n >> 8) & 255, n & 255, 255];\n}\n\nfunction resolvePaletteClassKey(\n\titem:\n\t\t| { classId?: string | null; className?: string | null }\n\t\t| null\n\t\t| undefined,\n): string {\n\tconst classId = String(item?.classId ?? \"\").trim();\n\tif (classId) return classId;\n\treturn String(item?.className ?? \"\").trim();\n}\n\nexport function buildClassPalette(\n\tclasses:\n\t\t| Array<{ classId?: string | null; className?: string | null; classColor?: string | null }>\n\t\t| null\n\t\t| undefined,\n): ClassPalette {\n\tconst palette: Array<[number, number, number, number]> = [\n\t\t[...DEFAULT_POINT_COLOR],\n\t];\n\tconst classToPaletteIndex = new Map<string, number>();\n\n\tfor (const item of classes ?? []) {\n\t\tconst classKey = resolvePaletteClassKey(item);\n\t\tif (!classKey || classToPaletteIndex.has(classKey)) continue;\n\n\t\tclassToPaletteIndex.set(classKey, palette.length);\n\t\tpalette.push(hexToRgba(item?.classColor));\n\t}\n\n\tconst colors = new Uint8Array(palette.length * 4);\n\tfor (let i = 0; i < palette.length; i += 1) {\n\t\tcolors[i * 4] = palette[i][0];\n\t\tcolors[i * 4 + 1] = palette[i][1];\n\t\tcolors[i * 4 + 2] = palette[i][2];\n\t\tcolors[i * 4 + 3] = palette[i][3];\n\t}\n\n\treturn { colors, classToPaletteIndex };\n}\n","import { buildPointHitIndex } from \"../wsi/point-hit-index-shared\";\nimport type {\n PointHitIndexWorkerRequest,\n PointHitIndexWorkerResponse,\n PointHitIndexWorkerSuccess,\n} from \"../wsi/point-hit-index-worker-protocol\";\nimport { nowMs } from \"../wsi/utils\";\n\nfunction toErrorMessage(error: unknown): string {\n if (error instanceof Error) return error.message;\n try {\n return String(error);\n } catch {\n return \"unknown worker error\";\n }\n}\n\nfunction handleRequest(msg: PointHitIndexWorkerRequest): PointHitIndexWorkerSuccess | null {\n const start = nowMs();\n const result = buildPointHitIndex({\n count: msg.count,\n positions: new Float32Array(msg.positions),\n drawIndices: msg.drawIndices ? new Uint32Array(msg.drawIndices) : null,\n sourceWidth: msg.sourceWidth,\n sourceHeight: msg.sourceHeight,\n });\n\n if (!result) {\n return null;\n }\n\n return {\n type: \"point-hit-index-success\",\n id: msg.id,\n cellSize: result.cellSize,\n safeCount: result.safeCount,\n cellCount: result.cellCount,\n hashCapacity: result.hashCapacity,\n hashTable: result.hashTable.buffer as ArrayBuffer,\n cellKeys: result.cellKeys.buffer as ArrayBuffer,\n cellOffsets: result.cellOffsets.buffer as ArrayBuffer,\n cellLengths: result.cellLengths.buffer as ArrayBuffer,\n pointIndices: result.pointIndices.buffer as ArrayBuffer,\n durationMs: nowMs() - start,\n };\n}\n\ninterface WorkerScope {\n postMessage(message: unknown, transfer?: Transferable[]): void;\n addEventListener(type: \"message\", listener: (event: MessageEvent<PointHitIndexWorkerRequest>) => void): void;\n}\n\nconst workerScope = self as unknown as WorkerScope;\n\nworkerScope.addEventListener(\"message\", (event: MessageEvent<PointHitIndexWorkerRequest>) => {\n const data = event.data;\n if (!data || data.type !== \"point-hit-index-request\") return;\n\n try {\n const result = handleRequest(data);\n if (!result) {\n const empty: PointHitIndexWorkerSuccess = {\n type: \"point-hit-index-success\",\n id: data.id,\n cellSize: 0,\n safeCount: 0,\n cellCount: 0,\n hashCapacity: 0,\n hashTable: new Int32Array(0).buffer,\n cellKeys: new Int32Array(0).buffer,\n cellOffsets: new Uint32Array(0).buffer,\n cellLengths: new Uint32Array(0).buffer,\n pointIndices: new Uint32Array(0).buffer,\n durationMs: 0,\n };\n workerScope.postMessage(empty, [\n empty.hashTable,\n empty.cellKeys,\n empty.cellOffsets,\n empty.cellLengths,\n empty.pointIndices,\n ]);\n return;\n }\n\n workerScope.postMessage(result, [\n result.hashTable,\n result.cellKeys,\n result.cellOffsets,\n result.cellLengths,\n result.pointIndices,\n ]);\n } catch (error) {\n const fail: PointHitIndexWorkerResponse = {\n type: \"point-hit-index-failure\",\n id: data.id,\n error: toErrorMessage(error),\n };\n workerScope.postMessage(fail);\n }\n});\n"],"names":["cellHash","cellX","cellY","mask","resolveGridSize","sourceWidth","sourceHeight","visibleCount","area","raw","sanitizeDrawIndices","safeCount","allValid","i","filtered","cursor","buildPointHitIndex","input","count","maxCountByPositions","drawIndices","cellSize","invCellSize","pointCellX","pointCellY","validCount","pi","px","py","estimatedCells","hashCapacity","hashMask","tempHashKeys","tempHashCounts","cellCount","pointCellSlot","cx","cy","slot","kx","oldCap","newKeys","newCounts","s","ocx","ocy","ns","cellKeys","cellOffsets","cellLengths","slotToCellIndex","cellIdx","offset","pointIndices","fillCursor","ci","srcIdx","finalCap","finalMask","hashTable","nowMs","toErrorMessage","error","handleRequest","msg","start","result","workerScope","event","data","empty","fail"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"roi-clip-worker-CHxxL_lK.js","sources":["../src/wsi/roi-geometry.ts","../src/wsi/utils.ts","../src/workers/roi-clip-worker.ts"],"sourcesContent":["import type { WsiRegionCoordinates } from \"./types\";\n\nexport type RoiCoordinate = [number, number];\nexport type RoiLinearRing = RoiCoordinate[];\nexport type RoiPolygonRings = RoiLinearRing[];\nexport type RoiMultiPolygon = RoiPolygonRings[];\nexport type RoiGeometry = RoiLinearRing | RoiPolygonRings | RoiMultiPolygon;\n\nexport function toRoiGeometry(coords: WsiRegionCoordinates | null | undefined): RoiGeometry | null | undefined;\nexport function toRoiGeometry(coords: unknown): RoiGeometry | null | undefined;\nexport function toRoiGeometry(coords: unknown): RoiGeometry | null | undefined {\n\tif (coords == null) return null;\n\tif (isLinearRing(coords) || isPolygonRings(coords) || isMultiPolygon(coords)) {\n\t\treturn coords;\n\t}\n\treturn null;\n}\n\nexport interface PreparedRoiPolygon {\n\touter: RoiLinearRing;\n\tholes: RoiLinearRing[];\n\tminX: number;\n\tminY: number;\n\tmaxX: number;\n\tmaxY: number;\n\tarea: number;\n}\n\nfunction isFiniteNumber(value: unknown): value is number {\n\treturn typeof value === \"number\" && Number.isFinite(value);\n}\n\nfunction isCoordinatePair(value: unknown): value is RoiCoordinate {\n\treturn (\n\t\tArray.isArray(value) &&\n\t\tvalue.length >= 2 &&\n\t\tisFiniteNumber(value[0]) &&\n\t\tisFiniteNumber(value[1])\n\t);\n}\n\nfunction isLinearRing(value: unknown): value is RoiLinearRing {\n\treturn Array.isArray(value) && value.length > 0 && value.every(point => isCoordinatePair(point));\n}\n\nfunction isPolygonRings(value: unknown): value is RoiPolygonRings {\n\treturn Array.isArray(value) && value.length > 0 && value.every(ring => isLinearRing(ring));\n}\n\nfunction isMultiPolygon(value: unknown): value is RoiMultiPolygon {\n\treturn Array.isArray(value) && value.length > 0 && value.every(polygon => isPolygonRings(polygon));\n}\n\nexport function closeRoiRing(coordinates: readonly RoiCoordinate[]): RoiLinearRing {\n\tif (!Array.isArray(coordinates) || coordinates.length < 3) return [];\n\tconst out: RoiLinearRing = [];\n\tfor (const point of coordinates) {\n\t\tif (!Array.isArray(point) || point.length < 2) continue;\n\t\tconst x = Number(point[0]);\n\t\tconst y = Number(point[1]);\n\t\tif (!Number.isFinite(x) || !Number.isFinite(y)) continue;\n\t\tconst prev = out[out.length - 1];\n\t\tif (prev && prev[0] === x && prev[1] === y) continue;\n\t\tout.push([x, y]);\n\t}\n\tif (out.length < 3) return [];\n\tconst first = out[0];\n\tconst last = out[out.length - 1];\n\tif (first[0] !== last[0] || first[1] !== last[1]) {\n\t\tout.push([first[0], first[1]]);\n\t}\n\treturn out.length >= 4 ? out : [];\n}\n\nexport function polygonSignedArea(ring: RoiLinearRing): number {\n\tif (!Array.isArray(ring) || ring.length < 4) return 0;\n\tlet sum = 0;\n\tfor (let i = 0; i < ring.length - 1; i += 1) {\n\t\tconst a = ring[i];\n\t\tconst b = ring[i + 1];\n\t\tsum += a[0] * b[1] - b[0] * a[1];\n\t}\n\treturn sum * 0.5;\n}\n\nfunction normalizePolygonRings(rings: RoiPolygonRings): RoiPolygonRings {\n\tif (!Array.isArray(rings) || rings.length === 0) return [];\n\tconst normalized: RoiLinearRing[] = [];\n\tfor (const ring of rings) {\n\t\tconst closed = closeRoiRing(ring);\n\t\tif (closed.length >= 4) normalized.push(closed);\n\t}\n\tif (normalized.length === 0) return [];\n\tif (normalized.length === 1) return [normalized[0]];\n\n\tlet outerIndex = 0;\n\tlet outerArea = 0;\n\tfor (let i = 0; i < normalized.length; i += 1) {\n\t\tconst area = Math.abs(polygonSignedArea(normalized[i]));\n\t\tif (area <= outerArea) continue;\n\t\touterArea = area;\n\t\touterIndex = i;\n\t}\n\n\tconst out: RoiPolygonRings = [normalized[outerIndex]];\n\tfor (let i = 0; i < normalized.length; i += 1) {\n\t\tif (i === outerIndex) continue;\n\t\tout.push(normalized[i]);\n\t}\n\treturn out;\n}\n\nexport function normalizeRoiGeometry(geometry: RoiGeometry | null | undefined): RoiMultiPolygon {\n\tif (!Array.isArray(geometry) || geometry.length === 0) return [];\n\n\tconst first = geometry[0];\n\tif (isCoordinatePair(first)) {\n\t\tconst polygon = normalizePolygonRings([geometry as RoiLinearRing]);\n\t\treturn polygon.length > 0 ? [polygon] : [];\n\t}\n\n\tif (!Array.isArray(first) || first.length === 0) return [];\n\tconst second = first[0];\n\tif (isCoordinatePair(second)) {\n\t\tconst polygon = normalizePolygonRings(geometry as RoiPolygonRings);\n\t\treturn polygon.length > 0 ? [polygon] : [];\n\t}\n\n\tif (!Array.isArray(second) || second.length === 0 || !isCoordinatePair(second[0])) {\n\t\treturn [];\n\t}\n\n\tconst out: RoiMultiPolygon = [];\n\tfor (const polygon of geometry as RoiMultiPolygon) {\n\t\tconst normalized = normalizePolygonRings(polygon);\n\t\tif (normalized.length > 0) out.push(normalized);\n\t}\n\treturn out;\n}\n\nexport function pointInRing(x: number, y: number, ring: RoiLinearRing): boolean {\n\tlet inside = false;\n\tfor (let i = 0, j = ring.length - 1; i < ring.length; j = i, i += 1) {\n\t\tconst xi = ring[i][0];\n\t\tconst yi = ring[i][1];\n\t\tconst xj = ring[j][0];\n\t\tconst yj = ring[j][1];\n\t\tif (yi === yj) continue;\n\t\tif ((yi > y) === (yj > y)) continue;\n\t\tconst intersect = x < ((xj - xi) * (y - yi)) / (yj - yi) + xi;\n\t\tif (intersect) inside = !inside;\n\t}\n\treturn inside;\n}\n\nexport function pointInPolygonWithHoles(\n\tx: number,\n\ty: number,\n\tpolygon: RoiPolygonRings,\n): boolean {\n\tif (!Array.isArray(polygon) || polygon.length === 0) return false;\n\tconst outer = polygon[0];\n\tif (!outer || outer.length < 4) return false;\n\tif (!pointInRing(x, y, outer)) return false;\n\tfor (let i = 1; i < polygon.length; i += 1) {\n\t\tconst hole = polygon[i];\n\t\tif (!hole || hole.length < 4) continue;\n\t\tif (pointInRing(x, y, hole)) return false;\n\t}\n\treturn true;\n}\n\nexport function prepareRoiPolygons(\n\tgeometries: readonly (RoiGeometry | null | undefined)[] | null | undefined,\n): PreparedRoiPolygon[] {\n\tconst prepared: PreparedRoiPolygon[] = [];\n\tfor (const geometry of geometries ?? []) {\n\t\tconst multipolygon = normalizeRoiGeometry(geometry);\n\t\tfor (const polygon of multipolygon) {\n\t\t\tconst outer = polygon[0];\n\t\t\tif (!outer || outer.length < 4) continue;\n\t\t\tlet minX = Infinity;\n\t\t\tlet minY = Infinity;\n\t\t\tlet maxX = -Infinity;\n\t\t\tlet maxY = -Infinity;\n\t\t\tfor (const [x, y] of outer) {\n\t\t\t\tif (x < minX) minX = x;\n\t\t\t\tif (x > maxX) maxX = x;\n\t\t\t\tif (y < minY) minY = y;\n\t\t\t\tif (y > maxY) maxY = y;\n\t\t\t}\n\t\t\tif (\n\t\t\t\t!Number.isFinite(minX) ||\n\t\t\t\t!Number.isFinite(minY) ||\n\t\t\t\t!Number.isFinite(maxX) ||\n\t\t\t\t!Number.isFinite(maxY)\n\t\t\t) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tlet area = Math.abs(polygonSignedArea(outer));\n\t\t\tfor (let i = 1; i < polygon.length; i += 1) {\n\t\t\t\tarea -= Math.abs(polygonSignedArea(polygon[i]));\n\t\t\t}\n\t\t\tprepared.push({\n\t\t\t\touter,\n\t\t\t\tholes: polygon.slice(1),\n\t\t\t\tminX,\n\t\t\t\tminY,\n\t\t\t\tmaxX,\n\t\t\t\tmaxY,\n\t\t\t\tarea: Math.max(1e-6, area),\n\t\t\t});\n\t\t}\n\t}\n\treturn prepared;\n}\n\nexport function pointInPreparedPolygon(\n\tx: number,\n\ty: number,\n\tpolygon: PreparedRoiPolygon,\n): boolean {\n\tif (x < polygon.minX || x > polygon.maxX || y < polygon.minY || y > polygon.maxY) {\n\t\treturn false;\n\t}\n\tif (!pointInRing(x, y, polygon.outer)) return false;\n\tfor (const hole of polygon.holes) {\n\t\tif (pointInRing(x, y, hole)) return false;\n\t}\n\treturn true;\n}\n\nexport function pointInAnyPreparedPolygon(\n\tx: number,\n\ty: number,\n\tpolygons: readonly PreparedRoiPolygon[],\n): boolean {\n\tfor (const polygon of polygons) {\n\t\tif (!pointInPreparedPolygon(x, y, polygon)) continue;\n\t\treturn true;\n\t}\n\treturn false;\n}\n","import { DEFAULT_POINT_COLOR } from \"./constants\";\nimport type { ClassPalette, WsiPointData, WsiViewState } from \"./types\";\n\nexport function clamp(value: number, min: number, max: number): number {\n\treturn Math.max(min, Math.min(max, value));\n}\n\nexport function calcScaleResolution(\n\timageMpp: number,\n\timageZoom: number,\n\tcurrentZoom: number,\n): number {\n\tconst mpp = Number(imageMpp);\n\tconst z0 = Number(imageZoom);\n\tconst z1 = Number(currentZoom);\n\tif (!Number.isFinite(mpp) || mpp <= 0) return 1;\n\tif (!Number.isFinite(z0) || !Number.isFinite(z1)) return mpp;\n\treturn Math.pow(2, z0 - z1) * mpp;\n}\n\nexport function calcScaleLength(\n\timageMpp: number,\n\timageZoom: number,\n\tcurrentZoom: number,\n): string {\n\tconst resolution = calcScaleResolution(imageMpp, imageZoom, currentZoom);\n\tlet length = 100 * resolution;\n\tif (Number(imageMpp)) {\n\t\tlet unit = \"μm\";\n\t\tif (length > 1000) {\n\t\t\tlength /= 1000;\n\t\t\tunit = \"mm\";\n\t\t}\n\t\treturn `${length.toPrecision(3)} ${unit}`;\n\t}\n\treturn `${Math.round(length * 1000) / 1000} pixels`;\n}\n\nexport function nowMs(): number {\n\tif (typeof performance !== \"undefined\" && typeof performance.now === \"function\") {\n\t\treturn performance.now();\n\t}\n\treturn Date.now();\n}\n\nexport function sanitizePointCount(pointData: WsiPointData): number {\n\tconst fillModesLength =\n\t\tpointData.fillModes instanceof Uint8Array\n\t\t\t? pointData.fillModes.length\n\t\t\t: Number.MAX_SAFE_INTEGER;\n\treturn Math.max(\n\t\t0,\n\t\tMath.min(\n\t\t\tMath.floor(pointData.count ?? 0),\n\t\t\tMath.floor((pointData.positions?.length ?? 0) / 2),\n\t\t\tpointData.paletteIndices?.length ?? 0,\n\t\t\tfillModesLength,\n\t\t),\n\t);\n}\n\nexport function isSameViewState(\n\ta: Partial<WsiViewState> | null | undefined,\n\tb: Partial<WsiViewState> | null | undefined,\n): boolean {\n\tif (!a && !b) return true;\n\tif (!a || !b) return false;\n\treturn (\n\t\tMath.abs((a.zoom ?? 0) - (b.zoom ?? 0)) < 1e-6 &&\n\t\tMath.abs((a.offsetX ?? 0) - (b.offsetX ?? 0)) < 1e-6 &&\n\t\tMath.abs((a.offsetY ?? 0) - (b.offsetY ?? 0)) < 1e-6 &&\n\t\tMath.abs((a.rotationDeg ?? 0) - (b.rotationDeg ?? 0)) < 1e-6\n\t);\n}\n\nexport function toBearerToken(value: string | null | undefined): string {\n\tconst trimmed = String(value ?? \"\").trim();\n\tif (!trimmed) return \"\";\n\tif (/^bearer\\s+/i.test(trimmed)) {\n\t\tconst token = trimmed.replace(/^bearer\\s+/i, \"\").trim();\n\t\treturn token ? `Bearer ${token}` : \"\";\n\t}\n\treturn `Bearer ${trimmed}`;\n}\n\nexport function hexToRgba(\n\thex: string | null | undefined,\n): [number, number, number, number] {\n\tconst value = String(hex ?? \"\").trim();\n\tconst match = value.match(/^#?([0-9a-fA-F]{6})$/);\n\tif (!match) return [...DEFAULT_POINT_COLOR];\n\n\tconst n = Number.parseInt(match[1], 16);\n\treturn [(n >> 16) & 255, (n >> 8) & 255, n & 255, 255];\n}\n\nexport function buildClassPalette(\n\tclasses:\n\t\t| Array<{ classId?: string | null; classColor?: string | null }>\n\t\t| null\n\t\t| undefined,\n): ClassPalette {\n\tconst palette: Array<[number, number, number, number]> = [\n\t\t[...DEFAULT_POINT_COLOR],\n\t];\n\tconst classToPaletteIndex = new Map<string, number>();\n\n\tfor (const item of classes ?? []) {\n\t\tconst classId = String(item?.classId ?? \"\");\n\t\tif (!classId || classToPaletteIndex.has(classId)) continue;\n\n\t\tclassToPaletteIndex.set(classId, palette.length);\n\t\tpalette.push(hexToRgba(item?.classColor));\n\t}\n\n\tconst colors = new Uint8Array(palette.length * 4);\n\tfor (let i = 0; i < palette.length; i += 1) {\n\t\tcolors[i * 4] = palette[i][0];\n\t\tcolors[i * 4 + 1] = palette[i][1];\n\t\tcolors[i * 4 + 2] = palette[i][2];\n\t\tcolors[i * 4 + 3] = palette[i][3];\n\t}\n\n\treturn { colors, classToPaletteIndex };\n}\n","import type {\n RoiClipWorkerDataRequest,\n RoiClipWorkerIndexRequest,\n RoiClipWorkerIndexSuccess,\n RoiClipWorkerRequest,\n RoiClipWorkerResponse,\n RoiClipWorkerSuccess,\n} from \"../wsi/point-clip-worker-protocol\";\nimport { pointInAnyPreparedPolygon, prepareRoiPolygons } from \"../wsi/roi-geometry\";\nimport { nowMs } from \"../wsi/utils\";\n\nfunction toErrorMessage(error: unknown): string {\n if (error instanceof Error) return error.message;\n try {\n return String(error);\n } catch {\n return \"unknown worker error\";\n }\n}\n\ninterface WorkerScope {\n postMessage(message: unknown, transfer?: Transferable[]): void;\n addEventListener(type: \"message\", listener: (event: MessageEvent<RoiClipWorkerRequest>) => void): void;\n}\n\nconst workerScope = self as unknown as WorkerScope;\n\nfunction handleDataRequest(msg: RoiClipWorkerDataRequest): RoiClipWorkerSuccess {\n const start = nowMs();\n const count = Math.max(0, Math.floor(msg.count));\n const positions = new Float32Array(msg.positions);\n const classes = new Uint16Array(msg.paletteIndices);\n const fillModes = msg.fillModes ? new Uint8Array(msg.fillModes) : null;\n const ids = msg.ids ? new Uint32Array(msg.ids) : null;\n\n const maxCountByPositions = Math.floor(positions.length / 2);\n const safeCount = Math.max(0, Math.min(count, maxCountByPositions, classes.length, fillModes ? fillModes.length : Number.MAX_SAFE_INTEGER));\n const hasFillModes = fillModes instanceof Uint8Array && fillModes.length >= safeCount;\n const hasIds = ids instanceof Uint32Array && ids.length >= safeCount;\n const prepared = prepareRoiPolygons(msg.polygons ?? []);\n\n if (safeCount === 0 || prepared.length === 0) {\n const empty: RoiClipWorkerSuccess = {\n type: \"roi-clip-success\",\n id: msg.id,\n count: 0,\n positions: new Float32Array(0).buffer,\n paletteIndices: new Uint16Array(0).buffer,\n durationMs: nowMs() - start,\n };\n if (hasFillModes) {\n empty.fillModes = new Uint8Array(0).buffer;\n }\n if (hasIds) {\n empty.ids = new Uint32Array(0).buffer;\n }\n return empty;\n }\n\n const nextPositions = new Float32Array(safeCount * 2);\n const nextClasses = new Uint16Array(safeCount);\n const nextFillModes = hasFillModes ? new Uint8Array(safeCount) : null;\n const nextIds = hasIds ? new Uint32Array(safeCount) : null;\n let cursor = 0;\n\n for (let i = 0; i < safeCount; i += 1) {\n const x = positions[i * 2];\n const y = positions[i * 2 + 1];\n if (!pointInAnyPreparedPolygon(x, y, prepared)) continue;\n nextPositions[cursor * 2] = x;\n nextPositions[cursor * 2 + 1] = y;\n nextClasses[cursor] = classes[i];\n if (nextFillModes) {\n nextFillModes[cursor] = fillModes![i];\n }\n if (nextIds) {\n nextIds[cursor] = ids![i];\n }\n cursor += 1;\n }\n\n const outPositions = nextPositions.slice(0, cursor * 2);\n const outClasses = nextClasses.slice(0, cursor);\n const outFillModes = nextFillModes ? nextFillModes.slice(0, cursor) : null;\n const outIds = nextIds ? nextIds.slice(0, cursor) : null;\n\n const success: RoiClipWorkerSuccess = {\n type: \"roi-clip-success\",\n id: msg.id,\n count: cursor,\n positions: outPositions.buffer,\n paletteIndices: outClasses.buffer,\n durationMs: nowMs() - start,\n };\n if (outFillModes) {\n success.fillModes = outFillModes.buffer;\n }\n if (outIds) {\n success.ids = outIds.buffer;\n }\n return success;\n}\n\nfunction handleIndexRequest(msg: RoiClipWorkerIndexRequest): RoiClipWorkerIndexSuccess {\n const start = nowMs();\n const count = Math.max(0, Math.floor(msg.count));\n const positions = new Float32Array(msg.positions);\n const maxCountByPositions = Math.floor(positions.length / 2);\n const safeCount = Math.max(0, Math.min(count, maxCountByPositions));\n const prepared = prepareRoiPolygons(msg.polygons ?? []);\n\n if (safeCount === 0 || prepared.length === 0) {\n return {\n type: \"roi-clip-index-success\",\n id: msg.id,\n count: 0,\n indices: new Uint32Array(0).buffer,\n durationMs: nowMs() - start,\n };\n }\n\n const out = new Uint32Array(safeCount);\n let cursor = 0;\n for (let i = 0; i < safeCount; i += 1) {\n const x = positions[i * 2];\n const y = positions[i * 2 + 1];\n if (!pointInAnyPreparedPolygon(x, y, prepared)) continue;\n out[cursor] = i;\n cursor += 1;\n }\n\n const outIndices = out.slice(0, cursor);\n return {\n type: \"roi-clip-index-success\",\n id: msg.id,\n count: cursor,\n indices: outIndices.buffer,\n durationMs: nowMs() - start,\n };\n}\n\nworkerScope.addEventListener(\"message\", (event: MessageEvent<RoiClipWorkerRequest>) => {\n const data = event.data;\n if (!data || (data.type !== \"roi-clip-request\" && data.type !== \"roi-clip-index-request\")) return;\n\n try {\n if (data.type === \"roi-clip-index-request\") {\n const response = handleIndexRequest(data);\n workerScope.postMessage(response, [response.indices]);\n return;\n }\n const response = handleDataRequest(data);\n const transfer: Transferable[] = [response.positions, response.paletteIndices];\n if (response.fillModes) {\n transfer.push(response.fillModes);\n }\n if (response.ids) {\n transfer.push(response.ids);\n }\n workerScope.postMessage(response, transfer);\n } catch (error) {\n const fail: RoiClipWorkerResponse = {\n type: \"roi-clip-failure\",\n id: data.id,\n error: toErrorMessage(error),\n };\n workerScope.postMessage(fail);\n }\n});\n"],"names":["isFiniteNumber","value","isCoordinatePair","closeRoiRing","coordinates","out","point","x","y","prev","first","last","polygonSignedArea","ring","sum","i","a","b","normalizePolygonRings","rings","normalized","closed","outerIndex","outerArea","area","normalizeRoiGeometry","geometry","polygon","second","pointInRing","inside","j","xi","yi","xj","yj","prepareRoiPolygons","geometries","prepared","multipolygon","outer","minX","minY","maxX","maxY","pointInPreparedPolygon","hole","pointInAnyPreparedPolygon","polygons","nowMs","toErrorMessage","error","workerScope","handleDataRequest","msg","start","count","positions","classes","fillModes","ids","maxCountByPositions","safeCount","hasFillModes","hasIds","empty","nextPositions","nextClasses","nextFillModes","nextIds","cursor","outPositions","outClasses","outFillModes","outIds","success","handleIndexRequest","outIndices","event","data","response","transfer","fail"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"roi-clip-worker-CHxxL_lK.js","sources":["../src/wsi/roi-geometry.ts","../src/wsi/utils.ts","../src/workers/roi-clip-worker.ts"],"sourcesContent":["import type { WsiRegionCoordinates } from \"./types\";\n\nexport type RoiCoordinate = [number, number];\nexport type RoiLinearRing = RoiCoordinate[];\nexport type RoiPolygonRings = RoiLinearRing[];\nexport type RoiMultiPolygon = RoiPolygonRings[];\nexport type RoiGeometry = RoiLinearRing | RoiPolygonRings | RoiMultiPolygon;\n\nexport function toRoiGeometry(coords: WsiRegionCoordinates | null | undefined): RoiGeometry | null | undefined;\nexport function toRoiGeometry(coords: unknown): RoiGeometry | null | undefined;\nexport function toRoiGeometry(coords: unknown): RoiGeometry | null | undefined {\n\tif (coords == null) return null;\n\tif (isLinearRing(coords) || isPolygonRings(coords) || isMultiPolygon(coords)) {\n\t\treturn coords;\n\t}\n\treturn null;\n}\n\nexport interface PreparedRoiPolygon {\n\touter: RoiLinearRing;\n\tholes: RoiLinearRing[];\n\tminX: number;\n\tminY: number;\n\tmaxX: number;\n\tmaxY: number;\n\tarea: number;\n}\n\nfunction isFiniteNumber(value: unknown): value is number {\n\treturn typeof value === \"number\" && Number.isFinite(value);\n}\n\nfunction isCoordinatePair(value: unknown): value is RoiCoordinate {\n\treturn (\n\t\tArray.isArray(value) &&\n\t\tvalue.length >= 2 &&\n\t\tisFiniteNumber(value[0]) &&\n\t\tisFiniteNumber(value[1])\n\t);\n}\n\nfunction isLinearRing(value: unknown): value is RoiLinearRing {\n\treturn Array.isArray(value) && value.length > 0 && value.every(point => isCoordinatePair(point));\n}\n\nfunction isPolygonRings(value: unknown): value is RoiPolygonRings {\n\treturn Array.isArray(value) && value.length > 0 && value.every(ring => isLinearRing(ring));\n}\n\nfunction isMultiPolygon(value: unknown): value is RoiMultiPolygon {\n\treturn Array.isArray(value) && value.length > 0 && value.every(polygon => isPolygonRings(polygon));\n}\n\nexport function closeRoiRing(coordinates: readonly RoiCoordinate[]): RoiLinearRing {\n\tif (!Array.isArray(coordinates) || coordinates.length < 3) return [];\n\tconst out: RoiLinearRing = [];\n\tfor (const point of coordinates) {\n\t\tif (!Array.isArray(point) || point.length < 2) continue;\n\t\tconst x = Number(point[0]);\n\t\tconst y = Number(point[1]);\n\t\tif (!Number.isFinite(x) || !Number.isFinite(y)) continue;\n\t\tconst prev = out[out.length - 1];\n\t\tif (prev && prev[0] === x && prev[1] === y) continue;\n\t\tout.push([x, y]);\n\t}\n\tif (out.length < 3) return [];\n\tconst first = out[0];\n\tconst last = out[out.length - 1];\n\tif (first[0] !== last[0] || first[1] !== last[1]) {\n\t\tout.push([first[0], first[1]]);\n\t}\n\treturn out.length >= 4 ? out : [];\n}\n\nexport function polygonSignedArea(ring: RoiLinearRing): number {\n\tif (!Array.isArray(ring) || ring.length < 4) return 0;\n\tlet sum = 0;\n\tfor (let i = 0; i < ring.length - 1; i += 1) {\n\t\tconst a = ring[i];\n\t\tconst b = ring[i + 1];\n\t\tsum += a[0] * b[1] - b[0] * a[1];\n\t}\n\treturn sum * 0.5;\n}\n\nfunction normalizePolygonRings(rings: RoiPolygonRings): RoiPolygonRings {\n\tif (!Array.isArray(rings) || rings.length === 0) return [];\n\tconst normalized: RoiLinearRing[] = [];\n\tfor (const ring of rings) {\n\t\tconst closed = closeRoiRing(ring);\n\t\tif (closed.length >= 4) normalized.push(closed);\n\t}\n\tif (normalized.length === 0) return [];\n\tif (normalized.length === 1) return [normalized[0]];\n\n\tlet outerIndex = 0;\n\tlet outerArea = 0;\n\tfor (let i = 0; i < normalized.length; i += 1) {\n\t\tconst area = Math.abs(polygonSignedArea(normalized[i]));\n\t\tif (area <= outerArea) continue;\n\t\touterArea = area;\n\t\touterIndex = i;\n\t}\n\n\tconst out: RoiPolygonRings = [normalized[outerIndex]];\n\tfor (let i = 0; i < normalized.length; i += 1) {\n\t\tif (i === outerIndex) continue;\n\t\tout.push(normalized[i]);\n\t}\n\treturn out;\n}\n\nexport function normalizeRoiGeometry(geometry: RoiGeometry | null | undefined): RoiMultiPolygon {\n\tif (!Array.isArray(geometry) || geometry.length === 0) return [];\n\n\tconst first = geometry[0];\n\tif (isCoordinatePair(first)) {\n\t\tconst polygon = normalizePolygonRings([geometry as RoiLinearRing]);\n\t\treturn polygon.length > 0 ? [polygon] : [];\n\t}\n\n\tif (!Array.isArray(first) || first.length === 0) return [];\n\tconst second = first[0];\n\tif (isCoordinatePair(second)) {\n\t\tconst polygon = normalizePolygonRings(geometry as RoiPolygonRings);\n\t\treturn polygon.length > 0 ? [polygon] : [];\n\t}\n\n\tif (!Array.isArray(second) || second.length === 0 || !isCoordinatePair(second[0])) {\n\t\treturn [];\n\t}\n\n\tconst out: RoiMultiPolygon = [];\n\tfor (const polygon of geometry as RoiMultiPolygon) {\n\t\tconst normalized = normalizePolygonRings(polygon);\n\t\tif (normalized.length > 0) out.push(normalized);\n\t}\n\treturn out;\n}\n\nexport function pointInRing(x: number, y: number, ring: RoiLinearRing): boolean {\n\tlet inside = false;\n\tfor (let i = 0, j = ring.length - 1; i < ring.length; j = i, i += 1) {\n\t\tconst xi = ring[i][0];\n\t\tconst yi = ring[i][1];\n\t\tconst xj = ring[j][0];\n\t\tconst yj = ring[j][1];\n\t\tif (yi === yj) continue;\n\t\tif ((yi > y) === (yj > y)) continue;\n\t\tconst intersect = x < ((xj - xi) * (y - yi)) / (yj - yi) + xi;\n\t\tif (intersect) inside = !inside;\n\t}\n\treturn inside;\n}\n\nexport function pointInPolygonWithHoles(\n\tx: number,\n\ty: number,\n\tpolygon: RoiPolygonRings,\n): boolean {\n\tif (!Array.isArray(polygon) || polygon.length === 0) return false;\n\tconst outer = polygon[0];\n\tif (!outer || outer.length < 4) return false;\n\tif (!pointInRing(x, y, outer)) return false;\n\tfor (let i = 1; i < polygon.length; i += 1) {\n\t\tconst hole = polygon[i];\n\t\tif (!hole || hole.length < 4) continue;\n\t\tif (pointInRing(x, y, hole)) return false;\n\t}\n\treturn true;\n}\n\nexport function prepareRoiPolygons(\n\tgeometries: readonly (RoiGeometry | null | undefined)[] | null | undefined,\n): PreparedRoiPolygon[] {\n\tconst prepared: PreparedRoiPolygon[] = [];\n\tfor (const geometry of geometries ?? []) {\n\t\tconst multipolygon = normalizeRoiGeometry(geometry);\n\t\tfor (const polygon of multipolygon) {\n\t\t\tconst outer = polygon[0];\n\t\t\tif (!outer || outer.length < 4) continue;\n\t\t\tlet minX = Infinity;\n\t\t\tlet minY = Infinity;\n\t\t\tlet maxX = -Infinity;\n\t\t\tlet maxY = -Infinity;\n\t\t\tfor (const [x, y] of outer) {\n\t\t\t\tif (x < minX) minX = x;\n\t\t\t\tif (x > maxX) maxX = x;\n\t\t\t\tif (y < minY) minY = y;\n\t\t\t\tif (y > maxY) maxY = y;\n\t\t\t}\n\t\t\tif (\n\t\t\t\t!Number.isFinite(minX) ||\n\t\t\t\t!Number.isFinite(minY) ||\n\t\t\t\t!Number.isFinite(maxX) ||\n\t\t\t\t!Number.isFinite(maxY)\n\t\t\t) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tlet area = Math.abs(polygonSignedArea(outer));\n\t\t\tfor (let i = 1; i < polygon.length; i += 1) {\n\t\t\t\tarea -= Math.abs(polygonSignedArea(polygon[i]));\n\t\t\t}\n\t\t\tprepared.push({\n\t\t\t\touter,\n\t\t\t\tholes: polygon.slice(1),\n\t\t\t\tminX,\n\t\t\t\tminY,\n\t\t\t\tmaxX,\n\t\t\t\tmaxY,\n\t\t\t\tarea: Math.max(1e-6, area),\n\t\t\t});\n\t\t}\n\t}\n\treturn prepared;\n}\n\nexport function pointInPreparedPolygon(\n\tx: number,\n\ty: number,\n\tpolygon: PreparedRoiPolygon,\n): boolean {\n\tif (x < polygon.minX || x > polygon.maxX || y < polygon.minY || y > polygon.maxY) {\n\t\treturn false;\n\t}\n\tif (!pointInRing(x, y, polygon.outer)) return false;\n\tfor (const hole of polygon.holes) {\n\t\tif (pointInRing(x, y, hole)) return false;\n\t}\n\treturn true;\n}\n\nexport function pointInAnyPreparedPolygon(\n\tx: number,\n\ty: number,\n\tpolygons: readonly PreparedRoiPolygon[],\n): boolean {\n\tfor (const polygon of polygons) {\n\t\tif (!pointInPreparedPolygon(x, y, polygon)) continue;\n\t\treturn true;\n\t}\n\treturn false;\n}\n","import { DEFAULT_POINT_COLOR } from \"./constants\";\nimport type { ClassPalette, WsiPointData, WsiViewState } from \"./types\";\n\nexport function clamp(value: number, min: number, max: number): number {\n\treturn Math.max(min, Math.min(max, value));\n}\n\nexport function calcScaleResolution(\n\timageMpp: number,\n\timageZoom: number,\n\tcurrentZoom: number,\n): number {\n\tconst mpp = Number(imageMpp);\n\tconst z0 = Number(imageZoom);\n\tconst z1 = Number(currentZoom);\n\tif (!Number.isFinite(mpp) || mpp <= 0) return 1;\n\tif (!Number.isFinite(z0) || !Number.isFinite(z1)) return mpp;\n\treturn Math.pow(2, z0 - z1) * mpp;\n}\n\nexport function calcScaleLength(\n\timageMpp: number,\n\timageZoom: number,\n\tcurrentZoom: number,\n): string {\n\tconst resolution = calcScaleResolution(imageMpp, imageZoom, currentZoom);\n\tlet length = 100 * resolution;\n\tif (Number(imageMpp)) {\n\t\tlet unit = \"μm\";\n\t\tif (length > 1000) {\n\t\t\tlength /= 1000;\n\t\t\tunit = \"mm\";\n\t\t}\n\t\treturn `${length.toPrecision(3)} ${unit}`;\n\t}\n\treturn `${Math.round(length * 1000) / 1000} pixels`;\n}\n\nexport function nowMs(): number {\n\tif (typeof performance !== \"undefined\" && typeof performance.now === \"function\") {\n\t\treturn performance.now();\n\t}\n\treturn Date.now();\n}\n\nexport function sanitizePointCount(pointData: WsiPointData): number {\n\tconst fillModesLength =\n\t\tpointData.fillModes instanceof Uint8Array\n\t\t\t? pointData.fillModes.length\n\t\t\t: Number.MAX_SAFE_INTEGER;\n\treturn Math.max(\n\t\t0,\n\t\tMath.min(\n\t\t\tMath.floor(pointData.count ?? 0),\n\t\t\tMath.floor((pointData.positions?.length ?? 0) / 2),\n\t\t\tpointData.paletteIndices?.length ?? 0,\n\t\t\tfillModesLength,\n\t\t),\n\t);\n}\n\nexport function isSameViewState(\n\ta: Partial<WsiViewState> | null | undefined,\n\tb: Partial<WsiViewState> | null | undefined,\n): boolean {\n\tif (!a && !b) return true;\n\tif (!a || !b) return false;\n\treturn (\n\t\tMath.abs((a.zoom ?? 0) - (b.zoom ?? 0)) < 1e-6 &&\n\t\tMath.abs((a.offsetX ?? 0) - (b.offsetX ?? 0)) < 1e-6 &&\n\t\tMath.abs((a.offsetY ?? 0) - (b.offsetY ?? 0)) < 1e-6 &&\n\t\tMath.abs((a.rotationDeg ?? 0) - (b.rotationDeg ?? 0)) < 1e-6\n\t);\n}\n\nexport function toBearerToken(value: string | null | undefined): string {\n\tconst trimmed = String(value ?? \"\").trim();\n\tif (!trimmed) return \"\";\n\tif (/^bearer\\s+/i.test(trimmed)) {\n\t\tconst token = trimmed.replace(/^bearer\\s+/i, \"\").trim();\n\t\treturn token ? `Bearer ${token}` : \"\";\n\t}\n\treturn `Bearer ${trimmed}`;\n}\n\nexport function hexToRgba(\n\thex: string | null | undefined,\n): [number, number, number, number] {\n\tconst value = String(hex ?? \"\").trim();\n\tconst match = value.match(/^#?([0-9a-fA-F]{6})$/);\n\tif (!match) return [...DEFAULT_POINT_COLOR];\n\n\tconst n = Number.parseInt(match[1], 16);\n\treturn [(n >> 16) & 255, (n >> 8) & 255, n & 255, 255];\n}\n\nfunction resolvePaletteClassKey(\n\titem:\n\t\t| { classId?: string | null; className?: string | null }\n\t\t| null\n\t\t| undefined,\n): string {\n\tconst classId = String(item?.classId ?? \"\").trim();\n\tif (classId) return classId;\n\treturn String(item?.className ?? \"\").trim();\n}\n\nexport function buildClassPalette(\n\tclasses:\n\t\t| Array<{ classId?: string | null; className?: string | null; classColor?: string | null }>\n\t\t| null\n\t\t| undefined,\n): ClassPalette {\n\tconst palette: Array<[number, number, number, number]> = [\n\t\t[...DEFAULT_POINT_COLOR],\n\t];\n\tconst classToPaletteIndex = new Map<string, number>();\n\n\tfor (const item of classes ?? []) {\n\t\tconst classKey = resolvePaletteClassKey(item);\n\t\tif (!classKey || classToPaletteIndex.has(classKey)) continue;\n\n\t\tclassToPaletteIndex.set(classKey, palette.length);\n\t\tpalette.push(hexToRgba(item?.classColor));\n\t}\n\n\tconst colors = new Uint8Array(palette.length * 4);\n\tfor (let i = 0; i < palette.length; i += 1) {\n\t\tcolors[i * 4] = palette[i][0];\n\t\tcolors[i * 4 + 1] = palette[i][1];\n\t\tcolors[i * 4 + 2] = palette[i][2];\n\t\tcolors[i * 4 + 3] = palette[i][3];\n\t}\n\n\treturn { colors, classToPaletteIndex };\n}\n","import type {\n RoiClipWorkerDataRequest,\n RoiClipWorkerIndexRequest,\n RoiClipWorkerIndexSuccess,\n RoiClipWorkerRequest,\n RoiClipWorkerResponse,\n RoiClipWorkerSuccess,\n} from \"../wsi/point-clip-worker-protocol\";\nimport { pointInAnyPreparedPolygon, prepareRoiPolygons } from \"../wsi/roi-geometry\";\nimport { nowMs } from \"../wsi/utils\";\n\nfunction toErrorMessage(error: unknown): string {\n if (error instanceof Error) return error.message;\n try {\n return String(error);\n } catch {\n return \"unknown worker error\";\n }\n}\n\ninterface WorkerScope {\n postMessage(message: unknown, transfer?: Transferable[]): void;\n addEventListener(type: \"message\", listener: (event: MessageEvent<RoiClipWorkerRequest>) => void): void;\n}\n\nconst workerScope = self as unknown as WorkerScope;\n\nfunction handleDataRequest(msg: RoiClipWorkerDataRequest): RoiClipWorkerSuccess {\n const start = nowMs();\n const count = Math.max(0, Math.floor(msg.count));\n const positions = new Float32Array(msg.positions);\n const classes = new Uint16Array(msg.paletteIndices);\n const fillModes = msg.fillModes ? new Uint8Array(msg.fillModes) : null;\n const ids = msg.ids ? new Uint32Array(msg.ids) : null;\n\n const maxCountByPositions = Math.floor(positions.length / 2);\n const safeCount = Math.max(0, Math.min(count, maxCountByPositions, classes.length, fillModes ? fillModes.length : Number.MAX_SAFE_INTEGER));\n const hasFillModes = fillModes instanceof Uint8Array && fillModes.length >= safeCount;\n const hasIds = ids instanceof Uint32Array && ids.length >= safeCount;\n const prepared = prepareRoiPolygons(msg.polygons ?? []);\n\n if (safeCount === 0 || prepared.length === 0) {\n const empty: RoiClipWorkerSuccess = {\n type: \"roi-clip-success\",\n id: msg.id,\n count: 0,\n positions: new Float32Array(0).buffer,\n paletteIndices: new Uint16Array(0).buffer,\n durationMs: nowMs() - start,\n };\n if (hasFillModes) {\n empty.fillModes = new Uint8Array(0).buffer;\n }\n if (hasIds) {\n empty.ids = new Uint32Array(0).buffer;\n }\n return empty;\n }\n\n const nextPositions = new Float32Array(safeCount * 2);\n const nextClasses = new Uint16Array(safeCount);\n const nextFillModes = hasFillModes ? new Uint8Array(safeCount) : null;\n const nextIds = hasIds ? new Uint32Array(safeCount) : null;\n let cursor = 0;\n\n for (let i = 0; i < safeCount; i += 1) {\n const x = positions[i * 2];\n const y = positions[i * 2 + 1];\n if (!pointInAnyPreparedPolygon(x, y, prepared)) continue;\n nextPositions[cursor * 2] = x;\n nextPositions[cursor * 2 + 1] = y;\n nextClasses[cursor] = classes[i];\n if (nextFillModes) {\n nextFillModes[cursor] = fillModes![i];\n }\n if (nextIds) {\n nextIds[cursor] = ids![i];\n }\n cursor += 1;\n }\n\n const outPositions = nextPositions.slice(0, cursor * 2);\n const outClasses = nextClasses.slice(0, cursor);\n const outFillModes = nextFillModes ? nextFillModes.slice(0, cursor) : null;\n const outIds = nextIds ? nextIds.slice(0, cursor) : null;\n\n const success: RoiClipWorkerSuccess = {\n type: \"roi-clip-success\",\n id: msg.id,\n count: cursor,\n positions: outPositions.buffer,\n paletteIndices: outClasses.buffer,\n durationMs: nowMs() - start,\n };\n if (outFillModes) {\n success.fillModes = outFillModes.buffer;\n }\n if (outIds) {\n success.ids = outIds.buffer;\n }\n return success;\n}\n\nfunction handleIndexRequest(msg: RoiClipWorkerIndexRequest): RoiClipWorkerIndexSuccess {\n const start = nowMs();\n const count = Math.max(0, Math.floor(msg.count));\n const positions = new Float32Array(msg.positions);\n const maxCountByPositions = Math.floor(positions.length / 2);\n const safeCount = Math.max(0, Math.min(count, maxCountByPositions));\n const prepared = prepareRoiPolygons(msg.polygons ?? []);\n\n if (safeCount === 0 || prepared.length === 0) {\n return {\n type: \"roi-clip-index-success\",\n id: msg.id,\n count: 0,\n indices: new Uint32Array(0).buffer,\n durationMs: nowMs() - start,\n };\n }\n\n const out = new Uint32Array(safeCount);\n let cursor = 0;\n for (let i = 0; i < safeCount; i += 1) {\n const x = positions[i * 2];\n const y = positions[i * 2 + 1];\n if (!pointInAnyPreparedPolygon(x, y, prepared)) continue;\n out[cursor] = i;\n cursor += 1;\n }\n\n const outIndices = out.slice(0, cursor);\n return {\n type: \"roi-clip-index-success\",\n id: msg.id,\n count: cursor,\n indices: outIndices.buffer,\n durationMs: nowMs() - start,\n };\n}\n\nworkerScope.addEventListener(\"message\", (event: MessageEvent<RoiClipWorkerRequest>) => {\n const data = event.data;\n if (!data || (data.type !== \"roi-clip-request\" && data.type !== \"roi-clip-index-request\")) return;\n\n try {\n if (data.type === \"roi-clip-index-request\") {\n const response = handleIndexRequest(data);\n workerScope.postMessage(response, [response.indices]);\n return;\n }\n const response = handleDataRequest(data);\n const transfer: Transferable[] = [response.positions, response.paletteIndices];\n if (response.fillModes) {\n transfer.push(response.fillModes);\n }\n if (response.ids) {\n transfer.push(response.ids);\n }\n workerScope.postMessage(response, transfer);\n } catch (error) {\n const fail: RoiClipWorkerResponse = {\n type: \"roi-clip-failure\",\n id: data.id,\n error: toErrorMessage(error),\n };\n workerScope.postMessage(fail);\n }\n});\n"],"names":["isFiniteNumber","value","isCoordinatePair","closeRoiRing","coordinates","out","point","x","y","prev","first","last","polygonSignedArea","ring","sum","i","a","b","normalizePolygonRings","rings","normalized","closed","outerIndex","outerArea","area","normalizeRoiGeometry","geometry","polygon","second","pointInRing","inside","j","xi","yi","xj","yj","prepareRoiPolygons","geometries","prepared","multipolygon","outer","minX","minY","maxX","maxY","pointInPreparedPolygon","hole","pointInAnyPreparedPolygon","polygons","nowMs","toErrorMessage","error","workerScope","handleDataRequest","msg","start","count","positions","classes","fillModes","ids","maxCountByPositions","safeCount","hasFillModes","hasIds","empty","nextPositions","nextClasses","nextFillModes","nextIds","cursor","outPositions","outClasses","outFillModes","outIds","success","handleIndexRequest","outIndices","event","data","response","transfer","fail"],"mappings":"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"}
|