MRI画像をスライダーを使ってウインドウ調整(OpenCV編)修正版(マウスイベント)

前回に引き続き、「MRI画像をスライダーを使ってウインドウ調整」の修正をしたいと思います。今回は、マウスイベントの機能を載せていきたいと思います。(以前にも似たような記事を書いています。参照してください。「マウスを使って画像を切り替える」


マウスホイールを使った画像変更

まず、「MRI画像をスライダーを使ってウインドウ調整」からマウスホイールのイベントを持ってきます。

def onMouse(event, x, y, flag, params):

    if event == cv2.EVENT_MOUSEWHEEL:  # ホイールを回したときの動作
        print('マウスホイール' + str(g))
        if flag > 0:       #もしflagが正の数だったら画像番号gを1引く
            g -= 1         # -= とはgの数から1を引いた数をiに代入すること
        elif flag < 0:     #もしflagが負の数だったら画像番号gを1足す
            g += 1         # += とはgの数から1を引いた数をgに代入すること

        if g <= 0:         #もし、gが0より小さかったら
            g = 0         #iは0
        elif g >= len(filenames)-1:  #もし、gが要素数以上だったら
            g = len(filenames)-1    #要素数より1少ない数に

これをまず、前回のコードに貼り付けていきます。

なお、前回変数はiを用いていましたがfor文でiを使っているので明確に区別するため今回はgという変数を指定しています。

前回のコードにマウスイベントのコードを張り付け


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

def onMouse(event, x, y, flag, params):

    if event == cv2.EVENT_MOUSEWHEEL:  # ホイールを回したときの動作
        print('マウスホイール' + str(g))
        if flag > 0:       
            g -= 1         
        elif flag < 0:     
            g += 1         

        if g <= 0:         
            g = 0         
        elif g >= len(filenames)-1:  
            g = len(filenames)-1    

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

filenames = fs.multi_fileselect

dcm = pydicom.dcmread(filenames[0])
row,columns = dcm.pixel_array.shape[0],dcm.pixel_array.shape[1]

dcm_copy = np.zeros((len(filenames), row, columns),dtype = 'int16')

for i in range(len(filenames)):
    dcm = pydicom.dcmread(filenames[i])
    dcm_arr = dcm.pixel_array
    dcm_copy[i] = dcm_arr

dcm_main = copy.deepcopy(dcm_copy)

cv2.namedWindow('dcm_image',cv2.WINDOW_NORMAL)

maxvalue = dcm_copy.max()
lookup_tbl = np.zeros(maxvalue+1, dtype='int16')

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

修正箇所


機能分散

以前は、while分を用いて画像表示をずっとループさせることで対処していました。しかし、そのループの中でww,wlの値を取得し、ルックアップテーブルの更新、それを画像配列に適応といった作業をしており非常に非効率的ですので、そこを修正していきたと思います。

まず、ルックアップテーブルの作成から画像配列への適応まではww,wlのスライダーを変更した時のみやればいい作業なので49~61行目までを24行目にある関数make_LUTの関数に入れてしまいましょう。passの一文は削除してください。

ちょっと話が脱線しますが、初めの表に以下の一文を追加しておいてください。

#coding: UTF-8

これが無いとprintのコードで日本語が使えません。。。。


# coding: UTF-8
import fileselect as fs   
import numpy as np
import pydicom
import math
import copy
import cv2

def onMouse(event, x, y, flag, params):

    if event == cv2.EVENT_MOUSEWHEEL:  
        if flag > 0:       
            g -= 1         
        elif flag < 0:     
            g += 1         

        if g <= 0:         
            g = 0         
        elif g >= len(filenames)-1:  
            g = len(filenames)-1    

def make_LUT(val):

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

filenames = fs.multi_fileselect

dcm = pydicom.dcmread(filenames[0])
row,columns = dcm.pixel_array.shape[0],dcm.pixel_array.shape[1]

dcm_copy = np.zeros((len(filenames), row, columns),dtype = 'int16')

for i in range(len(filenames)):
    dcm = pydicom.dcmread(filenames[i])
    dcm_arr = dcm.pixel_array
    dcm_copy[i] = dcm_arr

dcm_main = copy.deepcopy(dcm_copy)

cv2.namedWindow('dcm_image',cv2.WINDOW_NORMAL)

maxvalue = dcm_copy.max()
lookup_tbl = np.zeros(maxvalue+1, dtype='int16')

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

while 1:

    cv2.imshow('dcm_image', dcm_copy[0])

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

これで、機能の分散はできました。


プログラムを繋げていく

しかし、このままでは画像表示ができません。DICOM画像は画素値がjpegやping画像のように256階調で収まっていないので、それをwwの範囲を256階調に当てはめていく必要があります。その処理が前回のコードでは画像表示の直前にあったのですが、今回はmake_LUTの関数の中に入っていますので、画像選択の後に一度make_LUTの関数を走らなければなりません。

関数を呼び出す場所はwhile文の前で、トラックバーの設定が終わった60行目にしましょう。

make_LUTの関数はトラックバーを変更した時の関数でトラックバーの値を引数としてmake_LUTに渡します。しかし、make_LUTの関数内ではcv2.getTrackbarPosで値を取得しているので正直、この引数はあまり意味がありません。

なので、引数は適当に数字の’0’を入れておきます。

60行目に以下の一文を挿入してください。

make_LUT(0)

これで、ルックアップテーブルの作成と、それを適応した画像配列への適応への流れを作る事ができました。

ここで作成した画像配列を表示すればいいことになります。


配列のグローバル化

しかし、ここでまた問題があります。

make_LUTの関数内ではlookup_tblとdcm_copyの配列を使います。しかし、make_LUTの関数は引数を一つしか受けないという事です。

先ほど60行目に記入した関数を指定する場合は、いくらでも引数を指定できます。しかし、今回はスライダーの設定(58~59行目)と共有しています。そちらの方は引数を幾つも指定することはできません。

その為、関数外の変数、配列を使えるようにしなくてはいけません。

以下を見てください。

左側のコードでは、関数内で配列を5倍していますが、関数から戻って来ると値が元に戻ってしまっています。

関数内で更新したものは関数内でしか適応されません。(retuenで返せば別です)

右側のコードを見てください。関数内で’global 配列名(変数名)’とすることで引数として渡さなくても関数外の配列(変数)を使うことができるようになります。また、関数内で処理した結果も関数外に反映されます。

これを用いて画像表示用の配列を指定してあげましょう。

関数make_LUTの中で使いたい配列はlookup_tblとdcm_copyですので

24行目に

global lookup_tbl,dcm_copy

を追加します。この一文で配列lookup_tblとdcm_copyを使うことができるようになり、更新した結果も関数外で利用することができるようになりました。

いかがでしょうか?これで画像表示までできました。スライダーもきちんと機能していますよね?

しかし、まだマウスホイールで画像変更はできていません、次にそれを付け加えていきたいと思います。


マウスイベントの追加

まず、マウスイベント内で使っている変数gの初期化をしておかなくてはなりません。画像は選択した初めの画像を表示したいので0としておきましょう。

while分の中で宣言してしまうと常に0が代入されてしまうのでwhile文の直前で宣言しておきましょう 

g = 0

の一文をwhile文の前に入れておきます。

そして、マウスホイール動かした際の動作は、画像を切り替えて表示する事なのでマウスイベントを呼び出す関数はwhile文の中に書きます。

そして、マウスイベントに引き渡す変数はparamsの中にまとめて指定します。

今回はgという変数しかないので

(詳しくは過去の記事「マウスを使って画像を切り替える」を見てください。)

params = g

そしてマウスイベントを呼び出す

cv2.setMouseCallback(‘dcm_image’, onMouse, params)

最後に、マウスイベント関数内にparamsの中にgという変数が入っていますよ!とマウスイベントの中で示してあげる必要があります。

また、このマウスイベントで変更したgは関数外で使用しますのでグローバル化しておきます。

global g

そしてその後に

g = params

を書いてあげましょう。

いよいよ完成しました。


動かしてみましょう。

あれ、画像が変わらない。。。。


忘れていました。

cv2.imshow(‘dcm_image’, dcm_copy[0])

dcm_copy[0] を dcm_copy[g]

0 ⇒ g 

に変更をお忘れなく。

お疲れ様でした。

完成したコード


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

def onMouse(event, x, y, flag, params):
    global g,dcm_copy,filenames
    g = params

    if event == cv2.EVENT_MOUSEWHEEL:
        if flag > 0:
            g -= 1
        elif flag < 0:
            g += 1

        if g <= 0:
            g = 0
        elif g >= len(filenames)-1:
            g = len(filenames)-1

def make_LUT(val):
    global lookup_tbl, dcm_copy
    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())

filenames = fs.multi_files()

dcm = pydicom.dcmread(filenames[0])
row,columns = dcm.pixel_array.shape[0],dcm.pixel_array.shape[1]

dcm_copy = np.zeros((len(filenames), row, columns),dtype = 'int16')

for i in range(len(filenames)):
    dcm = pydicom.dcmread(filenames[i])
    dcm_arr = dcm.pixel_array
    dcm_copy[i] = dcm_arr

dcm_main = copy.deepcopy(dcm_copy)

cv2.namedWindow('dcm_image',cv2.WINDOW_NORMAL)

maxvalue = dcm_copy.max()
lookup_tbl = np.zeros(maxvalue+1, dtype='int16')

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

make_LUT(0)
g = 0

while 1:
    params = g
    cv2.setMouseCallback('dcm_image', onMouse, params)
    cv2.imshow('dcm_image', dcm_copy[g])

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

次回はトラックバーで画像調整したものをjpgやpngといった汎用画像に保存する方法をやっていきたいと思います。


環境

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

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