Pythonを使ってDICOMやNIfTIといった医療画像を読み込む

はじめに

この記事ではPythonを使った医療画像処理の方法について紹介しています。
初めてPythonに触れる方でも学びやすいようにGoogle Colaboratoryを使った例を示しています。
Google Colaboratoryの基本的な使い方については、たくさん解説記事があると思うのでそちらを参考にしてみてください。

また、今回の記事の内容については無料でNotebookをDLできるようにしているので、よければ使ってみてください。

Pythonを使って医療画像データを読み込む

ここではPythonを使った医療画像データ(DICOMとNIfTI)の読み込み方法について紹介します。
以下の例では"Structural MRI datasets"のデータを利用しています。

お持ちのデータを利用する場合はファイルの読み込みの箇所を変えるといったように適宜修正してお使いください。

準備

以下のようなフォルダの階層構造のもとで行っています。
自分で追加しているものはすべて"Structural MRI datasets"のものです。

home(デフォルトのフォルダ)
┗ sample_data(デフォルトで読み込まれるもの)
┗ DICOM(自分で追加したもの)
   ┗ FLAIR(自分で追加したもの)
   ┗ T1(自分で追加したもの)
   ┗ T2(自分で追加したもの)
   ┗ ROI(自分で追加したもの)
┗ NIfTI(自分で追加したもの)

Colaboratoryのデフォルト環境では、医療画像を処理するのに必要なパッケージがインストールされていません。

そのため、まずは必要なパッケージをインストールします。

!pip install pydicom

Collecting pydicom Downloading pydicom-2.3.1-py3-none-any.whl (2.0 MB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2.0/2.0 MB 26.7 MB/s eta 0:00:00
Installing collected packages: pydicom
Successfully installed pydicom-2.3.1

出力例

うまくいくと"Successfully …"という表示がでます。
こういった表示が出なかった場合は失敗していて、これ以降の処理を実行することができないので注意してください。

# パッケージ読み込み
import glob
from matplotlib import pyplot as plt
import cv2

DICOMデータ

import pydicom
# DICOM/FLAIR以下にある拡張子".dcm"のファイルをすべて取得
files_flair = glob.glob('DICOM/FLAIR/*.dcm')
# ファイル名順にきちんと並ぶようにソート
files_flair = sorted(files_flair)

print(files_flair)
print(len(files_flair))# ファイル数を確認

['DICOM/FLAIR/BRAINIX_DICOM_FLAIR_IM-0001-0001.dcm', 'DICOM/FLAIR/BRAINIX_DICOM_FLAIR_IM-0001-0002.dcm',

'DICOM/FLAIR/BRAINIX_DICOM_FLAIR_IM-0001-0022.dcm']
22

出力例

ファイル数が0になってしまうときはファイルのパスがきちんとあっているか確認しましょう。

DICOMデータの読み込み

# 例えば先頭のデータを1つ読み込んでみましょう
# 0を他の数字に変えると違うファイルになります
data = pydicom.dcmread(files_flair[0])
# ColaboratoryなどいわゆるNotebook上ならDICOMのデータがたくさん表示されます
data

Dataset.file_meta -------------------------------
(0002, 0000) File Meta Information Group Length UL: 210
(0002, 0001) File Meta Information Version OB: b'\x00\x01'
(0002, 0002) Media Storage SOP Class UID UI: MR Image Storage
(0002, 0003) Media Storage SOP Instance UID UI:

(7fe0, 0010) Pixel Data OW: Array of 165888 elements

出力例

一覧表示された各データ項目は、例えば"xxx.ProtocolName"のようにスペースなしで項目名を後ろにつけてあげることで参照できるようになっています。

自分で用意したDICOMファイルを使用する場合、どのファイルがどの方法で撮像したものかわからなくて困る場合があると思います。

そういったときは

  • 各ファイルのプロトコル名を表示させてみる

  • 撮像時の設定やビュワーで一度どのようなプロトコル名になっているのかを確認

といったことをして、対象となるものを探すと良いです。

name_protocol = []
for x in files_flair:
    data = pydicom.dcmread(x)
    name_protocol.append(data.ProtocolName)

# プロトコル名一覧の表示
# set型にすることで重複したものは省かれています
print(set(name_protocol))

{'sT2W/FLAIR SENSE'}

出力例

この例ではファイルが既に整理されているため、'sT2W/FLAIR SENSE'のみしか表示されません。
欲しいファイルのプロトコル名がわかったらif文を使って、それだけを抜粋すると良いです。

# 例えば"FLAIR"が入っているものだけを取得する場合myfiles = []
for x in files_flair:
    data = pydicom.dcmread(x)
    if 'FLAIR' in data.ProtocolName:
        myfiles.append(x)

print(myfiles)

['DICOM/FLAIR/BRAINIX_DICOM_FLAIR_IM-0001-0001.dcm', 'DICOM/FLAIR/BRAINIX_DICOM_FLAIR_IM-0001-0002.dcm',

'DICOM/FLAIR/BRAINIX_DICOM_FLAIR_IM-0001-0022.dcm']

出力例

各情報の書き換えと保存
患者名(PatientName)や患者ID(PatientID)を匿名化した上でファイルを利用したくなることはよくあると思います。
そういった場合は該当箇所の情報を書き換えた上で保存することができます。

# 各情報を表示
print('Patient Name: ', data.PatientName)

# 各情報を書き換え
data.PatientName = 'XXXXX'

# 書き換えたDICOMファイルを保存
data.save_as('changed.dcm')

# 書き換えられているのを確認
changed = pydicom.dcmread('changed.dcm')
print('Changed Name: ', changed.PatientName)

Patient Name: BRAINIX
Changed Name: XXXXX

出力例

Notebook上での画像表示

# 例えば1番目の画像を見る場合
data = pydicom.dcmread(files_flair[0])
img = data.pixel_array # これで画像情報だけを抜き出せます

# Notebook上で画像を表示
plt.imshow(img, cmap='gray')# cmap='gray'とすると、グレースケールになる
plt.show()

print('画像サイズ: ', img.shape)
出力された画像の例

医療画像を深層学習モデルにインプットとして渡すためにJPEGやPNGといった一般的な画像に変換したい場合があると思います。

そのようなときはOpenCVの機能を使って保存するのが楽だと思います。

# 保存するファイル名を.pngにすればPNGで保存されます
cv2.imwrite('flair.jpg', img)

ただ、上記の方法で簡単に保存できるものの、元画像と何か違う、あるいは真っ暗な画像になってしまう、といったことがあるかもしれません。

そのようになってしまうのは、

  • DICOMと保存先の画像との階調の違い

  • ウインドウ処理の有無

といったことが原因として考えられます。

今回のDICOMデータには"Window Center"や"Window Width"といった情報が含まれているため、ウインドウ処理を行った上でPNG形式で保存する例を以下に示しておきます。

wc  = data.WindowCenter
ww  = data.WindowWidth

# ウィンドウ処理
w_max = wc + ww/2                     
w_min = wc - ww/2
img_w = 255*(img - w_min)/(w_max - w_min)    
img_w[img_w > 255] = 255 # 255より大きいところを255にする
img_w[img_w < 0] = 0 # 0未満を0にする

cv2.imwrite('flair_w.png', img_w)

これで保存したものは先ほどのもののような違和感はなくなっていると思います

NIfTIデータ

import nibabel as nib

ここでは例としてFLAIR画像を読み込んでみます

data = nib.load('NIfTI/BRAINIX_NIFTI_FLAIR.nii.gz')
img_list = data.get_data()#これで画像一覧を取り出せます

NIfTIのデータは、DICOMと違って1ファイル中に複数画像が含まれているときがあります。

get_data()のあとはshapeを使って、どういった行列になっているのかを確認するのがおすすめです。

print(data.shape)

(288, 288, 22)

出力例

今回の場合、288×288の画像が22枚入っていることがわかります。
目的の1枚の画像を取得するときはnumpyの行列操作を応用すれば大丈夫です。

# i番目の画像を抜き出すとき(ここでは例えばi = 0)
i = 0
img = img_list[:,:,i]

plt.imshow(img, cmap='gray')
plt.show()
出力された画像の例

画像を表示させるとわかるように画像が90度回転しているので、回転させたいときは以下のように角度をそのまま引数に渡せるscipyを使うのが直感的にできておすすめです。

from scipy import ndimage
img_rotated = ndimage.rotate(img, 90, reshape=False)

plt.imshow(img_rotated, cmap='gray')
plt.show()
出力された画像の例

以上の処理が書かれたNotebookです。
よければ使ってみてください。

簡単な使い方についてはこの記事を参考にしてください。

他にもこういった記事が読みたい!これを解説してほしい!などご意見がありましたら、アンケートフォームからご意見をいただけるとうれしいです。

この記事が気に入ったらサポートをしてみませんか?