【初心者向け】基礎&実践プログラミング

初心者がつまづきやすいところ、最短で実力が身につく方法をお伝えします。

【Python】NIFITIからPNGへ変換 (COVID-19[新型コロナウイルス] CT例)

目的

NIFTIからPNGへ変換

NIfTI-Image-Converterを使用する。

COVID-19(新型コロナウイルス)患者のCTデータを例に、NIFTIからPNGへと変換していく。

準備

パッケージのインストール

shutilは、404のアクセスエラーが生じるが、ほっておいてもOK(2020/04/20)。

scipyに関しては、scipy.misc.imsaveのモジュールがversion 1.1.0以下でないとないためあえて古いバージョンでインストール。

scipy.misc.imsave — SciPy v1.1.0 Reference Guide

pip3 uninstall scipy
pip3 install scipy==1.1.0

pip3 install shutil  #2020/04/20 エラーが起きるが気にしなくても大丈夫

pip3 install nibabel

pip3 install numpy

nii2pngのインストール

こちらにアクセスし、任意のバージョンのコードをインストールする。

今回は、v0.2.9.9をインストールした。

ダウンロードした、NIfTI-Image-Converter-0.2.9.9.zipを解凍する。

解答したファイル内でNIfTI-Image-Converter-0.2.9.9/python/nii2png.pyをこの先利用する。 使用前に実行権限chmod +xをつけておくこと。

$ cd ~/Download

$ unzip NIfTI-Image-Converter-0.2.9.9.zip 

$ chmod +x NIfTI-Image-Converter-0.2.9.9/python/nii2png.py

実は、このままではscipy.misc.imsave()のところでエラーが生じるので、コードを以下のように編集する。

具体的には、import scipyのところをimport scipy.miscに変更する。

変更前
import scipy
変更後
import scipy.misc

さらに、出力する際に軟部組織条件、肺野条件等任意のコントラストを設定できるようにコントラストを変える関数を定義。

def change_contrast(data, wc, ww):
    max, min = wc + ww / 2, wc - ww / 2
    data_std = 255 * (data - min) / (max - min)
    data_std[data_std<0] = 0
    data_std[data_std>255] = 255
    return data_std

すべての修正を加えると以下のようなコードになる。

#!/usr/bin/env python
#########################################
#       nii2png for Python 3.7          #
#         NIfTI Image Converter         #
#                v0.2.9                 #
#                                       #
#     Written by Alexander Laurence     #
# http://Celestial.Tokyo/~AlexLaurence/ #
#    alexander.adamlaurence@gmail.com   #
#              09 May 2019              #
#              MIT License              #
#########################################

# change scipy to scipy.misc
import scipy.misc
import numpy
import shutil
import os
import nibabel
import sys
import getopt


def change_contrast(data, wc, ww):
    max, min = wc + ww / 2, wc - ww / 2
    data_std = 255 * (data - min) / (max - min)
    data_std[data_std<0] = 0
    data_std[data_std>255] = 255
    return data_std

def main(argv):
    inputfile = ''
    outputfile = ''

    # set window center and window width for CT images.
    wc = int(input('Window Center : '))
    ww = int(input('Window Width : '))
    try:
        opts, args = getopt.getopt(argv, "hi:o:", ["ifile=", "ofile="])
    except getopt.GetoptError:
        print('nii2png.py -i <inputfile> -o <outputfile>')
        sys.exit(2)
    for opt, arg in opts:
        if opt == '-h':
            print('nii2png.py -i <inputfile> -o <outputfile>')
            sys.exit()
        elif opt in ("-i", "--input"):
            inputfile = arg
        elif opt in ("-o", "--output"):
            outputfile = arg

    print('Input file is ', inputfile)
    print('Output folder is ', outputfile)

    # set fn as your 4d nifti file
    image_array = nibabel.load(inputfile).get_data()
    print(len(image_array.shape))

    # ask if rotate
    ask_rotate = input('Would you like to rotate the orientation? (y/n) ')

    if ask_rotate.lower() == 'y':
        ask_rotate_num = int(input('OK. By 90° 180° or 270°? '))
        if ask_rotate_num == 90 or ask_rotate_num == 180 or ask_rotate_num == 270:
            print('Got it. Your images will be rotated by {} degrees.'.format(
                ask_rotate_num))
        else:
            print('You must enter a value that is either 90, 180, or 270. Quitting...')
            sys.exit()
    elif ask_rotate.lower() == 'n':
        print('OK, Your images will be converted it as it is.')
    else:
        print('You must choose either y or n. Quitting...')
        sys.exit()

    # if 4D image inputted
    if len(image_array.shape) == 4:
        # set 4d array dimension values
        nx, ny, nz, nw = image_array.shape

        # set destination folder
        if not os.path.exists(outputfile):
            os.makedirs(outputfile)
            print("Created ouput directory: " + outputfile)

        print('Reading NIfTI file...')

        total_volumes = image_array.shape[3]
        total_slices = image_array.shape[2]

        # iterate through volumes
        for current_volume in range(0, total_volumes):
            slice_counter = 0
            # iterate through slices
            for current_slice in range(0, total_slices):
                if (slice_counter % 1) == 0:
                    # rotate or no rotate
                    if ask_rotate.lower() == 'y':
                        if ask_rotate_num == 90 or ask_rotate_num == 180 or ask_rotate_num == 270:
                            print('Rotating image...')
                            if ask_rotate_num == 90:
                                data = numpy.rot90(
                                    image_array[:, :, current_slice, current_volume])
                            elif ask_rotate_num == 180:
                                data = numpy.rot90(numpy.rot90(
                                    image_array[:, :, current_slice, current_volume]))
                            elif ask_rotate_num == 270:
                                data = numpy.rot90(numpy.rot90(numpy.rot90(
                                    image_array[:, :, current_slice, current_volume])))
                    elif ask_rotate.lower() == 'n':
                        data = image_array[:, :, current_slice, current_volume]

                    # change contrast
                    data = change_contrast(data, wc, ww)

                    # alternate slices and save as png
                    print('Saving image...')
                    image_name = inputfile[:-4] + "_t" + "{:0>3}".format(
                        str(current_volume+1)) + "_z" + "{:0>3}".format(str(current_slice+1)) + ".png"
                    scipy.misc.imsave(image_name, data)
                    print('Saved.')

                    # move images to folder
                    print('Moving files...')
                    src = image_name
                    shutil.move(src, outputfile)
                    slice_counter += 1
                    print('Moved.')

        print('Finished converting images')

    # else if 3D image inputted
    elif len(image_array.shape) == 3:
        # set 4d array dimension values
        nx, ny, nz = image_array.shape

        # set destination folder
        if not os.path.exists(outputfile):
            os.makedirs(outputfile)
            print("Created ouput directory: " + outputfile)

        print('Reading NIfTI file...')

        total_slices = image_array.shape[2]

        slice_counter = 0
        # iterate through slices
        for current_slice in range(0, total_slices):
            # alternate slices
            if (slice_counter % 1) == 0:
                # rotate or no rotate
                if ask_rotate.lower() == 'y':
                    if ask_rotate_num == 90 or ask_rotate_num == 180 or ask_rotate_num == 270:
                        if ask_rotate_num == 90:
                            data = numpy.rot90(
                                image_array[:, :, current_slice])
                        elif ask_rotate_num == 180:
                            data = numpy.rot90(numpy.rot90(
                                image_array[:, :, current_slice]))
                        elif ask_rotate_num == 270:
                            data = numpy.rot90(numpy.rot90(
                                numpy.rot90(image_array[:, :, current_slice])))
                elif ask_rotate.lower() == 'n':
                    data = image_array[:, :, current_slice]

                # change contrast
                data = change_contrast(data, wc, ww)

                # alternate slices and save as png
                if (slice_counter % 1) == 0:
                    print('Saving image...')
                    image_name = inputfile[:-4] + "_z" + \
                        "{:0>3}".format(str(current_slice+1)) + ".png"
                    scipy.misc.imsave(image_name, data)
                    print('Saved.')

                    # move images to folder
                    print('Moving image...')
                    src = image_name
                    shutil.move(src, outputfile)
                    slice_counter += 1
                    print('Moved.')

        print('Finished converting images')
    else:
        print('Not a 3D or 4D Image. Please try again.')


# call the function to start the program
if __name__ == "__main__":
    main(sys.argv[1:])

使い方

基本的な使い方は、以下の通り。 <outputfolder>は前もって作る必要はない。

$ python3 nii2png.py -i <inputfile> -o <outputfolder>

コントラストを決定するための、Window Center(WC)とWindow Level(WL)を聞かれる。

Window Center : -500
Window Width : 1400

コマンドを実行すると、画像を回転させるか聞いてくる。

一度出力されるPNGの様子をみて変えれば良いと思う。

Would you like to rotate the orientation? (y/n) y
OK. By 90° 180° or 270°? 90

MedSegからNIFTI画像を取得する。 今回は、COVID-19 CT segmentation datasetを利用。

ダウンロードすると以下のようなフォルダ構造になる。 ダウンロードしたファイルのに合わせてnii2png.pyも同じ階層においておく。

$ tree
.
├── Test-Images-Clinical-Details.csv
├── nii2png.py
├── tr_im.nii.gz
├── tr_mask.nii.gz
└── val_im.nii.gz

.nii.gz画像をpngに変換するには、以下のコマンドを打ち込む。

$ python3 nii2png.py -i tr_im.nii.gz -o png/tr_im
Window Center : -500
Window Width : 1400
Input file is  tr_im.nii.gz
Output folder is  png/tr_im
3
Would you like to rotate the orientation? (y/n) y
OK. By 90° 180° or 270°? 270
Got it. Your images will be rotated by 270 degrees.
Created ouput directory: png/tr_im
Reading NIfTI file...
Saving image...
Saved.
Moving image...
Moved.
...

$ python3 nii2png.py -i tr_mask.nii.gz -o png/tr_mask
Window Center : -500
Window Width : 1400
Input file is  tr_mask.nii.gz
Output folder is  png/tr_mask
3
Would you like to rotate the orientation? (y/n) y
OK. By 90° 180° or 270°? 270
Got it. Your images will be rotated by 270 degrees.
Created ouput directory: png/tr_mask
Reading NIfTI file...
Saving image...
Saved.
Moving image...
Moved.
...

$ python3 nii2png.py -i val_im.nii.gz -o png/val_im
Window Center : -500
Window Width : 1400
Input file is  val_im.nii.gz
Output folder is  png/val_im
3
Would you like to rotate the orientation? (y/n) y
OK. By 90° 180° or 270°? 270
Got it. Your images will be rotated by 270 degrees.
Created ouput directory: png/val_im
Reading NIfTI file...
Saving image...
Saved.
Moving image...
Moved.
...

出力結果

pngフォルダのpngをみるとこのような感じ。

tr_im

f:id:AIProgrammer:20200420223152j:plain

tr_mask

f:id:AIProgrammer:20200420223212j:plain

val_im

f:id:AIProgrammer:20200503035544p:plain

頑張れ!喝!!の代わりにB!ブックマークを押していただけるとただただうれしいです(^^)! ↓