VTK ์ขํ๊ณ์์
3๊ฐ ๋ทฐ ๋์์ BBox ๊ทธ๋ฆฌ๊ธฐ
Axial ์ฌ๋ผ์ด์ค์ 2D ํฝ์ ์ขํ๋ฅผ ์ด๋ป๊ฒ 3D ๊ณต๊ฐ์ผ๋ก ๋์ด์ฌ๋ฆฌ๊ณ , ๋ค์ SagittalยทCoronal ๋ทฐ์ Canvas ํฝ์ ๋ก ๋จ์ดํธ๋ฆฌ๋์ง โ VTK ์ขํ ๋ณํ ์ฒด์ธ์ ์ ๊ณผ์ ์ ์ฝ๋์ ํจ๊ป ์ค๋ช ํฉ๋๋ค.
๐ ์๋ฆฌ์ฆ: SEG NIfTI์์ BBox JSON์ผ๋ก
๋ฌด์์ด ์ด๋ ค์ด๊ฐ
2ํธ์์ ๊ตฌํํ drawBBoxOverlay๋ AxialGridViewer์์๋ง ๋์ํฉ๋๋ค. Canvas ํฝ์
์ขํ์ ๋ณต์
์ธ๋ฑ์ค๊ฐ 1:1๋ก ๋งคํ๋๊ธฐ ๋๋ฌธ์ ๋ณํ ์์ด ๋ฐ๋ก ์ธ ์ ์์์ต๋๋ค.
MPR ๋ทฐ(Axial/Sagittal/Coronal)๋ ๋ค๋ฆ ๋๋ค. VTK๋ ๋ด๋ถ์ ์ผ๋ก 3D ๊ณต๊ฐ์ WebGL๋ก ๋ ๋๋งํ ๋ค, ๊ทธ ๊ฒฐ๊ณผ๋ฅผ Canvas ํฝ์ ๋ก ๋ณต์ฌํฉ๋๋ค. ์ฌ์ฉ์๊ฐ ์คยทํฌ์ ํ๋ฉด ๊ฐ์ ๋ณต์ ์ด ๋ค๋ฅธ Canvas ์์น์ ๊ทธ๋ ค์ง๋๋ค. ๋ฐ๋ผ์ annotation ์ขํ๋ฅผ ๊ทธ ์๊ฐ์ ์นด๋ฉ๋ผ ์ํ์ ๋ง๊ฒ ๋ณํํด์ผ ํฉ๋๋ค.
์ฌ๊ธฐ์ ๋ ํ๋์ ๋๊ด์ด ์์ต๋๋ค. BE๊ฐ ๋ด๋ ค์ค annotation์ Axial ์ฌ๋ผ์ด์ค ๊ธฐ์ค์ 2D ์ขํ์ ๋๋ค. ์ด ์ขํ๋ฅผ Sagittal, Coronal ๋ทฐ์์๋ ๊ทธ๋ฆฌ๋ ค๋ฉด 2D โ 3D โ 2D ๋ ๋ฒ์ ๋ณํ์ด ํ์ํฉ๋๋ค.
Axial ๋ทฐ
annotation ์๋ณธ ์ขํ(ํฝ์ ) โ IJK ๋ณํ โ World โ Display. ํ์ฌ ์ฌ๋ผ์ด์ค์ ์ผ์นํ๋ annotation๋ง ๊ทธ๋ฆฝ๋๋ค.
Sagittal ๋ทฐ
annotation์์ 3D BBox๋ฅผ ์ฌ๊ตฌ์ฑ โ ํ์ฌ I๊ฐ BBox I ๋ฒ์์ ์ํ๋ฉด JรK ํฌ์ ์ฌ๊ฐํ์ ๊ทธ๋ฆฝ๋๋ค.
Coronal ๋ทฐ
annotation์์ 3D BBox๋ฅผ ์ฌ๊ตฌ์ฑ โ ํ์ฌ J๊ฐ BBox J ๋ฒ์์ ์ํ๋ฉด IรK ํฌ์ ์ฌ๊ฐํ์ ๊ทธ๋ฆฝ๋๋ค.
๋จผ์ ๋ฐ๋์ ์ง๊ณ ๊ฐ์ผ ํ ๊ฒ โ ๋ช ๋ช ์ญ์
์ด ํ๋ก์ ํธ์์ ๊ฐ์ฅ ๋ง์ ๋ฒ๊ทธ๋ฅผ ๋ง๋ ์์ธ์ ์ฝ๋์ ๋ณต์กํจ์ด ์๋๋ผ ๋ ๋๋ฌ ๋ด๋ถ ๋ช ์นญ๊ณผ UI ๋ผ๋ฒจ์ ๋ถ์ผ์น์ ๋๋ค. ์ฒ์ ์ ํ๋ฉด ๋ฐ๋์ ํผ๋์ค๋ฝ์ต๋๋ค.
renderers.coronal์ ์ฌ์ฉ์์๊ฒ Axial(์ถ๋ฐฉํฅ)๋ก ๋ณด์ด๋ ๋ทฐ์
๋๋ค. renderers.axial์ Coronal(๊ด์๋ฉด)์ผ๋ก ๋ณด์
๋๋ค. ์ด ์ญ์ ์ ๋ชจ๋ฅด๊ณ ์ฝ๋๋ฅผ ์์ฑํ๋ฉด ์ฌ๋ฐ๋ฅธ ๋ทฐ์ ์ฌ๋ฐ๋ฅธ ๋ฐ์ค๊ฐ ๊ทธ๋ ค์ง์ง ์์ต๋๋ค. BBoxOverlay ์ ์ฒด์์ ์ด ๋งคํ์ ์ผ๊ด๋๊ฒ ์ง์ผ์ผ ํฉ๋๋ค.
์ขํ ๋ณํ ์ฒด์ธ โ IJK โ World โ Display โ Canvas
BBoxOverlay์์ annotation ์ขํ๊ฐ Canvas ํฝ์ ๋ก ๋ณํ๋๊ธฐ๊น์ง 4๋จ๊ณ๋ฅผ ๊ฑฐ์นฉ๋๋ค.
โ ํฝ์ ์ขํ โ IJK
BE๊ฐ ๋ด๋ ค์ค graphic_data์ [px, py]๋ Axial ์ฌ๋ผ์ด์ค์ ํฝ์
์ขํ์
๋๋ค. Axial(coronal ๋ ๋๋ฌ)์์๋ K์ถ์ด ๊ณ ์ ๋๋ฏ๋ก px=i, py=j, k=sliceK๋ก ์ง์ ๋งคํ๋ฉ๋๋ค.
// โ ํฝ์ ์ขํ โ IJK // Axial(coronal ๋ ๋๋ฌ): IรJ ํ๋ฉด, K ๊ณ ์ const pixelToIJK = ( px: number, py: number, sliceK: number ): [number, number, number] => { return [px, py, sliceK]; // [i, j, k] };
โก IJK โ World (mm)
VTK ImageData์ spacing(๋ณต์ ๊ฐ๊ฒฉ)๊ณผ origin(์์ )์ ์ด์ฉํด ๋ฌผ๋ฆฌ์ mm ์ขํ๋ก ๋ณํํฉ๋๋ค.
// โก IJK โ World (mm ๋จ์) // World = origin + IJK * spacing const ijkToWorld = ( ijk: [number, number, number], imageData: VtkImageData, ): [number, number, number] => { const [si, sj, sk] = imageData.getSpacing(); const [oi, oj, ok] = imageData.getOrigin(); return [ oi + ijk[0] * si, oj + ijk[1] * sj, ok + ijk[2] * sk, ]; };
โข World โ Display (VTK ํฝ์ )
VTK์ vtkCoordinate๋ฅผ ์ด์ฉํด World ์ขํ๋ฅผ ํ์ฌ ๋ทฐํฌํธ์ Display ํฝ์
๋ก ๋ณํํฉ๋๋ค. ์ด ๋ณํ์ ์นด๋ฉ๋ผ์ ์คยทํฌ ์ํ๋ฅผ ์๋์ผ๋ก ๋ฐ์ํฉ๋๋ค.
// โข World โ Display (VTK ๋ด๋ถ ํฝ์ ์ขํ) // vtkCoordinate๊ฐ ์นด๋ฉ๋ผ ํ๋ ฌ์ ์๋ ๋ฐ์ const worldToDisplay = ( world: [number, number, number], renderer: VtkRendererInstance, renderWindow: VtkRenderWindow, ): [number, number] => { const coord = vtkCoordinate.newInstance(); coord.setCoordinateSystemToWorld(); coord.setValue(...world); const [dx, dy] = coord.getComputedDisplayValue(renderer); coord.delete(); // VTK ๋ฉ๋ชจ๋ฆฌ ๋ช ์ ํด์ // VTK Display Y์ถ์ ํ๋จ ๊ธฐ์ค โ ์๋จ ๊ธฐ์ค์ผ๋ก ๋ฐ์ const [, winH] = renderWindow.getSize(); return [dx, winH - dy]; };
โฃ Display โ Canvas (DPR ๋ณด์ )
๊ณ ํด์๋(Retina) ํ๋ฉด์์๋ ๋ธ๋ผ์ฐ์ ์ devicePixelRatio(DPR)๊ฐ 1 ์ด์์ ๋๋ค. VTK Display ์ขํ๋ ๋ฌผ๋ฆฌ ํฝ์ ๊ธฐ์ค์ด๋ฏ๋ก CSS ํฝ์ ๋ก ๋๋์ด Canvas์ ๊ทธ๋ ค์ผ ํฉ๋๋ค.
// โฃ Display โ Canvas CSS ํฝ์ (DPR ๋ณด์ ) const dpr = window.devicePixelRatio || 1; const canvasX = displayX / dpr; const canvasY = displayY / dpr; // Canvas๋ CSS ํฝ์ ๊ธฐ์ค์ผ๋ก strokeRect / moveTo ํธ์ถ ctx.strokeRect(canvasX, canvasY, width / dpr, height / dpr);
devicePixelRatio๋ ์ฐฝ ์ด๋์ด๋ ์ธ๋ถ ๋ชจ๋ํฐ ์ฐ๊ฒฐ ์ ๋ณ๊ฒฝ๋๋ฏ๋ก, drawOverlay() ํธ์ถ ์์ ์ ๋งค๋ฒ ์๋ก ์ฝ์ด์ผ ํฉ๋๋ค.
SagittalยทCoronal ๋ทฐ๋ฅผ ์ํ 3D BBox ์ฌ๊ตฌ์ฑ
Axial ๋ทฐ๋ annotation ์๋ณธ ์ขํ๋ฅผ ๊ทธ๋๋ก ์ฌ์ฉํ๋ฉด ๋ฉ๋๋ค. ํ์ง๋ง SagittalยทCoronal์์๋ ๋ณ๋ณ์ด ๋ช ๋ฒ ์ฌ๋ผ์ด์ค๋ถํฐ ๋ช ๋ฒ ์ฌ๋ผ์ด์ค๊น์ง ๊ฑธ์ณ ์๋์ง์, ๊ฐ๋ก์ธ๋ก ์ผ๋ง๋ ํฐ์ง๋ฅผ ์์์ผ ํฉ๋๋ค. ์ด ์ ๋ณด๋ฅผ ๋ชจ๋ ์ฌ๋ผ์ด์ค์ annotation์์ ๋์ ํด 3D ๋ฐ์ด๋ฉ ๋ฐ์ค(aneurysm_id๋ณ min/max IJK)๋ฅผ ๊ณ์ฐํฉ๋๋ค.
interface AneurysmBBox { iMin: number; iMax: number; // I์ถ (๊ฐ๋ก) ๋ฒ์ jMin: number; jMax: number; // J์ถ (์ธ๋ก) ๋ฒ์ kMin: number; kMax: number; // K์ถ (์ฌ๋ผ์ด์ค) ๋ฒ์ color: string; } /** * ์ ์ฒด annotation์์ aneurysm_id๋ณ 3D BBox๋ฅผ ๊ณ์ฐํ๋ค. * ๋ชจ๋ ์ฌ๋ผ์ด์ค์ graphic_data๋ฅผ ๋์ ํด min/max๋ฅผ ๊ตฌํ๋ค. */ function buildAneurysmBBoxes( annotations: BBoxAnnotationItem[] ): Map<number, AneurysmBBox> { const bboxMap = new Map<number, AneurysmBBox>(); for (const ann of annotations) { const { aneurysm_id, instance_number: k, graphic_data, color } = ann; const xs = graphic_data.map(p => p[0]); // I ์ขํ const ys = graphic_data.map(p => p[1]); // J ์ขํ const prev = bboxMap.get(aneurysm_id); bboxMap.set(aneurysm_id, { iMin: prev ? Math.min(prev.iMin, ...xs) : Math.min(...xs), iMax: prev ? Math.max(prev.iMax, ...xs) : Math.max(...xs), jMin: prev ? Math.min(prev.jMin, ...ys) : Math.min(...ys), jMax: prev ? Math.max(prev.jMax, ...ys) : Math.max(...ys), kMin: prev ? Math.min(prev.kMin, k) : k, kMax: prev ? Math.max(prev.kMax, k) : k, color, }); } return bboxMap; // aneurysm_id โ 3D BBox }
๋ทฐ๋ณ ๋ ๋๋ง ์ ๋ต โ drawOverlay ๊ตฌํ
์ธ ๋ทฐ ๊ฐ๊ฐ์ ๋ ๋๋ง ๋ก์ง์ ์ดํด๋ด ๋๋ค. ํต์ฌ์ ์ด๋ค IJK ์ ์ World๋ก ๋ณํํ๊ณ , ์ด๋ค ๋ ์ ์ด ์ฌ๊ฐํ์ ๋ ๊ผญ์ง์ ์ด ๋๋๊ฐ์ ๋๋ค.
function drawOverlay() { if (!annotations || !imageData || !sliceIndices) return; // 3D BBox ์ฌ์ ๊ณ์ฐ (annotation ์ ์ฒด ์ํ, 1ํ) const bboxMap = buildAneurysmBBoxes(annotations); // โโ Axial ๋ทฐ (coronal ๋ ๋๋ฌ) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ // ํ์ฌ K ์ฌ๋ผ์ด์ค์ ํด๋นํ๋ annotation ์๋ณธ ์ขํ ์ฌ์ฉ drawForRenderer('coronal', (ctx, renderer, rw) => { const k = sliceIndices.k; const sliceAnns = annotations.filter(a => a.instance_number === k); for (const ann of sliceAnns) { const xs = ann.graphic_data.map(p => p[0]); const ys = ann.graphic_data.map(p => p[1]); // ๋ ๊ผญ์ง์ : (iMin, jMin, k) and (iMax, jMax, k) const [x1, y1] = iJKToCanvas([Math.min(...xs), Math.min(...ys), k], renderer, rw); const [x2, y2] = iJKToCanvas([Math.max(...xs), Math.max(...ys), k], renderer, rw); ctx.strokeStyle = ann.color; ctx.strokeRect(x1, y1, x2 - x1, y2 - y1); } }); // โโ Sagittal ๋ทฐ (sagittal ๋ ๋๋ฌ) โโโโโโโโโโโโโโโโโโโโโโโโโโโโ // ํ์ฌ sliceI๊ฐ 3D BBox์ I ๋ฒ์์ ํฌํจ๋๋ฉด JรK ์ฌ๊ฐํ drawForRenderer('sagittal', (ctx, renderer, rw) => { const i = sliceIndices.i; bboxMap.forEach((bbox) => { if (i < bbox.iMin || i > bbox.iMax) return; // ๋ฒ์ ๋ฐ // Sagittal(I์ถ ๊ณ ์ ): ๊ฐ๋ก=K, ์ธ๋ก=J // ์ข์๋จ (i, jMin, kMin) โ ์ฐํ๋จ (i, jMax, kMax) const [x1, y1] = iJKToCanvas([i, bbox.jMin, bbox.kMin], renderer, rw); const [x2, y2] = iJKToCanvas([i, bbox.jMax, bbox.kMax], renderer, rw); ctx.strokeStyle = bbox.color; ctx.strokeRect( Math.min(x1, x2), Math.min(y1, y2), Math.abs(x2 - x1), Math.abs(y2 - y1), ); }); }); // โโ Coronal ๋ทฐ (axial ๋ ๋๋ฌ) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ // ํ์ฌ sliceJ๊ฐ 3D BBox์ J ๋ฒ์์ ํฌํจ๋๋ฉด IรK ์ฌ๊ฐํ drawForRenderer('axial', (ctx, renderer, rw) => { const j = sliceIndices.j; bboxMap.forEach((bbox) => { if (j < bbox.jMin || j > bbox.jMax) return; // ๋ฒ์ ๋ฐ // Coronal(J์ถ ๊ณ ์ ): ๊ฐ๋ก=I, ์ธ๋ก=K // ์ข์๋จ (iMin, j, kMin) โ ์ฐํ๋จ (iMax, j, kMax) const [x1, y1] = iJKToCanvas([bbox.iMin, j, bbox.kMin], renderer, rw); const [x2, y2] = iJKToCanvas([bbox.iMax, j, bbox.kMax], renderer, rw); ctx.strokeStyle = bbox.color; ctx.strokeRect( Math.min(x1, x2), Math.min(y1, y2), Math.abs(x2 - x1), Math.abs(y2 - y1), ); }); }); }
iJKToCanvas๋ โกโขโฃ๋ฅผ ํ ๋ฒ์ ์ํํ๋ ํฌํผ์
๋๋ค โ IJK โ World(ijkToWorld) โ Display(worldToDisplay) โ Canvas(รทDPR). ์ด ํจ์๊ฐ ์นด๋ฉ๋ผ ์ํ๋ฅผ ์๋ ๋ฐ์ํ๋ฏ๋ก ์คยทํฌ ํ์๋ ๋ฐ์ค๊ฐ ์ ํํ ๊ทธ๋ ค์ง๋๋ค.
๋ทฐ๋ณ ํฌ์ ์ขํ ์์ฝ
| ๋ทฐ (๋ ๋๋ฌ๋ช ) | ๊ณ ์ ์ถ | BBox ๋ ๊ผญ์ง์ IJK | ํํ ํ๋ฉด |
|---|---|---|---|
| Axial (coronal) | K = sliceK | (iMin, jMin, k) ~ (iMax, jMax, k) |
I(๊ฐ๋ก) ร J(์ธ๋ก) |
| Sagittal (sagittal) | I = sliceI | (i, jMin, kMin) ~ (i, jMax, kMax) |
K(๊ฐ๋ก) ร J(์ธ๋ก) |
| Coronal (axial) | J = sliceJ | (iMin, j, kMin) ~ (iMax, j, kMax) |
I(๊ฐ๋ก) ร K(์ธ๋ก) |
์คยทํฌยท๋ฆฌ์ฌ์ด์ฆ ๋๊ธฐํ โ onModified ๊ตฌ๋
bbox๊ฐ ์ฒ์ ๊ทธ๋ ค์ง ๋ค ์ฌ์ฉ์๊ฐ ์คยทํฌํ๋ฉด VTK WebGL ๋ ๋๋ง์ ์
๋ฐ์ดํธ๋์ง๋ง Canvas ์ค๋ฒ๋ ์ด๋ ๊ทธ๋๋ก์
๋๋ค. renderWindow.onModified()๋ฅผ ๊ตฌ๋
ํด VTK ๋ทฐ๊ฐ ๋ฐ๋ ๋๋ง๋ค Canvas๋ฅผ ์ฌ๊ทธ๋ฆฝ๋๋ค.
position: absolute์ ํฌ๋ช
Canvas๋ฅผ ์์ฑํฉ๋๋ค. VTK Canvas์ ์์ ํ ๊ฒน์น๋๋ก ํฌ๊ธฐ๋ฅผ ๋ง์ถฅ๋๋ค. renderWindow.onModified()๋ก VTK ๋ ๋ ์ด๋ฒคํธ๋ฅผ ๊ตฌ๋
ํฉ๋๋ค.drawOverlay()๊ฐ ํธ์ถ๋ฉ๋๋ค. ์ด ํจ์ ๋ด์์ worldToDisplay๊ฐ ํ์ฌ ์นด๋ฉ๋ผ ๊ธฐ์ค์ผ๋ก Display ์ขํ๋ฅผ ๋ค์ ๊ณ์ฐํ๋ฏ๋ก bbox๊ฐ ์ฌ๋ฐ๋ฅธ ์์น์ ๊ทธ๋ ค์ง๋๋ค.useEffect([sliceIndices])์์ drawOverlay()๋ฅผ ์ถ๊ฐ๋ก ํธ์ถํด ์ฌ๋ผ์ด์ค ์ด๋๋ ๋ฐ์ํฉ๋๋ค.sub.unsubscribe()๋ก VTK ์ด๋ฒคํธ ๊ตฌ๋
์ ํด์ ํ๊ณ , Canvas DOM์ ์ ๊ฑฐํฉ๋๋ค. ๋ฉ๋ชจ๋ฆฌ ๋์ ๋ฐฉ์ง.// BBoxOverlay.tsx โ ์ ์ฒด Effect ๊ตฌ์กฐ useEffect(() => { const rw = fullScreenRendererRef.current?.getRenderWindow(); if (!rw || !annotations || annotations.length === 0) return; // 1. Canvas ์์ฑ ๋ฐ ๋ฐฐ์น const canvas = document.createElement('canvas'); canvas.style.cssText = 'position:absolute;top:0;left:0;pointer-events:none;'; containerRef.current?.appendChild(canvas); // 2. Canvas ํฌ๊ธฐ โ VTK renderWindow ๋ฌผ๋ฆฌ ํฝ์ ๊ธฐ์ค const [w, h] = rw.getSize(); const dpr = window.devicePixelRatio || 1; canvas.width = w; canvas.height = h; canvas.style.width = `${w / dpr}px`; canvas.style.height = `${h / dpr}px`; // 3. VTK onModified ๊ตฌ๋ โ ์ค/ํฌ/๋ฆฌ์ฌ์ด์ฆ ์ ์ฌ๊ทธ๋ฆฌ๊ธฐ const sub = rw.onModified(() => drawOverlay(canvas)); drawOverlay(canvas); // ์ด๊ธฐ ๊ทธ๋ฆฌ๊ธฐ return () => { sub.unsubscribe(); canvas.remove(); }; }, [fullScreenRendererRef, annotations]); // 4. ์ฌ๋ผ์ด์ค ์ด๋ ์ ์ฌ๊ทธ๋ฆฌ๊ธฐ useEffect(() => { if (canvasRef.current) drawOverlay(canvasRef.current); }, [sliceIndices]);
BBoxOverlay ์ ์ฒด ๋์ ํ๋ฆ
๊ตฌํ ์ ์ฃผ์ํ ์
| ํจ์ | ์ฆ์ | ํด๊ฒฐ |
|---|---|---|
| ๋ ๋๋ฌ ๋ช ์นญ ์ญ์ | ๋ฐ์ค๊ฐ Axial์ ๊ทธ๋ ค์ผ ํ ๊ฒ์ด Coronal์ ๋ํ๋จ | coronalโAxial, axialโCoronal ๋งคํ ํ๋ฅผ ์ฝ๋ ์ฃผ์์ผ๋ก ๋ช
์ |
| VTK Y์ถ ๋ฐ์ ๋๋ฝ | ๋ฐ์ค๊ฐ ์์๋ ๋ค์งํ ๋ํ๋จ | winH - displayY๋ก ๋ฐ์ . getSize()[1]์ ๋งค๋ฒ ์ต์ ๊ฐ ์ฌ์ฉ |
| DPR ๋ณด์ ๋๋ฝ | Retina ํ๋ฉด์์ ๋ฐ์ค๊ฐ ์ ๋ฐ ํฌ๊ธฐ ๋๋ ์คํ์ ์ด๊ธ๋จ | Display ์ขํ รท devicePixelRatio. Canvas CSS ํฌ๊ธฐ๋ ๋์ผ ์ ์ฉ |
| vtkCoordinate delete ๋๋ฝ | ์ฅ์๊ฐ ์ฌ์ฉ ์ WebGL ๋ฉ๋ชจ๋ฆฌ ๋์ | coord.delete() ํญ์ ํธ์ถ. Object Pool ํจํด ์ ์ฉ ๊ถ์ฅ |
| drawOverlay ์ค๋ณต ํธ์ถ | ๋น ๋ฅธ ์คํฌ๋กค ์ ๋ถํ์ํ ๋ฆฌ๋ ๋ ๋์ | requestAnimationFrame์ผ๋ก debounce. ์ด๋ฏธ pending์ด๋ฉด skip |
| sliceIndices ref vs state | drawOverlay ํด๋ก์ ๊ฐ ์ค๋๋ ์ธ๋ฑ์ค๋ฅผ ์ฐธ์กฐ | drawOverlay ๋ด๋ถ์์ sliceIndices๋ฅผ ref๋ก ์ฝ๊ฑฐ๋ Effect ์์กด์ฑ ๋ช
์ |
vtkCoordinate๋ ์์ฑ ๋น์ฉ์ด ๋์ต๋๋ค. buildAneurysmBBoxes๋ก ๊ณ์ฐ๋ ๊ผญ์ง์ ์(๋ณ๋ณ ์ ร 2)๋ ๋ณดํต 10~30๊ฐ ์์ค์ด๋ฏ๋ก drawOverlay๋น ์์ฑยท์ญ์ ๊ฐ ํฌ๊ฒ ๋ฌธ์ ๋์ง ์์ต๋๋ค. ํ์ง๋ง annotation์ด ์๋ฐฑ ๊ฐ๋ผ๋ฉด Object Pool ํจํด(์ต๋ 4๊ฐ ์ฌ์ฌ์ฉ)์ ์ ์ฉํ์ธ์.
3ํธ ์์ฝ & ์๋ฆฌ์ฆ ๋ง๋ฌด๋ฆฌ
| ์ฃผ์ | ํต์ฌ ๋ด์ฉ |
|---|---|
| ์ขํ๊ณ ๋ช ๋ช ์ญ์ | coronal ๋ ๋๋ฌ = Axial UI / axial ๋ ๋๋ฌ = Coronal UI |
| ํฝ์ โ IJK | Axial: px=i, py=j, k=sliceK ์ง์ ๋งคํ |
| IJK โ World | origin + IJK ร spacing (imageData API) |
| World โ Display | vtkCoordinate ยท Y์ถ ๋ฐ์ (winH - dy) |
| Display โ Canvas | รท devicePixelRatio |
| 3D BBox ์ฌ๊ตฌ์ฑ | buildAneurysmBBoxes โ ์ฌ๋ผ์ด์ค ์ ์ฒด annotation ๋์ |
| Sagittal ํฌ์ | iMinโคsliceIโคiMax ์ฒดํฌ โ (i,jMin,kMin)~(i,jMax,kMax) |
| Coronal ํฌ์ | jMinโคsliceJโคjMax ์ฒดํฌ โ (iMin,j,kMin)~(iMax,j,kMax) |
| ์นด๋ฉ๋ผ ๋๊ธฐํ | renderWindow.onModified() โ drawOverlay() ์ฌํธ์ถ |
๐ ์๋ฆฌ์ฆ ์๊ฒฐ
SEG NIfTI โ BBox JSON ์ ํ 3๋ถ์์ด ์๋ฃ๋์์ต๋๋ค.
'๐ฑ๏ธ ๊ธฐ์ ๊ฒํ ' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
| [React] BBox JSON API ์ค๊ณ์ Canvas ์ค๋ฒ๋ ์ด ๊ตฌํ (0) | 2026.04.03 |
|---|---|
| [React] SEG NIfTI, ์ ๋ฒ๋ ธ๋ ๋ณ๋ชฉ์ ํด๋ถ์ BBox ์ ํ ๋ฐฐ๊ฒฝ (0) | 2026.04.03 |
| [React] SEG ์์ญ๋ง Zarr๋ก,๋๋จธ์ง๋ ์๋ณธ ๊ทธ๋๋ก (0) | 2026.04.03 |
| [React] NIfTI ์์ถ์ ๋ ๊ฐ๋.nii.gz vs Zarr (0) | 2026.04.03 |
| [React] Cornerstone3D์์ NIfTI๋ฅผ ์ด๋ค๋ฉด? 1024ร1024 ํด์๋๊น์ง (0) | 2026.04.03 |