画像間演算、サブトラクションをやってみる。

matplotlib

はじめに

今回は、業務中によく使用する画像間演算であるサブトラクションをやってみたいと思います。


コードの流れ

サブトラクションをやるには最低2枚の画像が必要となりますので、画像の読み込み工程を2つ作成します。その際、どちらの画像からどちらの画像を引き算するのか決めなくてはなりませんが、今回はコードを簡単にするため初めに取り込んだ画像から、2枚目に取り込んだ画像をひき算することにします。


続いて、計算結果を格納する配列を作成します。この配列は、取り込んだ画像と同じサイズの配列を作成します。


画像間演算、引き算のコードを書いていきます。


最後に、画像表示のコードを書いて完成となります。


コードを書いていく

画像のピクセルデータを取り込む

まずは、画像を読み込む工程からやっていきます。込みこむ工程は、画像のパスを変数に取り込んでから、それをクラスに渡して画像のピクセルデータを配列に取り込み形になります。

それではコードを書いていきたいと思います。

DICOM画像を使いますので、pydicomのインポートをします。

画像の読み込みは、毎回使用しているfileselectのモジュールを使用します。まだ、ご覧になっていない方はこちら。fileselectのファイルは、今回書いているコードと同じフォルダの中に入れておいてくださいね。

今回もフォルダで一括選択する方法で画像を読み込みたいと思います。


# -- coding utf-8 --
import pydicom
import fileselect as fs

filenames = fs.folder_fileselect()


まずは、fileselectのモジュールを用いてフォルダ内の画像パスをfilenamesの変数に取り込みます。

そして、その変数をClassに渡し画像のピクセルデータを配列に取り込みます。

以前の記事から、Classの部分のコードをコピーします。画像読み込みをクラスを使ってやってみたの記事はこちらの記事を参照ください。

クラスがよくわからない方はこちらを参照ください。

このクラスではnumpyを用いていますのでnumpyのインポートを忘れないようにしましょう。


# -- coding utf-8 --
import pydicom
import fileselect as fs
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)

filenames = fs.folder_fileselect()
f1 = Pixarr(filenames)

読み込んだ画像パスをClassに渡すのですが、ひかれる画像をf0、ひく画像をf1と名前を付けてクラスに渡します。(上記コード28行目)

これで、ひかれる画像のピクセルデータを取り込むことができました。

同様に、引く画像をf1として取り込みます。f0の時と同様にコードを書きます。(上記コード30~31行目)

これで、画像の読み込みは完了です。


画像間演算したデータを格納する配列を作成する

画像間演算のコードを書く前に、計算結果を格納する配列を作成します。

引く画像、引かれる画像と同一サイズの配列を作成します。f0,f1どちらでも構わないのですが同じ大きさの配列f2を作成します。その際に、0で初期化しておきます。

同一サイズの配列を作成するコードは

f2 = numpy.full_like(f0.pix_arr,0)

で作成できます。numpyはインポートする際npの名称でインポートしていますので、実際のコードは下記コード33行目の様にnp.full_likeの形になります。


# -- coding utf-8 --
import pydicom
import fileselect as fs
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)

filenames = fs.folder_fileselect()
f1 = Pixarr(filenames)

f2 = np.full_like(f0.pix_arr,0)


画像間演算、サブトラクションのコード

サブトラクションはピクセル同士の引き算ですので本来であれば

f2[i] = f0.pix_arr[i] – f1.pix_arr[i]

のコードをfou文で繰り返すと考えるかもしれませんが、実はpythonでの配列計算はとても簡単で

f2 = f0.pix_arr – f1.pix_arr

の一文だけで済んでしまいます。もちろん、引き算だけでなく足し算でも使えますし、掛け算、割り算でも使用可能です。

for文でピクセルごとに計算しているよりも全然高速です。

ここまでのコードは以下となります。


# -- coding utf-8 --
import pydicom
import fileselect as fs
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)

filenames = fs.folder_fileselect()
f1 = Pixarr(filenames)

f2 = np.full_like(f0.pix_arr,0)

f2 = f0.pix_arr - f1.pix_arr


画像表示のコード

画像表示はmatplotlibを使用したいと思いますので、まずはmatplotlibをインポートします。


# -- coding utf-8 --
import pydicom
import fileselect as fs
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)

filenames = fs.folder_fileselect()
f1 = Pixarr(filenames)

f2 = np.full_like(f0.pix_arr,0)

f2 = f0.pix_arr - f1.pix_arr

画像表示は引かれる画像、引く画像、計算後の画像の3画像表示したいと思います。

まずは、画像を表示設定からです。matplotlibで画像や、グラフ等を表示する際にまずはfigの設定をし、その後にaxの設定をします。ここが、よくわからないところかもしれませんが、考え方としては何もないところで絵を描こうとするときにまずは、画板や机を準備すると思います。それがfigにあたります。その後、その上に絵を描く紙を準備すると思います。それがaxにあたると考えやすいと思います。

figの設定は

fig = plt.figure()

チュートリアルはこちら

axの設定は

ax = fig.add_subplot()

で設定していきます。チュートリアルはこちら

figの大きさの設定をします。引数の中にfigsize =(横、縦)で設定をします。

fig = plt.figure(figsize=(15, 5))

どこに画像を表示するかはaxの括弧の中に引数として指定します。

ax0 = fig.add_subplot(1,3,1) #一番左に表示
ax1 = fig.add_subplot(1,3,2) #真ん中に表示
ax2 = fig.add_subplot(1,3,3) #一番右に表示

括弧内の初めの引数は縦方向の画像数を、2個目の引数は横方向の画像数、3個目の引数を何番目に画像を表示するかという設定になります。


続いて、axにどの画像を表示するかの設定に入ります。

引かれる画像をax0に、引く画像をax1に、演算後の画像をax2として設定していきます。

ax0.imshow(f0.pix_arr[sl],cmap= 'bone')
ax1.imshow(f1.pix_arr[sl],cmap= 'bone')
ax2.imshow(f2[sl],cmap= 'bone')

これで、画像表示の設定は完了です。


# -- 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)

filenames = fs.folder_fileselect()
f1 = Pixarr(filenames)


f2 = np.full_like(f0.pix_arr,0)

f2 = f0.pix_arr - f1.pix_arr

fig = plt.figure(figsize=(15, 5))
plt.subplots_adjust(left=0, right=1, bottom=0, top=1, wspace=0.05, hspace=0.05)
ax0 = fig.add_subplot(1,3,1)
ax1 = fig.add_subplot(1,3,2)
ax2 = fig.add_subplot(1,3,3)

ax0.imshow(f0.pix_arr[sl],cmap= 'bone')
ax1.imshow(f1.pix_arr[sl],cmap= 'bone')
ax2.imshow(f2[sl],cmap= 'bone')


axに画像の設定をした際に [sl] とありますが、これは、何枚目の画像を表示するかの変数になります。マウスホイールを回すことで画像を切り替えるための変数です。(上記コードの38行目 sl = 0 とコードを追加してください。)

その機能を実装していきます。

これは、マウスイベントと言って右クリックや、左クリック、マウスホイールを回す、または押す等のイベントで何をするか決められます。

マウスホイールのイベントは

fig.canvas.mpl_connect('scroll_event', wheel_scroll)

動作設定はdefで関数を作成して決めます。今回はwheel_scrollという名前で設定しますが、次回matplotlibのマウスイベントで詳しく説明したいと思いますので今回はコードの提示だけとします。


# -- 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

    ax0.imshow(f0.pix_arr[sl], cmap='bone')
    ax1.imshow(f1.pix_arr[sl], cmap='bone')
    ax2.imshow(f2[sl], cmap='bone')

    fig.canvas.draw()

filenames = fs.folder_fileselect()
f0 = Pixarr(filenames)

filenames = fs.folder_fileselect()
f1 = Pixarr(filenames)

f2 = np.full_like(f0.pix_arr,0)

f2 = f0.pix_arr - f1.pix_arr

sl =0

fig = plt.figure(figsize=(15, 5))
plt.subplots_adjust(left=0, right=1, bottom=0, top=1, wspace=0.05, hspace=0.05)
ax0 = fig.add_subplot(1,3,1)
ax1 = fig.add_subplot(1,3,2)
ax2 = fig.add_subplot(1,3,3)

ax0.imshow(f0.pix_arr[sl],cmap= 'bone')
ax1.imshow(f1.pix_arr[sl],cmap= 'bone')
ax2.imshow(f2[sl],cmap= 'bone')

fig.canvas.mpl_connect('scroll_event', wheel_scroll)

plt.show()

最後に

いかがでしたか?お持ちのDICOM画像でサブトラクションできましたでしょうか?

上記コードで実行した結果です。マンモのMRI Dynamic画像、一番左が単純、真ん中が動脈相、一番左がサブトラクション画像となります。

正常乳腺は引き算され、腫瘍は濃染されているのが確認できると思います。

皆さんも試してみてください。

お疲れ様でした。

タイトルとURLをコピーしました