Pay it Forward

[X-ray, CT] Dicom 파일 header 분석 및 python으로 dicom 다루기 본문

Bio, Medical/X-ray & CT

[X-ray, CT] Dicom 파일 header 분석 및 python으로 dicom 다루기

minjoony 2021. 8. 12. 20:28
728x90

인공지능을 이용한 메디컬 이미징분야를 다루다보면 필수적으로 마주하게 되는 dicom 파일에 대하여 알아보고,

python을 통해 dicom 파일을 다루는 방법에 대하여 살펴보고자 한다.

 

1. Dicom 파일 구성

Dicom 파일의 구성은 세부적으로는 아직 필요성을 느끼지 못하여 공부해보지 못했지만,

표면적으로는 dicom 이미지를 구성하는 pixel값들헤더(header)의 정보들이라고 생각된다.

 

MicroDicom viewer로 살펴본 dicom file의 이미지와 헤더

Dicom 파일을 python을 통해 처리하려면 위의 이미지와 더불어 헤더정보를 모두 이용하여야 한다.

헤더에는 이미지 환자의 정보, 촬영 병원 혹은 모달리티(modality)에 대한 정보 뿐 아니라 이미지에 대한 각종 정보(이미지가 저장된 비트 수 등)가 저장되어있다.

 

헤더 정보를 이용하지 않고 이미지만을 불러오는 경우에는 위 사진과 같이 viewer로 열었을때와, python에서 imshow등을 이용해서 열었을때의 이미지가 다를 수 있다.

 

또한, 딥러닝 모델의 경우 input 이미지의 pixel값 range가 일관성을 가져야 학습이 잘되는 경향이 있는데

여러 source로 부터 다운받은 dicom file을 헤더 정보를 사용한 전처리과정 없이 학습데이터로 사용할 경우 학습이 제대로 이루어지지 않는다.

2. Dicom 주요 헤더 정보

[ Bits Stored ]

Dicom 파일의 이미지 정보가 저장되어 있는 비트(bit)의 수이다.

dicom은 주로 10~16bit를 사용하여 정보가 저장된다.

즉, dicom 파일을 이루고있는 값들의 range를 알 수 있다.

 

이 정보가 중요한 이유는, 앞서 말했듯이 딥러닝 모델의 input range를 맞춰주어야 하기 때문이다.

만약 A dicom 파일은 10bit로 저장되어있고, B dicom 파일은 16bit로 저장되어있다면,

A의 maximum value는 $2^{10}-1$인 1023일 것이고, B는 $2^{16}-1$인 65535일 것이다.

이처럼 range의 차이가 무시무시하기 때문에 반드시 dicom 파일을 읽어온 이후에는 bits stored를 활용하여 range를 맞춰주어야 한다.

 

[ Rescale Slope / Rescale Intercept ]

Bits stored 정보와 다른 의미로 dicom 파일에 저장된 값을 전처리하기 위해 필요한 정보이다.

컴퓨터에 X-ray 이미지를 저장할 때에는 unsigned int로 저장되기 때문에 음수를 저장할 수 없는 문제가 생긴다.

즉, 이미지의 실제 값(image value)과 저장되는 값(stored value)간의 차이가 존재하게 되는데,

저장된 값으로부터 실제 값을 계산하기 위하여 rescale slope와 rescale intercept를 사용하는 것이다.

즉, 기울기와 비슷한 개념의 "Rescale Slope"와, offset과 비슷한 개념의 "Rescale Intercept"를 헤더에 저장해줌으로서, 아래와 같은 수식을 통해 저장된 값으로부터 실제 이미지 값으로 변경하여 준다.

$Image value = (Rescale Slope) * (Stored value) + (Rescale Intercept)$

 

예를 들어, CT에서 주로 쓰이는 unit인 HU는 음수값을 포함한다. 따라서 Rescale Intercept주로 음수부호를 갖는다.

Rescale Slope역시 정보저장에 필요한 bit수보다 적은 수의 bit에 모든 정보를 저장하기 위해서 scaling factor로서 필요하다.

 

[ Window Center / Window Width ]

Window center와 window width는 X-ray/CT 이미지에서 특정 조직을 더욱 잘 보이게 강조하기 위한 값이다.

특정 조직들은 일반적으로 갖는 HU값의 범위가 정해져있다.

따라서 window center/window width를 보고자 하는 조직의 HU range로 맞추어 영상에서 그 조직부분을 강조시키는 것이다.

해당 조직이 잘 보이도록 영상을 normalization을 해준다고 보아도 무방하다.

 

range는 다음과 같은 방식으로 맞춘다.

$range = (WindowCenter - \frac{WindowWidth}{2}, WindowCenter + \frac{WindowWidth}{2})$

 

 

[ Photometric Interpretation ]

Photometric interpretation은 dicom 이미지의 visualization과 관련된 정보이다.

이 헤더의 정보를 이용하지 않을 경우, viewer를 통해 열어본 이미지와 python에서 이미지를 plot했을 때, 색상반전이 있을 수 있다.

 

값으로는 "MONOCHROME1", "MONOCHROME2", "RGB"등이 있다.

MONOCHROME1: 최소값을 갖는 pixel이 하얀색으로 나타나는 gray scale image.

MONOCHROME2: 최소값을 갖는 pixel이 검정색으로 나타나는 gray scale image.

RGB: Color image.

 (Left)MONOCHROME2 (Right)MONOCHROME1

즉, MONOCHROME1 값을 갖는 이미지는 viewer로 이미지를 열었을 때, 최소값을 갖는 부분인 air 혹은 lung부분이 하얀색이다. (우측그림)

반대로 MONOCHROME2 값을 갖는 이미지는 해당 부분이 검정색을 띈다. (좌측그림)

 

3. Python으로 dicom 파일 다루기

데이터처리/인공지능 분야에서 주로 사용되는 프로그래밍 언어인 'python'에서 dicom 파일을 읽어오고, 전처리 하는 방법에 대해서 살펴보자.

 

Dicom image 및 header 읽어오기

Dicom 파일을 읽어오기 위해서는 pydicom 패키지를 사용하면 된다.

아래와 같이 image 정보 및 헤더를 읽어올 수 있다.

(헤더는 헤더이름을 띄어쓰기 없이 Camel case로 작성하여 읽어오면 된다.)

import pydicom

ds = pydicom.read_file(_file)

pixel_array = ds.pixel_array   # dicom image
Rescale_slope = ds.RescaleSlope   # dicom header (Rescale slope)
Rescale_intercept = ds.RescaleIntercept   # dicom header (Rescale intercept)
Window_center = ds.WindowCenter   # dicom header (Window center)
Window_width = ds.WindowWidth   # dicom header (Window width)
Photometric_interpretation = ds.PhotometricInterpretation   # dicom header (Photometric interpretation)

 

Dicom image 띄우기

파일을 읽어온 이후에 이미지를 확인하기 위해서는 읽어온 파일을 plot하는 것이 아닌 파일의 pixel_array attribute를 plot해주면 된다.

plt.imshow(ds.pixel_array, cmap='gray')

 

헤더정보를 활용하여 Dicom image 전처리하기

1. Dicom image pixel의 range를 bits stored 정보를 활용하여 normalization하기

ds = pydicom.read_file(file)
img = ds.pixel_array.astype(np.float32)
img = (img / (2 ** ds.BitsStored))

 

2. Dicom stored pixel 값을 실제 image 값으로 변환하기

if(('RescaleSlope' in ds) and ('RescaleIntercept' in ds)):
    pixel_array = (pixel_array * ds.RescaleSlope) + ds.RescaleIntercept

 

3. 보고자 하는 조직에 맞게 영상 강조하기

if('WindowCenter' in ds):
    if(type(ds.WindowCenter) == pydicom.multival.MultiValue):
        window_center = float(ds.WindowCenter[0])
        window_width = float(ds.WindowWidth[0])
        lwin = window_center - (window_width / 2.0)
        rwin = window_center + (window_width / 2.0)
    else:    
        window_center = float(ds.WindowCenter)
        window_width = float(ds.WindowWidth)
        lwin = window_center - (window_width / 2.0)
        rwin = window_center + (window_width / 2.0)
else:
	lwin = np.min(pixel_array)
	rwin = np.max(pixel_array)
    
    
pixel_array[np.where(pixel_array < lwin)] = lwin
pixel_array[np.where(pixel_array > rwin)] = rwin
pixel_array = pixel_array - lwin

 

4. Viewer에서 보이는대로 image 변경하기

if(ds.PhotometricInterpretation == 'MONOCHROME1'):
    pixel_array[np.where(pixel_array < lwin)] = lwin
    pixel_array[np.where(pixel_array > rwin)] = rwin
    pixel_array = pixel_array - lwin
    pixel_array = 1.0 - pixel_array

else:
    pixel_array[np.where(pixel_array < lwin)] = lwin
    pixel_array[np.where(pixel_array > rwin)] = rwin
    pixel_array = pixel_array - lwin

 

References

https://blog.kitware.com/dicom-rescale-intercept-rescale-slope-and-itk/

https://ichi.pro/ko/geulei-seukeil-ui-munje-dicom-windows-ihae-29979749225149

https://dicom.innolitics.com/ciods/cr-image/image-pixel/00280004

728x90
Comments