MRI画像をスライダーを使ってウインドウ調整2(OpenCv編)

OpenCV

それでは、コードを組み立てていきたいと思います。

前回のコードの流れにそってやっていきたいと思います。

コードの流れ


画像パスの取得

画像パスの取得です。以前やって「画像選択のプログラムを使いやすく」で作成したモジュールを使用したいと思います。複数の画像を選択したいので使用する関数は multi_fileselect を指定します。(今回は、画像を切り替えるプログラムは組み込みません。)


import fileselect as fs   #ファイルパス取得のモジュールをインポート

filenames = fs.multi_fileselect

上記コード2行目でfileselectのモジュールをインポートしています。そして、そのモジュールを使う時はfsという名前で使えるように設定。(前回作成したfileselectのファイルを今回作成するプログラムと同じフォルダに入れておいてください。)

4行目でfileselectモジュール内のmulti_fileselectの関数を呼び込み、その結果をfilenamesという変数の中に入れています。

モジュール化しておくことで、たった2行で画像ファイルのパスを取得できるようになっています。とても便利ですね~~。


画像表示用の配列を作成

画像表示用の配列を作成します。 初めは0で埋めた配列を作成します。 (このページから読んだ方は前回の「MRI画像をスライダーを使ってウインドウ調整1(OpenCv編)」を一度読んでいただくと意味が理解できると思います。)

作成の際には、画像の縦、横のピクセル数、画像枚数の3次元の配列を作成しますので、とりあえず、1枚画像を読み込んで縦横のピクセル数を取得します。画像の枚数は変数filenamesの要素数を取得することでわかります。

それでは作成していきますが、配列操作はnumpyを用いるのが便利なのでまずはnumpyをnpという名前で使えるようにインポートしましょう

0で初期化した配列を作成するのは以下のコードでできます。

np.zeros((z, y, x),dtype ,order)

1個の目の引数は配列の軸の設定です。zは画像の枚数、yは画像の縦方向のピクセル数、xは画像の横方向のピクセル数となります。

2個目の引数は格納される値の型となります。整数型か、小数点付きか、文字列なのか等の設定をします。今回は整数型となります。

3個目の引数は他次元配列の際に、行を優先するか、列を優先するかの設定です。オプションの設定となりますので今回は指定しません。


画像のピクセル数を取得してみましょう。

まずは、 pydicomのモジュールをインポートして画像を読み込みます。

読み込んだ画像の横方向を変数row、縦方向を変数columnsとして取得します。


import fileselect as fs   #ファイルパス取得のモジュールをインポート
import numpy np
import pydicom

filenames = fs.multi_fileselect

dcm = pydicom.dcmread(filenames[0])
row,columns = dcm.pix_array.shape[0],dcm.pix_array.shape[1]
       #読み込んだ画像の横方向を変数row、縦方向を変数columnsとして取得します。

dcm_copy = np.zeros((len(filenames), row, columns),dtype = int)
       #dcm_copyという名前で0で初期化した配列を作成


作成した配列に画像のピクセルデータを入れる

0で初期化した配列の中に画像のピクセルデータを入れていきます。

これは繰り返しの作業となりますのでfor文で処理していきたいと思います。


import fileselect as fs   #ファイルパス取得のモジュールをインポート
import numpy np
import pydicom

filenames = fs.multi_fileselect

dcm = pydicom.dcmread(filenames[0])
row,columns = dcm.pix_array.shape[0],dcm.pix_array.shape[1]
       #読み込んだ画像の横方向を変数row、縦方向を変数columnsとして取得します。

dcm_copy = np.zeros((len(filenames), row, columns),dtype = int)
       #dcm_copyという名前で0で初期化した配列を作成

for i in range(len(filenames)):
    dcm = pydicom.dcmread(filenames[i])
    dcm_arr = dcm.pixel_array
    dcm_copy[i] = dcm_arr.astype(np.int64) 
      #np.int64としてデータの型を指定しておきます。

これで、dcm_copyに選択した画像全てのピクセルデータが入りました。


スライダーの作成

スライダーを作成するには画像表示のウインドウ名が必要となりますので、まずは画像表示のウインドウの設定をします。

cv2.namedWindow(name,type)

第一引数のnameはウインドウ名です。

第二引数には表示形式をしていします。形式は以下の2種類です。

cv2.WINDOW_AUTOSIZE:デフォルト。ウィンドウサイズ固定表示

cv2.WINDOW_NORMAL:ウィンドウのサイズを変更可能にする

今回は、ウインドウサイズを変更できるようにしたいのでcv2.WINDOW_NORMAL を指定します。


import fileselect as fs   #ファイルパス取得のモジュールをインポート
import numpy np
import pydicom

filenames = fs.multi_fileselect

dcm = pydicom.dcmread(filenames[0])
row,columns = dcm.pix_array.shape[0],dcm.pix_array.shape[1]
       #読み込んだ画像の横方向を変数row、縦方向を変数columnsとして取得します。

dcm_copy = np.zeros((len(filenames), row, columns),dtype = int)
       #dcm_copyという名前で0で初期化した配列を作成

for i in range(len(filenames)):
    dcm = pydicom.dcmread(filenames[i])
    dcm_arr = dcm.pixel_array
    dcm_copy[i] = dcm_arr.astype(np.int64) 
      #np.int64としてデータの型を指定しておきます。

cv2.namedWindow('dcm_image',cv2.WINDOW_NORMAL)
      #ウインドウ名を'dcm_image'とし、ウインドウサイズを変更できるように設定

画像表示ウインドウの設定が終わったので、いよいよスライダーの設定に入ります。以下のコードで指定します。

cv2.createTrackbar( name , window_name , initial_value , max_value , definition )

作成するスライダーは2つ。WWとWLの2つです。それぞれ最大値をピクセルデータの最大値としたいので画像のピクセルデータが入っているdcm_copyの中の最大値を調べたいと思います。最大値は以下のコードで取得できます。

maxvalue = dcm_copy.max().astype(np.int64)

最後の .astype(np.int64) は取得した値の型を指定しておきます。(後の処理で型が違うとエラーが出てしまうので取得の段階で型の指定しておきます。)

スライダーの初期値はとりあえず、WLは画像最大値の半分、 WWは画像最大値の4分の1として設定してみましょう。

cv2.createTrackbar(“WL”, “dcm_image”, (maxvalue // 2), maxvalue, make_LUT)
cv2.createTrackbar(“WW”, “dcm_image”, (maxvalue // 4), maxvalue, make_LUT)

ここで、 maxvalue // 2 と maxvalue // 4 の’//’は間違いではなく、計算結果が少数にならないようにする演算子です。これにより小数点以下は切り捨てられます。

ちなみに、第5番目の引数はしていしないとエラーになってしまうので何もしない”make_LUT”という関数を作成しておきます。作成した関数で受ける引数は最低一つはないとこれもエラーとなってしまうので適当にvalという受けを書いておきます。


import fileselect as fs   #ファイルパス取得のモジュールをインポート
import numpy np
import pydicom

filenames = fs.multi_fileselect

dcm = pydicom.dcmread(filenames[0])
row,columns = dcm.pix_array.shape[0],dcm.pix_array.shape[1]
       #読み込んだ画像の横方向を変数row、縦方向を変数columnsとして取得します。

dcm_copy = np.zeros((len(filenames), row, columns),dtype = int)
       #dcm_copyという名前で0で初期化した配列を作成

for i in range(len(filenames)):
    dcm = pydicom.dcmread(filenames[i])
    dcm_arr = dcm.pixel_array
    dcm_copy[i] = dcm_arr.astype(np.int64) 
      #np.int64としてデータの型を指定しておきます。

cv2.namedWindow('dcm_image',cv2.WINDOW_NORMAL)
      #ウインドウ名を'dcm_image'とし、ウインドウサイズを変更できるように設定

maxvalue = dcm_copy.max().astype(np.int64)

cv2.createTrackbar("WL", "dcm_image", (maxvalue // 2), maxvalue, make_LUT)
cv2.createTrackbar("WW", "dcm_image", (maxvalue // 4), maxvalue, make_LUT)

def make_LUT(val):
    pass #何もしない


画像表示

いよいよ画像表示です。画像表示はスライダーをいじるたびに画像を変更しなくてはいけないのでループ構文whileを用いて作成します。

画素値255を超える画像はopenCVでは表示できないので、その処理をやっていきます。

まずは、WLよりWW/2より高いピクセル値を255、 WW/2より低いピクセルを0とした後に、その間のピクセル値を256階調に落とし込んでいく作業が必要になります。その作業をピクセルごとに計算して算出すると非常に処理に時間がかかってしまうのでルックアップテーブルという物を作成します。ルックアップテーブルとは、この数値はこの値という表を作成し、それをピクセルごとに参照することで計算処理を省く処理となります。

ルックアップテーブルを0で初期化した配列を作成します。ルックアップテーブルの最大値は画像の最大値となりますのでスライダーの最大値を取得した時に一緒に作成してしまいましょう。その際、要素数を最大値+1として指定しておきます。 (下のコード30行目)


import fileselect as fs   #ファイルパス取得のモジュールをインポート
import numpy np
import pydicom
import math
import copy

filenames = fs.multi_fileselect

dcm = pydicom.dcmread(filenames[0])
row,columns = dcm.pix_array.shape[0],dcm.pix_array.shape[1]
       #読み込んだ画像の横方向を変数row、縦方向を変数columnsとして取得します。

dcm_copy = np.zeros((len(filenames), row, columns),dtype = int)
       #dcm_copyという名前で0で初期化した配列を作成

for i in range(len(filenames)):
    dcm = pydicom.dcmread(filenames[i])
    dcm_arr = dcm.pixel_array
    dcm_copy[i] = dcm_arr.astype(np.int64) 
      #np.int64としてデータの型を指定しておきます。

dcm_main = copy.deepcopy(dcm_copy)
      #深いコピーで複製します。

cv2.namedWindow('dcm_image',cv2.WINDOW_NORMAL)
      #ウインドウ名を'dcm_image'とし、ウインドウサイズを変更できるように設定

maxvalue = dcm_copy.max().astype(np.int64)
lookup_tbl = np.zeros(maxvalue+1, dtype=np.int64)
      #作成要素数を最大値+1としておく。

cv2.createTrackbar("WL", "dcm_image", (maxvalue // 2), maxvalue, make_LUT)
cv2.createTrackbar("WW", "dcm_image", (maxvalue // 4), maxvalue, make_LUT)

while 1:
    wl = cv2.getTrackbarPos('WL', 'dcm_image')
    ww = cv2.getTrackbarPos('WW', 'dcm_image')

    ww_low = wl - ww // 2
    ww_high = wl + ww // 2
    lookup_tbl[0:ww_low] = 0
    lookup_tbl[ww_high:maxvalue] = 255
    for i in range(ww_low, ww_high, 1):
        lookup_tbl[i] = math.ceil((i - ww_low) * (256 / (ww_high - ww_low)))

    dcm_copy = lookup_tbl[dcm_main]

    dcm_copy =cv2.convertScaleAbs(dcm_copy, alpha=255/dcm_copy.max())
    cv2.imshow('dcm_image', dcm_copy[0])

    k = cv2.waitKey(1)
    if k == ord('q'):
        break

def make_LUT(val):
    pass #何もしない

スライダーの値を取得し、WLよりWW/2より高いピクセル値を255に WLよりWW/2より低いピクセル値を0にします。(上のコード42~43行目)

WWの間は1階調がどのくらいに当たるかを計算します。上記コード45行目の計算で求めます。ceil は小数点以下切り上げる構文となります。ちなみに、mathというライブラリの構文となりますのでmathもインポートしておいてください。

ルックアップテーブルの作成はこれで完了です。では画像のピクセル値をルックアップテーブルの値に当てはめる作業になるのですが、 ピクセルデータをそのまま変更してしまうと次にスライダーを動かした時、ルックアップテーブルを適応したピクセルデータにスライダーの設定が反映され画像表示がおかしくなってしまいますので画像ピクセルデータをdcm_mainとして複製しておきましょう。その際、普通に dcm_main =dcm_copyとしてもコピーされません。上記コードは参照という意味になってしまいます。これはpythonでは、浅いコピーということであり、今回は深いコピーという方法をつかって別の配列として作成してあげる必要があります。

深いコピーをしていきます。まず、copyというライブラリをインポート(5行目)します。その後、深いコピーをします。(上記コード23行目)

それでは、ルックアップテーブルを反映していきます。47行目のコードで反映されますが、 反映する配列は dcm_mainとし結果をdcm_copyとします。

いよいよ画像表示に入っていくのですが、実は 先ほどのceil関数は計算結果を切り上げ処理している関係で255階調をオーバーすることがありますのでここでまた255階調に落とし込む作業が必要になります。

cv2.convertScaleAbs(dcm_copy, alpha=255/dcm_copy.max())

cv2.convertScaleAbs はエッジ処理でよく使われるコードです。興味がある方は調べてみてください。

後は、忘れがちな待機処理(コード52行目から54行目)を書いて終了です。

正直、画像表示はImage-Jなど使えば簡単に表示できるのですが。これからいろいろと画像処理をしていこうと考えると避けては通れない道ですね。頑張っていきましょう!!
長くなりました。。。。。お疲れ様です。



環境

  • windows10
  • python3.6.1
  • Anaconda custom(64-bit)
  • PyCharm2020.2(Communication Edition)

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