NAS 上の jpg を dicom 変換して dcm4chee へ

現在、内視鏡画像は jpg ファイルをサーバー上で展開して閲覧していますが、それを dicom ファイル化して dcm4chee にインポートして閲覧します。

とても面倒なプロセスなので、ほとんどすべてを全自動で実行したいと思います。

そのプロセスを記録しておきます。

NASから mmr.sql3 を sftp ダウンロードする

まずは、内視鏡に関するデータが記録されている mmr.sql3 を NAS から ubuntu にダウンロードします。
これは、mmr.mdb を linux で開いて sqlite3 に書き込んだものです。

最初は scp だったのですが、どうも sftp の方が簡単そうなので sftp にします。


class JPG_DICOM():

    def __init__( self ):    
        self.config = {
            "host" : "192.168.0.11",
            "port" : 22,
            "username" : "user",
            "password"   : "moheno"
        }

    def sftp_connection(self):
        self.client = paramiko.SSHClient()
        self.client.set_missing_host_key_policy(paramiko.AutoAddPolicy)
        self.client.connect(self.config['host'], 
                       port = self.config['port'],
                       username = self.config['username'],
                       password = self.config['password']) 
        sftp_con = self.client.open_sftp() 
        return sftp_con
       
    def mmrDL( self ):
        sftp_con = self.sftp_connection() 
        sftp_con.get('MMR_PKG/mmr.sql3', '/var/www/html/MMR_PKG/mmr.sql3')
        self.client.close() 

if __name__ == "__main__": 
    dcm = JPG_DICOM()
    dcm.mmrDL()

このプログラムが動くためには NAS 側で sftp を有効にする必要があります。
「コントロールパネル」→「ファイルサービス」→「ftp」→「sftpを有効にする」にチェックを入れます。

そうして ubuntu のコマンドラインから以下のように打ち込みログインできることを確認。


sftp user@192.168.0.11

処理すべき検査 ID はテキストファイルから読み込む

処理すべき検査IDはデータベースを使って決定することばかり考えていましたが、テキストファイルを利用した方が簡単だと思います。

以下のような内容のテキストファイルを作成し、


studyID=23548

python で読み込むようにしました。


    def read_studyID(self):       
        with open( '/var/www/html/MMR_PKG/log/studyID.txt', mode='r') as f:
            firstline = f.readline().rstrip()
            self.cur_ID = firstline.replace( 'studyID=', '' )

検査IDから必要な変数をセットする

検査IDが決まったら、mmr.sql3 から必要なデータを抽出して変数にセットします。


    def setVal( self ):
        conn = sqlite3.connect( self.sql3 )
        cur = conn.cursor()
        cur.execute("select * from examine where examID = :id", {"id": self.cur_ID} )
        convInfo = cur.fetchone() 
        """ 各パラメータをセットする """
        self.studyDate = convInfo[9].replace('-','')
        self.studyTime = convInfo[8].split('_')[1]
        self.StudyID = convInfo[0]
        self.PatientID = convInfo[1]
        self.PatientBirthDate = convInfo[2].replace('/','')
        self.PatientName = convInfo[3]
        self.PatientSex = convInfo[4]
        self.AcquisitionDate = convInfo[9].replace('-','')                  
        self.mmrRemoteDir = 'MMR_PKG/' + convInfo[1] + '/' + convInfo[8] + '/SNAP'
        cur.close()
        conn.close() 

リモートファイルが存在するかどうか

リモートファイルが存在しない場合は sftp で持ってこれないので、存在するかどうかを確認する必要があります。

セットされた変数の中で「self.mmrRemoteDir」に jpg ファイルその他のデータが保存されています。


def exist_remotedir(self):
    sftp_con = self.sftp_connection()
    try: 
        sftp_con.chdir( self.mmrRemoteDir )
    except Exception as e:
        return False
    self.client.close()      
def nextIDset(self):
    next = int( self.cur_ID ) + 1
    self.cur_ID = str( next )
if __name__ == "__main__":
    dcm = JPG_DICOM()
    while dcm.exist_remotedir() is False:
        dcm.nextIDset()
        dcm.setVal()

「self.mmrRemoteDir」で ftp 接続して保存ディレクトリがなければ False を返すので、検査 ID + 1 にして検索を続行します。

jpg ファイルをローカルに sftp ダウンロード

「self.mmrRemoteDir」の中には不必要なファイルもあるので、必要なものだけをダウンロードします。


    def jpgDL(self):        
        sftp_con = self.sftp_connection()  
        sftp_con.chdir( self.mmrRemoteDir )  
        files = sftp_con.listdir()
        for remote_file in files:
            if 'snap' in remote_file:
                localfile = '/var/www/html/MMR_PKG/jpgtmp/' + remote_file
                sftp_con.get(remote_file, localfile)
        self.client.close()

「if ‘snap’ in remote_file:」で「snap」という文字列を含むファイルだけがダウンロードされます。

ダウンロードされた jpg ファイルをリストアップ

jpg ファイルは常に ubuntu の「/var/www/html/MMR_PKG/jpgtmp」にダウンロードされてくるので、その中を調べてファイル名をリストアップします。


    def set_imgArr( self ):
        imgDir = '/var/www/html/MMR_PKG/jpgtmp'
        for dir_path, dir_names, file_names in os.walk(imgDir):
            for file_name in file_names:
                self.imgArr.append( dir_path + '/' + file_name )
        self.imgArr.sort()

ファイル名からディレクトリを作成

新しいファイル構造は「/var/www/html/ES_DCM」下に作成します。

「/var/www/html/ES_DCM/2018/03/25/25416」のような感じです。


    def createDir( self ):
        ndir = '/var/www/html/ES_DCM/' + self.studyDate[0:4] + '/' + self.studyDate[4:6] + '/' + self.studyDate[6:] + '/' + self.StudyID + '/'
        self.copyto = ndir
        os.makedirs( ndir, exist_ok=True) 

jpg ファイルから dicom ファイルを作成する

リストアップされた画像ファイル情報を元に、jpg から dicom ファイルを作成します。


    def createDcm(self):
        ds = pydicom.dcmread(self.basedcm)
        ds.SpecificCharacterSet = 'ISO 2022 IR 13\ISO 2022 IR 87'
        acn = 0
        for eimg in self.imgArr:
            jpg_image = Image.open(eimg)
            """ 基礎画像情報 --- 画像の大きさ・ピクセルなど"""
            np_image = np.array(jpg_image.getdata(), dtype=np.uint8)[:,:3]
            ds.Rows = jpg_image.height
            ds.Columns = jpg_image.width
            ds.PhotometricInterpretation = "RGB"
            ds.SamplesPerPixel = 3
            ds.BitsStored = 8
            ds.BitsAllocated = 8
            ds.HighBit = 7
            ds.PixelRepresentation = 0
            ds.PixelData = np_image.tobytes()
            """  tag 書き込み --- データベースに記録されている検査データ """
            imagename = os.path.basename(eimg)
            imagename_noext = os.path.splitext( imagename )[0]
            self.StudyTime = imagename_noext.split('_')[1] + imagename_noext.split('_')[2] + imagename_noext.split('_')[3]
            self.AcquisitionTime = imagename_noext.split('_')[2]
            ds.StudyID = self.StudyID
            ds.PatientID = self.PatientID
            ds.PatientBirthDate = self.PatientBirthDate
            ds.PatientName = self.PatientName
            ds.PatientSex = self.PatientSex
            ds.StudyDate = self.AcquisitionDate
            ds.SeriesDate = self.AcquisitionDate
            ds.AcquisitionDate = self.AcquisitionDate
            ds.AcquisitionTime = self.AcquisitionTime
            ds.Modality = 'ES'
            """  tag 書き込み --- その他の情報"""
            ds.SOPClassUID = self.SOPClassUID
            ds.StudyInstanceUID = self.bn + '1.' + self.StudyID            
            ds.SeriesInstanceUID = self.bn + '2.' + self.StudyID + '.1'
            ds.SOPInstanceUID= self.bn + '5.' + self.StudyID + '.' + self.StudyTime + '.' + str(acn)
            ds.InstanceNumber = str( acn )
            """ 保存する """
            dcmfile = self.copyto + imagename_noext + '.dcm'
            ds.save_as(dcmfile)
            acn+= 1

dcm4chee にインポートする

シェルスクリプトを作成して python から起動します。
dicom ファイルは1ヶ所に集めてからインポートする方が遥かに高速です。

import.sh

#/bin/sh

origin="/var/www/html/ES_DCM"
destination="/var/www/html/dcmtotal/"
# maxdepth 6 、つまり6層深くまで検索して、dcm ファイルを探して「dcmtotal」に移動します
for file in `\find $origin -maxdepth 6 -name '*.dcm'`; do
    base=$(basename $file)
    mv $file $destination.$basename
done
# まとめてインポート
cd /var/www/html/dcm4che-2.0.29/bin
./dcmsnd DCM4CHEE@localhost:11112 $destination*.dcm
# コピー先とコピー元のファイルとディレクトリをすべて削除
rm $destination*.dcm
rm -rf $origin/*

    def shell(self):
        os.makedirs( '/var/www/html/ES_DCM/dcmtotal', exist_ok=True)     
        result = subprocess.run("/var/www/html/import.sh", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
        self.shellrtncode = result.returncode

シェルスクリプトが動くためには、dcm4chee が起動している必要があります。

シェルスクリプトが正常に終了すれば「result.returncode」には「0」が返されます。

log を記録する

これまで log はほとんど記録してきませんでした。
しかし、log を記録した方がいろいろと便利です。


    def rec_log(self, msg):
        dt_now = datetime.datetime.now()
        elapsed_time = time.time() - start
        lines = [
            dt_now.strftime('%Y年%m月%d日 %H:%M:%S') + '\n',
            'examID : ' + self.cur_ID + '\n',
            '処理画像数 : ' + str( len(self.imgArr) ) + '\n',
            msg + '\n',
            '実行時間(秒) : ' + str( elapsed_time ) + '\n',
            '-----------------------------------------------------------------\n'
        ]
        with open('/var/www/html/MMR_PKG/log/execution_log.txt', 'a', encoding='utf-8', newline='\n') as f:
            f.writelines(lines)

if __name__ == "__main__":   
    if dcm.shellrtncode == 0 :
        dcm.rec_log('インポート処理は成功しました')
    else:
        dcm.rec_log('インポート処理は失敗しました!')