Open Plant WSI
Quickstart

10분 내 첫 WSI 렌더

`WsiViewerCanvas`를 기준으로 타일, 포인트, ROI를 순서대로 연결합니다. 아래 코드는 현재 라이브러리 API(`src/index.ts`)에 맞춰 작성되어 있습니다.

1. 설치

npm install
npm run dev
현재 레포는 모노레포 패키지 publish 이전 단계입니다. 로컬 라이브러리 코드(`src`)를 직접 import 하거나 workspace 의존성으로 연결해 사용하세요.

2. 이미지 정보 조회 후 정규화

import { normalizeImageInfo, toBearerToken } from "open-plant";

const IMAGE_ID = "69898975a05a9cd9ec6fe311";
const token = "<access-token>";

const res = await fetch(
  `https://your-api.example.com/api/v4/images/${IMAGE_ID}/info`,
  {
    headers: { Authorization: toBearerToken(token) },
  },
);

if (!res.ok) throw new Error(`HTTP ${res.status}`);

const rawInfo = await res.json();
const TILE_BASE_URL = "https://your-s3-bucket.example.com/ims";
const source = normalizeImageInfo(rawInfo, TILE_BASE_URL);
const pointZstUrl = rawInfo?.mvtPath || "";

3. 최소 뷰어 렌더

import { useState } from "react";
import { WsiViewerCanvas } from "open-plant";

function Viewer({ source, token }) {
  const [viewState, setViewState] = useState();
  const [rotationResetNonce, setRotationResetNonce] = useState(0);
  const [selectedRoiId, setSelectedRoiId] = useState(null);

  return (
    <WsiViewerCanvas
      source={source}
      authToken={token}
      viewState={viewState}
      onViewStateChange={setViewState}
      ctrlDragRotate
      rotationResetNonce={rotationResetNonce}
      activeRegionId={selectedRoiId}
      onActiveRegionChange={setSelectedRoiId}
      onPointerWorldMove={(event) => {
        // event.coordinate -> [x, y] | null
      }}
      onStats={(s) => console.log(s)}
      style={{ width: "100vw", height: "100vh" }}
    />
  );
}

4. 카메라 제한/전환 + 타일 색상 보정

<WsiViewerCanvas
  source={source}
  imageColorSettings={{
    brightness: 0,
    contrast: 0,
    saturation: 0,
  }}
  minZoom={0.25}
  maxZoom={1}
  viewTransition={{ duration: 300 }}
  autoLiftRegionLabelAtMaxZoom
/>

색상 보정은 타일 셰이더에만 적용됩니다. point/cell marker, ROI, draw overlay 색상은 변경되지 않습니다.

<WsiViewerCanvas
  source={source}
  zoomSnaps={[1.25, 2.5, 5, 10, 20, 40]}
  zoomSnapFitAsMin
/>

zoomSnaps는 배율 단계이며, 내부적으로 source.mpp 기준 zoom 값으로 정규화됩니다.

5. 포인트 데이터 전달 + term 팔레트 매핑

포인트 로딩/파싱(ZST/MVT 디코딩 등)은 라이브러리 외부에서 처리합니다. 파싱 완료된 TypedArray를 뷰어에 전달하세요.

import { buildTermPalette } from "open-plant";

// positions: Float32Array [x0,y0,x1,y1,...] (직접 구현한 로더)
// paletteIndices: Uint16Array (term 테이블에서 매핑)

const termPalette = buildTermPalette(source.terms);

const pointData = {
  count: positions.length / 2,
  positions,
  paletteIndices,
  fillModes, // optional Uint8Array (0: ring, 1: solid)
};
<WsiViewerCanvas
  source={source}
  authToken={toBearerToken(token)}
  pointData={pointData}
  pointPalette={termPalette.colors}
/>

6. ROI 폴리곤 클리핑

<WsiViewerCanvas
  source={source}
  pointData={pointData}
  pointPalette={termPalette.colors}
  roiRegions={[
    {
      id: "roi-1",
      label: "Tumor Core",
      coordinates: [
        [12000, 14000],
        [18000, 14000],
        [18000, 20000],
        [12000, 20000],
        [12000, 14000],
      ],
    },
  ]}
  clipPointsToRois
/>

7. ROI term 통계 콜백

<WsiViewerCanvas
  source={source}
  pointData={pointData}
  roiRegions={regions}
  roiPaletteIndexToTermId={new Map([[1, "negative"], [2, "positive"]])}
  onRoiPointGroups={(stats) => {
    // stats.groups -> ROI별 term count
  }}
/>

8. ROI 가속 모드(worker / hybrid-webgpu)

import { getWebGpuCapabilities } from "open-plant";

const caps = await getWebGpuCapabilities();
const clipMode = caps.supported ? "hybrid-webgpu" : "worker";
<WsiViewerCanvas
  source={source}
  pointData={pointData}
  pointPalette={termPalette.colors}
  roiRegions={regions}
  clipPointsToRois
  clipMode={clipMode} // "sync" | "worker" | "hybrid-webgpu"
  onClipStats={(stats) => {
    console.log(stats.mode, stats.durationMs, stats.outputCount);
  }}
/>
권장 기본값: 먼저 clipMode="worker"를 사용하세요. hybrid-webgpu는 실데이터 벤치마크 후 선택하는 것을 권장합니다.