open-plant 1.2.4 → 1.2.6
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 +32 -0
- package/README.md +10 -2
- package/dist/assets/roi-clip-worker-D3hALtsJ.js +2 -0
- package/dist/assets/roi-clip-worker-D3hALtsJ.js.map +1 -0
- package/dist/index.cjs +7 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1949 -1359
- package/dist/index.js.map +1 -1
- package/dist/types/index.d.ts +3 -3
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/react/draw-layer.d.ts +14 -3
- package/dist/types/react/draw-layer.d.ts.map +1 -1
- package/dist/types/react/wsi-viewer-canvas.d.ts +24 -4
- package/dist/types/react/wsi-viewer-canvas.d.ts.map +1 -1
- package/dist/types/wsi/brush-stroke.d.ts +13 -0
- package/dist/types/wsi/brush-stroke.d.ts.map +1 -0
- 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-worker-protocol.d.ts +2 -0
- package/dist/types/wsi/point-clip-worker-protocol.d.ts.map +1 -1
- package/dist/types/wsi/point-clip.d.ts.map +1 -1
- package/dist/types/wsi/types.d.ts +1 -0
- package/dist/types/wsi/types.d.ts.map +1 -1
- package/dist/types/wsi/wsi-tile-renderer.d.ts +4 -0
- package/dist/types/wsi/wsi-tile-renderer.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/assets/roi-clip-worker-DdVYCepx.js +0 -2
- package/dist/assets/roi-clip-worker-DdVYCepx.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,38 @@ and this project follows [Semantic Versioning](https://semver.org/).
|
|
|
9
9
|
|
|
10
10
|
- No changes yet.
|
|
11
11
|
|
|
12
|
+
## [1.2.4] - 2026-02-25
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
- Point hit-test API on `WsiViewerCanvas`: `onPointHover`, `onPointClick`, and `getCellByCoordinatesRef`.
|
|
16
|
+
- Point hit event payload types: `PointHitEvent`, `PointHoverEvent`, `PointClickEvent` (including mouse button for context-menu flows).
|
|
17
|
+
- Optional point id channel on `WsiPointData` via `ids?: Uint32Array`.
|
|
18
|
+
- `pointSizeByZoom` customization for zoom-based point-size stops with linear interpolation.
|
|
19
|
+
- `PointSizeByZoom` and `BrushOptions` public type exports.
|
|
20
|
+
- Brush ROI draw tool (`drawTool: "brush"`) with configurable preview/cursor styling and polygon generation.
|
|
21
|
+
- New brush geometry builder utility: `src/wsi/brush-stroke.ts`.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
- Default high-zoom point-size profile is larger (improved readability at max zoom).
|
|
25
|
+
- Point clipping pipelines (`sync`, `worker`, `hybrid-webgpu`) now preserve `ids` through filtered outputs.
|
|
26
|
+
- Worker protocol/client now transfer `ids` buffers alongside point position/palette buffers when provided.
|
|
27
|
+
- Viewer point pick path now uses a spatial grid index and zoom-aware hit radius derived from rendered point size.
|
|
28
|
+
- Example app now demonstrates:
|
|
29
|
+
- Right-click cell context alert via `onPointClick`.
|
|
30
|
+
- Hover/click point debug state rendering.
|
|
31
|
+
- Brush tool controls (`radius`, `opacity`, preview mode).
|
|
32
|
+
- External point-size tuning via `pointSizeByZoom`.
|
|
33
|
+
|
|
34
|
+
### Docs
|
|
35
|
+
- Updated `README.md` feature table and usage example for `pointSizeByZoom` and point hit-test APIs.
|
|
36
|
+
- Updated EN/KO API reference docs for:
|
|
37
|
+
- `pointSizeByZoom`
|
|
38
|
+
- `onPointHover`, `onPointClick`, `getCellByCoordinatesRef`
|
|
39
|
+
- `WsiPointData.ids`
|
|
40
|
+
|
|
41
|
+
### Tests
|
|
42
|
+
- Added unit test coverage for `filterPointDataByPolygons` to verify `ids` are preserved for surviving points.
|
|
43
|
+
|
|
12
44
|
## [1.2.2] - 2026-02-25
|
|
13
45
|
|
|
14
46
|
### Added
|
package/README.md
CHANGED
|
@@ -65,8 +65,8 @@ Open Plant는 매 프레임 **캐시 전체**(최대 320장)를 viewport와 교
|
|
|
65
65
|
|
|
66
66
|
WebGL 캔버스(z-index: 1) 위에 Canvas 2D(z-index: 2)를 올려 어노테이션을 처리합니다.
|
|
67
67
|
draw mode가 아닐 때는 `pointerEvents: "none"`으로 이벤트가 WebGL에 바로 통과하고,
|
|
68
|
-
draw mode에 진입하면 `setPointerCapture`로 입력을
|
|
69
|
-
|
|
68
|
+
draw mode에 진입하면 `setPointerCapture`로 입력을 독점해 팬(드래그)은 차단합니다.
|
|
69
|
+
대신 wheel zoom은 오버레이에서 renderer로 전달해 draw/stamp 상태에서도 확대/축소를 유지합니다.
|
|
70
70
|
|
|
71
71
|
## Features
|
|
72
72
|
|
|
@@ -75,12 +75,14 @@ draw mode에 진입하면 `setPointerCapture`로 입력을 독점한 뒤 `intera
|
|
|
75
75
|
| **WebGL2 타일 렌더링** | 멀티 티어 타일 피라미드, LRU 캐시(320장), 저해상도 fallback 렌더링 |
|
|
76
76
|
| **회전 인터랙션** | `WsiViewState.rotationDeg`, `Ctrl/Cmd + drag` 회전, `resetRotation` 경로 |
|
|
77
77
|
| **포인트 오버레이** | WebGL2 `gl.POINTS`로 수십, 수백만 개 포인트를 팔레트 텍스처 기반 컬러링. 파싱된 TypedArray만 입력 |
|
|
78
|
+
| **포인트 크기 커스터마이즈** | `pointSizeByZoom` 객체로 zoom별 셀(px) 크기 지정 + 내부 선형 보간 |
|
|
78
79
|
| **모바일 타겟 성능** | iPhone 15급 환경에서 수백만 cell 워크로드를 전제로 pan/zoom 응답성을 유지하도록 설계 |
|
|
79
80
|
| **드로잉 / ROI 도구** | Freehand · Rectangle · Circular + Stamp(사각형/원, mm² 지정) |
|
|
80
81
|
| **고정 픽셀 스탬프** | `stamp-rectangle-4096px` + `stampOptions.rectanglePixelSize` |
|
|
81
82
|
| **ROI 포인트 클리핑** | `clipMode`: `sync` / `worker` / `hybrid-webgpu` (실험) |
|
|
82
83
|
| **ROI 통계 API** | `computeRoiPointGroups()` + `onRoiPointGroups` 콜백 |
|
|
83
84
|
| **ROI 커스텀 오버레이** | `resolveRegionStrokeStyle`, `overlayShapes` |
|
|
85
|
+
| **포인트 Hit-Test** | `onPointHover`, `onPointClick`, `getCellByCoordinatesRef`로 좌표→cell 매핑 |
|
|
84
86
|
| **WebGPU 연산 경로** | WebGPU capability 체크 + ROI bbox prefilter compute(실험) |
|
|
85
87
|
| **오버뷰 미니맵** | 썸네일 + 현재 뷰포트 인디케이터, 클릭/드래그 네비게이션 |
|
|
86
88
|
| **React 바인딩** | `<WsiViewerCanvas>`, `<DrawLayer>`, `<OverviewMap>` 컴포넌트 제공 |
|
|
@@ -137,6 +139,12 @@ import { WsiViewerCanvas } from "open-plant";
|
|
|
137
139
|
authToken={bearerToken}
|
|
138
140
|
pointData={pointPayload}
|
|
139
141
|
pointPalette={termPalette.colors}
|
|
142
|
+
pointSizeByZoom={{
|
|
143
|
+
1: 2.8,
|
|
144
|
+
6: 8.4,
|
|
145
|
+
10: 17.5,
|
|
146
|
+
12: 28,
|
|
147
|
+
}}
|
|
140
148
|
clipPointsToRois
|
|
141
149
|
clipMode="worker"
|
|
142
150
|
onClipStats={(s) => console.log(s.mode, s.durationMs)}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
(function(){"use strict";function d(){return typeof performance<"u"&&typeof performance.now=="function"?performance.now():Date.now()}function g(n){if(!Array.isArray(n)||n.length<3)return[];const t=n.map(([i,s])=>[i,s]),o=t[0],e=t[t.length-1];return!o||!e?[]:((o[0]!==e[0]||o[1]!==e[1])&&t.push([o[0],o[1]]),t)}function w(n){const t=[];for(const o of n??[]){const e=g(o);if(e.length<4)continue;let i=1/0,s=1/0,a=-1/0,r=-1/0;for(const[c,u]of e)c<i&&(i=c),c>a&&(a=c),u<s&&(s=u),u>r&&(r=u);!Number.isFinite(i)||!Number.isFinite(s)||t.push({ring:e,minX:i,minY:s,maxX:a,maxY:r})}return t}function U(n,t,o){let e=!1;for(let i=0,s=o.length-1;i<o.length;s=i,i+=1){const a=o[i][0],r=o[i][1],c=o[s][0],u=o[s][1];r>t!=u>t&&n<(c-a)*(t-r)/(u-r||Number.EPSILON)+a&&(e=!e)}return e}function M(n,t,o){for(const e of o)if(!(n<e.minX||n>e.maxX||t<e.minY||t>e.maxY)&&U(n,t,e.ring))return!0;return!1}function P(n){if(n instanceof Error)return n.message;try{return String(n)}catch{return"unknown worker error"}}const h=self;function F(n){const t=d(),o=Math.max(0,Math.floor(n.count)),e=new Float32Array(n.positions),i=new Uint16Array(n.paletteIndices),s=n.ids?new Uint32Array(n.ids):null,a=Math.floor(e.length/2),r=Math.max(0,Math.min(o,a,i.length)),c=s instanceof Uint32Array&&s.length>=r,u=w(n.polygons??[]);if(r===0||u.length===0){const p={type:"roi-clip-success",id:n.id,count:0,positions:new Float32Array(0).buffer,paletteIndices:new Uint16Array(0).buffer,durationMs:d()-t};return c&&(p.ids=new Uint32Array(0).buffer),p}const f=new Float32Array(r*2),x=new Uint16Array(r),y=c?new Uint32Array(r):null;let l=0;for(let p=0;p<r;p+=1){const I=e[p*2],b=e[p*2+1];M(I,b,u)&&(f[l*2]=I,f[l*2+1]=b,x[l]=i[p],y&&(y[l]=s[p]),l+=1)}const C=f.slice(0,l*2),E=x.slice(0,l),m=y?y.slice(0,l):null,A={type:"roi-clip-success",id:n.id,count:l,positions:C.buffer,paletteIndices:E.buffer,durationMs:d()-t};return m&&(A.ids=m.buffer),A}function q(n){const t=d(),o=Math.max(0,Math.floor(n.count)),e=new Float32Array(n.positions),i=Math.floor(e.length/2),s=Math.max(0,Math.min(o,i)),a=w(n.polygons??[]);if(s===0||a.length===0)return{type:"roi-clip-index-success",id:n.id,count:0,indices:new Uint32Array(0).buffer,durationMs:d()-t};const r=new Uint32Array(s);let c=0;for(let f=0;f<s;f+=1){const x=e[f*2],y=e[f*2+1];M(x,y,a)&&(r[c]=f,c+=1)}const u=r.slice(0,c);return{type:"roi-clip-index-success",id:n.id,count:c,indices:u.buffer,durationMs:d()-t}}h.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=q(t);h.postMessage(i,[i.indices]);return}const o=F(t),e=[o.positions,o.paletteIndices];o.ids&&e.push(o.ids),h.postMessage(o,e)}catch(o){const e={type:"roi-clip-failure",id:t.id,error:P(o)};h.postMessage(e)}})})();
|
|
2
|
+
//# sourceMappingURL=roi-clip-worker-D3hALtsJ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"roi-clip-worker-D3hALtsJ.js","sources":["../src/workers/roi-clip-worker.ts"],"sourcesContent":["import type { RoiCoordinate, RoiPolygon } from \"../wsi/point-clip\";\nimport type {\n RoiClipWorkerDataRequest,\n RoiClipWorkerIndexRequest,\n RoiClipWorkerIndexSuccess,\n RoiClipWorkerRequest,\n RoiClipWorkerResponse,\n RoiClipWorkerSuccess,\n} from \"../wsi/point-clip-worker-protocol\";\n\ninterface PreparedPolygon {\n ring: RoiPolygon;\n minX: number;\n minY: number;\n maxX: number;\n maxY: number;\n}\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 closeRing(coords: RoiPolygon): RoiPolygon {\n if (!Array.isArray(coords) || coords.length < 3) return [];\n const out = coords.map(([x, y]) => [x, y] as RoiCoordinate);\n const first = out[0];\n const last = out[out.length - 1];\n if (!first || !last) return [];\n if (first[0] !== last[0] || first[1] !== last[1]) {\n out.push([first[0], first[1]]);\n }\n return out;\n}\n\nfunction preparePolygons(polygons: RoiPolygon[]): PreparedPolygon[] {\n const prepared: PreparedPolygon[] = [];\n for (const poly of polygons ?? []) {\n const ring = closeRing(poly);\n if (ring.length < 4) continue;\n let minX = Infinity;\n let minY = Infinity;\n let maxX = -Infinity;\n let maxY = -Infinity;\n for (const [x, y] of ring) {\n if (x < minX) minX = x;\n if (x > maxX) maxX = x;\n if (y < minY) minY = y;\n if (y > maxY) maxY = y;\n }\n if (!Number.isFinite(minX) || !Number.isFinite(minY)) continue;\n prepared.push({ ring, minX, minY, maxX, maxY });\n }\n return prepared;\n}\n\nfunction isInsideRing(x: number, y: number, ring: RoiPolygon): boolean {\n let inside = false;\n for (let i = 0, j = ring.length - 1; i < ring.length; j = i, i += 1) {\n const xi = ring[i][0];\n const yi = ring[i][1];\n const xj = ring[j][0];\n const yj = ring[j][1];\n const intersect = yi > y !== yj > y && x < ((xj - xi) * (y - yi)) / (yj - yi || Number.EPSILON) + xi;\n if (intersect) inside = !inside;\n }\n return inside;\n}\n\nfunction isInsideAnyPolygon(x: number, y: number, polygons: PreparedPolygon[]): boolean {\n for (const poly of polygons) {\n if (x < poly.minX || x > poly.maxX || y < poly.minY || y > poly.maxY) {\n continue;\n }\n if (isInsideRing(x, y, poly.ring)) {\n return true;\n }\n }\n return false;\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 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));\n const hasIds = ids instanceof Uint32Array && ids.length >= safeCount;\n const prepared = preparePolygons(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 (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 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 (!isInsideAnyPolygon(x, y, prepared)) continue;\n nextPositions[cursor * 2] = x;\n nextPositions[cursor * 2 + 1] = y;\n nextTerms[cursor] = terms[i];\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 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 (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 = preparePolygons(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 (!isInsideAnyPolygon(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.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":["nowMs","closeRing","coords","out","x","y","first","last","preparePolygons","polygons","prepared","poly","ring","minX","minY","maxX","maxY","isInsideRing","inside","j","xi","yi","xj","yj","isInsideAnyPolygon","toErrorMessage","error","workerScope","handleDataRequest","msg","start","count","positions","terms","ids","maxCountByPositions","safeCount","hasIds","empty","nextPositions","nextTerms","nextIds","cursor","i","outPositions","outTerms","outIds","success","handleIndexRequest","outIndices","event","data","response","transfer","fail"],"mappings":"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"}
|