open-plant 1.4.9 → 1.4.11

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.
Files changed (35) hide show
  1. package/CHANGELOG.md +3 -3
  2. package/README.md +8 -7
  3. package/dist/assets/point-hit-index-worker-CNFA6pZm.js.map +1 -1
  4. package/dist/assets/{roi-clip-worker-BuNuUQQg.js → roi-clip-worker-CHxxL_lK.js} +2 -2
  5. package/dist/assets/roi-clip-worker-CHxxL_lK.js.map +1 -0
  6. package/dist/index.cjs +19 -17
  7. package/dist/index.cjs.map +1 -1
  8. package/dist/index.js +2027 -1983
  9. package/dist/index.js.map +1 -1
  10. package/dist/types/index.d.ts +6 -6
  11. package/dist/types/index.d.ts.map +1 -1
  12. package/dist/types/react/point-layer.d.ts +1 -0
  13. package/dist/types/react/point-layer.d.ts.map +1 -1
  14. package/dist/types/react/region-layer.d.ts +3 -1
  15. package/dist/types/react/region-layer.d.ts.map +1 -1
  16. package/dist/types/wsi/image-info.d.ts +7 -6
  17. package/dist/types/wsi/image-info.d.ts.map +1 -1
  18. package/dist/types/wsi/{roi-term-stats.d.ts → roi-class-stats.d.ts} +5 -5
  19. package/dist/types/wsi/roi-class-stats.d.ts.map +1 -0
  20. package/dist/types/wsi/types.d.ts +6 -7
  21. package/dist/types/wsi/types.d.ts.map +1 -1
  22. package/dist/types/wsi/utils.d.ts +6 -5
  23. package/dist/types/wsi/utils.d.ts.map +1 -1
  24. package/dist/types/wsi/wsi-normalize.d.ts +1 -0
  25. package/dist/types/wsi/wsi-normalize.d.ts.map +1 -1
  26. package/dist/types/wsi/wsi-render-pass.d.ts +1 -0
  27. package/dist/types/wsi/wsi-render-pass.d.ts.map +1 -1
  28. package/dist/types/wsi/wsi-renderer-types.d.ts +3 -1
  29. package/dist/types/wsi/wsi-renderer-types.d.ts.map +1 -1
  30. package/dist/types/wsi/wsi-shaders.d.ts.map +1 -1
  31. package/dist/types/wsi/wsi-tile-renderer.d.ts +2 -0
  32. package/dist/types/wsi/wsi-tile-renderer.d.ts.map +1 -1
  33. package/package.json +3 -1
  34. package/dist/assets/roi-clip-worker-BuNuUQQg.js.map +0 -1
  35. package/dist/types/wsi/roi-term-stats.d.ts.map +0 -1
package/CHANGELOG.md CHANGED
@@ -102,13 +102,13 @@ and this project follows [Semantic Versioning](https://semver.org/).
102
102
  - Region style resolver API: `resolveRegionStrokeStyle`.
103
103
  - Custom overlay shape API for patch/dashed guides: `overlayShapes`.
104
104
  - Fixed-pixel stamp tool: `stamp-rectangle-4096px` and `stampOptions.rectanglePixelSize`.
105
- - ROI term-group utility and callback path: `computeRoiPointGroups`, `onRoiPointGroups`.
105
+ - ROI class-group utility and callback path: `computeRoiPointGroups`, `onRoiPointGroups`.
106
106
  - Release gate workflow: `.github/workflows/release-gate.yml`.
107
107
  - PR template: `.github/pull_request_template.md`.
108
108
  - Contribution guides: root `CONTRIBUTING.md`, docs EN/KO `contributing.html`.
109
109
  - Hybrid WebGPU draw bridge payload support via `WsiPointData.drawIndices`.
110
110
  - Hybrid clip option `bridgeToDraw` and clip stat flag `bridgedToDraw`.
111
- - Unit test coverage for ROI term stats with draw-index bridge input.
111
+ - Unit test coverage for ROI class stats with draw-index bridge input.
112
112
  - Patch-intent draw path for `stamp-rectangle-4096px` with dedicated `onPatchComplete` callback.
113
113
  - Patch overlay channel on viewer (`patchRegions`, `patchStrokeStyle`) separated from ROI hover/active interaction.
114
114
  - Custom React overlay layer slots via `customLayers` for host-owned rendering pipelines.
@@ -124,7 +124,7 @@ and this project follows [Semantic Versioning](https://semver.org/).
124
124
  - Publish gate now enforces `npm run release:gate` via `prepublishOnly`.
125
125
 
126
126
  ### Docs
127
- - Updated EN/KO API and guides for rotation, pointer world callbacks, overlay shapes, 4096px patch intent flow, custom layers, and ROI term stats.
127
+ - Updated EN/KO API and guides for rotation, pointer world callbacks, overlay shapes, 4096px patch intent flow, custom layers, and ROI class stats.
128
128
  - Updated `todo.md` gap table with current support status and code-path references.
129
129
  - Added EN/KO migration guides with API stability/deprecation policy and release-gate contract.
130
130
  - Added EN/KO contributing pages and linked them across docs navigation.
package/README.md CHANGED
@@ -31,7 +31,7 @@ Open Plant는 WSI 렌더링 **한 가지만** 하도록 설계되었고, 그래
31
31
 
32
32
  ### Open Plant vs deck.gl vs OpenLayers — 숫자로 증명하는 압도적 차이
33
33
 
34
- 같은 **합성 포인트 데이터**(랜덤 2D 좌표 + 16-term 팔레트)를 **3개 엔진의 각각 최적 경로**로 나란히 측정했습니다. (Apple M-시리즈, Chrome 실측)
34
+ 같은 **합성 포인트 데이터**(랜덤 2D 좌표 + 16-class 팔레트)를 **3개 엔진의 각각 최적 경로**로 나란히 측정했습니다. (Apple M-시리즈, Chrome 실측)
35
35
 
36
36
  - **deck.gl v9** — `ScatterplotLayer` binary accessor (`data.attributes`에 TypedArray 직전달, JS 객체 0개)
37
37
  - **OpenLayers v10** — `WebGLVectorLayer` + `RenderFeature` (가장 가벼운 Feature 모델)
@@ -112,7 +112,7 @@ Open Plant는 “고사양 PC에서만 빠른 뷰어”가 아니라, iPhone 15
112
112
 
113
113
  범용 라이브러리는 포인트마다 인스턴스 버퍼에 position + RGBA를 넣어 **20바이트 이상** 씁니다.
114
114
  Open Plant는 `Float32Array`(x, y) 8바이트 + `Uint16Array`(palette index) 2바이트 = **10바이트**입니다.
115
- 색상은 1×N 팔레트 텍스처 1장에 들어가므로, term 색상을 바꿀 때 수백 바이트짜리 텍스처만 재업로드하면 됩니다.
115
+ 색상은 1×N 팔레트 텍스처 1장에 들어가므로, class 색상을 바꿀 때 수백 바이트짜리 텍스처만 재업로드하면 됩니다.
116
116
  50만 셀 기준 GPU 메모리가 **절반 이하**로 줄어듭니다.
117
117
 
118
118
  ### 프래그먼트 셰이더 안에서 끝나는 링 렌더링
@@ -202,7 +202,7 @@ src/
202
202
  │ ├── wsi-tile-renderer.ts # WebGL2 멀티티어 타일 + 포인트, 입력, 애니메이션
203
203
  │ ├── wsi-render-pass.ts # 프레임당 draw 순서: fallback 타일 → visible 타일 → 포인트
204
204
  │ ├── wsi-shaders.ts # 타일·포인트 GLSL 프로그램 초기화
205
- │ ├── wsi-point-data.ts # 포인트 VBO 업로드 (positions / terms / fillModes / drawIndices)
205
+ │ ├── wsi-point-data.ts # 포인트 VBO 업로드 (positions / classes / fillModes / drawIndices)
206
206
  │ ├── wsi-interaction.ts # 포인터·휠·스냅 줌 이벤트 처리
207
207
  │ ├── wsi-input-handlers.ts # interaction lock 등 래핑
208
208
  │ ├── wsi-zoom-snap.ts # 배율 스냅 애니메이션
@@ -216,7 +216,7 @@ src/
216
216
  │ ├── point-clip-worker-client.ts / point-clip-worker-protocol.ts
217
217
  │ ├── point-clip-hybrid.ts # WebGPU bbox prefilter (실험)
218
218
  │ ├── point-hit-index-*.ts # 포인트 공간 해시 인덱스 (워커)
219
- │ ├── roi-geometry.ts / roi-term-stats.ts / brush-stroke.ts
219
+ │ ├── roi-geometry.ts / roi-class-stats.ts / brush-stroke.ts
220
220
  │ ├── image-info.ts / types.ts / utils.ts / wkt.ts / webgpu.ts / constants.ts
221
221
  │ └── …
222
222
  ├── workers/
@@ -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. 기본 `48000` |
340
+ | `maxRenderedPoints` | `number` | visible bin budget. 기본 `52000` |
340
341
  | `zIndex` | `number` | 오버레이 draw priority |
341
342
  | `onStats` | `(stats) => void` | point count / render time 등 |
342
343
 
@@ -383,10 +384,10 @@ import {
383
384
  | `useViewerContext` | 컨텍스트 (`rendererRef`, `worldToScreen`, …) |
384
385
  | `WsiTileRenderer`, `M1TileRenderer`, `TileScheduler` | 코어 렌더러·타일 큐 |
385
386
  | `normalizeImageInfo`, `toTileUrl`, `toRoiGeometry`, `parseWkt` | 이미지/ROI/WKT |
386
- | `buildTermPalette`, `calcScaleResolution`, `calcScaleLength`, `toBearerToken`, `clamp`, `hexToRgba`, `isSameViewState` | 유틸 |
387
+ | `buildClassPalette`, `calcScaleResolution`, `calcScaleLength`, `toBearerToken`, `clamp`, `hexToRgba`, `isSameViewState` | 유틸 |
387
388
  | `filterPointDataByPolygons`, `filterPointIndicesByPolygons`, `filterPointDataByPolygonsInWorker`, `filterPointIndicesByPolygonsInWorker`, `terminateRoiClipWorker`, `filterPointDataByPolygonsHybrid` | ROI 클리핑 |
388
389
  | `buildPointSpatialIndexAsync`, `lookupCellIndex`, `terminatePointHitIndexWorker` | 포인트 공간 인덱스(워커) |
389
- | `computeRoiPointGroups` | ROI term 통계 |
390
+ | `computeRoiPointGroups` | ROI class 통계 |
390
391
  | `getWebGpuCapabilities`, `prefilterPointsByBoundsWebGpu` | WebGPU(실험) |
391
392
  | `closeRing`, `createRectangle`, `createCircle` | 도형 |
392
393
  | 타입 (`WsiViewerProps`, `WsiImageSource`, `WsiPointData`, `WsiViewState`, `DrawTool`, `PointHitEvent`, …) | TS |
@@ -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 { TermPalette, 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 buildTermPalette(\n\tterms:\n\t\t| Array<{ termId?: string | null; termColor?: string | null }>\n\t\t| null\n\t\t| undefined,\n): TermPalette {\n\tconst palette: Array<[number, number, number, number]> = [\n\t\t[...DEFAULT_POINT_COLOR],\n\t];\n\tconst termToPaletteIndex = new Map<string, number>();\n\n\tfor (const term of terms ?? []) {\n\t\tconst termId = String(term?.termId ?? \"\");\n\t\tif (!termId || termToPaletteIndex.has(termId)) continue;\n\n\t\ttermToPaletteIndex.set(termId, palette.length);\n\t\tpalette.push(hexToRgba(term?.termColor));\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, termToPaletteIndex };\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,2 +1,2 @@
1
- (function(){"use strict";function I(n){return typeof n=="number"&&Number.isFinite(n)}function b(n){return Array.isArray(n)&&n.length>=2&&I(n[0])&&I(n[1])}function X(n){if(!Array.isArray(n)||n.length<3)return[];const t=[];for(const i of n){if(!Array.isArray(i)||i.length<2)continue;const o=Number(i[0]),s=Number(i[1]);if(!Number.isFinite(o)||!Number.isFinite(s))continue;const f=t[t.length-1];f&&f[0]===o&&f[1]===s||t.push([o,s])}if(t.length<3)return[];const e=t[0],r=t[t.length-1];return(e[0]!==r[0]||e[1]!==r[1])&&t.push([e[0],e[1]]),t.length>=4?t:[]}function g(n){if(!Array.isArray(n)||n.length<4)return 0;let t=0;for(let e=0;e<n.length-1;e+=1){const r=n[e],i=n[e+1];t+=r[0]*i[1]-i[0]*r[1]}return t*.5}function x(n){if(!Array.isArray(n)||n.length===0)return[];const t=[];for(const o of n){const s=X(o);s.length>=4&&t.push(s)}if(t.length===0)return[];if(t.length===1)return[t[0]];let e=0,r=0;for(let o=0;o<t.length;o+=1){const s=Math.abs(g(t[o]));s<=r||(r=s,e=o)}const i=[t[e]];for(let o=0;o<t.length;o+=1)o!==e&&i.push(t[o]);return i}function z(n){if(!Array.isArray(n)||n.length===0)return[];const t=n[0];if(b(t)){const i=x([n]);return i.length>0?[i]:[]}if(!Array.isArray(t)||t.length===0)return[];const e=t[0];if(b(e)){const i=x(n);return i.length>0?[i]:[]}if(!Array.isArray(e)||e.length===0||!b(e[0]))return[];const r=[];for(const i of n){const o=x(i);o.length>0&&r.push(o)}return r}function F(n,t,e){let r=!1;for(let i=0,o=e.length-1;i<e.length;o=i,i+=1){const s=e[i][0],f=e[i][1],c=e[o][0],u=e[o][1];if(f===u||f>t==u>t)continue;n<(c-s)*(t-f)/(u-f)+s&&(r=!r)}return r}function U(n){const t=[];for(const e of n??[]){const r=z(e);for(const i of r){const o=i[0];if(!o||o.length<4)continue;let s=1/0,f=1/0,c=-1/0,u=-1/0;for(const[l,h]of o)l<s&&(s=l),l>c&&(c=l),h<f&&(f=h),h>u&&(u=h);if(!Number.isFinite(s)||!Number.isFinite(f)||!Number.isFinite(c)||!Number.isFinite(u))continue;let a=Math.abs(g(o));for(let l=1;l<i.length;l+=1)a-=Math.abs(g(i[l]));t.push({outer:o,holes:i.slice(1),minX:s,minY:f,maxX:c,maxY:u,area:Math.max(1e-6,a)})}}return t}function S(n,t,e){if(n<e.minX||n>e.maxX||t<e.minY||t>e.maxY||!F(n,t,e.outer))return!1;for(const r of e.holes)if(F(n,t,r))return!1;return!0}function N(n,t,e){for(const r of e)if(S(n,t,r))return!0;return!1}function y(){return typeof performance<"u"&&typeof performance.now=="function"?performance.now():Date.now()}function Y(n){if(n instanceof Error)return n.message;try{return String(n)}catch{return"unknown worker error"}}const A=self;function j(n){const t=y(),e=Math.max(0,Math.floor(n.count)),r=new Float32Array(n.positions),i=new Uint16Array(n.paletteIndices),o=n.fillModes?new Uint8Array(n.fillModes):null,s=n.ids?new Uint32Array(n.ids):null,f=Math.floor(r.length/2),c=Math.max(0,Math.min(e,f,i.length,o?o.length:Number.MAX_SAFE_INTEGER)),u=o instanceof Uint8Array&&o.length>=c,a=s instanceof Uint32Array&&s.length>=c,l=U(n.polygons??[]);if(c===0||l.length===0){const d={type:"roi-clip-success",id:n.id,count:0,positions:new Float32Array(0).buffer,paletteIndices:new Uint16Array(0).buffer,durationMs:y()-t};return u&&(d.fillModes=new Uint8Array(0).buffer),a&&(d.ids=new Uint32Array(0).buffer),d}const h=new Float32Array(c*2),P=new Uint16Array(c),M=u?new Uint8Array(c):null,m=a?new Uint32Array(c):null;let p=0;for(let d=0;d<c;d+=1){const q=r[d*2],C=r[d*2+1];N(q,C,l)&&(h[p*2]=q,h[p*2+1]=C,P[p]=i[d],M&&(M[p]=o[d]),m&&(m[p]=s[d]),p+=1)}const T=h.slice(0,p*2),B=P.slice(0,p),R=M?M.slice(0,p):null,E=m?m.slice(0,p):null,w={type:"roi-clip-success",id:n.id,count:p,positions:T.buffer,paletteIndices:B.buffer,durationMs:y()-t};return R&&(w.fillModes=R.buffer),E&&(w.ids=E.buffer),w}function k(n){const t=y(),e=Math.max(0,Math.floor(n.count)),r=new Float32Array(n.positions),i=Math.floor(r.length/2),o=Math.max(0,Math.min(e,i)),s=U(n.polygons??[]);if(o===0||s.length===0)return{type:"roi-clip-index-success",id:n.id,count:0,indices:new Uint32Array(0).buffer,durationMs:y()-t};const f=new Uint32Array(o);let c=0;for(let a=0;a<o;a+=1){const l=r[a*2],h=r[a*2+1];N(l,h,s)&&(f[c]=a,c+=1)}const u=f.slice(0,c);return{type:"roi-clip-index-success",id:n.id,count:c,indices:u.buffer,durationMs:y()-t}}A.addEventListener("message",n=>{const t=n.data;if(!(!t||t.type!=="roi-clip-request"&&t.type!=="roi-clip-index-request"))try{if(t.type==="roi-clip-index-request"){const i=k(t);A.postMessage(i,[i.indices]);return}const e=j(t),r=[e.positions,e.paletteIndices];e.fillModes&&r.push(e.fillModes),e.ids&&r.push(e.ids),A.postMessage(e,r)}catch(e){const r={type:"roi-clip-failure",id:t.id,error:Y(e)};A.postMessage(r)}})})();
2
- //# sourceMappingURL=roi-clip-worker-BuNuUQQg.js.map
1
+ (function(){"use strict";function I(n){return typeof n=="number"&&Number.isFinite(n)}function g(n){return Array.isArray(n)&&n.length>=2&&I(n[0])&&I(n[1])}function X(n){if(!Array.isArray(n)||n.length<3)return[];const t=[];for(const i of n){if(!Array.isArray(i)||i.length<2)continue;const o=Number(i[0]),s=Number(i[1]);if(!Number.isFinite(o)||!Number.isFinite(s))continue;const f=t[t.length-1];f&&f[0]===o&&f[1]===s||t.push([o,s])}if(t.length<3)return[];const e=t[0],r=t[t.length-1];return(e[0]!==r[0]||e[1]!==r[1])&&t.push([e[0],e[1]]),t.length>=4?t:[]}function m(n){if(!Array.isArray(n)||n.length<4)return 0;let t=0;for(let e=0;e<n.length-1;e+=1){const r=n[e],i=n[e+1];t+=r[0]*i[1]-i[0]*r[1]}return t*.5}function x(n){if(!Array.isArray(n)||n.length===0)return[];const t=[];for(const o of n){const s=X(o);s.length>=4&&t.push(s)}if(t.length===0)return[];if(t.length===1)return[t[0]];let e=0,r=0;for(let o=0;o<t.length;o+=1){const s=Math.abs(m(t[o]));s<=r||(r=s,e=o)}const i=[t[e]];for(let o=0;o<t.length;o+=1)o!==e&&i.push(t[o]);return i}function z(n){if(!Array.isArray(n)||n.length===0)return[];const t=n[0];if(g(t)){const i=x([n]);return i.length>0?[i]:[]}if(!Array.isArray(t)||t.length===0)return[];const e=t[0];if(g(e)){const i=x(n);return i.length>0?[i]:[]}if(!Array.isArray(e)||e.length===0||!g(e[0]))return[];const r=[];for(const i of n){const o=x(i);o.length>0&&r.push(o)}return r}function F(n,t,e){let r=!1;for(let i=0,o=e.length-1;i<e.length;o=i,i+=1){const s=e[i][0],f=e[i][1],c=e[o][0],u=e[o][1];if(f===u||f>t==u>t)continue;n<(c-s)*(t-f)/(u-f)+s&&(r=!r)}return r}function U(n){const t=[];for(const e of n??[]){const r=z(e);for(const i of r){const o=i[0];if(!o||o.length<4)continue;let s=1/0,f=1/0,c=-1/0,u=-1/0;for(const[l,h]of o)l<s&&(s=l),l>c&&(c=l),h<f&&(f=h),h>u&&(u=h);if(!Number.isFinite(s)||!Number.isFinite(f)||!Number.isFinite(c)||!Number.isFinite(u))continue;let a=Math.abs(m(o));for(let l=1;l<i.length;l+=1)a-=Math.abs(m(i[l]));t.push({outer:o,holes:i.slice(1),minX:s,minY:f,maxX:c,maxY:u,area:Math.max(1e-6,a)})}}return t}function S(n,t,e){if(n<e.minX||n>e.maxX||t<e.minY||t>e.maxY||!F(n,t,e.outer))return!1;for(const r of e.holes)if(F(n,t,r))return!1;return!0}function N(n,t,e){for(const r of e)if(S(n,t,r))return!0;return!1}function y(){return typeof performance<"u"&&typeof performance.now=="function"?performance.now():Date.now()}function Y(n){if(n instanceof Error)return n.message;try{return String(n)}catch{return"unknown worker error"}}const A=self;function j(n){const t=y(),e=Math.max(0,Math.floor(n.count)),r=new Float32Array(n.positions),i=new Uint16Array(n.paletteIndices),o=n.fillModes?new Uint8Array(n.fillModes):null,s=n.ids?new Uint32Array(n.ids):null,f=Math.floor(r.length/2),c=Math.max(0,Math.min(e,f,i.length,o?o.length:Number.MAX_SAFE_INTEGER)),u=o instanceof Uint8Array&&o.length>=c,a=s instanceof Uint32Array&&s.length>=c,l=U(n.polygons??[]);if(c===0||l.length===0){const d={type:"roi-clip-success",id:n.id,count:0,positions:new Float32Array(0).buffer,paletteIndices:new Uint16Array(0).buffer,durationMs:y()-t};return u&&(d.fillModes=new Uint8Array(0).buffer),a&&(d.ids=new Uint32Array(0).buffer),d}const h=new Float32Array(c*2),P=new Uint16Array(c),M=u?new Uint8Array(c):null,b=a?new Uint32Array(c):null;let p=0;for(let d=0;d<c;d+=1){const E=r[d*2],q=r[d*2+1];N(E,q,l)&&(h[p*2]=E,h[p*2+1]=q,P[p]=i[d],M&&(M[p]=o[d]),b&&(b[p]=s[d]),p+=1)}const B=h.slice(0,p*2),D=P.slice(0,p),R=M?M.slice(0,p):null,C=b?b.slice(0,p):null,w={type:"roi-clip-success",id:n.id,count:p,positions:B.buffer,paletteIndices:D.buffer,durationMs:y()-t};return R&&(w.fillModes=R.buffer),C&&(w.ids=C.buffer),w}function k(n){const t=y(),e=Math.max(0,Math.floor(n.count)),r=new Float32Array(n.positions),i=Math.floor(r.length/2),o=Math.max(0,Math.min(e,i)),s=U(n.polygons??[]);if(o===0||s.length===0)return{type:"roi-clip-index-success",id:n.id,count:0,indices:new Uint32Array(0).buffer,durationMs:y()-t};const f=new Uint32Array(o);let c=0;for(let a=0;a<o;a+=1){const l=r[a*2],h=r[a*2+1];N(l,h,s)&&(f[c]=a,c+=1)}const u=f.slice(0,c);return{type:"roi-clip-index-success",id:n.id,count:c,indices:u.buffer,durationMs:y()-t}}A.addEventListener("message",n=>{const t=n.data;if(!(!t||t.type!=="roi-clip-request"&&t.type!=="roi-clip-index-request"))try{if(t.type==="roi-clip-index-request"){const i=k(t);A.postMessage(i,[i.indices]);return}const e=j(t),r=[e.positions,e.paletteIndices];e.fillModes&&r.push(e.fillModes),e.ids&&r.push(e.ids),A.postMessage(e,r)}catch(e){const r={type:"roi-clip-failure",id:t.id,error:Y(e)};A.postMessage(r)}})})();
2
+ //# sourceMappingURL=roi-clip-worker-CHxxL_lK.js.map
@@ -0,0 +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\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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"}