Contents
はじめに
私たち診療放射線技師がプログラムを使って解析を行う時まず初めに画像を読み込むことではないでしょうか。
今まで、私が書いてきたコードは画像読み込みのコードはクラスを使わずに行ってきました。しかし、数回前にクラスの記事を書きましたので、今回はクラスを用いて画像を読み込むコードを書いていきたいと思います。
コードの流れ
まずは、コードの流れを考えてみましょう。
- ファイルを選択
- クラスで画像配列を取得
の作業となり、そこで決めなくてはいけないことは以下となります。
- ファイル選択の方法
- クラスに持たせる情報
- ピクセルデータを配列に取り込む方法
ファイル選択の方法
まずはファイルの選択方法です。個別で選択するか、フォルダで選択するか。今回はフォルダで選択する方法をやってみたいと思います。
フォルダで一括の関連記事は以下にありますので興味がある方はご覧ください。
なお、今回も「画像選択のプログラムを使いやすく!!」で作成したモジュールを用います。
クラスに持たせる情報
次にクラスに持たせる情報です。
- 配列を作成する際に必要な情報として画像の縦、横のピクセル数の情報
- 適正な条件で画像表示するためにWL,WW
を持たせたいと思います。全て予約タグとなっていますのでほぼすべてのモダリティー画像であるタグです。
ピクセル数のデータは(0028,0010)、(0028,0011)
WW、WLは(0028,1050)、(0028,1051)
に登録されています。
なお、コード中に記載する際は16進数である事を示す”0x”を追加して記載することを忘れずに。
ピクセルデータを配列に取り込む方法
ピクセルデータを画像配列に取り込む作業ですが、今回はフォルダで一括取り込みで行います。以前の記事で記載しましたがフォルダで一括選択の場合は画像番号を文字列とし扱われてしまうので順番に取り込んでいくと画像が途中で飛んでしまいます。
詳しくはこちらの記事でご確認ください(フォルダでファイルを一括選択した時の問題点)
コードを書いていく
それでは、コードを書いていきたいと思います。
ファイルを選択する
まずは、モジュールのインポートです。ファイル選択のモジュールをインポートします。
# -- coding utf-8 --
import import fileselect as fs
このモジュールは過去の記事で記載していますので以下のリンクよりご確認ください。
インポートしたモジュール内でフォルダでファイル一括選択の関数はfolder_fileselect()で作成してありますので、folder_fileselect()を呼び出しfilenamesの変数で受けとります。(下のコードの5行目になります。)
# -- coding utf-8 --
import fileselect as fs
filenames = fs.folder_fileselect()
これで、第1工程のファイルを選択が完了しました。
クラスで画像配列を取得
クラスで画像配列を取得していく工程ですが以下の順に進めていきます。
- クラスの宣言
- 初期化(初期設定)
- ピクセルデータを画像番号順に取得
クラスの設定
まずは、クラスの宣言となりますが、クラスの宣言は関数を書く時と同じで初めを
class
で始めます。その後に名前が続くのですが名前の1文字目を大文字で書くのが通例となっています。
今回の目的は画像の配列を取得することなので名前はPixarrとすることにします。
class Pixarr:
関数の時と違うのはクラスの宣言では引数を受けないという事です。(その後の初期化や、クラス内の関数で引数を受けます。)
ちなみに、クラスの宣言はモジュールや、ライブラリーのインポートの後、コードの初めに書きます。
初期化(初期設定)
初期設定です。初期設定も関数の時と同じですが一つ違うところが関数名は__init__(アンダーバーは前と後ろ2つづつ)で、引数でselfという物が必須という事です。なので
def __init__(self):
という事になります。
当初私は、ここでつまづきました。。。。。selfの意味が分からない。何故こんなことをしなければならないのか。同じように疑問に思われる方は以下の記事に私なりの解釈を記載していますので一読いただければと思います。
話が脱線しました。初期化の話に戻ります。
# -- coding utf-8 --
import fileselect as fs
class Pixarr:
def __init__(self):
filenames = fs.folder_fileselect()
初期化の部分では、それぞれに持たせる情報を設定するところですので持たせたい情報は縦、横のピクセル数、画像番号、WW、WLの5つとなりますので
それぞれ、row、column、ww、wlという変数にしたいと思います。
それぞれのデータは[0028,0010]、[0028,0011]、[0028,1050]、[0028,1051]のタグに登録されています。
ではどの様にしてそのデータを取得するかですが、今回はクラスを呼び出した時(インスタンスを生成するといいます。)にファイルパスを渡し、クラス初期化の時に1枚画像を読み込んでDICOMタグを取得したいと思います。
それでは、コードに足していきたいと思います。
DICOM画像を扱うのでまず、pydicomをインポートします。
そして、引数として受け取ったファイルパスから一番初めの画像を読み込みます。(下記コード8行目)そしてタグを順番に取得します。(下記コード10~14行目)
そして、忘れてはいけないインスタンス生成のコードを足します。(下記コード17行目)
# -- coding utf-8 --
import fileselect as fs
import pydicom
class Pixarr:
def __init__(self,filenames):
dcm = pydicom.dcmread(filenames[0])
self.row = dcm[0x0028,0x0010].value
self.column = dcm[0x0028,0x0011].value
self.ww = dcm[0x0028,0x1050].value
self.wl = dcm[0x0028,0x1050].value
filenames = fs.folder_fileselect()
f0 = Pixarr(filenames)
ここまでで、持たせる情報は設定は完了しました。
ピクセルデータを配列に取り込む方法
続いて、ピクセルデータをを配列に取り込んでいく作業に入りたいと思います。
まずは、ピクセルデータを取り込むための0で初期化した配列を生成したいと思います。変数名はpix_arrとします。配列生成はnumpyを使用しますのでライブラリーのインポートをします。(下記コード5行目)
次にピクセルデータを配列に入れていく工程に入るのですが、問題とな事が2つあります。フォルダ内に画像が1枚だけの場合for文で画像取り込みを行うとエラーが出てしまう件。フォルダ内画像が10枚以上になると画像番号を文字列に捉えられてしまい順番に画像が取り込まれない件があります。
まず、画像が一枚の時の対策として、if文で処理を変えていきたいと思います。まずlen関数を用いてfilenamesの数をカウントし、処理を変えることで対策したいと思います。(下記コード19~20)
その次に、画像番号順にピクセルデータを配列に取り込んでいく方法です。
対策としては、画像を込みこんで変数img_noに画像番号を取り込み、画像番号-1の配列にピクセルデータを入れていく感じになります。(下記コード22~25行目)
# -- coding utf-8 --
import fileselect as fs
import pydicom
import numpy as np
class Pixarr:
def __init__(self,filenames):
dcm = pydicom.dcmread(filenames[0])
self.row = dcm[0x0028,0x0010].value
self.column = dcm[0x0028,0x0011].value
self.ww = dcm[0x0028,0x1050].value
self.wl = dcm[0x0028,0x1050].value
self.pix_arr = np.zeros((len(filenames),self.row,self.column),
dtype ='int16')
if len(filenames) == 1:
self.pix_arr = dcm.pixel_array
else:
for i in range(len(filenames)):
dcm = pydicom.dcmread(filenames[i])
img_no = dcm[0x0020,0x0013].value
self.pix_arr[img_no-1] = dcm.pixel_array
filenames = fs.folder_fileselect()
f0 = Pixarr(filenames)
これで、コードは完了です。
確認作業
さて、きちんとクラスを用いて配列内にピクセルデータが取り込まれたか確認作業に入りたいと思います。
まずは、ピクセル数の確認です。コード30行目にprint文で表示してみましょう。
# -- coding utf-8 --
import fileselect as fs
import pydicom
import numpy as np
class Pixarr:
def __init__(self,filenames):
dcm = pydicom.dcmread(filenames[0])
self.row = dcm[0x0028,0x0010].value
self.column = dcm[0x0028,0x0011].value
self.ww = dcm[0x0028,0x1050].value
self.wl = dcm[0x0028,0x1050].value
self.pix_arr = np.zeros((len(filenames),self.row,self.column),
dtype ='int16')
if len(filenames) == 1:
self.pix_arr = dcm.pixel_array
else:
for i in range(len(filenames)):
dcm = pydicom.dcmread(filenames[i])
img_no = dcm[0x0020,0x0013].value
self.pix_arr[img_no-1] = dcm.pixel_array
filenames = fs.folder_fileselect()
f0 = Pixarr(filenames)
print('画像の縦方向のピクセル数は\t' + str(f0.row))
print('画像の横方向のピクセル数は\t' + str(f0.column))
print('画像のウインドウレベルは\t' + str(f0.wl))
print('画像のウインドウ幅は\t' + str(f0.ww))
いかがでしたか?情報はきちんと表示されましたか?
続いてmatplotlibを用いて画像表示をしてみたいと思います。
まずは、matplotlibのライブラリーをインポートします。(下記コード6行目)
表示する画像番号の変数を指定します。slとして初期値0を指定します。(下記コード36行目)
画像を表示するウインドウ、figの設定をして(下記コード38行目)画像を表示する領域の設定をします。(下記コード39行目)
表示する画像の指定をします。(下記コード41行目)
画像表示のコードを42行目に記載します。
# -- coding utf-8 --
import fileselect as fs
import pydicom
import numpy as np
import matplotlib.pyplot as plt
class Pixarr:
def __init__(self,filenames):
dcm = pydicom.dcmread(filenames[0])
self.row = dcm[0x0028,0x0010].value
self.column = dcm[0x0028,0x0011].value
self.ww = dcm[0x0028,0x1050].value
self.wl = dcm[0x0028,0x1050].value
self.pix_arr = np.zeros((len(filenames),self.row,self.column),
dtype ='int16')
if len(filenames) == 1:
self.pix_arr = dcm.pixel_array
else:
for i in range(len(filenames)):
dcm = pydicom.dcmread(filenames[i])
img_no = dcm[0x0020,0x0013].value
self.pix_arr[img_no-1] = dcm.pixel_array
filenames = fs.folder_fileselect()
f0 = Pixarr(filenames)
print('画像の縦方向のピクセル数は\t' + str(f0.row))
print('画像の横方向のピクセル数は\t' + str(f0.column))
print('画像のウインドウレベルは\t' + str(f0.wl))
print('画像のウインドウ幅は\t' + str(f0.ww))
sl =0
fig = plt.figure()
ax = fig.add_subplot()
ax.imshow(f0.pix_arr[sl],cmap= 'bone')
plt.show()
どうですか?画像移動のコードを記載していないので1枚目の画像が表示されましたか?
最後に、トラックボールで画像を変えるコードを書きます。
ここに関しては、今回は長くなっているので詳細を省きたいと思います。(下記コード28~43行目)
とマウスイベントに繋げるコード59行目を記載して完了です。
# -- coding utf-8 --
import fileselect as fs
import pydicom
import numpy as np
import matplotlib.pyplot as plt
class Pixarr:
def __init__(self,filenames):
dcm = pydicom.dcmread(filenames[0])
self.row = dcm[0x0028,0x0010].value
self.column = dcm[0x0028,0x0011].value
self.ww = dcm[0x0028,0x1050].value
self.wl = dcm[0x0028,0x1050].value
self.pix_arr = np.zeros((len(filenames),self.row,self.column),
dtype ='int16')
if len(filenames) == 1:
self.pix_arr = dcm.pixel_array
else:
for i in range(len(filenames)):
dcm = pydicom.dcmread(filenames[i])
img_no = dcm[0x0020,0x0013].value
self.pix_arr[img_no-1] = dcm.pixel_array
def wheel_scroll(event):
global sl
if event.button == 'down':
sl += 1
if sl > len(filenames)-1:
sl =0
if event.button == 'up':
sl -= 1
if sl < 0:
sl = len(filenames)-1
ax.imshow(f0.pix_arr[sl], cmap='bone')
fig.canvas.draw()
filenames = fs.folder_fileselect()
f0 = Pixarr(filenames)
print('画像の縦方向のピクセル数は\t' + str(f0.row))
print('画像の横方向のピクセル数は\t' + str(f0.column))
print('画像のウインドウレベルは\t' + str(f0.wl))
print('画像のウインドウ幅は\t' + str(f0.ww))
sl =0
fig = plt.figure()
ax = fig.add_subplot()
ax.imshow(f0.pix_arr[sl],cmap= 'bone')
fig.canvas.mpl_connect('scroll_event', wheel_scroll)
plt.show()
いかがでしたか?きちんとトラックボールで画像変更できましたでしょうか?
最後に
クラスを用いて画像ピクセルデータ、DICOMタグの情報を取り込む方法を行いました。画像間演算や、画像比較等2つ以上の画像ファイルを用いて解析をやる時にクラスを用いた方法というのは大変便利です。
是非ともマスターして自分なりに使ってみてください。
お疲れ様でした。
環境
- windows10
- python3.6.1
- Anaconda custom(64-bit)
- PyCharm2020.2(Communication Edition)
コメント