Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,13 +102,13 @@ and this project follows [Semantic Versioning](https://semver.org/).
- Region style resolver API: `resolveRegionStrokeStyle`.
- Custom overlay shape API for patch/dashed guides: `overlayShapes`.
- Fixed-pixel stamp tool: `stamp-rectangle-4096px` and `stampOptions.rectanglePixelSize`.
- ROI term-group utility and callback path: `computeRoiPointGroups`, `onRoiPointGroups`.
- ROI class-group utility and callback path: `computeRoiPointGroups`, `onRoiPointGroups`.
- Release gate workflow: `.github/workflows/release-gate.yml`.
- PR template: `.github/pull_request_template.md`.
- Contribution guides: root `CONTRIBUTING.md`, docs EN/KO `contributing.html`.
- Hybrid WebGPU draw bridge payload support via `WsiPointData.drawIndices`.
- Hybrid clip option `bridgeToDraw` and clip stat flag `bridgedToDraw`.
- Unit test coverage for ROI term stats with draw-index bridge input.
- Unit test coverage for ROI class stats with draw-index bridge input.
- Patch-intent draw path for `stamp-rectangle-4096px` with dedicated `onPatchComplete` callback.
- Patch overlay channel on viewer (`patchRegions`, `patchStrokeStyle`) separated from ROI hover/active interaction.
- Custom React overlay layer slots via `customLayers` for host-owned rendering pipelines.
Expand All @@ -124,7 +124,7 @@ and this project follows [Semantic Versioning](https://semver.org/).
- Publish gate now enforces `npm run release:gate` via `prepublishOnly`.

### Docs
- Updated EN/KO API and guides for rotation, pointer world callbacks, overlay shapes, 4096px patch intent flow, custom layers, and ROI term stats.
- Updated EN/KO API and guides for rotation, pointer world callbacks, overlay shapes, 4096px patch intent flow, custom layers, and ROI class stats.
- Updated `todo.md` gap table with current support status and code-path references.
- Added EN/KO migration guides with API stability/deprecation policy and release-gate contract.
- Added EN/KO contributing pages and linked them across docs navigation.
Expand Down
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ Open Plant는 WSI 렌더링 **한 가지만** 하도록 설계되었고, 그래

### Open Plant vs deck.gl vs OpenLayers — 숫자로 증명하는 압도적 차이

같은 **합성 포인트 데이터**(랜덤 2D 좌표 + 16-term 팔레트)를 **3개 엔진의 각각 최적 경로**로 나란히 측정했습니다. (Apple M-시리즈, Chrome 실측)
같은 **합성 포인트 데이터**(랜덤 2D 좌표 + 16-class 팔레트)를 **3개 엔진의 각각 최적 경로**로 나란히 측정했습니다. (Apple M-시리즈, Chrome 실측)

- **deck.gl v9** — `ScatterplotLayer` binary accessor (`data.attributes`에 TypedArray 직전달, JS 객체 0개)
- **OpenLayers v10** — `WebGLVectorLayer` + `RenderFeature` (가장 가벼운 Feature 모델)
Expand Down Expand Up @@ -112,7 +112,7 @@ Open Plant는 “고사양 PC에서만 빠른 뷰어”가 아니라, iPhone 15

범용 라이브러리는 포인트마다 인스턴스 버퍼에 position + RGBA를 넣어 **20바이트 이상** 씁니다.
Open Plant는 `Float32Array`(x, y) 8바이트 + `Uint16Array`(palette index) 2바이트 = **10바이트**입니다.
색상은 1×N 팔레트 텍스처 1장에 들어가므로, term 색상을 바꿀 때 수백 바이트짜리 텍스처만 재업로드하면 됩니다.
색상은 1×N 팔레트 텍스처 1장에 들어가므로, class 색상을 바꿀 때 수백 바이트짜리 텍스처만 재업로드하면 됩니다.
50만 셀 기준 GPU 메모리가 **절반 이하**로 줄어듭니다.

### 프래그먼트 셰이더 안에서 끝나는 링 렌더링
Expand Down Expand Up @@ -202,7 +202,7 @@ src/
│ ├── wsi-tile-renderer.ts # WebGL2 멀티티어 타일 + 포인트, 입력, 애니메이션
│ ├── wsi-render-pass.ts # 프레임당 draw 순서: fallback 타일 → visible 타일 → 포인트
│ ├── wsi-shaders.ts # 타일·포인트 GLSL 프로그램 초기화
│ ├── wsi-point-data.ts # 포인트 VBO 업로드 (positions / terms / fillModes / drawIndices)
│ ├── wsi-point-data.ts # 포인트 VBO 업로드 (positions / classes / fillModes / drawIndices)
│ ├── wsi-interaction.ts # 포인터·휠·스냅 줌 이벤트 처리
│ ├── wsi-input-handlers.ts # interaction lock 등 래핑
│ ├── wsi-zoom-snap.ts # 배율 스냅 애니메이션
Expand All @@ -216,7 +216,7 @@ src/
│ ├── point-clip-worker-client.ts / point-clip-worker-protocol.ts
│ ├── point-clip-hybrid.ts # WebGPU bbox prefilter (실험)
│ ├── point-hit-index-*.ts # 포인트 공간 해시 인덱스 (워커)
│ ├── roi-geometry.ts / roi-term-stats.ts / brush-stroke.ts
│ ├── roi-geometry.ts / roi-class-stats.ts / brush-stroke.ts
│ ├── image-info.ts / types.ts / utils.ts / wkt.ts / webgpu.ts / constants.ts
│ └── …
├── workers/
Expand Down Expand Up @@ -383,10 +383,10 @@ import {
| `useViewerContext` | 컨텍스트 (`rendererRef`, `worldToScreen`, …) |
| `WsiTileRenderer`, `M1TileRenderer`, `TileScheduler` | 코어 렌더러·타일 큐 |
| `normalizeImageInfo`, `toTileUrl`, `toRoiGeometry`, `parseWkt` | 이미지/ROI/WKT |
| `buildTermPalette`, `calcScaleResolution`, `calcScaleLength`, `toBearerToken`, `clamp`, `hexToRgba`, `isSameViewState` | 유틸 |
| `buildClassPalette`, `calcScaleResolution`, `calcScaleLength`, `toBearerToken`, `clamp`, `hexToRgba`, `isSameViewState` | 유틸 |
| `filterPointDataByPolygons`, `filterPointIndicesByPolygons`, `filterPointDataByPolygonsInWorker`, `filterPointIndicesByPolygonsInWorker`, `terminateRoiClipWorker`, `filterPointDataByPolygonsHybrid` | ROI 클리핑 |
| `buildPointSpatialIndexAsync`, `lookupCellIndex`, `terminatePointHitIndexWorker` | 포인트 공간 인덱스(워커) |
| `computeRoiPointGroups` | ROI term 통계 |
| `computeRoiPointGroups` | ROI class 통계 |
| `getWebGpuCapabilities`, `prefilterPointsByBoundsWebGpu` | WebGPU(실험) |
| `closeRing`, `createRectangle`, `createCircle` | 도형 |
| 타입 (`WsiViewerProps`, `WsiImageSource`, `WsiPointData`, `WsiViewState`, `DrawTool`, `PointHitEvent`, …) | TS |
Expand Down
6 changes: 3 additions & 3 deletions benchmark/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,10 @@ function initOP(canvas, data) {

const vs = `#version 300 es
precision highp float;
in vec2 aPos; in uint aTerm;
in vec2 aPos; in uint aClass;
uniform mat3 uCam; uniform float uSz;
flat out uint vT;
void main(){ vec3 c=uCam*vec3(aPos,1.); gl_Position=vec4(c.xy,0.,1.); gl_PointSize=uSz; vT=aTerm; }`;
void main(){ vec3 c=uCam*vec3(aPos,1.); gl_Position=vec4(c.xy,0.,1.); gl_PointSize=uSz; vT=aClass; }`;
const fs = `#version 300 es
precision highp float;
flat in uint vT;
Expand Down Expand Up @@ -100,7 +100,7 @@ void main(){
const tb = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, tb);
gl.bufferData(gl.ARRAY_BUFFER, data.idx, gl.STATIC_DRAW);
const tl = gl.getAttribLocation(pg, "aTerm");
const tl = gl.getAttribLocation(pg, "aClass");
gl.enableVertexAttribArray(tl);
gl.vertexAttribIPointer(tl, 1, gl.UNSIGNED_SHORT, 0, 0);
const pt = gl.createTexture();
Expand Down
14 changes: 10 additions & 4 deletions docs/en/api-reference.html
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ <h2>Legacy <code>WsiViewerCanvas</code> <span style="color:#b33;">removed in v1.
<li><strong>Historical prop list:</strong> preserved in git history and in <code>migration-1.4.0.md</code> mapping tables.</li>
</ul>
<div class="callout">
ROI term groups: call <code>computeRoiPointGroups()</code> from app code instead of <code>onRoiPointGroups</code>. <code>customLayers</code> has no direct replacement — use <code>useViewerContext</code> + a host React overlay.
ROI class groups: call <code>computeRoiPointGroups()</code> from app code instead of <code>onRoiPointGroups</code>. <code>customLayers</code> has no direct replacement — use <code>useViewerContext</code> + a host React overlay.
</div>
</section>

Expand Down Expand Up @@ -299,10 +299,15 @@ <h2>Core types</h2>
maxTierZoom: number;
tilePath: string;
tileBaseUrl: string;
terms: WsiTerm[];
tileUrlBuilder?: (tier: number, x: number, y: number) => string;
}

interface WsiClass {
classId: string;
className: string;
classColor: string;
}

interface WsiPointData {
count: number;
positions: Float32Array;
Expand Down Expand Up @@ -374,14 +379,15 @@ <h2>Utility exports</h2>
<tbody>
<tr><td><code>normalizeImageInfo(raw, tileBaseUrl)</code></td><td>Converts backend payload + tile base URL to <code>WsiImageSource</code>.</td></tr>
<tr><td><code>toTileUrl(source, tier, x, y)</code></td><td>Builds IMS tile URL.</td></tr>
<tr><td><code>buildTermPalette(terms)</code></td><td>Builds termId to palette mapping and RGBA texture.</td></tr>
<tr><td><code>normalizeImageClasses(raw)</code></td><td>Normalizes class metadata from backend payloads.</td></tr>
<tr><td><code>buildClassPalette(classes)</code></td><td>Builds classId to palette mapping and RGBA texture.</td></tr>
<tr><td><code>filterPointDataByPolygons(data, polygons)</code></td><td>Filters points inside ROI polygons only.</td></tr>
<tr><td><code>filterPointDataByPolygonsInWorker(data, polygons)</code></td><td>Runs ROI clipping in a dedicated worker thread.</td></tr>
<tr><td><code>terminateRoiClipWorker()</code></td><td>Terminates the shared ROI clip worker instance (useful on teardown/tests).</td></tr>
<tr><td><code>filterPointIndicesByPolygons(data, polygons)</code></td><td>Returns original point indices inside polygons for patch JSON export pipelines.</td></tr>
<tr><td><code>filterPointIndicesByPolygonsInWorker(data, polygons)</code></td><td>Worker variant returning point indices + timing metadata.</td></tr>
<tr><td><code>filterPointDataByPolygonsHybrid(data, polygons, options?)</code></td><td>Experimental hybrid clipping (WebGPU bbox prefilter + exact polygon test). Use <code>{ bridgeToDraw: true }</code> to return full buffers + <code>drawIndices</code> bridge payload.</td></tr>
<tr><td><code>computeRoiPointGroups(pointData, regions, options)</code></td><td>Computes per-ROI term counts (<code>roiPointGroups</code>-style) from typed point buffers.</td></tr>
<tr><td><code>computeRoiPointGroups(pointData, regions, options)</code></td><td>Computes per-ROI class counts (<code>roiPointGroups</code>-style) from typed point buffers.</td></tr>
<tr><td><code>getWebGpuCapabilities()</code></td><td>Returns WebGPU support/adapter/features for runtime decisions.</td></tr>
<tr><td><code>prefilterPointsByBoundsWebGpu(positions, count, bounds)</code></td><td>Experimental compute pass to classify points by ROI bounding boxes.</td></tr>
<tr><td><code>calcScaleResolution(imageMpp, imageZoom, currentZoom)</code></td><td>Returns microns-per-screen-pixel for the current zoom.</td></tr>
Expand Down
4 changes: 2 additions & 2 deletions docs/en/architecture.html
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ <h2>Point pipeline</h2>
<div class="flow-step"><strong>GPU upload:</strong> positions and palette index buffers.</div>
<div class="flow-step"><strong>Shader render:</strong> palette-based point fragments in ring/solid mode via <code>fillModes</code>.</div>
<div class="flow-step"><strong>ROI filter:</strong> optional polygon clip before upload/render via <code>clipMode</code>.</div>
<div class="flow-step"><strong>ROI stats:</strong> optional per-ROI term aggregation via <code>computeRoiPointGroups</code> / <code>onRoiPointGroups</code>.</div>
<div class="flow-step"><strong>ROI stats:</strong> optional per-ROI class aggregation via <code>computeRoiPointGroups</code> / <code>onRoiPointGroups</code>.</div>
</div>
</section>

Expand Down Expand Up @@ -160,7 +160,7 @@ <h2>WebGPU expansion path (compute-focused)</h2>
<div class="card-grid">
<div class="card"><h3>ROI culling</h3><p>Started with bbox prefilter compute pass, then exact polygon phase.</p></div>
<div class="card"><h3>LOD aggregation</h3><p>Low-zoom density aggregation and binning on GPU.</p></div>
<div class="card"><h3>Term histogram</h3><p>ROI-level term counts and positivity stats in parallel.</p></div>
<div class="card"><h3>Class histogram</h3><p>ROI-level class counts and positivity stats in parallel.</p></div>
<div class="card"><h3>Interop</h3><p>Pack compute output for direct WebGL buffer upload.</p></div>
</div>
<div class="callout">
Expand Down
8 changes: 4 additions & 4 deletions docs/en/draw-and-roi.html
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ <h2>ROI Draw</h2>
<a href="#persist">Persisted regions</a>
<a href="#style">Custom styles</a>
<a href="#clip">Point clipping</a>
<a href="#roi-stats">ROI term stats</a>
<a href="#roi-stats">ROI class stats</a>
</aside>

<article class="content">
Expand Down Expand Up @@ -293,13 +293,13 @@ <h2>7. Render cells only inside drawn regions</h2>
</section>

<section class="panel reveal" id="roi-stats">
<h2>8. ROI term-count stats API</h2>
<h2>8. ROI class-count stats API</h2>
<pre><code class="language-tsx">import { computeRoiPointGroups } from "open-plant";

const stats = computeRoiPointGroups(pointData, regions, {
paletteIndexToTermId: ["bg", "negative", "positive"],
paletteIndexToClassId: ["bg", "negative", "positive"],
});
// stats.groups -&gt; [{ regionId, totalCount, termCounts[] }]</code></pre>
// stats.groups -&gt; [{ regionId, totalCount, classCounts[] }]</code></pre>
<p>
Call <code>computeRoiPointGroups</code> when <code>pointData</code> or <code>regions</code> change — it replaces the removed <code>onRoiPointGroups</code> prop from legacy <code>WsiViewerCanvas</code>.
</p>
Expand Down
20 changes: 10 additions & 10 deletions docs/en/getting-started.html
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ <h2>Quick Start</h2>
<a href="#camera-color">Camera + Color</a>
<a href="#points">Load Points</a>
<a href="#roi">ROI Clip</a>
<a href="#roi-stats">ROI Term Stats</a>
<a href="#roi-stats">ROI Class Stats</a>
<a href="#clip-accel">ROI Acceleration</a>
</aside>

Expand Down Expand Up @@ -138,12 +138,12 @@ <h2>5. Provide point data and build palette</h2>
Point loading and parsing (e.g. ZST/MVT decoding) is <strong>not</strong> part of the library.
Parse points externally, then pass typed arrays to the viewer.
</p>
<pre><code class="language-ts">import { buildTermPalette } from "open-plant";
<pre><code class="language-ts">import { buildClassPalette } from "open-plant";

// positions: Float32Array [x0,y0,x1,y1,...] (your own loader)
// paletteIndices: Uint16Array (mapped from your term table)
// paletteIndices: Uint16Array (mapped from your class table)

const termPalette = buildTermPalette(source.terms);
const classPalette = buildClassPalette(classes);

const pointData = {
count: positions.length / 2,
Expand All @@ -155,7 +155,7 @@ <h3>Composition API</h3>
<pre><code class="language-tsx">import { WsiViewer, PointLayer } from "open-plant";

&lt;WsiViewer source={source} authToken={toBearerToken(token)}&gt;
&lt;PointLayer data={pointData} palette={termPalette.colors} /&gt;
&lt;PointLayer data={pointData} palette={classPalette.colors} /&gt;
&lt;/WsiViewer&gt;</code></pre>
</section>

Expand All @@ -178,7 +178,7 @@ <h2>6. ROI polygon clipping</h2>
&lt;WsiViewer source={source}&gt;
&lt;PointLayer
data={pointData}
palette={termPalette.colors}
palette={classPalette.colors}
clipEnabled
clipToRegions={roiRegions}
/&gt;
Expand All @@ -187,16 +187,16 @@ <h2>6. ROI polygon clipping</h2>
</section>

<section class="panel reveal" id="roi-stats">
<h2>7. ROI term stats</h2>
<h2>7. ROI class stats</h2>
<p>
<code>onRoiPointGroups</code> was removed with <code>WsiViewerCanvas</code>. Call <code>computeRoiPointGroups</code> when your data changes:
</p>
<pre><code class="language-ts">import { computeRoiPointGroups } from "open-plant";

const stats = computeRoiPointGroups(pointData, regions, {
paletteIndexToTermId: new Map([[1, "negative"], [2, "positive"]]),
paletteIndexToClassId: new Map([[1, "negative"], [2, "positive"]]),
});
// stats.groups -&gt; per-ROI term counts</code></pre>
// stats.groups -&gt; per-ROI class counts</code></pre>
</section>

<section class="panel reveal" id="clip-accel">
Expand All @@ -208,7 +208,7 @@ <h2>8. ROI acceleration mode (worker / hybrid-webgpu)</h2>
<pre><code class="language-tsx">&lt;WsiViewer source={source}&gt;
&lt;PointLayer
data={pointData}
palette={termPalette.colors}
palette={classPalette.colors}
clipEnabled
clipToRegions={regions}
clipMode={clipMode}
Expand Down
2 changes: 1 addition & 1 deletion docs/en/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ <h3>Tile Renderer</h3>
</div>
<div class="card">
<h3>Point Renderer</h3>
<p>Accepts pre-parsed typed arrays, term-color palette texture rendering, and ROI clipping.</p>
<p>Accepts pre-parsed typed arrays, class-color palette texture rendering, and ROI clipping.</p>
</div>
<div class="card">
<h3>Draw Layer</h3>
Expand Down
2 changes: 1 addition & 1 deletion docs/en/migration-guide.html
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ <h3>New DrawTool types</h3>
<h3>No direct layer props (app-side)</h3>
<ul>
<li><code>customLayers</code> → <code>useViewerContext</code> + host React overlay</li>
<li><code>onRoiPointGroups</code> / <code>roiPaletteIndexToTermId</code> → <code>computeRoiPointGroups()</code></li>
<li><code>onRoiPointGroups</code> / <code>roiPaletteIndexToClassId</code> → <code>computeRoiPointGroups()</code></li>
</ul>
</section>

Expand Down
Loading
Loading