수민 '-'

플오그래밍

제가 작성하는 모든 글은 절대 상업적인 이용이 아니며, 그저 개인적인 공부 용도로만 사용하는 것임을 밝힙니다.

메디컬 이미지 NIfTI 실습 - DICOM montage, 변환, 3D 슬라이스 GIF

앞선 메디컬 이미지 개념 정리에서 포맷 차이를 봤다면, 이번에는 DICOM 폴더를 실제로 읽고 NIfTI로 변환한 뒤 3D 볼륨 슬라이스를 GIF로 확인하는 흐름을 실습해본다. 핵심은 DICOM 스택 확인 → NIfTI 변환 → 축 전환(transpose) → 애니메이션 시각화 순서다.

이번 글에서는 제공된 노트북 코드를 그대로 기준으로, 실행 흐름이 끊기지 않게 단계별로 정리한다.


1. 데이터 압축 해제 및 라이브러리 설치

!unzip -q '/content/drive/MyDrive/랭체인 AI 영상객체탐지분석 플랫폼 구축/14. 메디컬 이미지/data/brain_images.zip'
!pip install scikit-image
!pip install natsort
!pip install pydicom
import matplotlib.pyplot as plt
import pydicom
import numpy as np

from skimage.util import montage
from natsort import natsorted
from pathlib import Path

2. DICOM 슬라이스 로드 및 montage 시각화

BASE_PATH = Path('/content/brain_images/00825/T2w')
dicom_images = natsorted(list(BASE_PATH.glob('*.dcm')))
print(f'이미지의 총 개수: {len(dicom_images)}')
concat_images = np.array([pydicom.dcmread(i).pixel_array for i in dicom_images])
plt.figure(figsize=(10, 10))
# 여러 장의 2D 슬라이스를 3행 15열 격자로 붙여서 한 장으로 표시
plt.imshow(montage(concat_images, grid_shape=(3, 15)), cmap='bone')

여기서 montage는 폴더 안 슬라이스가 정상적으로 읽혔는지 빠르게 품질 점검할 때 유용하다.


3. DICOM → NIfTI 변환

!pip install nibabel
!pip install dicom2nifti
import dicom2nifti
import nibabel as nib
# .nii.gz 생성
dicom2nifti.convert_directory('/content/brain_images/00825/T2w', './')
nifiti_path = '/content/8_t2w.nii.gz'

sample_img = nib.load(nifiti_path)
sample_img = np.asanyarray(sample_img.dataobj)
print(f'nifti file shape: {sample_img.shape}')  # (height, width, depth)
transpose_img = sample_img.transpose((2, 1, 0))
transpose_img.shape

transpose((2, 1, 0))는 축 순서를 바꿔 슬라이스 탐색 방향을 맞출 때 자주 쓰는 전처리다.


4. 두 번째 볼륨도 동일하게 변환

dicom2nifti.convert_directory('/content/brain_images/00829/T2w', './')
nifiti_path2 = '/content/501_t2w.nii.gz'

sample_img2 = nib.load(nifiti_path2)
sample_img2 = np.asanyarray(sample_img2.dataobj)

transpose_img2 = sample_img2.transpose((2, 1, 0))
transpose_img2.shape

5. 3D 슬라이스를 GIF로 저장하는 클래스

import matplotlib.animation as anim
from IPython.display import Image as show_gif

class ImageToGIF:
    def __init__(self, size=(500, 500), xy_text=(80, 30), dpi=100, cmap='CMRmap'):
        self.fig = plt.figure()
        self.fig.set_size_inches(size[0] / dpi, size[1] / dpi)
        self.xy_text = xy_text
        self.cmap = cmap

        # [left, bottom, width, height]
        self.ax = self.fig.add_axes([0, 0, 1, 1])
        self.ax.set_xticks([])
        self.ax.set_yticks([])
        self.images = []

    def add(self, image, label, with_mask=False):
        plt.set_cmap(self.cmap)
        plt_img = self.ax.imshow(image, animated=True)
        plt_text = self.ax.text(*self.xy_text, label, color='red')
        to_plot = [plt_img, plt_text]
        self.images.append(to_plot)
        plt.close()

    def save(self, filename, fps):
        animation = anim.ArtistAnimation(self.fig, self.images)
        animation.save(filename, writer='imagemagick', fps=fps)

6. 첫 번째 볼륨 GIF 생성

sample_data_gif = ImageToGIF()
label = nifiti_path.replace("/", ".").split('.')[-2]
filename = f'{label}_3d_2d.gif'

for i in range(transpose_img.shape[0]):
    image = transpose_img[i]
    sample_data_gif.add(image, label=f'{label}_{str(i)}')

sample_data_gif.save(filename, fps=15)
show_gif(filename, format='png')

7. 다른 축 방향으로 슬라이스 GIF 생성

첫 번째 축이 아니라 세 번째 축 방향으로 순회하면 다른 단면을 볼 수 있다. MRI 볼륨은 축 방향에 따라 보이는 해부학적 단면이 달라진다.

sample_data_gif2 = ImageToGIF()
label = nifiti_path.replace("/", ".").split('.')[-2]
filename = f'{label}_3d_2d_2.gif'

for i in range(transpose_img2.shape[2]):
    image = transpose_img2[:, :, i]
    sample_data_gif2.add(image, label=f'{label}_{str(i)}')

sample_data_gif2.save(filename, fps=15)
show_gif(filename, format='png')

8. 180도 회전 후 GIF 생성

np.rot90(..., k=2)를 쓰면 90도를 두 번 적용해 총 180도 회전시킬 수 있다.

sample_data_gif2 = ImageToGIF()
label2 = nifiti_path.replace("/", ".").split('.')[-2]
filename2 = f'{label2}_3d_2d_2.gif'

for i in range(transpose_img2.shape[2]):
    # rot90: 90도 회전 함수
    # k=2: 90도 2번 -> 총 180도 회전
    # axes=(1,0): 1번 축과 0번 축 기준 회전
    image = np.rot90(transpose_img2[..., i], k=2, axes=(1, 0))
    sample_data_gif2.add(image, label=f'{label}_{str(i)}')

sample_data_gif2.save(filename2, fps=15)
show_gif(filename2, format='png')

마치며

  • DICOM 폴더 단위 데이터를 먼저 montage로 확인하면 데이터 품질 점검이 빠르다.
  • dicom2nifti + nibabel 조합으로 DICOM 스택을 NIfTI 볼륨으로 변환해 딥러닝 파이프라인에 쉽게 연결할 수 있다.
  • transposerot90는 단면 방향/표시 방향을 맞추는 데 핵심 전처리다.
  • 다음에는 이 볼륨 데이터를 기준으로 3D 세그멘테이션 입력 텐서 구성과 증강 파이프라인까지 이어서 정리해보면 좋다.