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

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

[React] SEG ์˜์—ญ๋งŒ Zarr๋กœ,๋‚˜๋จธ์ง€๋Š” ์›๋ณธ ๊ทธ๋Œ€๋กœ

SEG๋งŒ Zarr๋กœ โ€” ์„ ํƒ์  ์ฒญํฌ ๋ณ€ํ™˜ ์ „๋žต
Sparse Zarr Strategy

SEG ์˜์—ญ๋งŒ Zarr๋กœ,
๋‚˜๋จธ์ง€๋Š” ์›๋ณธ ๊ทธ๋Œ€๋กœ

์„ธ๊ทธ๋ฉ˜ํ…Œ์ด์…˜์ด ์žˆ๋Š” ์ฒญํฌ๋งŒ ์„ ํƒ์ ์œผ๋กœ Zarr๋กœ ๋ณ€ํ™˜ํ•˜๋Š” Sparse Zarr ์ „๋žต โ€” ์•„์ด๋””์–ด์˜ ํƒ€๋‹น์„ฑ, ๊ตฌํ˜„ ๋ฐฉ์‹, ์˜ˆ์ƒ ๋ฌธ์ œ์ ์„ ๊ธฐ์ˆ ์ ์œผ๋กœ ๊ฒ€ํ† ํ•ฉ๋‹ˆ๋‹ค.

Sparse Zarr write_empty_chunks=False SEG BBox ๋ถ„์„ ํ•˜์ด๋ธŒ๋ฆฌ๋“œ ์„œ๋น™ fill_value=0 ์„ ํƒ์  ์ฒญํฌ ๋ณ€ํ™˜
01 ยท Idea Validation

์ด ์•„์ด๋””์–ด, ๊ธฐ์ˆ ์ ์œผ๋กœ ํƒ€๋‹นํ•œ๊ฐ€?

๊ฒฐ๋ก ๋ถ€ํ„ฐ ๋งํ•˜๋ฉด โ€” ํƒ€๋‹นํ•˜๋ฉฐ, Zarr๊ฐ€ ๋„ค์ดํ‹ฐ๋ธŒ๋กœ ์ง€์›ํ•˜๋Š” ๊ฐœ๋…์ž…๋‹ˆ๋‹ค.

์„ธ๊ทธ๋ฉ˜ํ…Œ์ด์…˜(SEG) NIfTI๋Š” ๋ณธ์งˆ์ ์œผ๋กœ ํฌ์†Œ(sparse) ๋ฐ์ดํ„ฐ์ž…๋‹ˆ๋‹ค. ๋‡Œ์ข…์–‘, ๋ณ‘๋ณ€, ํ˜ˆ๊ด€ ๋“ฑ ๋ ˆ์ด๋ธ”์ด ์กด์žฌํ•˜๋Š” ๋ณต์…€์€ ์ „์ฒด ๋ณผ๋ฅจ์˜ ๊ทนํžˆ ์ผ๋ถ€์ด๊ณ , ๋‚˜๋จธ์ง€ ๋Œ€๋ถ€๋ถ„์€ 0(๋ฐฐ๊ฒฝ)์ž…๋‹ˆ๋‹ค. Zarr๋Š” ์ด ํŠน์„ฑ์„ write_empty_chunks=False + fill_value=0์œผ๋กœ ๋„ค์ดํ‹ฐ๋ธŒํ•˜๊ฒŒ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค โ€” ๊ฐ’์ด 0์ธ ์ฒญํฌ ํŒŒ์ผ์€ ๋””์Šคํฌ์— ์ €์žฅํ•˜์ง€ ์•Š๊ณ , ์ฝ์„ ๋•Œ ์•”๋ฌต์ ์œผ๋กœ 0์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ’ก ํ•ต์‹ฌ ์ธ์‚ฌ์ดํŠธ: Zarr์˜ Sparse ๋ฐฐ์—ด ํŠน์„ฑ์„ ํ™œ์šฉํ•˜๋ฉด SEG ๋ฐ์ดํ„ฐ๊ฐ€ ์กด์žฌํ•˜๋Š” ์ฒญํฌ๋งŒ ๋ฌผ๋ฆฌ์ ์œผ๋กœ ์ €์žฅ๋ฉ๋‹ˆ๋‹ค. SEG๊ฐ€ ์—†๋Š” ์ฒญํฌ๋Š” ํŒŒ์ผ ์ž์ฒด๊ฐ€ ์—†์–ด๋„ ํด๋ผ์ด์–ธํŠธ๋Š” fill_value(0)๋ฅผ ๋ฐ›์Šต๋‹ˆ๋‹ค. ์ฆ‰, "SEG๊ฐ€ ์žˆ๋Š” ๊ณณ๋งŒ Zarr๋กœ"๋Š” ๋ณ„๋„ ๊ตฌํ˜„ ์—†์ด write_empty_chunks=False ํ•˜๋‚˜๋กœ ์ž๋™ ๋‹ฌ์„ฑ๋ฉ๋‹ˆ๋‹ค.
~5%
์ผ๋ฐ˜ SEG์˜ ๋น„์˜(้ž้›ถ) ๋ณต์…€ ๋น„์œจ
~5%
์‹ค์ œ ์ €์žฅ๋˜๋Š” Zarr ์ฒญํฌ ๋น„์œจ
95%โ†“
Zarr ์Šคํ† ๋ฆฌ์ง€ ์ ˆ๊ฐ
0ms
๋นˆ ์ฒญํฌ ์‘๋‹ต ์˜ค๋ฒ„ํ—ค๋“œ
02 ยท Sparsity of SEG

SEG ๋ฐ์ดํ„ฐ๋Š” ์™œ ํฌ์†Œํ•œ๊ฐ€

๋‡Œ MRI์—์„œ ์ข…์–‘ ์„ธ๊ทธ๋ฉ˜ํ…Œ์ด์…˜์„ ์˜ˆ๋กœ ๋“ค๋ฉด, ์ „์ฒด ๋ณผ๋ฅจ 512ร—512ร—200 = 52M ๋ณต์…€ ์ค‘ ์‹ค์ œ ๋ ˆ์ด๋ธ”์ด ์žˆ๋Š” ๋ณต์…€์€ ์ˆ˜์ฒœ~์ˆ˜๋งŒ ๊ฐœ์— ๋ถˆ๊ณผํ•ฉ๋‹ˆ๋‹ค. 64ร—64ร—64 ์ฒญํฌ ๊ธฐ์ค€์œผ๋กœ ๋ณด๋ฉด, SEG ๋ฐ์ดํ„ฐ๋ฅผ ํฌํ•จํ•˜๋Š” ์ฒญํฌ๋Š” ์ „์ฒด์˜ ๊ทนํžˆ ์ผ๋ถ€์ž…๋‹ˆ๋‹ค.

SEG NIfTI์˜ ํฌ์†Œ์„ฑ โ€” 512ร—512ร—200 ๋ณผ๋ฅจ ์˜ˆ์‹œ ์ฒญํฌ ๋ถ„ํ•  (64ร—64ร—64) ๋นˆ ์ฒญํฌ (fill_value=0, ํŒŒ์ผ ์—†์Œ) SEG ์žˆ๋Š” ์ฒญํฌ (ํŒŒ์ผ ์กด์žฌ) ์‹ค์ œ ๋””์Šคํฌ ์ €์žฅ (write_empty_chunks=False) ์ผ๋ฐ˜ Zarr (์ „์ฒด ์ €์žฅ) 64ร—64ร—64 ์ฒญํฌ ์ „์ฒด = 8ร—8ร—4 = 256๊ฐœ ํŒŒ์ผ ~600 MB (Int16 ๊ธฐ์ค€) Sparse Zarr (SEG๋งŒ ์ €์žฅ) SEG ํฌํ•จ ์ฒญํฌ 7๊ฐœ๋งŒ ๋ฌผ๋ฆฌ ํŒŒ์ผ ๋‚˜๋จธ์ง€ 249๊ฐœ = fill_value ์•”๋ฌต ๋ฐ˜ํ™˜ ~16 MB (97% ์ ˆ๊ฐ) ๐ŸŽ‰ ๋นˆ ์ฒญํฌ ์ฝ๊ธฐ ๋™์ž‘ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋นˆ ์ฒญํฌ ์š”์ฒญ โ†’ ์„œ๋ฒ„: ํŒŒ์ผ ์—†์Œ(404) โ†’ Zarr ํด๋ผ์ด์–ธํŠธ: fill_value(0) ๋ฐ˜ํ™˜ โ†’ ๋ธŒ๋ผ์šฐ์ €์—์„œ๋Š” 0์œผ๋กœ ์ฑ„์›Œ์ง„ ๋ฐฐ์—ด์„ ๋ฐ›์€ ๊ฒƒ๊ณผ ๋™์ผํ•˜๊ฒŒ ์ฒ˜๋ฆฌ โœ“ SEG ์—†๋Š” ์˜์—ญ = ๋ฐฐ๊ฒฝ(0) ์ •ํ™•ํžˆ ํ‘œํ˜„ โœ“ ์ถ”๊ฐ€ ๋กœ์ง ๋ถˆํ•„์š” โœ“ Zarr ์ŠคํŽ™ ํ‘œ์ค€ ๋™์ž‘

์„ธ๊ทธ๋ฉ˜ํ…Œ์ด์…˜ ๋ณผ๋ฅจ์—์„œ ์‹ค์ œ๋กœ ๋ ˆ์ด๋ธ”์ด ์žˆ๋Š” ๋ณต์…€ ๋น„์œจ์€ ๋งค์šฐ ๋‚ฎ์Šต๋‹ˆ๋‹ค.

๋‡Œ์ข…์–‘ SEG (GBM)
~1~3%
ํ ๊ฒฐ์ ˆ SEG
~0.5~1%
ํ˜ˆ๊ด€ SEG (์ „์ฒด)
~5~10%
๋‡Œ ๊ตฌ์กฐ ์ „์ฒด ํŒŒ์…€๋ ˆ์ด์…˜
~50~70%

* ๋ณ‘๋ณ€ SEG๋Š” ํฌ์†Œ๋„๊ฐ€ ๋†’์•„ Sparse Zarr ํšจ๊ณผ๊ฐ€ ๊ทน๋Œ€ํ™”๋จ. ํŒŒ์…€๋ ˆ์ด์…˜์ฒ˜๋Ÿผ ์ „์ฒด๋ฅผ ์ฑ„์šฐ๋Š” SEG๋Š” ์ด์  ๊ฐ์†Œ

03 ยท Hybrid Architecture

ํ•˜์ด๋ธŒ๋ฆฌ๋“œ ์„œ๋น™ ์•„ํ‚คํ…์ฒ˜ โ€” ์ „์ฒด ๊ตฌ์กฐ

Base ๋ณผ๋ฅจ์€ ๊ธฐ์กด .nii.gz ๊ทธ๋Œ€๋กœ, SEG๋งŒ Sparse Zarr๋กœ ์„œ๋น™ํ•˜๋Š” ํ•˜์ด๋ธŒ๋ฆฌ๋“œ ๊ตฌ์กฐ์ž…๋‹ˆ๋‹ค. ์›น ๋ทฐ์–ด๋Š” ๋‘ ์†Œ์Šค๋ฅผ ์กฐํ•ฉํ•ด ๋ Œ๋”๋งํ•ฉ๋‹ˆ๋‹ค.

์„œ๋ฒ„ ์Šคํ† ๋ฆฌ์ง€ brain.nii.gz Base ๋ณผ๋ฅจ ์›๋ณธ 1.2 GB (๋ณ€๊ฒฝ ์—†์Œ) seg.nii.zarr/ Sparse Zarr SEG ~16 MB (SEG ์ฒญํฌ๋งŒ) REST API / Static Server nii.gz: Range Request ์ง€์› | zarr: ์ฒญํฌ ํŒŒ์ผ ์ง์ ‘ ์„œ๋น™ (์ •์ ) CORS ํ—ค๋” ์„ค์ • ํ•„์š” ์›น ๋ทฐ์–ด (๋ธŒ๋ผ์šฐ์ €) Base NIfTI Loader nifti-volume-loader Range Request๋กœ ์Šฌ๋ผ์ด์Šค SEG Zarr Loader ์ปค์Šคํ…€ ImageLoader SEG ์ฒญํฌ ๋ณ‘๋ ฌ fetch Cornerstone3D Viewport Base: VTK ImageData (์ „์ฒด ๋ณผ๋ฅจ) SEG: Sparse Zarr Overlay (SEG ์ฒญํฌ๋งŒ) ๋‘ ๋ ˆ์ด์–ด๋ฅผ ํ•ฉ์„ฑํ•ด ๋ Œ๋”๋ง ๋ Œ๋”๋ง ๊ฒฐ๊ณผ Base: ์›๋ณธ ๊ทธ๋ ˆ์ด์Šค์ผ€์ผ ๋ณผ๋ฅจ SEG ์˜์—ญ: ์ปฌ๋Ÿฌ ์˜ค๋ฒ„๋ ˆ์ด (์ฆ‰์‹œ ํ‘œ์‹œ)
๐Ÿ“ Base ๋ณผ๋ฅจ์€ ์ „ํ˜€ ๊ฑด๋“œ๋ฆฌ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๊ธฐ์กด .nii.gz ํŒŒ์ผ์„ ๊ทธ๋Œ€๋กœ ๋‘๊ณ  SEG NIfTI๋งŒ Sparse Zarr๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค. Base๋Š” ๊ธฐ์กด nifti-volume-loader๋กœ ๋กœ๋“œํ•˜๊ณ , SEG๋Š” ์ปค์Šคํ…€ Zarr ImageLoader๋กœ ๋กœ๋“œํ•ด ๋‘ ๋ ˆ์ด์–ด๋ฅผ ๋ทฐํฌํŠธ์—์„œ ํ•ฉ์„ฑํ•ฉ๋‹ˆ๋‹ค.
04 ยท Sparse Zarr ์›๋ฆฌ

write_empty_chunks=False โ€” ํ•ต์‹ฌ ๋ฉ”์ปค๋‹ˆ์ฆ˜

Zarr๊ฐ€ SEG Sparse ์ €์žฅ์„ ์–ด๋–ป๊ฒŒ ์ฒ˜๋ฆฌํ•˜๋Š”์ง€ ๋‹จ๊ณ„๋ณ„๋กœ ์‚ดํŽด๋ด…๋‹ˆ๋‹ค.

1
SEG NIfTI ๋กœ๋“œ โ†’ ์ฒญํฌ ๋‹จ์œ„ ์Šค์บ”
nibabel๋กœ SEG .nii.gz๋ฅผ ๋กœ๋“œํ•˜๊ณ  Zarr์— ์“ฐ๊ธฐ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค. write_empty_chunks=False๋กœ ์„ค์ •ํ•˜๋ฉด ๊ฐ ์ฒญํฌ๋ฅผ ์“ฐ๊ธฐ ์ „์— ์ „์ฒด๊ฐ€ fill_value(0)์ธ์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
2
๋น„์˜(้ž้›ถ) ๋ณต์…€ ํฌํ•จ ์ฒญํฌ โ†’ ๋””์Šคํฌ์— ์••์ถ• ์ €์žฅ
ํ•˜๋‚˜๋ผ๋„ ๋ ˆ์ด๋ธ”(>0)์ด ์žˆ๋Š” ์ฒญํฌ๋Š” ํŒŒ์ผ๋กœ ์ €์žฅ๋ฉ๋‹ˆ๋‹ค. ์˜ˆ: c/2/3/1. ํ•ด๋‹น ์ฒญํฌ๋Š” ๋‚ด๋ถ€์˜ ๋ฐฐ๊ฒฝ(0) ๋ณต์…€๋„ ํ•จ๊ป˜ ์••์ถ• ์ €์žฅ๋ฉ๋‹ˆ๋‹ค. blosc/zstd๋กœ ์••์ถ•ํ•˜๋ฉด 0์ด ๋งŽ์€ ์ฒญํฌ๋Š” ์••์ถ•๋ฅ ์ด ๋งค์šฐ ๋†’์Šต๋‹ˆ๋‹ค.
3
์ˆœ์ˆ˜ ๋ฐฐ๊ฒฝ(0) ์ฒญํฌ โ†’ ํŒŒ์ผ ๋ฏธ์ƒ์„ฑ (์•”๋ฌต์  0)
๋ชจ๋“  ๋ณต์…€์ด 0์ธ ์ฒญํฌ๋Š” ํŒŒ์ผ์„ ์“ฐ์ง€ ์•Š๊ณ  ๊ฑด๋„ˆ๋œ๋‹ˆ๋‹ค. ๋””์Šคํฌ์— ์•„๋ฌด ํŒŒ์ผ๋„ ์—†์Šต๋‹ˆ๋‹ค. ์ด๊ฒƒ์ด SEG Sparse Zarr์˜ ํ•ต์‹ฌ์ž…๋‹ˆ๋‹ค.
4
ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋นˆ ์ฒญํฌ ์š”์ฒญ ์‹œ
๋ธŒ๋ผ์šฐ์ €๊ฐ€ c/0/0/0 ๊ฐ™์€ ๋นˆ ์ฒญํฌ๋ฅผ HTTP GET์œผ๋กœ ์š”์ฒญํ•˜๋ฉด ์„œ๋ฒ„๊ฐ€ 404๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. Zarr ํด๋ผ์ด์–ธํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” ์ด๋ฅผ "ํ•ด๋‹น ์ฒญํฌ๊ฐ€ fill_value(0)๋กœ๋งŒ ์ฑ„์›Œ์ง„ ๊ฒƒ"์œผ๋กœ ํ•ด์„ํ•˜๊ณ  ์ž๋™์œผ๋กœ 0 ๋ฐฐ์—ด์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ์—๋Ÿฌ๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค.
5
๊ฒฐ๊ณผ: ์ „์ฒด SEG ๋ณผ๋ฅจ์ด ์ •ํ™•ํžˆ ์žฌํ˜„๋จ
์‹ค์ œ ํŒŒ์ผ์ด ์žˆ๋Š” ์ฒญํฌ โ†’ ์‹ค์ œ ๋ ˆ์ด๋ธ”, ํŒŒ์ผ ์—†๋Š” ์ฒญํฌ โ†’ 0. ์ „์ฒด๋ฅผ ํ•ฉ์น˜๋ฉด ์›๋ณธ SEG NIfTI์™€ ๋™์ผํ•œ ๋ฐ์ดํ„ฐ๊ฐ€ ๋ฉ๋‹ˆ๋‹ค. ํด๋ผ์ด์–ธํŠธ๋Š” ์ฐจ์ด๋ฅผ ๋ชจ๋ฆ…๋‹ˆ๋‹ค.
# Sparse Zarr SEG ์ƒ์„ฑ โ€” write_empty_chunks=False ํ•ต์‹ฌ
import zarr
import nibabel as nib
import numpy as np

# SEG NIfTI ๋กœ๋“œ
seg_img = nib.load('seg.nii.gz')
seg_data = seg_img.get_fdata().astype(np.uint8)

# โ”€โ”€ Sparse Zarr ์ƒ์„ฑ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
store = zarr.open('seg.nii.zarr', mode='w')
seg_arr = store.require_dataset(
    'seg',
    shape=seg_data.shape,          # (512, 512, 300)
    chunks=(64, 64, 64),           # ์ฒญํฌ ํฌ๊ธฐ
    dtype=np.uint8,
    fill_value=0,                  # ๋นˆ ์ฒญํฌ = 0์œผ๋กœ ํ•ด์„
    compressor=zarr.Blosc(
        cname='zstd', clevel=5,
        shuffle=zarr.Blosc.BITSHUFFLE  # 0 ๋งŽ์€ SEG์— ํšจ๊ณผ์ 
    )
)

# โ˜… ํ•ต์‹ฌ: ๋นˆ ์ฒญํฌ๋Š” ํŒŒ์ผ ์ €์žฅ ์•ˆ ํ•จ
seg_arr.write_empty_chunks = False  # ๋˜๋Š” ์ƒ์„ฑ ์‹œ config์—์„œ ์„ค์ •
seg_arr[:] = seg_data                # SEG ์ฒญํฌ๋งŒ ํŒŒ์ผ ์ƒ์„ฑ๋จ

# ๊ฒฐ๊ณผ ํ™•์ธ
import os
chunk_files = list(store.store.keys())
print(f"์ด ๊ฐ€๋Šฅํ•œ ์ฒญํฌ ์ˆ˜: {np.prod([s//c for s,c in zip(seg_data.shape,[64,64,64])])}")
print(f"์‹ค์ œ ์ €์žฅ๋œ ์ฒญํฌ: {len([k for k in chunk_files if not k.endswith('.json')])}")
# ์ด ๊ฐ€๋Šฅํ•œ ์ฒญํฌ ์ˆ˜: 256  โ†’  ์‹ค์ œ ์ €์žฅ๋œ ์ฒญํฌ: 7 (SEG ์žˆ๋Š” ๊ฒƒ๋งŒ)
05 ยท BBox Pre-Analysis

SEG BBox ์‚ฌ์ „ ๋ถ„์„์œผ๋กœ ๋ณ€ํ™˜ ๊ฐ€์†

"SEG๊ฐ€ ์žˆ๋Š” ๊ณณ์„ ์•Œ๊ณ  ์žˆ๋‹ค๋ฉด"์ด๋ผ๋Š” ์ „์ œ๊ฐ€ ์ถ”๊ฐ€๋œ ๊ฒฝ์šฐ, ๋ณ€ํ™˜ ์ „์— ๋ฐ”์šด๋”ฉ ๋ฐ•์Šค๋ฅผ ๋จผ์ € ๊ณ„์‚ฐํ•ด ๋” ํšจ์œจ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ „์ฒด ๋ณผ๋ฅจ์„ ์ˆœํšŒํ•˜์ง€ ์•Š๊ณ  SEG ์กด์žฌ ๊ตฌ๊ฐ„๋งŒ ์ฒญํฌ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ”

๋ฐฉ๋ฒ• A: BBox ์‚ฌ์ „ ๊ณ„์‚ฐ

SEG NIfTI ์ „์ฒด๋ฅผ ์Šค์บ”ํ•ด ๋น„์˜ ๋ณต์…€์˜ min/max ์ขŒํ‘œ๋ฅผ ๊ตฌํ•˜๊ณ , ํ•ด๋‹น ์ฒญํฌ ๋ฒ”์œ„๋งŒ Zarr๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ๋‚˜๋จธ์ง€ ์ฒญํฌ๋Š” ์•„์˜ˆ ์ ‘๊ทผํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

โšก

๋ฐฉ๋ฒ• B: write_empty_chunks=False

BBox ๋ถ„์„ ์—†์ด ๊ทธ๋ƒฅ ์ „์ฒด๋ฅผ ์”๋‹ˆ๋‹ค. Zarr๊ฐ€ ์ž๋™์œผ๋กœ ๋นˆ ์ฒญํฌ๋ฅผ ๊ฑด๋„ˆ๋œ๋‹ˆ๋‹ค. ๊ตฌํ˜„์ด ๋‹จ์ˆœํ•˜๊ณ , ๋Œ€์šฉ๋Ÿ‰์—์„œ๋„ ์“ฐ๊ธฐ ์˜ค๋ฒ„ํ—ค๋“œ๊ฐ€ ๋‚ฎ์Šต๋‹ˆ๋‹ค.

๐ŸŽฏ

๋ฐฉ๋ฒ• C: Lazy/On-demand ๋ณ€ํ™˜

AI ์„œ๋ฒ„๊ฐ€ SEG๋ฅผ ์ƒ์„ฑํ•˜๋Š” ์ฆ‰์‹œ ๋ ˆ์ด๋ธ”๋ณ„ BBox๋ฅผ ๋ฐ˜ํ™˜ โ†’ ์›น ๋ทฐ์–ด ์š”์ฒญ ์‹œ ํ•ด๋‹น ์ฒญํฌ๋งŒ ์‹ค์‹œ๊ฐ„ ๋ณ€ํ™˜ยท์บ์‹ฑํ•ฉ๋‹ˆ๋‹ค.

# ๋ฐฉ๋ฒ• A: BBox ์‚ฌ์ „ ๋ถ„์„ โ†’ ์ฒญํฌ ๋ฒ”์œ„๋งŒ ๋ณ€ํ™˜
import numpy as np

def get_seg_chunk_range(seg_data: np.ndarray, chunk_size: int = 64):
    """SEG ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ๋Š” ์ฒญํฌ ์ขŒํ‘œ ๋ฒ”์œ„๋ฅผ ๋ฐ˜ํ™˜"""
    nonzero = np.nonzero(seg_data)  # ๋น„์˜ ๋ณต์…€ ์ขŒํ‘œ
    if len(nonzero[0]) == 0:
        return None  # ๋ ˆ์ด๋ธ” ์—†์Œ

    min_z, max_z = nonzero[2].min(), nonzero[2].max()
    min_y, max_y = nonzero[1].min(), nonzero[1].max()
    min_x, max_x = nonzero[0].min(), nonzero[0].max()

    # ์ฒญํฌ ์ขŒํ‘œ๋กœ ๋ณ€ํ™˜ (์ฒญํฌ ๊ฒฝ๊ณ„๋กœ ํ™•์žฅ)
    return {
        'z': (int(min_z // chunk_size), int(max_z // chunk_size) + 1),
        'y': (int(min_y // chunk_size), int(max_y // chunk_size) + 1),
        'x': (int(min_x // chunk_size), int(max_x // chunk_size) + 1),
    }

chunk_range = get_seg_chunk_range(seg_data)
# {'z': (2, 5), 'y': (3, 5), 'x': (3, 6)} โ†’ 2ร—2ร—3 = 12๊ฐœ ์ฒญํฌ๋งŒ ๋ณ€ํ™˜

# BBox ๋ฒ”์œ„ ์Šฌ๋ผ์ด์Šค๋งŒ Zarr์— ์“ฐ๊ธฐ
if chunk_range:
    z0, z1 = chunk_range['z']
    y0, y1 = chunk_range['y']
    x0, x1 = chunk_range['x']
    for zi in range(z0, z1):
        for yi in range(y0, y1):
            for xi in range(x0, x1):
                sl = (
                    slice(xi*64, (xi+1)*64),
                    slice(yi*64, (yi+1)*64),
                    slice(zi*64, (zi+1)*64)
                )
                chunk = seg_data[sl]
                if chunk.any():      # SEG ์žˆ๋Š” ์ฒญํฌ๋งŒ
                    seg_arr[sl] = chunk  # ํŒŒ์ผ ์ƒ์„ฑ
โšก BBox ์‚ฌ์ „ ๋ถ„์„(๋ฐฉ๋ฒ• A)์€ ๋ณ€ํ™˜ ์‹œ๊ฐ„์„ ์ถ”๊ฐ€๋กœ 90%+ ๋‹จ์ถ•ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. SEG๊ฐ€ ๋ณผ๋ฅจ์˜ 5%์— ์žˆ๋‹ค๋ฉด ์ „์ฒด ์Šค์บ” ๋Œ€์‹  ํ•ด๋‹น ์ฒญํฌ๋งŒ ์ ‘๊ทผํ•ด ์“ฐ๊ธฐ IO๋ฅผ ์ตœ์†Œํ™”ํ•ฉ๋‹ˆ๋‹ค. AI ํŒŒ์ดํ”„๋ผ์ธ์—์„œ ์ด๋ฏธ ๋ ˆ์ด๋ธ” BBox๋ฅผ ์•Œ๊ณ  ์žˆ๋‹ค๋ฉด ๋ณ„๋„ ์Šค์บ” ์—†์ด ๋ฐ”๋กœ ํ™œ์šฉํ•˜์„ธ์š”.
06 ยท Storage & Performance

์Šคํ† ๋ฆฌ์ง€ ์ ˆ๊ฐ & ์„ฑ๋Šฅ ๋น„๊ต

ํ•ญ๋ชฉSEG .nii.gz (๊ธฐ์กด)SEG ์ „์ฒด ZarrSEG Sparse Zarr
(SEG ์˜์—ญ๋งŒ)
ํŒŒ์ผ ํฌ๊ธฐ / ์šฉ๋Ÿ‰ ~30 MB (gzip ์••์ถ•) ~600 MB (Int16, 64ยณ ์ฒญํฌ) ~16 MB (SEG ์ฒญํฌ๋งŒ)
์ฒซ ๋ Œ๋” ๋Œ€๊ธฐ ์‹œ๊ฐ„ 30 MB ์ „์ฒด ์ˆ˜์‹  ํ›„ (~5์ดˆ) ํ•ด๋‹น ์ฒญํฌ ์ฆ‰์‹œ (~์ˆ˜๋ฐฑms) ํ•ด๋‹น ์ฒญํฌ ์ฆ‰์‹œ
ํ˜„์žฌ ์Šฌ๋ผ์ด์Šค fetch ์ „์ฒด gzip ํ•ด์ œ ํ•„์š” ํ•ด๋‹น ์ฒญํฌ 4~8๊ฐœ fetch SEG ์ฒญํฌ๋งŒ fetch
๋นˆ ์Šฌ๋ผ์ด์Šค=0 ์ฆ‰์‹œ ๋ฐ˜ํ™˜
๋ณ€ํ™˜ ์‹œ๊ฐ„ ๋ถˆํ•„์š” ~30์ดˆ (์ „์ฒด ๋ณผ๋ฅจ) ~3์ดˆ (BBox ๋ถ„์„ ํฌํ•จ)
ํŒŒ์ผ ์ˆ˜ 1๊ฐœ 256๊ฐœ (8ร—8ร—4) 7~15๊ฐœ (SEG ์ฒญํฌ๋งŒ)
Base ๋ณผ๋ฅจ ๋ณ€๊ฒฝ ์—†์Œ ์—†์Œ ์—†์Œ (์™„์ „ ๋ถ„๋ฆฌ)
๊ตฌํ˜„ ๋ณต์žก๋„ ๋‚ฎ์Œ ์ค‘๊ฐ„ ์ค‘๊ฐ„+ (์ปค์Šคํ…€ ๋กœ๋” ํ•„์š”)

* 512ร—512ร—300 ๋ณผ๋ฅจ, ์ข…์–‘ SEG (~3% ๋น„์˜ ๋ณต์…€), 64ร—64ร—64 ์ฒญํฌ ๊ธฐ์ค€ ์ถ”์ •์น˜

07 ยท Issues

์˜ˆ์ƒ ๋ฌธ์ œ์  & ๋Œ€์‘ ์ „๋žต

1
์ฒญํฌ ๊ฒฝ๊ณ„ ๋ ˆ์ด๋ธ” ์ž˜๋ฆผ (Boundary Clipping)
SEG ๋ ˆ์ด๋ธ”์ด ์ฒญํฌ ๊ฒฝ๊ณ„์— ๊ฑธ์ณ ์žˆ์„ ๋•Œ โ€” 64๋ฒˆ์งธ ๋ณต์…€์— ๋ ˆ์ด๋ธ”์ด ์žˆ๊ณ  65๋ฒˆ์งธ๋Š” ์—†๋Š” ๊ฒฝ์šฐ โ€” ๋‘ ๊ฐœ์˜ ์ธ์ ‘ ์ฒญํฌ์— ๋ฐ์ดํ„ฐ๊ฐ€ ๋‚˜๋‰˜์–ด ์ €์žฅ๋ฉ๋‹ˆ๋‹ค. ์ด ์ž์ฒด๋Š” ๋ฌธ์ œ๊ฐ€ ์•„๋‹ˆ์ง€๋งŒ, ํด๋ผ์ด์–ธํŠธ์—์„œ ์ฒญํฌ๋ฅผ ์กฐํ•ฉํ•  ๋•Œ ๊ฒฝ๊ณ„ ์ฒ˜๋ฆฌ๋ฅผ ์ •ํ™•ํžˆ ํ•ด์•ผ ๋ Œ๋”๋ง ์ด์Œ์ƒˆ ์•„ํ‹ฐํŒฉํŠธ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.

๋Œ€์‘: Zarr ํด๋ผ์ด์–ธํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ(zarrita ๋“ฑ)๊ฐ€ ์ž๋™์œผ๋กœ ์ธ์ ‘ ์ฒญํฌ๋ฅผ ์ด์–ด๋ถ™์ž…๋‹ˆ๋‹ค. ์Šฌ๋ผ์ด์Šค ๋‹จ์œ„๋กœ ์š”์ฒญ ์‹œ ํ•„์š”ํ•œ ์ฒญํฌ๊ฐ€ ์ž๋™ ๊ฒฐ์ •๋ฉ๋‹ˆ๋‹ค.
2
๋นˆ ์ฒญํฌ 404 ์—๋Ÿฌ ์ฒ˜๋ฆฌ โ€” ํด๋ผ์ด์–ธํŠธ ๊ตฌํ˜„ ์˜์กด
์ปค์Šคํ…€ Zarr ImageLoader๋ฅผ ์ง์ ‘ ๊ตฌํ˜„ํ•  ๊ฒฝ์šฐ, ๋นˆ ์ฒญํฌ์˜ HTTP 404๋ฅผ "์—๋Ÿฌ"๊ฐ€ ์•„๋‹Œ "์ •์ƒ ์‘๋‹ต(fill_value)"์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋Š” ๋กœ์ง์„ ๋ช…์‹œ์ ์œผ๋กœ ๊ตฌํ˜„ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. zarrita, zarr-js ๊ฐ™์€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” ์ด๋ฅผ ์ž๋™ ์ฒ˜๋ฆฌํ•˜์ง€๋งŒ, ์ง์ ‘ fetch๋กœ ๊ตฌํ˜„ํ•œ๋‹ค๋ฉด ๋†“์น˜๊ธฐ ์‰ฌ์šด ํ•จ์ •์ž…๋‹ˆ๋‹ค.

๋Œ€์‘: ๋ฐ˜๋“œ์‹œ ๊ฒ€์ฆ๋œ Zarr ํด๋ผ์ด์–ธํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜, 404 โ†’ 0-filled ๋ฐฐ์—ด ๋ฐ˜ํ™˜ ๋กœ์ง์„ ๋ช…์‹œ์ ์œผ๋กœ ํ…Œ์ŠคํŠธํ•ฉ๋‹ˆ๋‹ค.
3
Base + SEG ์ขŒํ‘œ ์ •๋ ฌ (Alignment)
Base .nii.gz์™€ SEG .nii.zarr์˜ ๋ณต์…€ ๊ฒฉ์ž๊ฐ€ ์ •ํ™•ํžˆ ์ผ์น˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋™์ผํ•œ origin, spacing, direction์ด์–ด์•ผ ์˜ค๋ฒ„๋ ˆ์ด๊ฐ€ ๋งž์Šต๋‹ˆ๋‹ค. AI ํŒŒ์ดํ”„๋ผ์ธ์—์„œ ์ƒ์„ฑ๋œ SEG๊ฐ€ Base์™€ ๋‹ค๋ฅธ ํ•ด์ƒ๋„๋‚˜ ํฌ๊ธฐ๋ผ๋ฉด ๋ฆฌ์ƒ˜ํ”Œ๋ง์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

๋Œ€์‘: Zarr ๋ณ€ํ™˜ ์ „ nibabel๋กœ affine ํ–‰๋ ฌ์„ ๋น„๊ตํ•ด ์ผ์น˜๋ฅผ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค. zarr.json์— NIfTI affine ์ •๋ณด๋ฅผ ์ €์žฅํ•˜๊ณ , ํด๋ผ์ด์–ธํŠธ์—์„œ ๋กœ๋“œ ์‹œ Base์™€ ๋น„๊ตยท๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค.
4
ํŒŒ์…€๋ ˆ์ด์…˜์ฒ˜๋Ÿผ ์ „์ฒด๋ฅผ ์ฑ„์šฐ๋Š” SEG๋Š” ์ด์  ๊ฐ์†Œ
FreeSurfer ๋‡Œ ํŒŒ์…€๋ ˆ์ด์…˜์ฒ˜๋Ÿผ ์ „์ฒด ๋ณผ๋ฅจ์˜ 50~70%์— ๋ ˆ์ด๋ธ”์ด ์žˆ๋Š” ๊ฒฝ์šฐ ๋นˆ ์ฒญํฌ ๋น„์œจ์ด ๋‚ฎ์•„ Sparse ํšจ๊ณผ๊ฐ€ ์ค„์–ด๋“ญ๋‹ˆ๋‹ค. ์ด ๊ฒฝ์šฐ ๋ชจ๋“  ์ฒญํฌ๊ฐ€ ์ €์žฅ๋˜์–ด ์ „์ฒด Zarr์™€ ์ฐจ์ด๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.

๋Œ€์‘: ๋ณ€ํ™˜ ์ „ ๋น„์˜ ๋ณต์…€ ๋น„์œจ์„ ๊ณ„์‚ฐํ•ด 30% ๋ฏธ๋งŒ์ผ ๋•Œ๋งŒ Sparse Zarr๋ฅผ ์„ ํƒํ•˜๊ณ , ๊ทธ ์ด์ƒ์ด๋ฉด ์ผ๋ฐ˜ Zarr ๋˜๋Š” .nii.gz๋ฅผ ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค.
5
CORS + ๋‘ ์„œ๋ฒ„ ์†Œ์Šค ํ˜ผ์šฉ
Base๋Š” REST API๋กœ, SEG๋Š” ์ •์  ์„œ๋ฒ„(S3)๋กœ ์„œ๋น™ํ•œ๋‹ค๋ฉด ๋‘ ๊ฐœ์˜ ์„œ๋กœ ๋‹ค๋ฅธ origin์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์™€์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ฐ๊ฐ CORS ์„ค์ •์ด ํ•„์š”ํ•˜๊ณ , ๋„คํŠธ์›Œํฌ ๊ฒฝ๋กœ๊ฐ€ ๋‹ค๋ฅด๋ฉด ๋ ˆ์ดํ„ด์‹œ๊ฐ€ ๋‹ฌ๋ผ ๋‘ ๋ ˆ์ด์–ด์˜ ๋กœ๋”ฉ ์™„๋ฃŒ ์‹œ์ ์ด ๋ถˆ์ผ์น˜ํ•ฉ๋‹ˆ๋‹ค.

๋Œ€์‘: Base์™€ SEG๋ฅผ ๋™์ผํ•œ ๋„๋ฉ”์ธ์œผ๋กœ ์„œ๋น™ํ•˜๊ฑฐ๋‚˜, ํ”„๋ก์‹œ ๋ ˆ์ด์–ด๋ฅผ ๋‘์–ด ๋‹จ์ผ origin์œผ๋กœ ํ†ต์ผํ•ฉ๋‹ˆ๋‹ค. ๋กœ๋”ฉ ์ค‘์—๋Š” Base๋งŒ ๋จผ์ € ํ‘œ์‹œํ•˜๊ณ  SEG๊ฐ€ ๋กœ๋“œ๋˜๋ฉด ์˜ค๋ฒ„๋ ˆ์ด๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ์ ์ง„์  ํ‘œ์‹œ๋ฅผ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.
08 ยท Client Implementation

์›น ๋ทฐ์–ด ํ†ตํ•ฉ โ€” ์ปค์Šคํ…€ SEG Zarr ๋กœ๋”

// seg-zarr-loader.ts โ€” SEG Sparse Zarr ์ปค์Šคํ…€ ImageLoader
import { imageLoader, metaData } from '@cornerstonejs/core';
import * as zarr from 'zarrita';

interface SegZarrMeta {
  store: zarr.Array;
  shape: number[];
  chunkSize: number;
  affine: number[][];  // NIfTI sform์œผ๋กœ๋ถ€ํ„ฐ
}

const segStores = new Map<string, SegZarrMeta>();

// 1. Zarr ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์ดˆ๊ธฐํ™”
export async function initSegZarr(zarrUrl: string) {
  const fetchStore = new zarr.FetchStore(zarrUrl);
  const arr = await zarr.open(fetchStore.resolve('seg'), { kind: 'array' });
  const attrs = await zarr.getAttributes(fetchStore.resolve('seg'));

  segStores.set(zarrUrl, {
    store: arr,
    shape: arr.shape,
    chunkSize: arr.chunks[0],  // 64
    affine: attrs.nifti_header?.affine,
  });
  return arr.shape[2];  // ์Šฌ๋ผ์ด์Šค ์ˆ˜ ๋ฐ˜ํ™˜
}

// 2. ์Šฌ๋ผ์ด์Šค ๋‹จ์œ„ SEG ImageLoader
function segZarrLoader(imageId: string) {
  // imageId: 'seg-zarr://http://server/seg.nii.zarr#150'
  const [url, sliceStr] = imageId.replace('seg-zarr://', '').split('#');
  const sliceIdx = parseInt(sliceStr);
  const meta = segStores.get(url);

  const promise = (async () => {
    // Zarr์—์„œ ํ•ด๋‹น ์Šฌ๋ผ์ด์Šค fetch
    // ๋นˆ ์ฒญํฌ(SEG ์—†์Œ) โ†’ zarrita๊ฐ€ ์ž๋™์œผ๋กœ 0 ๋ฐฐ์—ด ๋ฐ˜ํ™˜ (404 ์ฒ˜๋ฆฌ ๋‚ด์žฅ)
    const sliceData = await zarr.get(meta.store, [null, null, sliceIdx]);

    return {
      imageId,
      rows: sliceData.shape[0],
      columns: sliceData.shape[1],
      columnPixelSpacing: 1,
      rowPixelSpacing: 1,
      color: false,
      minPixelValue: 0,
      maxPixelValue: 255,
      // Uint8 SEG ๋ฐ์ดํ„ฐ
      getPixelData: () => new Uint8Array(sliceData.data.buffer),
    };
  })();

  return { promise };
}

// 3. Cornerstone3D์— ๋“ฑ๋ก
imageLoader.registerImageLoader('seg-zarr', segZarrLoader);

// โ”€โ”€ ์‚ฌ์šฉ ์˜ˆ์‹œ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
const sliceCount = await initSegZarr('http://server/seg.nii.zarr');

// SEG imageId ๋ฐฐ์—ด ์ƒ์„ฑ
const segImageIds = Array.from({ length: sliceCount }, (_, i) =>
  `seg-zarr://http://server/seg.nii.zarr#${i}`
);

// SEG Viewport์— ๋กœ๋“œ (Stack or Volume)
viewport.setStack(segImageIds);
09 ยท Conclusion

๊ฒฐ๋ก  โ€” ์ด ์ „๋žต์€ ์–ธ์ œ ์“ฐ๊ณ , ์–ธ์ œ ํ”ผํ•ด์•ผ ํ•˜๋‚˜

โœ…

์ด ์ „๋žต์ด ํšจ๊ณผ์ ์ธ ๊ฒฝ์šฐ

  • ๋ณ‘๋ณ€ SEG โ€” ์ข…์–‘, ๊ฒฐ์ ˆ, ๊ฒฝ์ƒ‰ ๋“ฑ (์ „์ฒด ๋Œ€๋น„ <30%)
  • ํ˜ˆ๊ด€ SEG โ€” ์„ ํ˜• ๊ตฌ์กฐ, ํฌ์†Œ
  • AI ๋ชจ๋ธ์ด SEG BBox๋ฅผ ํ•จ๊ป˜ ์ถœ๋ ฅํ•˜๋Š” ํŒŒ์ดํ”„๋ผ์ธ
  • Base ๋ณผ๋ฅจ์€ ๊ทธ๋Œ€๋กœ ๋‘๊ณ  SEG๋งŒ ๋น ๋ฅด๊ฒŒ ๋กœ๋“œํ•˜๊ณ  ์‹ถ์„ ๋•Œ
  • S3/CDN์—์„œ ์ •์  ์„œ๋น™ ๊ฐ€๋Šฅํ•œ ํ™˜๊ฒฝ
โš ๏ธ

ํ”ผํ•ด์•ผ ํ•˜๊ฑฐ๋‚˜ ํ•œ๊ณ„์ธ ๊ฒฝ์šฐ

  • ํŒŒ์…€๋ ˆ์ด์…˜ SEG โ€” ์ „์ฒด ์ฑ„์›€ (Sparse ์ด์  ์—†์Œ)
  • ์ปค์Šคํ…€ Zarr ๋กœ๋” ์—†์ด CS3D ๊ธฐ๋ณธ ์‚ฌ์šฉ ์‹œ
  • CORS ์„ค์ • ๋ถˆ๊ฐ€๋Šฅํ•œ ํ™˜๊ฒฝ
  • Base์™€ SEG ํ•ด์ƒ๋„ยท์ขŒํ‘œ๊ณ„๊ฐ€ ๋‹ค๋ฅธ ๊ฒฝ์šฐ
  • ์‹ค์‹œ๊ฐ„ SEG ์—…๋ฐ์ดํŠธ๊ฐ€ ๋นˆ๋ฒˆํ•œ ๊ฒฝ์šฐ (์บ์‹œ ๋ฌดํšจํ™” ๋ณต์žก)
๐Ÿงญ ์ด ์ „๋žต์˜ ํ•ต์‹ฌ ๊ฐ€์น˜๋Š” "Base๋ฅผ ๊ฑด๋“œ๋ฆฌ์ง€ ์•Š๊ณ  SEG๋งŒ ์ตœ์ ํ™”"ํ•œ๋‹ค๋Š” ์ ์ž…๋‹ˆ๋‹ค. ๊ธฐ์กด .nii.gz ์„œ๋น™ ํŒŒ์ดํ”„๋ผ์ธ์„ ์œ ์ง€ํ•˜๋ฉด์„œ, SEG๋งŒ Sparse Zarr๋กœ ๊ต์ฒดํ•ด ์ดˆ๊ธฐ ๋กœ๋”ฉ ์†๋„์™€ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ๋™์‹œ์— ๊ฐœ์„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํŠนํžˆ AI ์ถ”๋ก  ๊ฒฐ๊ณผ SEG๋ฅผ ์›น ๋ทฐ์–ด์—์„œ ์ฆ‰์‹œ ์˜ค๋ฒ„๋ ˆ์ดํ•ด์•ผ ํ•˜๋Š” ์‹œ๋‚˜๋ฆฌ์˜ค์— ์ ํ•ฉํ•ฉ๋‹ˆ๋‹ค.
์ฒดํฌํฌ์ธํŠธ์ƒํƒœ๋น„๊ณ 
๊ธฐ์ˆ ์  ํƒ€๋‹น์„ฑ๊ฒ€์ฆ๋จZarr write_empty_chunks=False ๋„ค์ดํ‹ฐ๋ธŒ ์ง€์›
Base ๋ณผ๋ฅจ ์˜ํ–ฅ์—†์Œ์™„์ „ํžˆ ๋ถ„๋ฆฌ๋œ ํŒŒ์ดํ”„๋ผ์ธ
์Šคํ† ๋ฆฌ์ง€ ์ ˆ๊ฐ~95%๋ณ‘๋ณ€ SEG ๊ธฐ์ค€ (ํฌ์†Œ๋„ ๋†’์„์ˆ˜๋ก ํšจ๊ณผ ํผ)
CS3D ๊ณต์‹ ์ง€์›์—†์Œ์ปค์Šคํ…€ ImageLoader ๊ตฌํ˜„ ํ•„์š”
๊ตฌํ˜„ ๋‚œ์ด๋„์ค‘๊ฐ„zarrita + ์ปค์Šคํ…€ ๋กœ๋” ์•ฝ 200์ค„
ํŒŒ์…€๋ ˆ์ด์…˜ SEG ์ ํ•ฉ์„ฑ๋‚ฎ์Œ๋น„์˜ ๋น„์œจ ๋†’์œผ๋ฉด ์ด์  ๊ฐ์†Œ
ํ”„๋กœ๋•์…˜ ์•ˆ์ •์„ฑ๊ฒ€์ฆ ํ•„์š”404โ†’0 ์ฒ˜๋ฆฌ, ์ฒญํฌ ๊ฒฝ๊ณ„ ๊ฒ€์ฆ ํ•„์ˆ˜