open-plant 1.2.21 → 1.3.1
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/CHANGELOG.md +19 -1
- package/README.md +129 -23
- package/dist/assets/point-hit-index-worker-CNFA6pZm.js +2 -0
- package/dist/assets/point-hit-index-worker-CNFA6pZm.js.map +1 -0
- package/dist/assets/roi-clip-worker-BDVQwN2T.js.map +1 -1
- package/dist/index.cjs +7 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +3365 -3000
- package/dist/index.js.map +1 -1
- package/dist/types/core/ortho-camera.d.ts +7 -0
- package/dist/types/core/ortho-camera.d.ts.map +1 -1
- package/dist/types/index.d.ts +5 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/react/draw-layer-brush.d.ts +9 -0
- package/dist/types/react/draw-layer-brush.d.ts.map +1 -0
- package/dist/types/react/draw-layer-label.d.ts +21 -0
- package/dist/types/react/draw-layer-label.d.ts.map +1 -0
- package/dist/types/react/draw-layer-overlay.d.ts +18 -0
- package/dist/types/react/draw-layer-overlay.d.ts.map +1 -0
- package/dist/types/react/draw-layer-stamp.d.ts +24 -0
- package/dist/types/react/draw-layer-stamp.d.ts.map +1 -0
- package/dist/types/react/draw-layer-types.d.ts +243 -0
- package/dist/types/react/draw-layer-types.d.ts.map +1 -0
- package/dist/types/react/draw-layer-utils.d.ts +30 -0
- package/dist/types/react/draw-layer-utils.d.ts.map +1 -0
- package/dist/types/react/draw-layer.d.ts +4 -187
- package/dist/types/react/draw-layer.d.ts.map +1 -1
- package/dist/types/react/wsi-region-hit-utils.d.ts +21 -0
- package/dist/types/react/wsi-region-hit-utils.d.ts.map +1 -0
- package/dist/types/react/wsi-viewer-canvas.d.ts.map +1 -1
- package/dist/types/workers/point-hit-index-worker.d.ts +2 -0
- package/dist/types/workers/point-hit-index-worker.d.ts.map +1 -0
- package/dist/types/wsi/brush-stroke.d.ts.map +1 -1
- package/dist/types/wsi/image-info.d.ts +26 -1
- package/dist/types/wsi/image-info.d.ts.map +1 -1
- package/dist/types/wsi/point-clip-hybrid.d.ts.map +1 -1
- package/dist/types/wsi/point-clip-worker-client.d.ts.map +1 -1
- package/dist/types/wsi/point-clip.d.ts +1 -1
- package/dist/types/wsi/point-clip.d.ts.map +1 -1
- package/dist/types/wsi/point-hit-index-shared.d.ts +25 -0
- package/dist/types/wsi/point-hit-index-shared.d.ts.map +1 -0
- package/dist/types/wsi/point-hit-index-worker-client.d.ts +18 -0
- package/dist/types/wsi/point-hit-index-worker-client.d.ts.map +1 -0
- package/dist/types/wsi/point-hit-index-worker-protocol.d.ts +30 -0
- package/dist/types/wsi/point-hit-index-worker-protocol.d.ts.map +1 -0
- package/dist/types/wsi/roi-geometry.d.ts +3 -0
- package/dist/types/wsi/roi-geometry.d.ts.map +1 -1
- package/dist/types/wsi/roi-term-stats.d.ts.map +1 -1
- package/dist/types/wsi/tile-scheduler.d.ts.map +1 -1
- package/dist/types/wsi/utils.d.ts +3 -2
- package/dist/types/wsi/utils.d.ts.map +1 -1
- package/dist/types/wsi/worker-client.d.ts +28 -0
- package/dist/types/wsi/worker-client.d.ts.map +1 -0
- package/dist/types/wsi/wsi-canvas-lifecycle.d.ts +15 -0
- package/dist/types/wsi/wsi-canvas-lifecycle.d.ts.map +1 -0
- package/dist/types/wsi/wsi-input-handlers.d.ts +67 -0
- package/dist/types/wsi/wsi-input-handlers.d.ts.map +1 -0
- package/dist/types/wsi/wsi-interaction.d.ts +50 -0
- package/dist/types/wsi/wsi-interaction.d.ts.map +1 -0
- package/dist/types/wsi/wsi-lifecycle-ops.d.ts +38 -0
- package/dist/types/wsi/wsi-lifecycle-ops.d.ts.map +1 -0
- package/dist/types/wsi/wsi-normalize.d.ts +19 -0
- package/dist/types/wsi/wsi-normalize.d.ts.map +1 -0
- package/dist/types/wsi/wsi-point-data.d.ts +14 -0
- package/dist/types/wsi/wsi-point-data.d.ts.map +1 -0
- package/dist/types/wsi/wsi-render-pass.d.ts +39 -0
- package/dist/types/wsi/wsi-render-pass.d.ts.map +1 -0
- package/dist/types/wsi/wsi-renderer-types.d.ts +130 -0
- package/dist/types/wsi/wsi-renderer-types.d.ts.map +1 -0
- package/dist/types/wsi/wsi-shaders.d.ts +4 -0
- package/dist/types/wsi/wsi-shaders.d.ts.map +1 -0
- package/dist/types/wsi/wsi-tile-cache.d.ts +8 -0
- package/dist/types/wsi/wsi-tile-cache.d.ts.map +1 -0
- package/dist/types/wsi/wsi-tile-renderer.d.ts +9 -69
- package/dist/types/wsi/wsi-tile-renderer.d.ts.map +1 -1
- package/dist/types/wsi/wsi-tile-visibility.d.ts +22 -0
- package/dist/types/wsi/wsi-tile-visibility.d.ts.map +1 -0
- package/dist/types/wsi/wsi-view-animation.d.ts +4 -0
- package/dist/types/wsi/wsi-view-animation.d.ts.map +1 -0
- package/dist/types/wsi/wsi-view-ops.d.ts +17 -0
- package/dist/types/wsi/wsi-view-ops.d.ts.map +1 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,7 +7,25 @@ and this project follows [Semantic Versioning](https://semver.org/).
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
### Added
|
|
11
|
+
- Web Worker 기반 포인트 공간 인덱스 빌드 (`point-hit-index-worker`). 메인 스레드 ~175ms 블로킹 제거.
|
|
12
|
+
- `FlatPointSpatialIndex` flat typed array 자료구조 + open-addressing hash table lookup.
|
|
13
|
+
- `buildPointSpatialIndexAsync()`, `lookupCellIndex()`, `terminatePointHitIndexWorker()` public API.
|
|
14
|
+
- 인접 tier (T±1) 타일 prefetch: 빠른 줌 시 blank frame 감소.
|
|
15
|
+
- `getVisibleTilesForTier(tier)` public method on `WsiTileRenderer`.
|
|
16
|
+
- 최적화 리포트: `perf-optimization-report.md`.
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
- `pointHitIndex` 빌드가 `useMemo` (동기) → `useEffect` + `useState` (비동기 워커)로 전환.
|
|
20
|
+
- 워커 인덱스 알고리즘: nested `Map` → 4-pass counting sort (GC-free, typed arrays only).
|
|
21
|
+
- 워커 프로토콜: positions/ids를 워커가 반환하지 않음 — 메인 스레드가 원본 참조 (전송량 ~120MB → ~48MB).
|
|
22
|
+
- `getCellByCoordinates` 내부 lookup: nested `Map.get()` → `Int32Array` hash table O(1).
|
|
23
|
+
- 타일 스케줄링: 현재 tier 외 인접 tier 타일을 `distance2` 페널티 기반 낮은 우선순위로 포함.
|
|
24
|
+
- 내부 perf logging (`logPerf`, `shouldLogPerf`, `PERF_LOG_*`) 제거.
|
|
25
|
+
|
|
26
|
+
### Docs
|
|
27
|
+
- `performance-optimization.md` 업데이트: 워커 인덱스, prefetch, flat hash 내용 추가.
|
|
28
|
+
- `README.md` 프로젝트 구조: 워커/클라이언트 파일 반영.
|
|
11
29
|
|
|
12
30
|
## [1.2.4] - 2026-02-25
|
|
13
31
|
|
package/README.md
CHANGED
|
@@ -73,12 +73,19 @@ draw mode에 진입하면 `setPointerCapture`로 입력을 독점해 팬(드래
|
|
|
73
73
|
| | |
|
|
74
74
|
|---|---|
|
|
75
75
|
| **WebGL2 타일 렌더링** | 멀티 티어 타일 피라미드, LRU 캐시(320장), 저해상도 fallback 렌더링 |
|
|
76
|
+
| **타일 전용 색상 보정** | `imageColorSettings`로 brightness/contrast/saturation 실시간 반영 (cell/ROI/draw overlay는 영향 없음) |
|
|
76
77
|
| **회전 인터랙션** | `WsiViewState.rotationDeg`, `Ctrl/Cmd + drag` 회전, `resetRotation` 경로 |
|
|
78
|
+
| **줌 범위 제어 + 전환 애니메이션** | `minZoom`/`maxZoom` clamp + `viewTransition`(duration/easing) |
|
|
77
79
|
| **포인트 오버레이** | WebGL2 `gl.POINTS`로 수십, 수백만 개 포인트를 팔레트 텍스처 기반 컬러링. 파싱된 TypedArray만 입력 |
|
|
78
80
|
| **포인트 크기 커스터마이즈** | `pointSizeByZoom` 객체로 zoom별 셀(px) 크기 지정 + 내부 선형 보간 |
|
|
81
|
+
| **포인트 렌더 모드 제어** | `pointData.fillModes`로 ring/solid 렌더링 제어 |
|
|
79
82
|
| **모바일 타겟 성능** | iPhone 15급 환경에서 수백만 cell 워크로드를 전제로 pan/zoom 응답성을 유지하도록 설계 |
|
|
80
|
-
| **드로잉 / ROI 도구** | Freehand · Rectangle · Circular + Stamp(사각형/원, mm² 지정) |
|
|
83
|
+
| **드로잉 / ROI 도구** | Freehand · Rectangle · Circular · Brush + Stamp(사각형/원, mm² 지정) |
|
|
81
84
|
| **고정 픽셀 스탬프** | `stamp-rectangle-4096px` + `stampOptions.rectanglePixelSize` |
|
|
85
|
+
| **브러시 UX 제어** | `brushOptions` (`radius`, `edgeDetail`, `edgeSmoothing`, `clickSelectRoi` 등) |
|
|
86
|
+
| **ROI 인터랙션 제어** | `activeRegionId` controlled/uncontrolled + contour/label 기반 hit-test |
|
|
87
|
+
| **ROI 라벨 동적 제어** | `resolveRegionLabelStyle` + `autoLiftRegionLabelAtMaxZoom` |
|
|
88
|
+
| **실시간 면적 툴팁** | `drawAreaTooltip`으로 draw 중 mm² 표시 |
|
|
82
89
|
| **ROI 포인트 클리핑** | `clipMode`: `sync` / `worker` / `hybrid-webgpu` (실험) |
|
|
83
90
|
| **ROI 통계 API** | `computeRoiPointGroups()` + `onRoiPointGroups` 콜백 |
|
|
84
91
|
| **ROI 커스텀 오버레이** | `resolveRegionStrokeStyle`, `overlayShapes` |
|
|
@@ -111,11 +118,14 @@ src/
|
|
|
111
118
|
│ ├── point-clip.ts # ROI 포인트 클리핑
|
|
112
119
|
│ ├── point-clip-worker-client.ts # ROI 워커 클리핑 클라이언트
|
|
113
120
|
│ ├── point-clip-hybrid.ts # WebGPU + polygon 하이브리드 클리핑(실험)
|
|
121
|
+
│ ├── point-hit-index-worker-client.ts # 포인트 공간 인덱스 워커 클라이언트
|
|
122
|
+
│ ├── point-hit-index-worker-protocol.ts # 인덱스 워커 메시지 프로토콜
|
|
114
123
|
│ ├── webgpu.ts # WebGPU capability/compute 유틸
|
|
115
124
|
│ ├── image-info.ts # 이미지 메타데이터 정규화
|
|
116
125
|
│ └── utils.ts # 팔레트, 색상, 토큰 유틸리티
|
|
117
126
|
├── workers/
|
|
118
|
-
│
|
|
127
|
+
│ ├── roi-clip-worker.ts # ROI point-in-polygon worker
|
|
128
|
+
│ └── point-hit-index-worker.ts # 포인트 공간 인덱스 빌드 worker
|
|
119
129
|
└── react/ # React 컴포넌트
|
|
120
130
|
├── wsi-viewer-canvas.tsx # 전체 기능 WSI 뷰어
|
|
121
131
|
├── draw-layer.tsx # 드로잉 오버레이
|
|
@@ -126,7 +136,7 @@ src/
|
|
|
126
136
|
|
|
127
137
|
### `<WsiViewerCanvas>`
|
|
128
138
|
|
|
129
|
-
전체 기능을 갖춘 WSI 뷰어 컴포넌트.
|
|
139
|
+
전체 기능을 갖춘 WSI 뷰어 컴포넌트. 실사용 시 대부분의 기능은 이 컴포넌트 하나로 제어합니다.
|
|
130
140
|
|
|
131
141
|
```jsx
|
|
132
142
|
import { WsiViewerCanvas } from "open-plant";
|
|
@@ -137,8 +147,8 @@ import { WsiViewerCanvas } from "open-plant";
|
|
|
137
147
|
imageColorSettings={{ brightness: 0, contrast: 0, saturation: 0 }}
|
|
138
148
|
ctrlDragRotate
|
|
139
149
|
rotationResetNonce={rotationResetNonce}
|
|
140
|
-
minZoom={0.25}
|
|
141
|
-
maxZoom={
|
|
150
|
+
minZoom={0.25} // 미지정 시 fitZoom * 0.5
|
|
151
|
+
maxZoom={1} // 미지정 시 fitZoom * 8
|
|
142
152
|
viewTransition={{ duration: 300 }}
|
|
143
153
|
authToken={bearerToken}
|
|
144
154
|
pointData={pointPayload}
|
|
@@ -152,7 +162,7 @@ import { WsiViewerCanvas } from "open-plant";
|
|
|
152
162
|
clipPointsToRois
|
|
153
163
|
clipMode="worker"
|
|
154
164
|
onClipStats={(s) => console.log(s.mode, s.durationMs)}
|
|
155
|
-
drawTool=
|
|
165
|
+
drawTool={drawTool}
|
|
156
166
|
drawFillColor="transparent"
|
|
157
167
|
activeRegionId={selectedRoiId} // controlled: 외부에서 active ROI 제어
|
|
158
168
|
onActiveRegionChange={setSelectedRoiId} // 내부 클릭/탭 선택 변경 알림
|
|
@@ -190,6 +200,7 @@ import { WsiViewerCanvas } from "open-plant";
|
|
|
190
200
|
onRoiPointGroups={(stats) => console.log(stats.groups)}
|
|
191
201
|
onDrawComplete={(result) => {
|
|
192
202
|
if (result.intent === "roi") handleRoi(result);
|
|
203
|
+
if (result.intent === "brush") handleBrush(result);
|
|
193
204
|
}}
|
|
194
205
|
onPatchComplete={(patch) => {
|
|
195
206
|
// stamp-rectangle-4096px 전용
|
|
@@ -200,23 +211,118 @@ import { WsiViewerCanvas } from "open-plant";
|
|
|
200
211
|
/>
|
|
201
212
|
```
|
|
202
213
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
`
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
`
|
|
209
|
-
`
|
|
210
|
-
`
|
|
211
|
-
`
|
|
212
|
-
`
|
|
213
|
-
`
|
|
214
|
-
`
|
|
215
|
-
|
|
216
|
-
`
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
214
|
+
#### 동작 규약 (중요)
|
|
215
|
+
|
|
216
|
+
- `mpp`(microns per pixel)는 스탬프 mm² 환산에 사용됩니다. 미지정 시 물리 크기는 근사치입니다.
|
|
217
|
+
- `imageColorSettings`는 타일 레이어에만 적용됩니다. 포인트/ROI/드로잉은 영향받지 않습니다.
|
|
218
|
+
- ROI hit-test는 **contour + nametag 영역** 기준입니다. ROI 내부 fill은 클릭/hover 영역에서 제외됩니다.
|
|
219
|
+
- `activeRegionId`를 주면 controlled mode, 생략하면 uncontrolled mode로 동작합니다.
|
|
220
|
+
- `minZoom`/`maxZoom`은 휠/더블클릭/`setViewState`/`fitToImage` 전 경로에 동일 clamp가 적용됩니다.
|
|
221
|
+
- `viewTransition`은 `setViewState`/`fitToImage`/`zoomBy` 전환에 적용되며 `duration` 최대값은 `2000ms`입니다.
|
|
222
|
+
- `drawFillColor` 기본값은 `transparent`입니다.
|
|
223
|
+
- `brushOptions.radius`는 HTML/CSS px 기준이며, 줌이 바뀌어도 on-screen 크기는 고정됩니다.
|
|
224
|
+
- `brushOptions.clickSelectRoi=true`이면 브러시 탭(드래그 없음) 시 ROI를 먼저 선택하고, ROI 외부 탭은 일반 브러시 결과를 반환합니다.
|
|
225
|
+
- `autoLiftRegionLabelAtMaxZoom=true`이면 `maxZoom` 도달 시 라벨이 위로 `20px` 애니메이션 이동하고, 이탈 시 원위치로 내려옵니다.
|
|
226
|
+
- `drawAreaTooltip.enabled=true`이면 freehand/rectangle/circular 그리기 중 커서 근처에 실시간 면적(mm²)을 표시합니다.
|
|
227
|
+
- `roiRegions[].coordinates`는 ring / polygon(with holes) / multipolygon을 모두 지원합니다.
|
|
228
|
+
|
|
229
|
+
#### WsiViewerCanvas Props By Concern
|
|
230
|
+
|
|
231
|
+
**View / Camera**
|
|
232
|
+
|
|
233
|
+
| Prop | Type | Notes |
|
|
234
|
+
|---|---|---|
|
|
235
|
+
| `source` | `WsiImageSource \| null` | 필수 입력 메타데이터 |
|
|
236
|
+
| `viewState` | `Partial<WsiViewState> \| null` | 외부 제어 시점 |
|
|
237
|
+
| `onViewStateChange` | `(next) => void` | 내부 변경 통지 |
|
|
238
|
+
| `fitNonce` | `number` | 변경 시 fit 재실행 |
|
|
239
|
+
| `rotationResetNonce` | `number` | 변경 시 회전 0도 |
|
|
240
|
+
| `ctrlDragRotate` | `boolean` | 기본 `true` |
|
|
241
|
+
| `minZoom` / `maxZoom` | `number` | 미지정 시 `fitZoom*0.5` / `fitZoom*8` |
|
|
242
|
+
| `viewTransition` | `{ duration?: number; easing?: (t)=>number }` | 기본 즉시 반영(duration 0) |
|
|
243
|
+
| `authToken` | `string` | 타일/포인트 요청 인증 |
|
|
244
|
+
| `overviewMapConfig` | `OverviewMapConfig` | 미니맵 표시/옵션 |
|
|
245
|
+
|
|
246
|
+
**Tile / Point / Clip**
|
|
247
|
+
|
|
248
|
+
| Prop | Type | Notes |
|
|
249
|
+
|---|---|---|
|
|
250
|
+
| `imageColorSettings` | `WsiImageColorSettings \| null` | brightness/contrast/saturation 입력 범위 `[-100, 100]` |
|
|
251
|
+
| `pointData` | `WsiPointData \| null` | `positions`, `paletteIndices` 필수 |
|
|
252
|
+
| `pointPalette` | `Uint8Array \| null` | RGBA 팔레트 텍스처 |
|
|
253
|
+
| `pointSizeByZoom` | `Record<number, number>` | continuous zoom stop |
|
|
254
|
+
| `pointStrokeScale` | `number` | point ring 두께 스케일 |
|
|
255
|
+
| `clipPointsToRois` | `boolean` | ROI 외부 포인트 필터 |
|
|
256
|
+
| `clipMode` | `"sync" \| "worker" \| "hybrid-webgpu"` | 기본 `"worker"` |
|
|
257
|
+
| `onClipStats` | `(event) => void` | clip 실행 통계 |
|
|
258
|
+
| `onRoiPointGroups` | `(stats) => void` | ROI term 통계 |
|
|
259
|
+
| `roiPaletteIndexToTermId` | `ReadonlyMap<number,string> \| readonly string[]` | ROI term 매핑 |
|
|
260
|
+
|
|
261
|
+
**ROI / Draw / Overlay**
|
|
262
|
+
|
|
263
|
+
| Prop | Type | Notes |
|
|
264
|
+
|---|---|---|
|
|
265
|
+
| `roiRegions` / `roiPolygons` | `WsiRegion[]` / `DrawRegionCoordinates[]` | 영속 ROI 입력 |
|
|
266
|
+
| `patchRegions` | `WsiRegion[]` | patch 전용 표시 채널 |
|
|
267
|
+
| `interactionLock` | `boolean` | pan/zoom 잠금 |
|
|
268
|
+
| `drawTool` | `DrawTool` | 기본 `"cursor"` |
|
|
269
|
+
| `stampOptions` | `StampOptions` | mm² / 고정 px stamp 크기 |
|
|
270
|
+
| `brushOptions` | `BrushOptions` | 브러시 궤적/커서/탭 선택 |
|
|
271
|
+
| `drawFillColor` | `string` | draw preview fill, 기본 `transparent` |
|
|
272
|
+
| `regionStrokeStyle` / `regionStrokeHoverStyle` / `regionStrokeActiveStyle` | `Partial<RegionStrokeStyle>` | ROI 외곽선 스타일 |
|
|
273
|
+
| `patchStrokeStyle` | `Partial<RegionStrokeStyle>` | patch 선 스타일 |
|
|
274
|
+
| `resolveRegionStrokeStyle` | `RegionStrokeStyleResolver` | 상태별 동적 stroke |
|
|
275
|
+
| `regionLabelStyle` | `Partial<RegionLabelStyle>` | 기본 배지 스타일 override |
|
|
276
|
+
| `resolveRegionLabelStyle` | `RegionLabelStyleResolver` | 줌/region별 동적 라벨 스타일 |
|
|
277
|
+
| `autoLiftRegionLabelAtMaxZoom` | `boolean` | max zoom 도달 시 라벨 auto-lift |
|
|
278
|
+
| `drawAreaTooltip` | `DrawAreaTooltipOptions` | draw 중 실시간 mm² tooltip |
|
|
279
|
+
| `overlayShapes` | `DrawOverlayShape[]` | 커스텀 도형/반전 마스크 |
|
|
280
|
+
| `customLayers` | `WsiCustomLayer[]` | host React 오버레이 슬롯 |
|
|
281
|
+
| `activeRegionId` | `string \| number \| null` | controlled active ROI |
|
|
282
|
+
|
|
283
|
+
**Events / Refs**
|
|
284
|
+
|
|
285
|
+
| Prop | Type | Notes |
|
|
286
|
+
|---|---|---|
|
|
287
|
+
| `onStats` | `(stats: WsiRenderStats) => void` | 프레임 통계 |
|
|
288
|
+
| `onTileError` | `(event: WsiTileErrorEvent) => void` | 타일 로드 실패 |
|
|
289
|
+
| `onContextLost` / `onContextRestored` | `() => void` | WebGL 컨텍스트 이벤트 |
|
|
290
|
+
| `onPointerWorldMove` | `(event) => void` | world 좌표 포인터 스트림 |
|
|
291
|
+
| `onPointHover` / `onPointClick` | `(event) => void` | 포인트 hit 이벤트 |
|
|
292
|
+
| `getCellByCoordinatesRef` | `MutableRefObject<(coord)=>PointHitEvent \| null>` | imperative 좌표 hit-test |
|
|
293
|
+
| `onRegionHover` / `onRegionClick` | `(event) => void` | region hit 이벤트 |
|
|
294
|
+
| `onActiveRegionChange` | `(regionId) => void` | active 변경 통지 |
|
|
295
|
+
| `onDrawComplete` | `(result: DrawResult) => void` | `intent: "roi" \| "patch" \| "brush"` |
|
|
296
|
+
| `onPatchComplete` | `(result: PatchDrawResult) => void` | `stamp-rectangle-4096px` 전용 |
|
|
297
|
+
| `className` / `style` | `string` / `CSSProperties` | 컨테이너 스타일 |
|
|
298
|
+
|
|
299
|
+
### `<DrawLayer>`
|
|
300
|
+
|
|
301
|
+
독립 오버레이 드로잉 컴포넌트입니다. `WsiViewerCanvas` 내부에서 자동 사용되지만, 필요하면 별도로 직접 사용할 수 있습니다.
|
|
302
|
+
|
|
303
|
+
- 지원 툴: `freehand`, `rectangle`, `circular`, `brush`, `stamp-*`
|
|
304
|
+
- 브러시는 화면 픽셀 기준 반경 + `edgeDetail`/`edgeSmoothing` 옵션을 사용합니다.
|
|
305
|
+
- `Esc`로 현재 드로잉 세션을 취소할 수 있습니다.
|
|
306
|
+
|
|
307
|
+
### `<OverviewMap>`
|
|
308
|
+
|
|
309
|
+
현재 뷰포트를 표시하는 인터랙티브 미니맵입니다. `overviewMapConfig.show`를 `true`로 설정하면 `WsiViewerCanvas`에 함께 렌더링됩니다.
|
|
310
|
+
|
|
311
|
+
## API
|
|
312
|
+
|
|
313
|
+
| Export | 설명 |
|
|
314
|
+
|---|---|
|
|
315
|
+
| `WsiViewerCanvas`, `DrawLayer`, `OverviewMap`, `TileViewerCanvas` | React 컴포넌트 |
|
|
316
|
+
| `WsiTileRenderer`, `M1TileRenderer`, `TileScheduler` | 렌더러/스케줄러 클래스 |
|
|
317
|
+
| `normalizeImageInfo`, `toTileUrl` | 이미지 메타데이터/타일 URL 유틸 |
|
|
318
|
+
| `buildTermPalette`, `calcScaleResolution`, `calcScaleLength`, `toBearerToken` | 공통 유틸 |
|
|
319
|
+
| `filterPointDataByPolygons`, `filterPointDataByPolygonsInWorker`, `filterPointDataByPolygonsHybrid` | ROI 포인트 클리핑 |
|
|
320
|
+
| `filterPointIndicesByPolygons`, `filterPointIndicesByPolygonsInWorker`, `terminateRoiClipWorker` | 인덱스 기반 클리핑/워커 관리 |
|
|
321
|
+
| `buildPointSpatialIndexAsync`, `lookupCellIndex`, `terminatePointHitIndexWorker` | 포인트 공간 인덱스 (워커) |
|
|
322
|
+
| `computeRoiPointGroups` | ROI term 통계 |
|
|
323
|
+
| `getWebGpuCapabilities`, `prefilterPointsByBoundsWebGpu` | WebGPU capability/연산(실험) |
|
|
324
|
+
| `closeRing`, `createRectangle`, `createCircle` | 도형 유틸 |
|
|
325
|
+
| 타입 export (`WsiViewerCanvasProps`, `WsiImageSource`, `WsiPointData`, `WsiViewTransitionOptions` 등) | TypeScript 통합용 공개 타입 |
|
|
220
326
|
|
|
221
327
|
### `<DrawLayer>`
|
|
222
328
|
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
(function(){"use strict";function x(e,l,n){return(e*73856093^l*19349663)>>>0&n}function v(e,l,n){if(e<=0||l<=0||n<=0)return 256;const o=Math.max(1,e*l),r=Math.sqrt(o/Math.max(1,n))*4;return Math.max(24,Math.min(1024,r))}function Z(e,l){if(!(e instanceof Uint32Array)||e.length===0)return null;let n=!0;for(let r=0;r<e.length;r+=1)if(!(e[r]<l)){n=!1;break}if(n)return e;const o=new Uint32Array(e.length);let h=0;for(let r=0;r<e.length;r+=1)e[r]>=l||(o[h]=e[r],h+=1);return h>0?o.subarray(0,h):null}function q(e){const l=Math.max(0,Math.floor(e.count)),n=Math.floor(e.positions.length/2),o=Math.max(0,Math.min(l,n));if(o<=0)return null;const h=Z(e.drawIndices??null,o),r=h?h.length:o;if(r===0)return null;const L=v(e.sourceWidth,e.sourceHeight,r),H=1/L,O=new Int32Array(r),P=new Int32Array(r);let a=0;if(h)for(let t=0;t<r;t+=1){const i=h[t],c=e.positions[i*2],s=e.positions[i*2+1];!Number.isFinite(c)||!Number.isFinite(s)||(O[a]=Math.floor(c*H),P[a]=Math.floor(s*H),a+=1)}else for(let t=0;t<o;t+=1){const i=e.positions[t*2],c=e.positions[t*2+1];!Number.isFinite(i)||!Number.isFinite(c)||(O[a]=Math.floor(i*H),P[a]=Math.floor(c*H),a+=1)}if(a===0)return null;let g=Math.min(a,Math.max(64,a>>>3));(!Number.isFinite(g)||g<=0)&&(g=a);let u=1;for(;u<g*2;)u<<=1;let y=u-1,f=new Int32Array(u*2),d=new Int32Array(u);f.fill(2147483647);let I=0;const A=new Int32Array(a);for(let t=0;t<a;t+=1){const i=O[t],c=P[t];let s=x(i,c,y);for(;;){const T=f[s*2];if(T===2147483647){if(f[s*2]=i,f[s*2+1]=c,d[s]=1,A[t]=s,I+=1,I*4>u*3){const B=u;u<<=1,y=u-1;const S=new Int32Array(u*2),G=new Int32Array(u);S.fill(2147483647);for(let w=0;w<B;w+=1){if(f[w*2]===2147483647)continue;const R=f[w*2],Y=f[w*2+1];let M=x(R,Y,y);for(;S[M*2]!==2147483647;)M=M+1&y;S[M*2]=R,S[M*2+1]=Y,G[M]=d[w]}for(f=S,d=G,s=x(i,c,y);f[s*2]!==i||f[s*2+1]!==c;)s=s+1&y;A[t]=s}break}if(T===i&&f[s*2+1]===c){d[s]+=1,A[t]=s;break}s=s+1&y}}const b=new Int32Array(I*2),D=new Uint32Array(I),F=new Uint32Array(I),N=new Int32Array(u);N.fill(-1);let p=0,K=0;for(let t=0;t<u;t+=1)f[t*2]!==2147483647&&(b[p*2]=f[t*2],b[p*2+1]=f[t*2+1],D[p]=K,F[p]=d[t],N[t]=p,K+=d[t],p+=1);const U=new Uint32Array(a),_=new Uint32Array(I);if(_.set(D),h)for(let t=0;t<a;t+=1){const i=N[A[t]];U[_[i]]=h[t],_[i]+=1}else{let t=0;for(let i=0;i<o;i+=1){const c=e.positions[i*2],s=e.positions[i*2+1];if(!Number.isFinite(c)||!Number.isFinite(s))continue;const T=N[A[t]];U[_[T]]=i,_[T]+=1,t+=1}}let C=1;for(;C<I*2;)C<<=1;const z=C-1,E=new Int32Array(C);E.fill(-1);for(let t=0;t<I;t+=1){const i=b[t*2],c=b[t*2+1];let s=x(i,c,z);for(;E[s]!==-1;)s=s+1&z;E[s]=t}return{cellSize:L,safeCount:o,cellCount:I,hashCapacity:C,hashTable:E,cellKeys:b,cellOffsets:D,cellLengths:F,pointIndices:U}}function k(){return typeof performance<"u"&&typeof performance.now=="function"?performance.now():Date.now()}function X(e){if(e instanceof Error)return e.message;try{return String(e)}catch{return"unknown worker error"}}function W(e){const l=k(),n=q({count:e.count,positions:new Float32Array(e.positions),drawIndices:e.drawIndices?new Uint32Array(e.drawIndices):null,sourceWidth:e.sourceWidth,sourceHeight:e.sourceHeight});return n?{type:"point-hit-index-success",id:e.id,cellSize:n.cellSize,safeCount:n.safeCount,cellCount:n.cellCount,hashCapacity:n.hashCapacity,hashTable:n.hashTable.buffer,cellKeys:n.cellKeys.buffer,cellOffsets:n.cellOffsets.buffer,cellLengths:n.cellLengths.buffer,pointIndices:n.pointIndices.buffer,durationMs:k()-l}:null}const m=self;m.addEventListener("message",e=>{const l=e.data;if(!(!l||l.type!=="point-hit-index-request"))try{const n=W(l);if(!n){const o={type:"point-hit-index-success",id:l.id,cellSize:0,safeCount:0,cellCount:0,hashCapacity:0,hashTable:new Int32Array(0).buffer,cellKeys:new Int32Array(0).buffer,cellOffsets:new Uint32Array(0).buffer,cellLengths:new Uint32Array(0).buffer,pointIndices:new Uint32Array(0).buffer,durationMs:0};m.postMessage(o,[o.hashTable,o.cellKeys,o.cellOffsets,o.cellLengths,o.pointIndices]);return}m.postMessage(n,[n.hashTable,n.cellKeys,n.cellOffsets,n.cellLengths,n.pointIndices])}catch(n){const o={type:"point-hit-index-failure",id:l.id,error:X(n)};m.postMessage(o)}})})();
|
|
2
|
+
//# sourceMappingURL=point-hit-index-worker-CNFA6pZm.js.map
|
|
@@ -0,0 +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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"roi-clip-worker-BDVQwN2T.js","sources":["../src/wsi/roi-geometry.ts","../src/workers/roi-clip-worker.ts"],"sourcesContent":["export type RoiCoordinate = [number, number];\nexport type RoiLinearRing = RoiCoordinate[];\nexport type RoiPolygonRings = RoiLinearRing[];\nexport type RoiMultiPolygon = RoiPolygonRings[];\nexport type RoiGeometry = RoiLinearRing | RoiPolygonRings | RoiMultiPolygon;\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 (!geometry) return [];\n\n\tif (isLinearRing(geometry)) {\n\t\tconst polygon = normalizePolygonRings([geometry]);\n\t\treturn polygon.length > 0 ? [polygon] : [];\n\t}\n\n\tif (isPolygonRings(geometry)) {\n\t\tconst polygon = normalizePolygonRings(geometry);\n\t\treturn polygon.length > 0 ? [polygon] : [];\n\t}\n\n\tif (isMultiPolygon(geometry)) {\n\t\tconst out: RoiMultiPolygon = [];\n\t\tfor (const polygon of geometry) {\n\t\t\tconst normalized = normalizePolygonRings(polygon);\n\t\t\tif (normalized.length > 0) out.push(normalized);\n\t\t}\n\t\treturn out;\n\t}\n\n\treturn [];\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\tconst intersect =\n\t\t\tyi > y !== yj > y &&\n\t\t\tx < ((xj - xi) * (y - yi)) / ((yj - yi) || Number.EPSILON) + 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 {\n pointInAnyPreparedPolygon,\n prepareRoiPolygons,\n} from \"../wsi/roi-geometry\";\nimport type {\n RoiClipWorkerDataRequest,\n RoiClipWorkerIndexRequest,\n RoiClipWorkerIndexSuccess,\n RoiClipWorkerRequest,\n RoiClipWorkerResponse,\n RoiClipWorkerSuccess,\n} from \"../wsi/point-clip-worker-protocol\";\n\nfunction nowMs(): number {\n if (typeof performance !== \"undefined\" && typeof performance.now === \"function\") {\n return performance.now();\n }\n return Date.now();\n}\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 terms = 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, terms.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 nextTerms = 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 nextTerms[cursor] = terms[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 outTerms = nextTerms.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: outTerms.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","isLinearRing","point","isPolygonRings","ring","isMultiPolygon","polygon","closeRoiRing","coordinates","out","x","y","prev","first","last","polygonSignedArea","sum","i","a","b","normalizePolygonRings","rings","normalized","closed","outerIndex","outerArea","area","normalizeRoiGeometry","geometry","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","terms","fillModes","ids","maxCountByPositions","safeCount","hasFillModes","hasIds","empty","nextPositions","nextTerms","nextFillModes","nextIds","cursor","outPositions","outTerms","outFillModes","outIds","success","handleIndexRequest","outIndices","event","data","response","transfer","fail"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"roi-clip-worker-BDVQwN2T.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\treturn coords as RoiGeometry;\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 (!geometry) return [];\n\n\tif (isLinearRing(geometry)) {\n\t\tconst polygon = normalizePolygonRings([geometry]);\n\t\treturn polygon.length > 0 ? [polygon] : [];\n\t}\n\n\tif (isPolygonRings(geometry)) {\n\t\tconst polygon = normalizePolygonRings(geometry);\n\t\treturn polygon.length > 0 ? [polygon] : [];\n\t}\n\n\tif (isMultiPolygon(geometry)) {\n\t\tconst out: RoiMultiPolygon = [];\n\t\tfor (const polygon of geometry) {\n\t\t\tconst normalized = normalizePolygonRings(polygon);\n\t\t\tif (normalized.length > 0) out.push(normalized);\n\t\t}\n\t\treturn out;\n\t}\n\n\treturn [];\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\tconst intersect =\n\t\t\tyi > y !== yj > y &&\n\t\t\tx < ((xj - xi) * (y - yi)) / ((yj - yi) || Number.EPSILON) + 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 { 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 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 terms = 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, terms.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 nextTerms = 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 nextTerms[cursor] = terms[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 outTerms = nextTerms.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: outTerms.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","isLinearRing","point","isPolygonRings","ring","isMultiPolygon","polygon","closeRoiRing","coordinates","out","x","y","prev","first","last","polygonSignedArea","sum","i","a","b","normalizePolygonRings","rings","normalized","closed","outerIndex","outerArea","area","normalizeRoiGeometry","geometry","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","terms","fillModes","ids","maxCountByPositions","safeCount","hasFillModes","hasIds","empty","nextPositions","nextTerms","nextFillModes","nextIds","cursor","outPositions","outTerms","outFillModes","outIds","success","handleIndexRequest","outIndices","event","data","response","transfer","fail"],"mappings":"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"}
|