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

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

[React] NIfTI ์••์ถ•์˜ ๋‘ ๊ฐˆ๋ž˜.nii.gz vs Zarr

NIfTI .gz vs Zarr ์™„์ „ ๋ถ„์„
Format Deep Dive

NIfTI ์••์ถ•์˜ ๋‘ ๊ฐˆ๋ž˜
.nii.gz vs Zarr

gzip ๋‹จ์ผ ํŒŒ์ผ ์••์ถ•๊ณผ ์ฒญํฌ ๊ธฐ๋ฐ˜ Zarr ํฌ๋งท์˜ ๊ตฌ์กฐ์  ์ฐจ์ด๋ถ€ํ„ฐ, NIfTI ๋ฐ์ดํ„ฐ์— ์ ์šฉํ–ˆ์„ ๋•Œ ์›น ๋ทฐ์–ด์—์„œ ๋ฐœ์ƒํ•˜๋Š” ๋ฌธ์ œ์ ๊ณผ ๊ธฐ์ˆ  ์ ์šฉ ๋ฐฉ์‹๊นŒ์ง€ ์™„์ „ ๋ถ„์„ํ•ฉ๋‹ˆ๋‹ค.

gzip vs Zarr ์ฒญํฌ ๊ธฐ๋ฐ˜ ์ŠคํŠธ๋ฆฌ๋ฐ HTTP Range Request ๋ฉ€ํ‹ฐํ•ด์ƒ๋„ ํ”ผ๋ผ๋ฏธ๋“œ NIfTI-Zarr (nii.zarr) ์›น ๋ทฐ์–ด ์ ์šฉ
01 ยท Background

์™œ ์ง€๊ธˆ ์ด ๋น„๊ต๊ฐ€ ์ค‘์š”ํ•œ๊ฐ€

NIfTI(.nii.gz)๋Š” ์‹ ๊ฒฝ์˜์ƒ ์—ฐ๊ตฌ์˜ ํ‘œ์ค€ ํฌ๋งท์œผ๋กœ, ์ˆ˜์‹ญ ๋…„๊ฐ„ ๋‹จ์ˆœํ•˜๊ณ  ์•ˆ์ •์ ์ด์—ˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ AI ๋ชจ๋ธ ์ถœ๋ ฅ๋ฌผ, ๋Œ€ํ˜• ๋‡Œ์˜์ƒ, ๊ณ ํ•ด์ƒ๋„ ์˜๋ฃŒ ๋ณผ๋ฅจ์ด ์ผ์ƒํ™”๋˜๋ฉด์„œ ๋‹จ์ผ ํŒŒ์ผ gzip ์••์ถ• ๋ฐฉ์‹์˜ ํ•œ๊ณ„๊ฐ€ ๋“œ๋Ÿฌ๋‚˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ํŠนํžˆ ์›น ๋ธŒ๋ผ์šฐ์ €์—์„œ ๋Œ€์šฉ๋Ÿ‰ ๋ณผ๋ฅจ์„ ์‹ค์‹œ๊ฐ„์œผ๋กœ ๋ Œ๋”๋งํ•˜๋Š” ์‹œ๋‚˜๋ฆฌ์˜ค์—์„œ, .nii.gz๋Š” ๊ตฌ์กฐ์ ์œผ๋กœ ๋ถˆ๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

Zarr๋Š” ๊ธฐํ›„ยท์ฒœ๋ฌธยท์œ ์ „์ฒด ๋ถ„์•ผ์—์„œ ํŽ˜ํƒ€๋ฐ”์ดํŠธ๊ธ‰ ๋ฐฐ์—ด ๋ฐ์ดํ„ฐ๋ฅผ ํด๋ผ์šฐ๋“œ๋กœ ์„œ๋น™ํ•˜๋˜ ๊ธฐ์ˆ ์ž…๋‹ˆ๋‹ค. ์ตœ๊ทผ ์‹ ๊ฒฝ์˜์ƒ ์ปค๋ฎค๋‹ˆํ‹ฐ๊ฐ€ ์ด๋ฅผ NIfTI์— ์ ‘๋ชฉํ•œ NIfTI-Zarr(nii.zarr) ์ŠคํŽ™์„ ์ œ์•ˆํ•˜๋ฉด์„œ ์›น ์˜๋ฃŒ ์˜์ƒ ๋ทฐ์–ด์™€์˜ ์ ‘์ ์ด ์ƒ๊ฒผ์Šต๋‹ˆ๋‹ค.

1๊ฐœ
.nii.gz ํŒŒ์ผ ๋‹จ์œ„
N๊ฐœ
Zarr ๋…๋ฆฝ ์ฒญํฌ ์ˆ˜
์ „์ฒด
.gz ์ตœ์†Œ ๋‹ค์šด๋กœ๋“œ
1์Šฌ๋ผ์ด์Šค
Zarr ์ตœ์†Œ ๋‹ค์šด๋กœ๋“œ
02 ยท .nii.gz ๊ตฌ์กฐ

gzip ์••์ถ•์˜ ๋™์ž‘ ๋ฐฉ์‹

gzip์€ ์ŠคํŠธ๋ฆผ ๊ธฐ๋ฐ˜ ์••์ถ• ์•Œ๊ณ ๋ฆฌ์ฆ˜์ž…๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ๋ฅผ ์ˆœ์„œ๋Œ€๋กœ ์ฝ์œผ๋ฉด์„œ ๋ฐ˜๋ณต ํŒจํ„ด์„ LZ77 ์•Œ๊ณ ๋ฆฌ์ฆ˜์œผ๋กœ ์••์ถ•ํ•ฉ๋‹ˆ๋‹ค. .nii.gz๋Š” NIfTI ํ—ค๋” + ๋ณผ๋ฅจ ์ „์ฒด๋ฅผ ํ•˜๋‚˜์˜ gzip ์ŠคํŠธ๋ฆผ์œผ๋กœ ๋ฌถ์€ ๋‹จ์ผ ํŒŒ์ผ์ž…๋‹ˆ๋‹ค.

brain.nii.gz โ€” ๋‹จ์ผ gzip ์ŠคํŠธ๋ฆผ NIfTI ํ—ค๋” ๋ณผ๋ฅจ ํ”ฝ์…€ ๋ฐ์ดํ„ฐ (์—ฐ์† ๋‹จ์ผ ๋ธ”๋ก) ์Šฌ๋ผ์ด์Šค 0 โ†’ ์Šฌ๋ผ์ด์Šค 1 โ†’ ยทยทยท โ†’ ์Šฌ๋ผ์ด์Šค N (์ˆœ์ฐจ ์ €์žฅ, ๋žœ๋ค ์ ‘๊ทผ ๋ถˆ๊ฐ€) ํŠน์ • ์Šฌ๋ผ์ด์Šค์— ์ ‘๊ทผํ•˜๋ ค๋ฉด? โš  ํŒŒ์ผ ์ „์ฒด๋ฅผ ์ˆœ์„œ๋Œ€๋กœ ์••์ถ• ํ•ด์ œํ•ด์•ผ๋งŒ ์ž„์˜ ์œ„์น˜ ์ ‘๊ทผ ๊ฐ€๋Šฅ gzip ์ŠคํŠธ๋ฆผ ํŠน์„ฑ: ์ค‘๊ฐ„ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฑด๋„ˆ๋›ธ ์ˆ˜ ์—†์Œ โ†’ ์Šฌ๋ผ์ด์Šค 150๋ฒˆ ์ ‘๊ทผ = 0๋ฒˆ๋ถ€ํ„ฐ 150๋ฒˆ๊นŒ์ง€ ์ „๋ถ€ ์••์ถ• ํ•ด์ œ
โœ…

.nii.gz ์žฅ์ 

  • ๋‹จ์ผ ํŒŒ์ผ โ€” ๊ด€๋ฆฌยท๋ฐฐํฌยท๊ณต์œ  ๊ฐ„๋‹จ
  • ๋†’์€ ์••์ถ•๋ฅ  (์˜๋ฃŒ ๋ฐ์ดํ„ฐ 3~5:1)
  • ๋ชจ๋“  NIfTI ๋„๊ตฌ ์™„๋ฒฝ ํ˜ธํ™˜
  • ์ˆœ์ฐจ ์ฒ˜๋ฆฌ์— ์ตœ์  (AI ํ•™์Šต ๋“ฑ)
  • ๊ตฌํ˜„ ๋ณต์žก๋„ ๋‚ฎ์Œ
โŒ

.nii.gz ๋‹จ์ 

  • ์ž„์˜ ์ ‘๊ทผ ๋ถˆ๊ฐ€ โ€” ์ „์ฒด ํ•ด์ œ ํ•„์š”
  • ์›น ์ŠคํŠธ๋ฆฌ๋ฐ ๋ถˆ๊ฐ€ (gzip์€ ์ŠคํŠธ๋ฆผ)
  • ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ ๋ถˆ๊ฐ€ (์ˆœ์ฐจ ์˜์กด)
  • ๋ฉ€ํ‹ฐํ•ด์ƒ๋„ ์ €์žฅ ๋ถˆ๊ฐ€
  • ๋Œ€์šฉ๋Ÿ‰ ์ดˆ๊ธฐ ๋กœ๋”ฉ ์ˆ˜์‹ญ ์ดˆ
03 ยท Zarr ๊ตฌ์กฐ

Zarr์˜ ์ฒญํฌ ๊ธฐ๋ฐ˜ ์•„ํ‚คํ…์ฒ˜

Zarr๋Š” N์ฐจ์› ๋ฐฐ์—ด์„ ๋…๋ฆฝ์ ์ธ ์ฒญํฌ(chunk) ๋‹จ์œ„๋กœ ๋ถ„ํ•  ์ €์žฅํ•˜๋Š” ํฌ๋งท์ž…๋‹ˆ๋‹ค. ๊ฐ ์ฒญํฌ๋Š” ๊ฐœ๋ณ„ ํŒŒ์ผ(๋˜๋Š” ๊ฐ์ฒด)๋กœ ์ €์žฅ๋˜๊ณ , ๋…๋ฆฝ์ ์œผ๋กœ ์••์ถ•๋˜์–ด ์žˆ์–ด ํŠน์ • ์ฒญํฌ๋งŒ HTTP ์š”์ฒญ์œผ๋กœ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋””๋ ‰ํ† ๋ฆฌ ๊ธฐ๋ฐ˜ ๊ตฌ์กฐ์ด๋ฉฐ JSON ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๊ฐ€ ์ž๊ธฐ ์„œ์ˆ ์ (self-describing)์œผ๋กœ ํฌํ•จ๋ฉ๋‹ˆ๋‹ค.

brain.zarr/ โ€” ๋””๋ ‰ํ† ๋ฆฌ ๊ตฌ์กฐ zarr.json (๋ฉ”ํƒ€๋ฐ์ดํ„ฐ) c/0/0/0 c/0/0/1 c/0/1/0 c/0/1/1 c/1/0/0 c/1/0/1 ยทยทยท ์ฒญํฌ ์ˆ˜๋งŒํผ ํŒŒ์ผ ยทยทยท ๊ฐ ์ฒญํฌ = ๋…๋ฆฝ ์••์ถ• ๋ฐ”์ด๋„ˆ๋ฆฌ ์ฒญํฌ ํฌ๊ธฐ ์˜ˆ: 64ร—64ร—64 ๋ณต์…€ ์ฒญํฌ ๋‹จ์œ„ HTTP ์ ‘๊ทผ ํ˜„์žฌ ๋ทฐํฌํŠธ (์Šฌ๋ผ์ด์Šค 150๋ฒˆ) ์ฒญํฌ ์š”์ฒญ ์ฒญํฌ ์š”์ฒญ ํ•„์š”ํ•œ 2~4๊ฐœ ์ฒญํฌ๋งŒ HTTP Range GET ์ •์  HTTP ์„œ๋ฒ„ S3 / Nginx / CDN ์ฒญํฌ ํŒŒ์ผ ์‘๋‹ต Zarr ์ฒญํฌ ์ ‘๊ทผ ๊ฒฐ๊ณผ โœ“ ์Šฌ๋ผ์ด์Šค 150 โ†’ ํ•ด๋‹น ์ฒญํฌ๋งŒ fetch โœ“ ์••์ถ• ํ•ด์ œ: ํ•ด๋‹น ์ฒญํฌ๋งŒ โœ“ ๋‚˜๋จธ์ง€ ์Šฌ๋ผ์ด์Šค ๋‹ค์šด๋กœ๋“œ ์—†์Œ โœ“ ์ „์ฒด ๋Œ€๋น„ ์ˆ˜๋ฐฑ๋ถ„์˜ 1 ๋ฐ์ดํ„ฐ
โœ…

Zarr ์žฅ์ 

  • ์ž„์˜ ์ ‘๊ทผ โ€” ํŠน์ • ์ฒญํฌ๋งŒ HTTP GET
  • ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ โ€” ์ฒญํฌ ๋™์‹œ fetch ๊ฐ€๋Šฅ
  • ๋ฉ€ํ‹ฐํ•ด์ƒ๋„ ํ”ผ๋ผ๋ฏธ๋“œ ๋‚ด์žฅ
  • Progressive ๋ Œ๋”๋ง (์ฒญํฌ ๋„์ฐฉ์ˆœ)
  • ํด๋ผ์šฐ๋“œ(S3, GCS) ๋„ค์ดํ‹ฐ๋ธŒ
  • ์ •์  HTTP ์„œ๋ฒ„๋งŒ์œผ๋กœ ์„œ๋น™ ๊ฐ€๋Šฅ
  • ๋‹ค์–‘ํ•œ ์••์ถ• ์•Œ๊ณ ๋ฆฌ์ฆ˜ ์„ ํƒ
โŒ

Zarr ๋‹จ์ 

  • ํŒŒ์ผ ์ˆ˜ ํญ๋ฐœ (์ˆ˜๋ฐฑ~์ˆ˜์ฒœ ๊ฐœ ์ฒญํฌ)
  • ๊ธฐ์กด NIfTI ๋„๊ตฌ ๋ฏธํ˜ธํ™˜
  • ๋ณ€ํ™˜(์ „์ฒ˜๋ฆฌ) ํŒŒ์ดํ”„๋ผ์ธ ํ•„์š”
  • ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ๋™๊ธฐํ™” ๊ด€๋ฆฌ ํ•„์š”
  • ์†Œํ˜• ๋ณผ๋ฅจ์—์„œ๋Š” ์˜ค๋ฒ„ํ—ค๋“œ
  • NIfTI qform/sform ์ €์žฅ ๋ถˆํ‘œ์ค€
04 ยท ํ•ต์‹ฌ ๋น„๊ต

.nii.gz vs Zarr โ€” ํ•ญ๋ชฉ๋ณ„ ๋Œ€์กฐ

ํ•ญ๋ชฉ.nii.gzZarr์›น ๋ทฐ์–ด ์˜ํ–ฅ
์ €์žฅ ๋‹จ์œ„๋‹จ์ผ ํŒŒ์ผ์ฒญํฌ ๋””๋ ‰ํ† ๋ฆฌZarr๊ฐ€ ์Šฌ๋ผ์ด์Šค ๋‹จ์œ„ fetch ๊ฐ€๋Šฅ
์ž„์˜ ์ ‘๊ทผ๋ถˆ๊ฐ€๊ฐ€๋Šฅ (์ฒญํฌ ๋‹จ์œ„)Zarr๋งŒ ํ˜„์žฌ ์Šฌ๋ผ์ด์Šค ์ฆ‰์‹œ ํ‘œ์‹œ
์••์ถ• ์•Œ๊ณ ๋ฆฌ์ฆ˜gzip ์ „์šฉgzip, zstd, blosc, lz4 ๋“ฑ ์„ ํƒZarr+blosc/zstd: ํ•ด์ œ ์†๋„ 5~10ร—โ†‘
์••์ถ• ๋ฒ”์œ„ํŒŒ์ผ ์ „์ฒด ๋‹จ์ผ ์••์ถ•์ฒญํฌ๋ณ„ ๋…๋ฆฝ ์••์ถ•ํ•„์š”ํ•œ ์ฒญํฌ๋งŒ ํ•ด์ œ, CPU ์ ˆ์•ฝ
๋ฉ€ํ‹ฐํ•ด์ƒ๋„์—†์Œํ”ผ๋ผ๋ฏธ๋“œ ๋‚ด์žฅZarr: ์ €ํ•ด์ƒ๋„ ๋จผ์ € โ†’ ๊ณ ํ•ด์ƒ๋„ ์ˆœ
๋ณ‘๋ ฌ fetch๋ถˆ๊ฐ€๊ฐ€๋Šฅ (์ฒญํฌ ๋…๋ฆฝ)์—ฌ๋Ÿฌ ์ฒญํฌ ๋™์‹œ ์š”์ฒญ ๊ฐ€๋Šฅ
์„œ๋ฒ„ ์š”๊ตฌ์‚ฌํ•ญRange Request ์ง€์› ํ•„์š”์ •์  HTTP๋งŒ์œผ๋กœ ๊ฐ€๋ŠฅZarr: S3/CDN ์ง๊ฒฐ ์„œ๋น™ ๊ฐ€๋Šฅ
์ฒซ ๋ Œ๋” ์‹œ๊ฐ„์ „์ฒด ๋‹ค์šด๋กœ๋“œ ํ›„ํ•ด๋‹น ์ฒญํฌ ๋„์ฐฉ ์ฆ‰์‹œ๋Œ€์šฉ๋Ÿ‰์—์„œ ์ˆ˜์‹ญ ์ดˆ vs ์ˆ˜ ์ดˆ ์ฐจ์ด
๋„๊ตฌ ํ˜ธํ™˜์„ฑ์™„๋ฒฝ ํ˜ธํ™˜์ „์šฉ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ธฐ์กด NIfTI ํŒŒ์ดํ”„๋ผ์ธ ์ˆ˜์ • ํ•„์š”
ํŒŒ์ผ ์ˆ˜1๊ฐœ์ˆ˜๋ฐฑ~์ˆ˜์ฒœ ๊ฐœํŒŒ์ผ์‹œ์Šคํ…œ ๊ด€๋ฆฌ ๋ณต์žก๋„ ์ฆ๊ฐ€
๋ณ€ํ™˜ ํ•„์š”๋ถˆํ•„์š”niiโ†’zarr ์ „์ฒ˜๋ฆฌ์„œ๋ฒ„ ํŒŒ์ดํ”„๋ผ์ธ ๊ตฌ์„ฑ ํ•„์š”

์••์ถ•/ํ•ด์ œ ์†๋„ ๋น„๊ต (1024ร—1024ร—300 ๋ณผ๋ฅจ ๊ธฐ์ค€)

.nii.gz (gzip) ์••์ถ• ํ•ด์ œ
๊ธฐ์ค€ 1ร—
Zarr + gzip ์ฒญํฌ
~1.5ร— ๋น ๋ฆ„
Zarr + zstd ์ฒญํฌ
~3ร— ๋น ๋ฆ„
Zarr + Blosc/LZ4 ์ฒญํฌ
~8ร— ๋น ๋ฆ„

* ์ „์ฒด ๋ณผ๋ฅจ ๊ธฐ์ค€์ด ์•„๋‹Œ ๋‹จ์ผ ์Šฌ๋ผ์ด์Šค ํ•ด๋‹น ์ฒญํฌ ํ•ด์ œ ๊ธฐ์ค€ / ์‹คํ™˜๊ฒฝ ์ฐจ์ด ์กด์žฌ

05 ยท NIfTI + Zarr

NIfTI์— Zarr๋ฅผ ์ ์šฉํ•œ๋‹ค๋ฉด โ€” NIfTI-Zarr

์‹ ๊ฒฝ์˜์ƒ ์ปค๋ฎค๋‹ˆํ‹ฐ๋Š” ๋‘ ํฌ๋งท์˜ ์žฅ์ ์„ ๊ฒฐํ•ฉํ•œ NIfTI-Zarr(nii.zarr) ์ŠคํŽ™์„ ์ œ์•ˆํ–ˆ์Šต๋‹ˆ๋‹ค. OME-Zarr๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•˜๋˜ NIfTI ํ—ค๋”(qform/sform ๋“ฑ)๋ฅผ Zarr ๋‚ด๋ถ€์— raw bytes๋กœ ๋ณด์กดํ•˜์—ฌ, ๊ธฐ์กด NIfTI ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ์™€ Zarr์˜ ์ฒญํฌ ์ ‘๊ทผ์„ ๋™์‹œ์— ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

brain.nii.zarr/ โ€” NIfTI-Zarr ๊ตฌ์กฐ zarr.json shape, chunks, dtype, axes nifti/ (raw NIfTI ํ—ค๋”) qform, sform, pixdim ์™„์ „ ๋ณด์กด 0/ (์›๋ณธ) 1024ร—1024ร—300 1/ (ยฝ ํ•ด์ƒ๋„) 512ร—512ร—150 2/ (ยผ ํ•ด์ƒ๋„) 256ร—256ร—75 3/ (โ…› ํ•ด์ƒ๋„) 128ร—128ร—37 ยทยทยท ๊ฐ ํ•ด์ƒ๋„ ๋ ˆ๋ฒจ ๋‚ด๋ถ€: 64ร—64ร—64 ์ฒญํฌ๋กœ ๋ถ„ํ•  โ†’ HTTP GET์œผ๋กœ ๋…๋ฆฝ ์ ‘๊ทผ ์›น ๋ทฐ์–ด๊ฐ€ ํ˜„์žฌ ์คŒ ๋ ˆ๋ฒจ์— ๋งž๋Š” ํ•ด์ƒ๋„ ๋ ˆ๋ฒจ ์„ ํƒ โ†’ ํ•„์š”ํ•œ ์ฒญํฌ๋งŒ fetch โ†’ Progressive ๋ Œ๋”๋ง Google Maps ๋ฐฉ์‹ โ€” ์คŒ ์•„์›ƒ: ์ €ํ•ด์ƒ๋„, ์คŒ ์ธ: ๊ณ ํ•ด์ƒ๋„ ์ฒญํฌ ์ž๋™ ์„ ํƒ
๐Ÿ“ NIfTI-Zarr๋Š” OME-Zarr ์™„์ „ ํ˜ธํ™˜์ž…๋‹ˆ๋‹ค. ๊ธฐ์กด OME-Zarr ๋ทฐ์–ด(Neuroglancer, napari ๋“ฑ)์—์„œ ๊ทธ๋Œ€๋กœ ์—ด๋ฆฌ๋ฉฐ, NIfTI ํ—ค๋”๋Š” ๋ณ„๋„ ๋ณด์กด๋˜์–ด nibabel๋กœ๋„ ์ฝ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. nifti-zarr PyPI ํŒจํ‚ค์ง€(nii2zarr)๋กœ ๋ณ€ํ™˜ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

์ฒญํฌ ํฌ๊ธฐ ์„ ํƒ ์ „๋žต

์ฒญํฌ ํฌ๊ธฐ๋Š” Zarr์—์„œ ๊ฐ€์žฅ ์ค‘์š”ํ•œ ์„ฑ๋Šฅ ๊ฒฐ์ • ์š”์†Œ์ž…๋‹ˆ๋‹ค. ๋„ˆ๋ฌด ์ž‘์œผ๋ฉด HTTP ์š”์ฒญ ์ˆ˜๊ฐ€ ํญ๋ฐœํ•˜๊ณ , ๋„ˆ๋ฌด ํฌ๋ฉด gzip๊ณผ ๋‹ค๋ฅผ ๋ฐ” ์—†์–ด์ง‘๋‹ˆ๋‹ค.

์ฒญํฌ ํฌ๊ธฐHTTP ์š”์ฒญ ์ˆ˜ (์Šฌ๋ผ์ด์Šค 1์žฅ)์žฅ์ ๋‹จ์ ์ถ”์ฒœ ์‹œ๋‚˜๋ฆฌ์˜ค
16ร—16ร—16~16๊ฐœ ์š”์ฒญ์„ธ๋ฐ€ํ•œ ๋ถ€๋ถ„ ์ ‘๊ทผ์š”์ฒญ ์ˆ˜ ํญ๋ฐœ, ์˜ค๋ฒ„ํ—ค๋“œ ํผ๋น„๊ถŒ์žฅ
64ร—64ร—64~4๊ฐœ ์š”์ฒญ๊ท ํ˜•์  ์„ฑ๋Šฅ์ผ๋ถ€ ๋ถˆํ•„์š” ๋ฐ์ดํ„ฐ ํฌํ•จ3D ๋ทฐ์–ด ๊ถŒ์žฅ
1ร—512ร—5121๊ฐœ ์š”์ฒญ์Šฌ๋ผ์ด์Šค ๋‹จ์œ„ ์™„๋ฒฝ ์ ‘๊ทผZ๋ฐฉํ–ฅ ์ ‘๊ทผ ์‹œ ๋‹ค์ˆ˜ ์š”์ฒญ2D MPR ๊ถŒ์žฅ
512ร—512ร—11๊ฐœ ์š”์ฒญAxial ๋ทฐ ์ตœ์ Sagittal/Coronal ๋น„ํšจ์œจAxial ์ „์šฉ
06 ยท ๊ธฐ์ˆ  ์ ์šฉ ๋ฐฉ์‹

์›น ๋ทฐ์–ด์—์„œ์˜ Zarr ์ ์šฉ ์•„ํ‚คํ…์ฒ˜

Cornerstone3D + NIfTI-Zarr ์กฐํ•ฉ์—์„œ ๋ฐ์ดํ„ฐ๊ฐ€ ๋ธŒ๋ผ์šฐ์ €์— ๋„๋‹ฌํ•˜๋Š” ์ „์ฒด ํ๋ฆ„์ž…๋‹ˆ๋‹ค. ๊ธฐ์กด .nii.gz ๋ฐฉ์‹๊ณผ ๋‹ฌ๋ฆฌ ๊ฐ ์Šฌ๋ผ์ด์Šค ์š”์ฒญ์ด ๋…๋ฆฝ์ ์ธ HTTP ์ฒญํฌ fetch๋กœ ๋Œ€์ฒด๋ฉ๋‹ˆ๋‹ค.

1
์„œ๋ฒ„: .nii.gz โ†’ .nii.zarr ๋ณ€ํ™˜ (์ „์ฒ˜๋ฆฌ)
AI ์ถ”๋ก  ์™„๋ฃŒ ํ›„ ๋˜๋Š” ์ €์žฅ ์‹œ์ ์— nii2zarr ๋„๊ตฌ๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ๋ฉ€ํ‹ฐํ•ด์ƒ๋„ ํ”ผ๋ผ๋ฏธ๋“œ ์ƒ์„ฑ + 64ร—64ร—64 ์ฒญํฌ ๋ถ„ํ•  + zstd ์••์ถ•์„ ์ž๋™์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค. S3, GCS, Nginx ๋“ฑ ์ •์  ์„œ๋ฒ„์— ์—…๋กœ๋“œํ•ฉ๋‹ˆ๋‹ค.

nii2zarr --chunk 64 --levels 4 --compressor blosc brain.nii.gz brain.nii.zarr/
2
๋ธŒ๋ผ์šฐ์ €: zarr.json ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ fetch (1ํšŒ)
๋ณผ๋ฅจ shape, chunk ํฌ๊ธฐ, ํ•ด์ƒ๋„ ๋ ˆ๋ฒจ, dtype, ์••์ถ• ์•Œ๊ณ ๋ฆฌ์ฆ˜ ์ •๋ณด๋ฅผ JSON์œผ๋กœ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค. ์ด ๋‹จ๊ณ„์—์„œ ํ”ฝ์…€ ๋ฐ์ดํ„ฐ๋Š” ์ „ํ˜€ ์—†์Šต๋‹ˆ๋‹ค. ์‘๋‹ต ํฌ๊ธฐ ์ˆ˜ KB, ์ˆ˜์‹ญ ms ๋‚ด ์™„๋ฃŒ๋ฉ๋‹ˆ๋‹ค.
3
ํ•ด์ƒ๋„ ๋ ˆ๋ฒจ ์„ ํƒ (๋ฉ€ํ‹ฐํ•ด์ƒ๋„ ํ™œ์šฉ)
์ฒซ ํ‘œ์‹œ๋Š” ๊ฐ€์žฅ ๋‚ฎ์€ ํ•ด์ƒ๋„(level 3: 128ร—128ร—37)๋ฅผ fetchํ•ฉ๋‹ˆ๋‹ค. ์ „์ฒด ๋ณผ๋ฅจ์„ ์ˆ˜ KB~์ˆ˜์‹ญ KB๋กœ ํ‘œํ˜„ํ•˜๋ฏ€๋กœ ๊ฑฐ์˜ ์ฆ‰์‹œ ๋ Œ๋”๋ง๋ฉ๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ์คŒ์ธํ•˜๊ฑฐ๋‚˜ ํŠน์ • ์Šฌ๋ผ์ด์Šค๋ฅผ ์„ ํƒํ•˜๋ฉด ํ•ด๋‹น ์˜์—ญ์˜ ๊ณ ํ•ด์ƒ๋„(level 0) ์ฒญํฌ๋ฅผ ์ถ”๊ฐ€ fetchํ•ฉ๋‹ˆ๋‹ค.
4
ํ˜„์žฌ ์Šฌ๋ผ์ด์Šค ์ฒญํฌ ๋ณ‘๋ ฌ fetch
๋ทฐํฌํŠธ์— ํ•„์š”ํ•œ ์ฒญํฌ ์ขŒํ‘œ๋ฅผ ๊ณ„์‚ฐํ•˜๊ณ  ํ•ด๋‹น ์ฒญํฌ ํŒŒ์ผ๋“ค์„ ๋ณ‘๋ ฌ HTTP GET์œผ๋กœ ์š”์ฒญํ•ฉ๋‹ˆ๋‹ค. 64ร—64ร—64 ์ฒญํฌ ๊ธฐ์ค€์œผ๋กœ 512ร—512 ์Šฌ๋ผ์ด์Šค๋Š” ์•ฝ 4~8๊ฐœ ์ฒญํฌ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ์ฒญํฌ ๋„์ฐฉ ์ฆ‰์‹œ ํ•ด๋‹น ์˜์—ญ ๋ Œ๋”๋ง์„ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค.
5
Prefetch (์ธ์ ‘ ์Šฌ๋ผ์ด์Šค ์ฒญํฌ)
ํ˜„์žฌ ์Šฌ๋ผ์ด์Šค ๋ Œ๋”๋ง ์™„๋ฃŒ ํ›„ ์ธ์ ‘ ์Šฌ๋ผ์ด์Šค์˜ ์ฒญํฌ๋ฅผ ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ํ”„๋ฆฌํŒจ์น˜ํ•ฉ๋‹ˆ๋‹ค. ์Šคํฌ๋กค ์‹œ ์ด๋ฏธ ์บ์‹œ๋œ ์ฒญํฌ๋Š” ์ฆ‰์‹œ ๋ Œ๋”๋ง๋ฉ๋‹ˆ๋‹ค.

Cornerstone3D ์ปค์Šคํ…€ Image Loader ๊ตฌํ˜„

Cornerstone3D์—๋Š” Zarr ๊ณต์‹ ๋กœ๋”๊ฐ€ ์—†์œผ๋ฏ€๋กœ, ์ปค์Šคํ…€ ImageLoader๋ฅผ ๊ตฌํ˜„ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

// zarr-image-loader.ts โ€” ์ปค์Šคํ…€ Zarr ์ฒญํฌ ๊ธฐ๋ฐ˜ Image Loader
import * as zarr from 'zarrita';  // ๋˜๋Š” zarr-js
import { imageLoader } from '@cornerstonejs/core';

let zarrStore: zarr.Array | null = null;

// 1. Zarr ์Šคํ† ์–ด ์ดˆ๊ธฐํ™” (zarr.json ์ฝ๊ธฐ)
export async function initZarrStore(url: string) {
  const store = new zarr.FetchStore(url);
  zarrStore = await zarr.open(store, { kind: 'array' });
}

// 2. ์Šฌ๋ผ์ด์Šค ๋‹จ์œ„ ImageLoader ๋“ฑ๋ก
export function zarritaImageLoader(imageId: string) {
  const sliceIndex = parseSliceIndex(imageId);  // zarr://url#150

  const promise = (async () => {
    // ํ•ด๋‹น ์Šฌ๋ผ์ด์Šค์˜ ์ฒญํฌ๋งŒ fetch (zarrita๊ฐ€ ์ž๋™์œผ๋กœ ์ฒญํฌ ์ขŒํ‘œ ๊ณ„์‚ฐ)
    const sliceData = await zarr.get(zarrStore, [sliceIndex, null, null]);

    return {
      imageId,
      minPixelValue: min(sliceData),
      maxPixelValue: max(sliceData),
      rows: sliceData.shape[0],
      columns: sliceData.shape[1],
      getPixelData: () => new Float32Array(sliceData.data),
    };
  })();

  return { promise };
}

// 3. Cornerstone3D์— ๋“ฑ๋ก
imageLoader.registerImageLoader('zarr', zarritaImageLoader);
07 ยท ์˜ˆ์ƒ ๋ฌธ์ œ์ 

Zarr ๋„์ž… ์‹œ ์˜ˆ์ƒ๋˜๋Š” ๋ฌธ์ œ์ 

1
ํŒŒ์ผ ์ˆ˜ ํญ๋ฐœ โ€” ์Šคํ† ๋ฆฌ์ง€ ๊ด€๋ฆฌ ๋ณต์žก๋„ ๊ธ‰์ฆ
1024ร—1024ร—300 ๋ณผ๋ฅจ์„ 64ร—64ร—64 ์ฒญํฌ๋กœ ๋ถ„ํ• ํ•˜๋ฉด ์ฒญํฌ ์ˆ˜ = ceil(1024/64) ร— ceil(1024/64) ร— ceil(300/64) = 16 ร— 16 ร— 5 = 1,280๊ฐœ ํŒŒ์ผ. ๋ฉ€ํ‹ฐํ•ด์ƒ๋„ 4๋ ˆ๋ฒจ ํฌํ•จ ์‹œ ์•ฝ 1,700๊ฐœ+. S3์—์„œ๋Š” ํŒŒ์ผ ์ˆ˜ ๊ณผ๊ธˆ์ด ์—†์ง€๋งŒ, ๋กœ์ปฌ ํŒŒ์ผ์‹œ์Šคํ…œ์—์„œ๋Š” inode ์†Œ์ง„ ์œ„ํ—˜์ด ์žˆ์Šต๋‹ˆ๋‹ค.

๋Œ€์‘: Zarr v3์˜ Sharding ๊ธฐ๋Šฅ์œผ๋กœ ์—ฌ๋Ÿฌ ์ฒญํฌ๋ฅผ ํ•˜๋‚˜์˜ shard ํŒŒ์ผ๋กœ ๋ฌถ์–ด ํŒŒ์ผ ์ˆ˜๋ฅผ ํš๊ธฐ์ ์œผ๋กœ ์ค„์ž…๋‹ˆ๋‹ค. ๋˜๋Š” ZIP ๊ธฐ๋ฐ˜ Zarr store(.ome.zarr.zip)๋กœ ๋‹จ์ผ ํŒŒ์ผ๋กœ ํŒจํ‚ค์ง•ํ•ฉ๋‹ˆ๋‹ค.
2
CORS ์ •์ฑ… โ€” ์ฒญํฌ fetch ์‹œ ๋ธŒ๋ผ์šฐ์ € ์ฐจ๋‹จ
๋ธŒ๋ผ์šฐ์ €์—์„œ ๋‹ค๋ฅธ ๋„๋ฉ”์ธ์˜ Zarr ์ฒญํฌ๋ฅผ fetchํ•˜๋ฉด CORS ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ์ •์  ์„œ๋ฒ„(S3, Nginx)์— ๋ฐ˜๋“œ์‹œ CORS ํ—ค๋”๋ฅผ ์„ค์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ฒญํฌ๋งˆ๋‹ค preflight ์š”์ฒญ์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์–ด ์„ฑ๋Šฅ ์ €ํ•˜๊ฐ€ ์ƒ๊น๋‹ˆ๋‹ค.

๋Œ€์‘: ์„œ๋ฒ„์— Access-Control-Allow-Origin: * + Access-Control-Allow-Headers: Range ์„ค์ •. ๋™์ผ ๋„๋ฉ”์ธ ์„œ๋น™์œผ๋กœ CORS ์ž์ฒด๋ฅผ ์ œ๊ฑฐํ•˜๋Š” ๊ฒƒ์ด ์ตœ์„ ์ž…๋‹ˆ๋‹ค.
3
NIfTI ์ขŒํ‘œ๊ณ„ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ๋ณด์กด ๋ฌธ์ œ
ํ‘œ์ค€ OME-Zarr๋Š” NIfTI์˜ qform/sform ํ–‰๋ ฌ์„ ์ €์žฅํ•˜๋Š” ๊ณต๊ฐ„์ด ์—†์Šต๋‹ˆ๋‹ค. NIfTI-Zarr(nii.zarr) ์ŠคํŽ™์ด ์ด๋ฅผ ํ•ด๊ฒฐํ•˜์ง€๋งŒ, Cornerstone3D์˜ nifti-volume-loader๋Š” ํ˜„์žฌ nii.zarr ํฌ๋งท์„ ์ง์ ‘ ์ง€์›ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ปค์Šคํ…€ ImageLoader์—์„œ NIfTI ํ—ค๋”๋ฅผ ๋ณ„๋„ ํŒŒ์‹ฑํ•ด MetaData Provider์— ์ˆ˜๋™ ๋“ฑ๋กํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๋Œ€์‘: zarr.json ์†์„ฑ์— NIfTI ํ—ค๋” ์ •๋ณด๋ฅผ JSON์œผ๋กœ ์ถ”๊ฐ€ ์ €์žฅํ•˜๊ณ , ImageLoader ์ดˆ๊ธฐํ™” ์‹œ MetaData Provider์— ๋“ฑ๋กํ•˜๋Š” ์œ ํ‹ธ๋ฆฌํ‹ฐ ํ•จ์ˆ˜๋ฅผ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.
4
์ฒญํฌ ๊ฒฝ๊ณ„ ์•„ํ‹ฐํŒฉํŠธ (๋ Œ๋”๋ง ์ด์Œ์ƒˆ)
์ฒญํฌ ํฌ๊ธฐ๊ฐ€ ์Šฌ๋ผ์ด์Šค ํฌ๊ธฐ์™€ ๋งž์ง€ ์•Š์œผ๋ฉด ์ฒญํฌ ๊ฒฝ๊ณ„์—์„œ ๋ณด๊ฐ„ ์•„ํ‹ฐํŒฉํŠธ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํŠนํžˆ Volume Rendering์—์„œ ์ฒญํฌ ๊ฒฝ๊ณ„๊ฐ€ ๋ฐ์€ ์„ ์œผ๋กœ ๋ณด์ด๋Š” ํ˜„์ƒ์ด ๋‚˜ํƒ€๋‚ฉ๋‹ˆ๋‹ค.

๋Œ€์‘: ์Šฌ๋ผ์ด์Šค ์ถ•(Z) ๋ฐฉํ–ฅ์˜ ์ฒญํฌ ํฌ๊ธฐ๋ฅผ 1๋กœ ์„ค์ •(1ร—512ร—512)ํ•˜๋ฉด ์Šฌ๋ผ์ด์Šค ๋‹จ์œ„ ์ฒญํฌ๊ฐ€ ๋˜์–ด MPR ๋ทฐ์–ด์—์„œ ์•„ํ‹ฐํŒฉํŠธ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. Volume Rendering์—๋Š” 64ร—64ร—64 ์ž…๋ฐฉํ˜• ์ฒญํฌ๊ฐ€ ์ ํ•ฉํ•ฉ๋‹ˆ๋‹ค.
5
HTTP ์š”์ฒญ ์ˆ˜ ์ฆ๊ฐ€ โ†’ ๋ ˆ์ดํ„ด์‹œ ๋ˆ„์ 
์Šฌ๋ผ์ด์Šค ํ•˜๋‚˜์— 4~8๊ฐœ ์ฒญํฌ ์š”์ฒญ์ด ๋ฐœ์ƒํ•˜๋ฉด, HTTP ์—ฐ๊ฒฐ ์˜ค๋ฒ„ํ—ค๋“œ๊ฐ€ ๋ˆ„์ ๋ฉ๋‹ˆ๋‹ค. ํŠนํžˆ ๊ณ ๋ ˆ์ดํ„ด์‹œ ํ™˜๊ฒฝ(ํ•ด์™ธ ํด๋ผ์šฐ๋“œ)์—์„œ๋Š” ์ฒญํฌ ์ˆ˜ร—RTT๊ฐ€ ์ฒด๊ฐ ์†๋„์— ์˜ํ–ฅ์„ ์ค๋‹ˆ๋‹ค.

๋Œ€์‘: HTTP/2 ๋ฉ€ํ‹ฐํ”Œ๋ ‰์‹ฑ์œผ๋กœ ๋ณ‘๋ ฌ ์š”์ฒญ์„ ๋‹จ์ผ ์—ฐ๊ฒฐ์— ๋ฌถ์–ด ํ•ด๊ฒฐํ•ฉ๋‹ˆ๋‹ค. ์ฒญํฌ ํฌ๊ธฐ๋ฅผ ํ‚ค์šฐ๊ฑฐ๋‚˜(128ร—128ร—128) Zarr v3 Sharding์„ ์‚ฌ์šฉํ•ด ์š”์ฒญ ์ˆ˜๋ฅผ ์ค„์ž…๋‹ˆ๋‹ค.
6
๋ณ€ํ™˜ ํŒŒ์ดํ”„๋ผ์ธ ๊ตฌ์ถ• ๋น„์šฉ
AI ๋ชจ๋ธ์ด .nii.gz๋กœ ๊ฒฐ๊ณผ๋ฅผ ์ถœ๋ ฅํ•˜๋Š” ๊ฒฝ์šฐ, ์›น ์„œ๋น™ ์ „ Zarr ๋ณ€ํ™˜ ๋‹จ๊ณ„๋ฅผ ํŒŒ์ดํ”„๋ผ์ธ์— ์ถ”๊ฐ€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. 1024ร—1024ร—300 ๋ณผ๋ฅจ ๋ณ€ํ™˜ ์‹œ CPU 30์ดˆ~์ˆ˜ ๋ถ„์ด ์†Œ์š”๋ฉ๋‹ˆ๋‹ค.

๋Œ€์‘: AI ์ถ”๋ก  ์™„๋ฃŒ ํ›„ ๋น„๋™๊ธฐ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—…(Celery, AWS Lambda)์œผ๋กœ ๋ณ€ํ™˜ํ•˜๊ณ , ๋ณ€ํ™˜ ์™„๋ฃŒ ์ „์—๋Š” .nii.gz ์›๋ณธ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์ตœ์ข…์ ์œผ๋กœ AI ๋ชจ๋ธ์ด ์ง์ ‘ Zarr๋กœ ์ถœ๋ ฅํ•˜๋„๋ก ํŒŒ์ดํ”„๋ผ์ธ์„ ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค.
08 ยท ์ „ํ™˜ ์ „๋žต

์‹ค์ œ ๋„์ž… ๋กœ๋“œ๋งต โ€” ๋‹จ๊ณ„๋ณ„ ์ „ํ™˜

๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฅผ ํ•œ ๋ฒˆ์— Zarr๋กœ ์ „ํ™˜ํ•˜๋Š” ๊ฒƒ์€ ์œ„ํ—˜ํ•ฉ๋‹ˆ๋‹ค. ๊ธฐ์กด .nii.gz์™€ Zarr๋ฅผ ๊ณต์กดํ•˜๋Š” ๋‹จ๊ณ„์  ์ „ํ™˜ ์ „๋žต์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค.

1๋‹จ๊ณ„
๋ณ‘๋ ฌ ์ œ๊ณต (ํ˜„์žฌ .nii.gz ์œ ์ง€)
์‹ ๊ทœ AI ์ถœ๋ ฅ๋ฌผ์— ํ•œํ•ด Zarr ๋ณ€ํ™˜ ํŒŒ์ดํ”„๋ผ์ธ์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. ํด๋ผ์ด์–ธํŠธ๊ฐ€ Accept: application/zarr ํ—ค๋”๋กœ Zarr๋ฅผ ์š”์ฒญํ•˜๋ฉด Zarr ์Šคํ† ์–ด URL์„ ์‘๋‹ตํ•˜๊ณ , ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ๊ธฐ์กด .nii.gz URL์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ๊ธฐ์กด ์‹œ์Šคํ…œ์„ ์ „ํ˜€ ๊ฑด๋“œ๋ฆฌ์ง€ ์•Š์•„ ์•ˆ์ „ํ•ฉ๋‹ˆ๋‹ค.
2๋‹จ๊ณ„
์›น ๋ทฐ์–ด ์ปค์Šคํ…€ ImageLoader ๊ฐœ๋ฐœ ๋ฐ ๊ฒ€์ฆ
Zarr ์ฒญํฌ ๊ธฐ๋ฐ˜ ์ปค์Šคํ…€ ImageLoader๋ฅผ ๊ฐœ๋ฐœํ•˜๊ณ , ์‹ค์ œ ๋ณผ๋ฅจ์œผ๋กœ ์ขŒํ‘œ๊ณ„(LPS ๋ณ€ํ™˜), W/L ์ž๋™ ๊ณ„์‚ฐ, ์ฒญํฌ ์บ์‹ฑ ๋™์ž‘์„ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค. A/B ํ…Œ์ŠคํŠธ๋กœ .nii.gz์™€ Zarr์˜ ๋กœ๋”ฉ ์‹œ๊ฐ„์„ ๋น„๊ตํ•ฉ๋‹ˆ๋‹ค.
3๋‹จ๊ณ„
๋ฉ€ํ‹ฐํ•ด์ƒ๋„ + ์ ์ง„์  ๋ Œ๋”๋ง ๊ตฌํ˜„
์ฒซ ๋ Œ๋”์— ์ €ํ•ด์ƒ๋„(level 3)๋ฅผ ํ‘œ์‹œํ•˜๊ณ , ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ๊ณ ํ•ด์ƒ๋„(level 0)๋ฅผ ์ฑ„์šฐ๋Š” Progressive ๋ Œ๋”๋ง์„ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž ์คŒ ๋ ˆ๋ฒจ์— ๋”ฐ๋ผ ์ž๋™์œผ๋กœ ํ•ด์ƒ๋„ ๋ ˆ๋ฒจ์„ ์„ ํƒํ•˜๋Š” LOD(Level of Detail) ๋กœ์ง์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
4๋‹จ๊ณ„
AI ํŒŒ์ดํ”„๋ผ์ธ ์ง์ ‘ Zarr ์ถœ๋ ฅ์œผ๋กœ ์ „ํ™˜
๋ณ€ํ™˜ ์˜ค๋ฒ„ํ—ค๋“œ๋ฅผ ์ œ๊ฑฐํ•˜๊ธฐ ์œ„ํ•ด AI ๋ชจ๋ธ์ด ๊ฒฐ๊ณผ๋ฅผ ์ง์ ‘ Zarr ์Šคํ† ์–ด๋กœ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. Python zarr ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ ์ฒญํฌ ๋‹จ์œ„ ์“ฐ๊ธฐ๊ฐ€ ๊ฐ€๋Šฅํ•˜๋ฏ€๋กœ, ์ถ”๋ก ์ด ์™„๋ฃŒ๋˜๋Š” ์Šฌ๋ผ์ด์Šค๋ถ€ํ„ฐ ์ฆ‰์‹œ Zarr์— ์ €์žฅํ•˜๋ฉด ์ถ”๋ก  ์™„๋ฃŒ์™€ ๋™์‹œ์— ์›น ๋ทฐ์–ด๊ฐ€ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.
# ์„œ๋ฒ„ ์‚ฌ์ด๋“œ: NIfTI โ†’ Zarr ๋ณ€ํ™˜ ํŒŒ์ดํ”„๋ผ์ธ (Python)
from niizarr import nii2zarr
import zarr
import nibabel as nib

# ๋ฐฉ๋ฒ• 1: nii2zarr CLI ๋„๊ตฌ
# nii2zarr --chunk 64 --levels 4 --compressor blosc input.nii.gz output.nii.zarr

# ๋ฐฉ๋ฒ• 2: Python API๋กœ ์ง์ ‘ ๋ณ€ํ™˜
img = nib.load('brain.nii.gz')
data = img.get_fdata()

store = zarr.open('brain.nii.zarr', mode='w')
# ์ฒญํฌ: Z์ถ• 1์Šฌ๋ผ์ด์Šค = MPR ์ตœ์ , X/Y๋Š” ์ „์ฒด
arr = store.require_dataset(
    'data',
    shape=data.shape,
    chunks=(1, data.shape[1], data.shape[2]),  # 1ร—Hร—W
    dtype=data.dtype,
    compressor=zarr.Blosc(cname='zstd', clevel=3, shuffle=zarr.Blosc.BITSHUFFLE)
)
arr[:] = data

# NIfTI ํ—ค๋”๋ฅผ zarr.json ์†์„ฑ์— ์ €์žฅ
store['data'].attrs['nifti_header'] = {
    'pixdim': img.header['pixdim'].tolist(),
    'sform_code': int(img.header['sform_code']),
    'srow_x': img.header['srow_x'].tolist(),
    'srow_y': img.header['srow_y'].tolist(),
    'srow_z': img.header['srow_z'].tolist(),
}
09 ยท Conclusion

๊ฒฐ๋ก  โ€” ์–ธ์ œ .nii.gz๋ฅผ, ์–ธ์ œ Zarr๋ฅผ ์„ ํƒํ•ด์•ผ ํ•˜๋‚˜

์‹œ๋‚˜๋ฆฌ์˜ค.nii.gzZarr
AI ํ•™์Šต ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ (์ˆœ์ฐจ ์ฝ๊ธฐ)์ ํ•ฉ์˜ค๋ฒ„ํ—ค๋“œ
๋กœ์ปฌ ๋ถ„์„ ๋„๊ตฌ (FSL, FreeSurfer)์™„๋ฒฝ ํ˜ธํ™˜๋ณ„๋„ ๋ณ€ํ™˜ ํ•„์š”
์›น ๋ทฐ์–ด ๋Œ€์šฉ๋Ÿ‰ ๋ณผ๋ฅจ ์„œ๋น™์ดˆ๊ธฐ ๋กœ๋”ฉ ๋А๋ฆผ์ฒญํฌ ๋‹จ์œ„ ์ฆ‰์‹œ ํ‘œ์‹œ
ํด๋ผ์šฐ๋“œ(S3) ์ €์žฅ & ์„œ๋น™Range Request ํ•„์š”์ •์  HTTP ๋ฐ”๋กœ ๊ฐ€๋Šฅ
๋ฉ€ํ‹ฐํ•ด์ƒ๋„ ์ ์ง„์  ๋ Œ๋”๋ง๋ถˆ๊ฐ€ํ”ผ๋ผ๋ฏธ๋“œ ๋‚ด์žฅ
์†Œ์šฉ๋Ÿ‰ ๋ณผ๋ฅจ (512ร—512ร—50 ์ดํ•˜)๊ฐ„๋‹จํ•˜๊ณ  ์ถฉ๋ถ„์ „ํ™˜ ๋น„์šฉ ๋Œ€๋น„ ์ด๋“ ์ ์Œ
1024ร—1024+ ๊ณ ํ•ด์ƒ๋„ ๋ณผ๋ฅจgzip ํ•ด์ œ ์ˆ˜๋ถ„ ์†Œ์š”ํ•„์š” ์ฒญํฌ๋งŒ ์ˆ˜ ์ดˆ
์‹ค์‹œ๊ฐ„ ํ˜‘์—… / ๋ณ‘๋ ฌ ์ ‘๊ทผ๋‹จ์ผ ์ŠคํŠธ๋ฆผ์ฒญํฌ ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ
๐Ÿงญ ๊ถŒ์žฅ ์ด์ค‘ ์ „๋žต: AI ํ•™์Šต ํŒŒ์ดํ”„๋ผ์ธ๊ณผ ๊ธฐ์กด ๋ถ„์„ ๋„๊ตฌ์—๋Š” .nii.gz๋ฅผ ์œ ์ง€ํ•˜๊ณ , ์›น ๋ทฐ์–ด ์„œ๋น™ ์ „์šฉ์œผ๋กœ๋งŒ Zarr๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ๋™์ผ ๋ณผ๋ฅจ์„ ๋‘ ํฌ๋งท์œผ๋กœ ์ €์žฅํ•˜๋”๋ผ๋„, ๋Œ€์šฉ๋Ÿ‰ ๋ณผ๋ฅจ์—์„œ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜ ํ–ฅ์ƒ์ด ์Šคํ† ๋ฆฌ์ง€ ๋น„์šฉ๋ณด๋‹ค ํ›จ์”ฌ ํฝ๋‹ˆ๋‹ค. ํŠนํžˆ 1024ร—1024 ์ด์ƒ์˜ ๊ณ ํ•ด์ƒ๋„ ๋ณผ๋ฅจ์—์„œ Zarr์˜ ๋ฉ€ํ‹ฐํ•ด์ƒ๋„ + ์ฒญํฌ ์ŠคํŠธ๋ฆฌ๋ฐ์€ .nii.gz์™€ ๋น„๊ตํ•  ์ˆ˜ ์—†๋Š” UX๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
โš ๏ธ ํ˜„์žฌ Cornerstone3D๋Š” Zarr๋ฅผ ๊ณต์‹ ์ง€์›ํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ, ์ปค์Šคํ…€ ImageLoader ๊ฐœ๋ฐœ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. NIfTI-Zarr(nii.zarr) ์ŠคํŽ™์€ ์•„์ง RC ๋‹จ๊ณ„์ด๋ฏ€๋กœ ํ”„๋กœ๋•์…˜ ๋„์ž… ์ „ ์ŠคํŽ™ ์•ˆ์ •์„ฑ์„ ํ™•์ธํ•˜์„ธ์š”. ๋Œ€์•ˆ์œผ๋กœ Zarr ๋ณ€ํ™˜ + ์ปค์Šคํ…€ REST API๋กœ ์Šฌ๋ผ์ด์Šค ๋‹จ์œ„ PNG๋ฅผ ์ œ๊ณตํ•˜๋Š” ํ•˜์ด๋ธŒ๋ฆฌ๋“œ ๋ฐฉ์‹๋„ ํ˜„์‹ค์ ์ธ ์„ ํƒ์ž…๋‹ˆ๋‹ค.