๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ

๐Ÿ–ฑ๏ธ ๊ธฐ์ˆ  ๊ฒ€ํ† 

[React] Cornerstone3D์˜ ์ตœ์ ํ™”์™€์˜์ƒ ๋กœ๋“œ ๊ธฐ์ˆ  ์™„์ „ ๋ถ„์„

Cornerstone3D ์ตœ์ ํ™” & ์˜์ƒ ๋กœ๋“œ ๊ธฐ์ˆ 
Technical Deep Dive

Cornerstone3D์˜ ์ตœ์ ํ™”์™€
์˜์ƒ ๋กœ๋“œ ๊ธฐ์ˆ  ์™„์ „ ๋ถ„์„

Web Worker ๋””์ฝ”๋”ฉ, ์ŠคํŠธ๋ฆฌ๋ฐ ๋ณผ๋ฅจ, HTJ2K Progressive Loading, VoxelManager๊นŒ์ง€ โ€” ๋‚ด๋ถ€ ๋™์ž‘ ์›๋ฆฌ๋ฅผ ํŒŒํ—ค์นฉ๋‹ˆ๋‹ค.

Streaming Volume HTJ2K Progressive Web Worker ๋””์ฝ”๋”ฉ VoxelManager Request Pool GPU ํ…์Šค์ฒ˜ ๊ณต์œ 
01 ยท Overview

์™œ ์˜๋ฃŒ ์˜์ƒ ๋กœ๋”ฉ์€ ํŠน๋ณ„ํžˆ ์–ด๋ ต๋‚˜?

์ผ๋ฐ˜ ์›น ์ด๋ฏธ์ง€์™€ ๋‹ฌ๋ฆฌ ์˜๋ฃŒ ์˜์ƒ ๋ฐ์ดํ„ฐ๋Š” ๊ทน๋‹จ์ ์ธ ์ œ์•ฝ ์กฐ๊ฑด์„ ๊ฐ€์ง‘๋‹ˆ๋‹ค. 512ร—512 ํ”ฝ์…€ CT ์Šฌ๋ผ์ด์Šค 300์žฅ์€ ์••์ถ• ํ•ด์ œ ์‹œ ์•ฝ 150MB์ด๋ฉฐ, PET-CT Fusion์ฒ˜๋Ÿผ ์—ฌ๋Ÿฌ ์‹œ๋ฆฌ์ฆˆ๋ฅผ ๋™์‹œ์— ๋ Œ๋”๋งํ•  ๊ฒฝ์šฐ GPU์™€ CPU ๋ฉ”๋ชจ๋ฆฌ ๋ชจ๋‘ ๋น ๋ฅด๊ฒŒ ํ•œ๊ณ„์— ๋„๋‹ฌํ•ฉ๋‹ˆ๋‹ค. Cornerstone3D๋Š” ์ด ๋ฌธ์ œ๋ฅผ ๋‹จ์ผ ๊ธฐ์ˆ ์ด ์•„๋‹Œ ์—ฌ๋Ÿฌ ๋ ˆ์ด์–ด์˜ ์ตœ์ ํ™” ์ „๋žต์„ ์กฐํ•ฉํ•ด ํ•ด๊ฒฐํ•ฉ๋‹ˆ๋‹ค.

150MB+
CT 300์Šฌ๋ผ์ด์Šค ๋น„์••์ถ•
1ํšŒ
GPU ํ…์Šค์ฒ˜ ์—…๋กœ๋“œ ํšŸ์ˆ˜
50%โ†“
VoxelManager ๋ฉ”๋ชจ๋ฆฌ ์ ˆ๊ฐ
66ms
HTJ2K ์ฒซ ๋ Œ๋” ์‹œ๊ฐ„
๐Ÿ’ก ์ด ๊ธ€์—์„œ ๋‹ค๋ฃจ๋Š” ๊ธฐ์ˆ ๋“ค์€ ๋‹จ๋…์œผ๋กœ ์ž‘๋™ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. Request Pool โ†’ Web Worker ๋””์ฝ”๋”ฉ โ†’ ์ŠคํŠธ๋ฆฌ๋ฐ โ†’ GPU ์—…๋กœ๋“œ โ†’ Prefetch๊ฐ€ ์œ ๊ธฐ์ ์œผ๋กœ ๋งž๋ฌผ๋ ค ์ž‘๋™ํ•˜๋Š” ๊ตฌ์กฐ์ž…๋‹ˆ๋‹ค.
02 ยท Architecture

์˜์ƒ ๋กœ๋”ฉ ์ „์ฒด ํŒŒ์ดํ”„๋ผ์ธ

DICOMweb ์„œ๋ฒ„์—์„œ ์˜์ƒ์„ ์š”์ฒญํ•ด ํ™”๋ฉด์— ํ‘œ์‹œํ•˜๊ธฐ๊นŒ์ง€, Cornerstone3D ๋‚ด๋ถ€์—์„œ ์–ด๋–ค ๋‹จ๊ณ„๊ฐ€ ์ˆœ์„œ๋Œ€๋กœ ์‹คํ–‰๋˜๋Š”์ง€ ์ „์ฒด ํ๋ฆ„์„ ์‚ดํŽด๋ด…๋‹ˆ๋‹ค.

APPLICATION LAYER REQUEST POOL MANAGER WEB WORKER (์˜คํ”„๋ฉ”์ธ์Šค๋ ˆ๋“œ) CACHE LAYER GPU / RENDERING PREFETCH (๋ฐฑ๊ทธ๋ผ์šด๋“œ) Image ID ๋“ฑ๋ก wadors:// URL Viewport ์š”์ฒญ loadImage / loadVolume Priority Queue interaction > prefetch > thumbnail HTTP Fetch WADO-RS ยท Byte Range ๋น„๋™๊ธฐ ๋ถ„๋ฆฌ fetch โ†” decode ๋…๋ฆฝ DICOM ํŒŒ์‹ฑ dicomParser ยท ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์ฝ”๋ฑ ๋””์ฝ”๋”ฉ JPEG2K ยท HTJ2K ยท RLE Pixel Data ์ฒ˜๋ฆฌ rescale ยท native type ์œ ์ง€ Image Cache ์ €์žฅ ๋‹จ์ผ ์บ์‹œ ยท LRU ์ œ๊ฑฐ Volume ์Šฌ๋ผ์ด์Šค ์‚ฝ์ž… pixelData โ†’ volume buffer ํ…์Šค์ฒ˜ ์—…๋กœ๋“œ texSubImage3D ยท ์Šฌ๋ผ์ด์Šค ๋‹จ์œ„ ์˜คํ”„์Šคํฌ๋ฆฐ ๋ Œ๋”๋ง Shared Mapper โ†’ ๋ทฐํฌํŠธ ๋ณต์‚ฌ Position-aware Prefetch โ†’ Cache
03 ยท Web Worker

์˜คํ”„๋ฉ”์ธ์Šค๋ ˆ๋“œ ๋””์ฝ”๋”ฉ โ€” ํ™”๋ฉด์ด ๋ฉˆ์ถ”์ง€ ์•Š๋Š” ์ด์œ 

๋ธŒ๋ผ์šฐ์ €์˜ ๋ฉ”์ธ ์Šค๋ ˆ๋“œ์—์„œ DICOM ํŒŒ์ผ์„ ๋””์ฝ”๋”ฉํ•˜๋ฉด UI๊ฐ€ ์™„์ „ํžˆ ๋ฉˆ์ถฅ๋‹ˆ๋‹ค. Cornerstone3D๋Š” Web Worker๋ฅผ ํ†ตํ•ด ๋””์ฝ”๋”ฉ ์—ฐ์‚ฐ์„ ์™„์ „ํžˆ ๋ฉ”์ธ ์Šค๋ ˆ๋“œ ๋ฐ–์œผ๋กœ ๋ถ„๋ฆฌํ•ฉ๋‹ˆ๋‹ค. v2.0๋ถ€ํ„ฐ๋Š” HTTP Fetch์™€ Decode๋ฅผ ๋…๋ฆฝ์ ์œผ๋กœ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌํ•ด ํŒŒ์ดํ”„๋ผ์ธ ํšจ์œจ์„ ๊ทน๋Œ€ํ™”ํ•ฉ๋‹ˆ๋‹ค.

Legacy ๋ฐฉ์‹

๋ฉ”์ธ ์Šค๋ ˆ๋“œ ์ˆœ์ฐจ ์ฒ˜๋ฆฌ

  • fetch โ†’ decode โ†’ render ์ˆœ์ฐจ ์‹คํ–‰
  • ๋””์ฝ”๋”ฉ ์ค‘ UI ์™„์ „ ๋ธ”๋กœํ‚น
  • HTTP ์š”์ฒญ 6๊ฐœ ๋‹จ์œ„ ๋ฌถ์Œ ์ฒ˜๋ฆฌ
  • ํ•œ ์ด๋ฏธ์ง€ ์ง€์—ฐ ์‹œ ์ „์ฒด ๋Œ€๊ธฐ
Cornerstone3D โ€” ํ˜„์žฌ

Fetch / Decode ์™„์ „ ๋ถ„๋ฆฌ

  • Fetch์™€ Decode๋ฅผ ๋…๋ฆฝ ๋น„๋™๊ธฐ ํ๋กœ ์šด์˜
  • ๋””์ฝ”๋”ฉ ์ค‘์—๋„ UI ๋ฐ˜์‘ ์œ ์ง€
  • Worker Pool๋กœ ๋‹ค์ค‘ ๋ณ‘๋ ฌ ๋””์ฝ”๋”ฉ
  • Transferable ๊ฐ์ฒด๋กœ ๋ณต์‚ฌ ์—†์ด ๋ฐ์ดํ„ฐ ์ „๋‹ฌ
โšก v2.0๋ถ€ํ„ฐ Web Worker๋Š” SharedArrayBuffer ์˜์กด์„ฑ์„ ์ œ๊ฑฐํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด์ „์—๋Š” SAB๋ฅผ ์œ„ํ•ด ์„œ๋ฒ„์— COOP/COEP ํ—ค๋” ์„ค์ •์ด ํ•„์š”ํ–ˆ์ง€๋งŒ, ์ด์ œ Transferable ๋ฐฉ์‹์œผ๋กœ ๋™์ผํ•œ ์„ฑ๋Šฅ์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.

์ง€์›ํ•˜๋Š” ์ฝ”๋ฑ ๋””์ฝ”๋”๋Š” Workers ๋‚ด์—์„œ ๋™์  ๋กœ๋“œ๋ฉ๋‹ˆ๋‹ค.

Transfer Syntaxํฌ๋งท๋””์ฝ”๋”ํŠน์ง•
1.2.840.10008.1.2.1UncompressedNative๋ณ„๋„ ๋””์ฝ”๋” ๋ถˆํ•„์š”
1.2.840.10008.1.2.4.90JPEG 2000 Losslessopenjpeg (WASM)๊ณ ํ’ˆ์งˆ ๋ฌด์†์‹ค
1.2.840.10008.1.2.4.202HTJ2Kopenhtj2k (WASM)Progressive ๋””์ฝ”๋”ฉ ์ง€์›
1.2.840.10008.1.2.4.70JPEG Losslesslibjpeg (WASM)โ€”
1.2.840.10008.1.2.5RLE LosslessBuilt-inโ€”
04 ยท Request Pool Manager

Request Pool Manager โ€” ์š”์ฒญ ์šฐ์„ ์ˆœ์œ„ ์ œ์–ด

Cornerstone3D๋Š” ๋ชจ๋“  ์ด๋ฏธ์ง€ ์š”์ฒญ์„ ๋‹จ์ˆœํžˆ FIFO๋กœ ์ฒ˜๋ฆฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. RequestPoolManager๊ฐ€ ์š”์ฒญ์„ 3๋‹จ๊ณ„ ์šฐ์„ ์ˆœ์œ„ ํ๋กœ ๋ถ„๋ฅ˜ํ•˜์—ฌ ์‚ฌ์šฉ์ž ์ธํ„ฐ๋ž™์…˜์— ์˜ํ–ฅ์ด ์—†๋„๋ก ์Šค์ผ€์ค„๋งํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ–ฑ๏ธ

interaction

์‚ฌ์šฉ์ž๊ฐ€ ์ง์ ‘ ๋ณด๊ณ  ์žˆ๋Š” ์Šฌ๋ผ์ด์Šค. ์ฆ‰์‹œ ๋กœ๋“œ. ๋‹ค๋ฅธ ์š”์ฒญ๋ณด๋‹ค ํ•ญ์ƒ ์šฐ์„ .

๐Ÿ“ฆ

prefetch

ํ˜„์žฌ ์Šฌ๋ผ์ด์Šค ์ธ๊ทผ ํ”„๋ฆฌํŒจ์น˜. ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์กฐ์šฉํžˆ ๋กœ๋“œ.

๐Ÿ–ผ๏ธ

thumbnail

์ธ๋„ค์ผยท์ €ํ•ด์ƒ๋„ ๋ฏธ๋ฆฌ๋ณด๊ธฐ. ๊ฐ€์žฅ ๋‚ฎ์€ ์šฐ์„ ์ˆœ์œ„.

๐Ÿ”„ ์ธํ„ฐ๋ž™์…˜(์Šคํฌ๋กค, ํด๋ฆญ) ๋ฐœ์ƒ ์‹œ ํ˜„์žฌ ์ง„ํ–‰ ์ค‘์ธ prefetch ์š”์ฒญ์„ ์ฆ‰์‹œ ์ค‘๋‹จํ•˜๊ณ  interaction ํ๋ฅผ ๋จผ์ € ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ์ธํ„ฐ๋ž™์…˜ ์ข…๋ฃŒ ํ›„ ๋‚จ์€ prefetch๊ฐ€ ์žฌ๊ฐœ๋ฉ๋‹ˆ๋‹ค. ์ด ๋•๋ถ„์— ๋น ๋ฅธ ์Šคํฌ๋กค์—๋„ ํ˜„์žฌ ์Šฌ๋ผ์ด์Šค๊ฐ€ ๋Š๊ธฐ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
// Request Pool ์šฐ์„ ์ˆœ์œ„ ์„ค์ • ์˜ˆ์‹œ
import { requestPoolManager } from '@cornerstonejs/core';

// ์ตœ๋Œ€ ๋™์‹œ ์š”์ฒญ ์ˆ˜ ์กฐ์ •
requestPoolManager.setMaxSimultaneousRequests(
  'interaction', 6   // ๊ธฐ๋ณธ๊ฐ’: 6
);
requestPoolManager.setMaxSimultaneousRequests(
  'prefetch', 10    // ๊ธฐ๋ณธ๊ฐ’: 10
);

// ์ˆ˜๋™์œผ๋กœ interaction ์šฐ์„ ์ˆœ์œ„ ์š”์ฒญ ์ถ”๊ฐ€
requestPoolManager.addRequest(
  requestFn, 'interaction', /* addToBeginning = */ true
);
05 ยท Streaming Volume

์ŠคํŠธ๋ฆฌ๋ฐ ๋ณผ๋ฅจ ๋กœ๋” โ€” ์ ์ง„์  3D ๋ Œ๋”๋ง

์ผ๋ฐ˜์ ์ธ ๋ณผ๋ฅจ ๋กœ๋”๋Š” ๋ชจ๋“  ์Šฌ๋ผ์ด์Šค๋ฅผ ๋ฉ”๋ชจ๋ฆฌ์— ์˜ฌ๋ฆฐ ํ›„์—์•ผ ๋ Œ๋”๋ง์„ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค. Cornerstone3D์˜ StreamingImageVolume์€ ์Šฌ๋ผ์ด์Šค๊ฐ€ ํ•˜๋‚˜์”ฉ ๋„์ฐฉํ•˜๋Š” ์ฆ‰์‹œ GPU ํ…์Šค์ฒ˜๋ฅผ ๋ถ€๋ถ„ ์—…๋ฐ์ดํŠธํ•ด ๋ Œ๋”๋งํ•ฉ๋‹ˆ๋‹ค.

1
๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ํ”„๋ฆฌํŒจ์น˜ (์ „์ฒด ์‹œ๋ฆฌ์ฆˆ)
๋ชจ๋“  imageId์˜ PixelSpacing, ImagePosition ๋“ฑ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋งŒ ๋จผ์ € ์ˆ˜์ง‘ํ•ฉ๋‹ˆ๋‹ค. ์‹ค์ œ ํ”ฝ์…€ ๋ฐ์ดํ„ฐ๋Š” ์•„์ง ๊ฐ€์ ธ์˜ค์ง€ ์•Š์•„ ๋งค์šฐ ๋น ๋ฆ…๋‹ˆ๋‹ค.
2
Volume ์‚ฌ์ „ ํ• ๋‹น (createAndCacheVolume)
๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ๊ธฐ๋ฐ˜์œผ๋กœ ์ „์ฒด ๋ณผ๋ฅจ ํฌ๊ธฐ๋ฅผ ๊ณ„์‚ฐํ•ด ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ๋ฏธ๋ฆฌ ์˜ˆ์•ฝํ•ฉ๋‹ˆ๋‹ค. ์ด๋•Œ GPU ํ…์Šค์ฒ˜๋„ ๋นˆ ์ƒํƒœ๋กœ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค.
3
์Šฌ๋ผ์ด์Šค ๋‹จ์œ„ ๋กœ๋“œ & GPU ๋ถ€๋ถ„ ์—…๋ฐ์ดํŠธ
๊ฐ ์Šฌ๋ผ์ด์Šค๊ฐ€ ๋„์ฐฉํ•˜๋ฉด texSubImage3D๋กœ GPU ํ…์Šค์ฒ˜์˜ ํ•ด๋‹น Z ์˜คํ”„์…‹ ์œ„์น˜๋งŒ ๋ถ€๋ถ„ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค. ์ „์ฒด ํ…์Šค์ฒ˜๋ฅผ ์žฌ์—…๋กœ๋“œํ•˜์ง€ ์•Š์•„ ๋งค์šฐ ํšจ์œจ์ ์ž…๋‹ˆ๋‹ค.
4
skipCreateImage โ€” Image ๊ฐ์ฒด ์ƒ์„ฑ ์ƒ๋žต
๋ณผ๋ฅจ ๋กœ๋”ฉ ์‹œ ๊ฐœ๋ณ„ Cornerstone Image ๊ฐ์ฒด ์ƒ์„ฑ์„ ๊ฑด๋„ˆ๋œ๋‹ˆ๋‹ค. pixelData๋ฅผ ๋ณผ๋ฅจ ๋ฒ„ํผ์— ์ง์ ‘ ์‚ฝ์ž…ํ•˜๋ฏ€๋กœ ๋ฉ”๋ชจ๋ฆฌ์™€ ์‹œ๊ฐ„ ๋ชจ๋‘ ์ ˆ์•ฝ๋ฉ๋‹ˆ๋‹ค.
5
์ฆ‰์‹œ ๋ Œ๋”๋ง (Progressive ํ‘œ์‹œ)
์Šฌ๋ผ์ด์Šค ์ ˆ๋ฐ˜๋งŒ ๋กœ๋“œ๋œ ์ƒํƒœ์—์„œ๋„ ํ˜„์žฌ ๋„์ฐฉํ•œ ๋ฐ์ดํ„ฐ๋กœ ๋ Œ๋”๋ง์ด ์ง„ํ–‰๋ฉ๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๋Š” ๋กœ๋”ฉ ์ค‘์—๋„ ๋ณผ๋ฅจ์„ ํƒ์ƒ‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
// ์ŠคํŠธ๋ฆฌ๋ฐ ๋ณผ๋ฅจ ์ƒ์„ฑ & ๋กœ๋“œ
import { volumeLoader } from '@cornerstonejs/core';

const volumeId = 'cornerstoneStreamingImageVolume:CT_VOLUME';

// 1๋‹จ๊ณ„: ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ๊ธฐ๋ฐ˜ ์‚ฌ์ „ ํ• ๋‹น
const volume = await volumeLoader.createAndCacheVolume(volumeId, {
  imageIds: ctImageIds,  // 300์žฅ ์Šฌ๋ผ์ด์Šค imageId ๋ฐฐ์—ด
});

// 2๋‹จ๊ณ„: ์ŠคํŠธ๋ฆฌ๋ฐ ๋กœ๋“œ ์‹œ์ž‘ (์ฆ‰์‹œ ๋ Œ๋”๋ง ๊ฐ€๋Šฅ)
volume.load();  // await ๋ถˆํ•„์š” โ€” ์Šฌ๋ผ์ด์Šค ๋„์ฐฉ ์‹œ ์ž๋™ ๋ Œ๋”๋ง

// viewport์— ๋ณผ๋ฅจ ์—ฐ๊ฒฐ
viewport.setVolumes([{ volumeId }]);
06 ยท Progressive Loading

HTJ2K Progressive Loading โ€” 66ms ์ฒซ ๋ Œ๋”

HTJ2K(High Throughput JPEG 2000)๋Š” DICOM ํ‘œ์ค€์— ์ƒˆ๋กœ ์ถ”๊ฐ€๋œ ์••์ถ• ํฌ๋งท์œผ๋กœ, ์ฒซ N ๋ฐ”์ดํŠธ๋งŒ์œผ๋กœ ์ €ํ•ด์ƒ๋„ ๋ฒ„์ „์„ ์ฆ‰์‹œ ๋””์ฝ”๋”ฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Cornerstone3D๋Š” ์ด๋ฅผ ํ™œ์šฉํ•ด ์ „์ฒด ์ด๋ฏธ์ง€๊ฐ€ ๋„์ฐฉํ•˜๊ธฐ ์ „์— ๋จผ์ € ์ €ํ•ด์ƒ๋„๋ฅผ ํ‘œ์‹œํ•˜๊ณ , ๋ฐ์ดํ„ฐ๊ฐ€ ๋ˆ„์ ๋ ์ˆ˜๋ก ํ•ด์ƒ๋„๋ฅผ ์ ์ง„์ ์œผ๋กœ ํ–ฅ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค.

JLS (์ผ๋ฐ˜ JPEG LS)
4,586ms
JLS Reduced
359ms
HTJ2K
66ms
HTJ2K Byte Range
45ms

* 3036ร—3036 CT ์ด๋ฏธ์ง€ ๊ธฐ์ค€, ์ฒซ ๋ Œ๋” ์‹œ๊ฐ„ (4G ๋„คํŠธ์›Œํฌ)

JLS ์ „์ฒด ์ˆ˜์‹  ํ›„ ์ฒซ ๋ Œ๋” (4,586ms) ์ €ํ•ด์ƒ๋„ ํ‘œ์‹œ (360ms) ์ „์ฒด ํ’ˆ์งˆ ๋„๋‹ฌ (4,903ms) 45ms! HTJ2K Byte Range โ€” ์ „์ฒด ๋น„ํŠธ์ŠคํŠธ๋ฆผ ์ˆ˜์‹ ํ•˜๋ฉฐ ํ•ด์ƒ๋„ ์ ์ง„ ํ–ฅ์ƒ (4,610ms) 0ms โ† ์‹œ๊ฐ„ โ†’ 5,000ms

Progressive Loading์€ 2๋‹จ๊ณ„ Retrieve ์„ค์ •์œผ๋กœ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค.

// HTJ2K Progressive Loading ์„ค์ •
import { utilities } from '@cornerstonejs/core';

const retrieveConfig = {
  stages: [
    { id: 'initialImages', retrieveType: 'singleFast' },  // ์ €ํ•ด์ƒ๋„ ๋จผ์ €
    { id: 'fullResolution', retrieveType: 'singleFinal' }, // ์ „์ฒด ํ’ˆ์งˆ
  ],
  retrieveOptions: {
    singleFast: {
      streaming: true,
      urlParameters: 'accept=image/jhc',  // HTJ2K ์š”์ฒญ
    },
    singleFinal: { streaming: true },
  },
};

// ์Šคํƒ ์ „์ฒด์— ์ ์šฉ
utilities.imageRetrieveMetadataProvider.add('stack', retrieveConfig);

// ๋˜๋Š” ๋ณผ๋ฅจ ์ „์ฒด์— ์ ์šฉ
utilities.imageRetrieveMetadataProvider.add(volumeId, retrieveConfig);
โš ๏ธ HTJ2K Byte Range๋Š” ์„œ๋ฒ„๊ฐ€ Byte Range Request๋ฅผ ์ง€์›ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์„œ๋ฒ„ ๋ฏธ์ง€์› ์‹œ singleFast ์Šคํ…Œ์ด์ง€ ์š”์ฒญ์ด ์‹คํŒจํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ‘œ์ค€ HTJ2K ์ŠคํŠธ๋ฆฌ๋ฐ(streaming: true๋งŒ)์€ Byte Range ์—†์ด๋„ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค.
07 ยท Memory

๋ฉ”๋ชจ๋ฆฌ ์ตœ์ ํ™” โ€” VoxelManager & ๋‹จ์ผ ์บ์‹œ

Cornerstone3D 2.0์˜ ๊ฐ€์žฅ ํฐ ๋‚ด๋ถ€ ๋ณ€ํ™”๋Š” VoxelManager ๋„์ž…๊ณผ ๋‹จ์ผ Image Cache ์•„ํ‚คํ…์ฒ˜๋กœ์˜ ์ „ํ™˜์ž…๋‹ˆ๋‹ค. ๊ธฐ์กด์—๋Š” Image Cache์™€ Volume Cache๊ฐ€ ๋ณ„๋„๋กœ ์กด์žฌํ•ด ๊ฐ™์€ ๋ฐ์ดํ„ฐ๊ฐ€ ์ด์ค‘์œผ๋กœ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ์ฐจ์ง€ํ–ˆ์Šต๋‹ˆ๋‹ค.

Cornerstone3D v1

์ด์ค‘ ์บ์‹œ ๊ตฌ์กฐ

  • Image Cache + Volume Cache ๋ณ„๋„ ์šด์˜
  • ๊ฐ™์€ ์Šฌ๋ผ์ด์Šค ๋ฐ์ดํ„ฐ ๋‘ ๊ณณ์— ์ค‘๋ณต ์ €์žฅ
  • ๋Œ€ํ˜• ์Šค์นผ๋ผ ๋ฐฐ์—ด(scalarData) CPU์— ์œ ์ง€
  • ๋ณผ๋ฅจ ์ „์ฒด ๋ฉ”๋ชจ๋ฆฌ = CPU ๋ฐฐ์—ด + GPU ํ…์Šค์ฒ˜
  • Stack โ†” Volume ์„ธ๊ทธ๋ฉ˜ํ…Œ์ด์…˜ ๋™๊ธฐํ™” ๋ฒ„๊ทธ
Cornerstone3D v2

๋‹จ์ผ Image Cache + VoxelManager

  • ์˜ค์ง Image Cache ํ•˜๋‚˜๋งŒ ์‚ฌ์šฉ (๋‹จ์ผ ์ง„์‹ค)
  • ๋ณผ๋ฅจ ๋ Œ๋”๋ง ์‹œ CPU ์Šค์นผ๋ผ ๋ฐฐ์—ด ๋ถˆํ•„์š”
  • Image โ†’ GPU ์ง์ ‘ ์ŠคํŠธ๋ฆฌ๋ฐ (CPU ์šฐํšŒ)
  • Native ๋ฐ์ดํ„ฐ ํƒ€์ž… ์œ ์ง€ โ†’ ๋ Œ๋” ์‹œ ๋ณ€ํ™˜
  • ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰ ์•ฝ 50% ์ ˆ๊ฐ
v1 โ€” ์ด์ค‘ ์บ์‹œ Image Cache Volume Cache ์Šฌ๋ผ์ด์Šค ร— N scalarData ๋ฐฐ์—ด (Large) CPU ๋ฉ”๋ชจ๋ฆฌ (์ค‘๋ณต) Image + Volume scalarData ๋™์‹œ ์ ์œ  GPU ํ…์Šค์ฒ˜ v2 โ€” ๋‹จ์ผ ์บ์‹œ Image Cache (๋‹จ์ผ ์ง„์‹ค) ์Šฌ๋ผ์ด์Šค ร— N (Native ํƒ€์ž…) GPU ํ…์Šค์ฒ˜ Image โ†’ GPU ์ง์ ‘ ์ŠคํŠธ๋ฆฌ๋ฐ CPU ์Šค์นผ๋ผ ๋ฐฐ์—ด ๋ถˆํ•„์š” ๐ŸŽ‰
// v1: scalarData ์ง์ ‘ ์ ‘๊ทผ (๋น„๊ถŒ์žฅ)
const scalarData = volume.scalarData;          // โŒ v2์—์„œ ์ œ๊ฑฐ๋จ

// v2: VoxelManager๋กœ ํšจ์œจ์  ์ ‘๊ทผ
const vm = volume.voxelManager;
const value = vm.getAtIndex(flatIndex);         // โœ… ๋‹จ์ผ ๋ณต์…€ ์ ‘๊ทผ
const value = vm.getAtIJK(i, j, k);            // โœ… IJK ์ขŒํ‘œ ์ ‘๊ทผ

// ๋Œ€๋Ÿ‰ ์ฒ˜๋ฆฌ ์‹œ forEach ํ™œ์šฉ
vm.forEach(({ value, index }) => {
  if (value > 100) vm.setAtIndex(index, 100);
});

// โš ๏ธ ์ตœํ›„ ์ˆ˜๋‹จ์œผ๋กœ๋งŒ ์‚ฌ์šฉ (๋ฉ”๋ชจ๋ฆฌ 2๋ฐฐ ์ ์œ )
const fullArray = vm.getCompleteScalarDataArray();
08 ยท Cache Management

Cache API โ€” ๋ฉ”๋ชจ๋ฆฌ ํ•œ๋„ & ์ž๋™ ํ•ด์ œ

๋Œ€ํ˜• ๋ณผ๋ฅจ์„ ์—ฌ๋Ÿฌ ๊ฐœ ๋กœ๋“œํ•˜๋ฉด ๋ธŒ๋ผ์šฐ์ €๊ฐ€ OOM(Out of Memory)์œผ๋กœ ํƒญ์„ ์ข…๋ฃŒํ•ฉ๋‹ˆ๋‹ค. Cornerstone3D์˜ Cache API๋Š” ์ตœ๋Œ€ ๋ฉ”๋ชจ๋ฆฌ ํ•œ๋„๋ฅผ ์„ค์ •ํ•˜๊ณ , ์ดˆ๊ณผ ์‹œ ์ž๋™์œผ๋กœ ์˜ค๋ž˜๋œ ๋ฐ์ดํ„ฐ๋ฅผ ํ•ด์ œํ•˜๋Š” LRU ๋ฐฉ์‹์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ’พ

์ตœ๋Œ€ ๋ฉ”๋ชจ๋ฆฌ ํ•œ๋„ ์„ค์ •

์ „์ฒด ์ด๋ฏธ์ง€ ์บ์‹œ๊ฐ€ ์ฐจ์ง€ํ•  ์ˆ˜ ์žˆ๋Š” ์ตœ๋Œ€ ๋ฐ”์ดํŠธ๋ฅผ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค. ์ดˆ๊ณผ ์‹œ ๊ฐ€์žฅ ์˜ค๋ž˜ ์‚ฌ์šฉ๋˜์ง€ ์•Š์€ ์ด๋ฏธ์ง€๋ถ€ํ„ฐ ํ•ด์ œ๋ฉ๋‹ˆ๋‹ค.

๐Ÿ—‘๏ธ

์ž๋™ ๊ณต๊ฐ„ ํ™•๋ณด

์ƒˆ ๋ณผ๋ฅจ ํ• ๋‹น ์ „ decacheIfNecessaryUntilBytesAvailable()๋กœ ํ•„์š”ํ•œ ๊ณต๊ฐ„์„ ์ž๋™ ํ™•๋ณดํ•ฉ๋‹ˆ๋‹ค.

// ์บ์‹œ ์ตœ๋Œ€ ๋ฉ”๋ชจ๋ฆฌ ์„ค์ • (๊ธฐ๋ณธ: 1GB)
import { cache } from '@cornerstonejs/core';

cache.setMaxCacheSize(2 * 1024 * 1024 * 1024);  // 2GB

// ํ˜„์žฌ ์‚ฌ์šฉ๋Ÿ‰ ํ™•์ธ
const { numImagesCached, cacheSizeInBytes } = cache.getCacheStatus();

// ํŠน์ • ๋ณผ๋ฅจ ์ œ๊ฑฐ
cache.purgeVolumeCache(volumeId);

// ์ „์ฒด ์บ์‹œ ๋น„์šฐ๊ธฐ
cache.purgeCache();
09 ยท GPU Optimization

Shared Volume Mapper โ€” GPU ํ…์Šค์ฒ˜ 1ํšŒ ์—…๋กœ๋“œ

PET-CT Fusion์ฒ˜๋Ÿผ ๊ฐ™์€ ๋ณผ๋ฅจ ๋ฐ์ดํ„ฐ๋ฅผ ์—ฌ๋Ÿฌ ๋ทฐํฌํŠธ์—์„œ ๋‹ค๋ฅธ ์นด๋ฉ”๋ผ ๊ฐ๋„๋กœ ํ‘œ์‹œํ•ด์•ผ ํ•  ๋•Œ, ๋‚˜์ด๋ธŒํ•œ ๊ตฌํ˜„์€ ๋ทฐํฌํŠธ ์ˆ˜๋งŒํผ GPU ํ…์Šค์ฒ˜๋ฅผ ๋ณต์ œํ•ฉ๋‹ˆ๋‹ค. Cornerstone3D๋Š” Shared Volume Mapper๋กœ GPU ํ…์Šค์ฒ˜๋ฅผ ๋‹จ 1ํšŒ๋งŒ ์—…๋กœ๋“œํ•˜๊ณ  ๋ชจ๋“  ๋ทฐํฌํŠธ๊ฐ€ ์žฌ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

CT Axial camera A CT Sagittal camera B CT Coronal camera C Fusion Axial CT + PET blend PET Axial SUV range PET Sagittal SUV range CT Volume Mapper (๊ณต์œ ) GPU ํ…์Šค์ฒ˜ 1ํšŒ ์—…๋กœ๋“œ ยท 150MB CT Axial ยท Sagittal ยท Coronal ยท Fusion ๋ชจ๋‘ ์žฌ์‚ฌ์šฉ PET Volume Mapper (๊ณต์œ ) GPU ํ…์Šค์ฒ˜ 1ํšŒ ์—…๋กœ๋“œ ยท 50MB PET Axial ยท Sagittal ยท Fusion ๋ชจ๋‘ ์žฌ์‚ฌ์šฉ vtkStreamingOpenGLTexture texSubImage3D๋กœ ์Šฌ๋ผ์ด์Šค ๋‹จ์œ„ ๋ถ€๋ถ„ ์—…๋ฐ์ดํŠธ
๐Ÿ”ง ๋‚ด๋ถ€์ ์œผ๋กœ vtkStreamingOpenGLTexture๋ฅผ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•ํ•˜์—ฌ texSubImage3D()๋กœ ์Šฌ๋ผ์ด์Šค ๋‹จ์œ„ ๋ถ€๋ถ„ ์—…๋ฐ์ดํŠธ๋ฅผ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค. ์ „์ฒด ํ…์Šค์ฒ˜๋ฅผ ๊ต์ฒดํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ํ•ด๋‹น Z ์Šฌ๋ผ์ด์Šค ์˜คํ”„์…‹๋งŒ ๊ฐฑ์‹ ํ•˜๋ฏ€๋กœ ์ŠคํŠธ๋ฆฌ๋ฐ ์ค‘์—๋„ GPU ๋ถ€ํ•˜๊ฐ€ ์ตœ์†Œํ™”๋ฉ๋‹ˆ๋‹ค.
10 ยท Prefetch

Position-aware Prefetch โ€” ์œ„์น˜ ๊ธฐ๋ฐ˜ ์Šค๋งˆํŠธ ํ”„๋ฆฌํŒจ์น˜

๋‹จ์ˆœ ์ˆœ์ฐจ ํ”„๋ฆฌํŒจ์น˜(1๋ฒˆ โ†’ 2๋ฒˆ โ†’ 3๋ฒˆ ...)๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ์ค‘๊ฐ„ ์Šฌ๋ผ์ด์Šค์—์„œ ์‹œ์ž‘ํ•˜๋ฉด ๋น„ํšจ์œจ์ ์ž…๋‹ˆ๋‹ค. Cornerstone3D์˜ stackPrefetch๋Š” ํ˜„์žฌ ์Šฌ๋ผ์ด์Šค ์œ„์น˜๋ฅผ ๊ธฐ์ค€์œผ๋กœ ๊ฐ€๊นŒ์šด ์Šฌ๋ผ์ด์Šค๋ถ€ํ„ฐ ์šฐ์„  ๋กœ๋“œํ•˜๊ณ , ์—ฌ๋Ÿฌ ๋ทฐํฌํŠธ์˜ ํ”„๋ฆฌํŒจ์น˜๊ฐ€ ์„œ๋กœ ๊ฐ„์„ญํ•˜์ง€ ์•Š๋„๋ก ์กฐ์œจํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ“

Position-aware (์œ„์น˜ ์ธ์‹)

ํ˜„์žฌ ์Šฌ๋ผ์ด์Šค ์ธ๋ฑ์Šค๋ฅผ ์ค‘์‹ฌ์œผ๋กœ ยฑ๋ฐฉํ–ฅ์œผ๋กœ ๊ต์ฐจํ•˜๋ฉฐ ๋กœ๋“œํ•ฉ๋‹ˆ๋‹ค. ์Šฌ๋ผ์ด์Šค 100์—์„œ ์‹œ์ž‘ํ•˜๋ฉด 101, 99, 102, 98 ์ˆœ์œผ๋กœ ํ”„๋ฆฌํŒจ์น˜ํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ–ฅ๏ธ

Multi-viewport ์ธ์‹

์—ฌ๋Ÿฌ ๋ทฐํฌํŠธ๊ฐ€ ๋™์ผํ•œ Request Pool์„ ๊ณต์œ ํ•ฉ๋‹ˆ๋‹ค. ํ•œ ๋ทฐํฌํŠธ์˜ ํ”„๋ฆฌํŒจ์น˜๊ฐ€ ๋‹ค๋ฅธ ๋ทฐํฌํŠธ์˜ interaction ์š”์ฒญ์„ ๋ง‰์ง€ ์•Š๋„๋ก ์šฐ์„ ์ˆœ์œ„๋ฅผ ์กฐ์œจํ•ฉ๋‹ˆ๋‹ค.

// stackPrefetch ํ™œ์„ฑํ™”
import { StackScrollTool, ToolGroupManager } from '@cornerstonejs/tools';
import { cornerstoneTools } from '@cornerstonejs/tools';

const { StackScrollTool, StackScrollMouseWheelTool, PanTool, ZoomTool,
        ToolGroupManager, Enums: csToolsEnums } = cornerstoneTools;

// ToolGroup์— Prefetch ํ™œ์„ฑํ™”
cornerstoneTools.addTool(cornerstoneTools.StackScrollTool);
toolGroup.addTool(StackScrollTool.toolName, {
  configuration: {
    prefetchCount: 10,     // ์•ž๋’ค 10์žฅ์”ฉ ํ”„๋ฆฌํŒจ์น˜
    preventHandleOutsideImage: false,
  },
});
11 ยท Summary

์ตœ์ ํ™” ๊ธฐ์ˆ  ํ•œ๋ˆˆ์— ๋ณด๊ธฐ

๊ธฐ์ˆ ์ ์šฉ ์œ„์น˜ํšจ๊ณผ๋„์ž… ๋ฒ„์ „
์˜คํ”„์Šคํฌ๋ฆฐ WebGL ์ปจํ…์ŠคํŠธRenderingEngine๋ทฐํฌํŠธ N๊ฐœ๋„ WebGL ์ปจํ…์ŠคํŠธ 1๊ฐœv1.0
Shared Volume MapperGPU ๋ Œ๋”๋ง๋™์ผ ๋ณผ๋ฅจ GPU ํ…์Šค์ฒ˜ 1ํšŒ ์—…๋กœ๋“œv1.0
Web Worker ๋ถ„๋ฆฌ ๋””์ฝ”๋”ฉDICOM ๋กœ๋”ฉ๋ฉ”์ธ ์Šค๋ ˆ๋“œ ๋ธ”๋กœํ‚น ์—†์Œv1.0
Fetch / Decode ๋…๋ฆฝ ๋น„๋™๊ธฐRequest PoolํŒŒ์ดํ”„๋ผ์ธ ์ฒ˜๋ฆฌ๋Ÿ‰ ์ฆ๊ฐ€v2.0
์ŠคํŠธ๋ฆฌ๋ฐ ๋ณผ๋ฅจ ๋กœ๋”Volume ๋กœ๋”ฉ์Šฌ๋ผ์ด์Šค ๋„์ฐฉ ์ฆ‰์‹œ ๋ Œ๋”๋งv1.0
skipCreateImageVolume ๋กœ๋”ฉImage ๊ฐ์ฒด ์ƒ์„ฑ ์˜ค๋ฒ„ํ—ค๋“œ ์ œ๊ฑฐv1.0
texSubImage3D ๋ถ€๋ถ„ ์—…๋ฐ์ดํŠธGPU ์—…๋กœ๋“œ์ŠคํŠธ๋ฆฌ๋ฐ ์ค‘ GPU ๋ถ€ํ•˜ ์ตœ์†Œํ™”v1.0
HTJ2K Progressive Loading๋„คํŠธ์›Œํฌ์ฒซ ๋ Œ๋” 66ms (vs 4,586ms)v1.x
Priority Queue (3๋‹จ๊ณ„)Request Pool์ธํ„ฐ๋ž™์…˜ ์ค‘ ๋Š๊น€ ๋ฐฉ์ง€v1.x
Position-aware PrefetchStack ๋ทฐ์–ดํ˜„์žฌ ์œ„์น˜ ๊ธฐ์ค€ ์Šค๋งˆํŠธ ํ”„๋ฆฌํŒจ์น˜v1.x
VoxelManager๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ๋ฉ”๋ชจ๋ฆฌ ~50% ์ ˆ๊ฐ, CPU ๋ฐฐ์—ด ์ œ๊ฑฐv2.0
๋‹จ์ผ Image Cache์บ์‹œ ๊ตฌ์กฐImage/Volume ์ด์ค‘ ์บ์‹œ ์ œ๊ฑฐv2.0
Native ํƒ€์ž… ์ €์žฅ์บ์‹œ๋ถˆํ•„์š”ํ•œ ํƒ€์ž… ๋ณ€ํ™˜ ์ œ๊ฑฐv2.0
๐Ÿงญ ์ปค์Šคํ…€ VTK.js ๊ตฌํ˜„๊ณผ ๋น„๊ตํ–ˆ์„ ๋•Œ, ์ด ์ตœ์ ํ™” ๊ธฐ๋ฒ•๋“ค์„ ๋ชจ๋‘ ์ง์ ‘ ๊ตฌํ˜„ํ•˜๋ ค๋ฉด ์ƒ๋‹นํ•œ ์‹œ๊ฐ„์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ํŠนํžˆ HTJ2K Progressive Loading, VoxelManager, Request Pool Priority Queue๋Š” Cornerstone3D๊ฐ€ ์ œ๊ณตํ•˜๋Š” ํ•ต์‹ฌ ๊ฐ€์น˜์ž…๋‹ˆ๋‹ค. ๋ฐ˜๋ฉด ๋น„ํ‘œ์ค€ REST API ๊ธฐ๋ฐ˜ ๋ฐ์ดํ„ฐ(NIfTI .nii.gz)๋ฅผ ๋‹ค๋ฃจ๊ฑฐ๋‚˜ Canvas ๊ธฐ๋ฐ˜ ์ปค์Šคํ…€ ๋ Œ๋”๋ง์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ์—๋Š” ์ง์ ‘ VTK.js ํŒŒ์ดํ”„๋ผ์ธ์„ ๊ตฌ์„ฑํ•˜๋Š” ๊ฒƒ์ด ๋” ์ ํ•ฉํ•ฉ๋‹ˆ๋‹ค.