jpg(png)から dicom ファイルを作成しタグ編集

最近では、レントゲン画像だけじゃなくて内視鏡画像や超音波画像も dicom ファイルにしてサーバー上でコントロールすることが多いようです。

確かに、dicom ファイルのタグにいろいろと記録できて便利ですが、dicom 変換するとファイル容量がかなり大きくなります。

しかし dicom 化は流れなので、一度実験してみました。

jpg を dicom 変換

これは、python で簡単にできるかと思ったのですが、ちょっと調べた感じでは情報がありません。ひょっとして python ではできない?

そこで、windows 上で動く「Access to create」というソフトを virtualbox の windows 10 にインストールしてみました。

このソフトは、jpg を DCM に変換するものでとても簡単に動くのですが、検査日はきちんと反映されませんでした。

検査日は「2019年11月11日」にしたのですが。

dicom ファイルを dcm4chee にインポート

これは簡単にできます。

情報としては、患者ID(カルテ番号)・氏名・生年月日・検査日・検査番号・装置の 6 項目です。

dcm4chee で閲覧すると、

検査日が、「2022年4月21日」とインポートした日になっています。

でも、dicom 変換された内視鏡画像を簡単に dcm4chee に取り込むことができました。

何らかの方法で、jpg を dcm に一括変換できれば内視鏡画像の dicom サーバーが構築できます。

ちなみに、jpg ファイルの容量は 81.4 KB で dicom ファイルは 713.6 KB でした。

python で jpg から dicom ファイル

python を使えば、jpg から dicom ファイルを作成することができます。やはり python はすごい。
https://pycad.co/convert-jpg-or-png-images-into-dicom/

まずは必要なライブラリをインストールします。


pip install pydicom
pip install numpy
pip install pillow

上に提示した参考サイトによると、元になる dicom ファイルが必要らしい。

これは何でもいいらしく、以下のような私がデスクトップで使っている png ファイルを GIMP で読み込んで、test.dcm でエクスポートしたものを使いました。

そうして、以下のような python プログラムを実行します。


import pydicom
import numpy as np
from PIL import Image
ds = pydicom.dcmread('/home/user/dcm/test.dcm') # pre-existing dicom file
jpg_image = Image.open('/home/user/aaa/test.jpg') # the PNG or JPG file to be replace
if jpg_image.mode == 'RGB':
    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()
    ds.save_as('/home/user/result_rgb.dcm')

test.dcm を基礎にして、test.jpg ファイルから必要な情報を読み込んで、新たな dicom ファイルを作成してresult_rgb.dcm として出力する感じでしょうか。

result_rgb.dcm を weasis で見ると、

きちんと見ることができます。

もちろんタグには何も書き込んでいないので、StudyID = 1、氏名 = 「DOE WILBER」、ID = 「314159265」、sex = 「other」、0 year と書かれています。

この dicom ファイルのタグにこの画像の情報を python を使って書き込めばいいはずです。

dicom ファイルのタグ一覧

次のような python で dicom ファイルのタグを一覧できます。


import pydicom
ds = pydicom.dcmread('sample.dcm',force=True)
for tag in ds.dir():
    print(tag)

結果は、


AccessionNumber
AcquisitionDate
AcquisitionNumber
AcquisitionTime
AdditionalPatientHistory
BitsAllocated
BitsStored
Columns
ContentDate
ContentTime
ConvolutionKernel
DataCollectionDiameter
DistanceSourceToDetector
DistanceSourceToPatient
Exposure
ExposureTime
FilterType
FocalSpots
FrameOfReferenceUID
GantryDetectorTilt
GeneratorPower
HighBit
ImageOrientationPatient
ImagePositionPatient
ImageType
InstanceCreationDate
InstanceCreationTime
InstanceNumber
InstitutionName
KVP
Manufacturer
ManufacturerModelName
Modality
NameOfPhysiciansReadingStudy
OperatorsName
OtherPatientIDs
PatientAge
PatientBirthDate
PatientID
PatientName
PatientPosition
PatientSex
PerformedProcedureStepDescription
PerformedProcedureStepID
PerformedProcedureStepStartDate
PerformedProcedureStepStartTime
PerformingPhysicianName
PhotometricInterpretation
PixelData
PixelPaddingValue
PixelRepresentation
PixelSpacing
PositionReferenceIndicator
ProtocolName
ReconstructionDiameter
ReferencedImageSequence
ReferringPhysicianName
RescaleIntercept
RescaleSlope
RescaleType
RevolutionTime
RotationDirection
Rows
SOPClassUID
SOPInstanceUID
SamplesPerPixel
ScanOptions
SeriesDate
SeriesDescription
SeriesInstanceUID
SeriesNumber
SeriesTime
SingleCollimationWidth
SliceLocation
SliceThickness
SoftwareVersions
SpacingBetweenSlices
SpecificCharacterSet
SpiralPitchFactor
StationName
StudyDate
StudyDescription
StudyID
StudyInstanceUID
StudyTime
TableFeedPerRotation
TableHeight
TableSpeed
TotalCollimationWidth
WindowCenter
WindowWidth
XRayTubeCurrent

全部で 92 項目あります。

jpg を一括して dicom ファイルに

jpg を dicom ファイルに変換することは可能ですが、一括して変換する場合を考えます。

dicom ファイルというのはとてもおもしろいファイルで、 weasis で閲覧すると、ファイル名が異なっていてもタグ情報が同じだと全く同じ画像と判断するようです。

つまり、001.dcm から 050.dcm まで 50 枚の dicom ファイルがあって、そのタグ情報が全く一緒だと、それを weasis で閲覧しても 1 枚しか表示されません。

そこで、インスタンス名を jpg ファイル名で登録することにしました。


self.instance = os.path.splitext(file_name)[0]
.....
ds.SOPInstanceUID = self.instance

この場合、インスタンス名はファイル名なので「snap_160220_091713_62」のような感じで、これを dicom ファイルのタグに書き込みます。
基本的に、この文字列はおそらく一意です。

これによって、例えば 30 枚の jpg ファイルから 30 枚の dicom ファイルができて、weasis で閲覧すると 30 枚の画像を見ることができるのですが、順番がバラバラです。

日付や時刻などを変えてみたのですが、思い通りに閲覧することができません。

これはかなりの難問で、解決には相当の時間がかかるかもしれません。

表示の順序は InstanceNumber

dicom のタグ名からそれらしいものを片っ端から実験してみて「InstanceNumber」が表示順を規定していることがわかりました。

処理すべき画像ファイルディレクトリ内を調べて、jpg をリストアップしファイル名順にソートした後で、順番に InstanceNumber を書き込めばその通りに表示されます。

MMR_PKG のファイル構造は以下のようになっています。


MMR_PKG
└── 21400279
    └── 160220_090209_80
        ├── MOVIE
        ├── SCENE
        ├── SNAP
        │   ├── snap_160220_091152_81_2.jpg
        │   ├── snap_160220_091203_56_2.jpg
        │   ├── snap_160220_091314_44_2.jpg
        │   ├── snap_160220_091332_70_2.jpg
        │   ├── snap_160220_091339_80_2.jpg
        │   ├── snap_160220_091600_04_2.jpg
        │   ├── snap_160220_091606_71_2.jpg
        │   ├── snap_160220_091611_55_2.jpg
        │   ├── snap_160220_091617_89_2.jpg
        │   └── snap_160220_091842_32_2.jpg
        └── info_160220_090209_80.xml

この jpg ファイルを抽出して配列化し(python の場合はリスト)、その配列をソートすれば撮影された順番に並べ替えられます。なぜなら、ファイル名に西暦年の一部と時刻が入っているからです。

そして dicom ファイルの InstanceNumber にオートインクリメントで書き込みます。


ds.InstanceNumber = str( acn )
acn+= 1

acn の初期値を 0 としてループで回せば、「0, 1, 2, 3 ….」と InstanceNumber の書き込まれた dicom ファイルが作成されます。
weasis は、この InstanceNumber の順番に表示するのです。

文字化けに対する対策

dicom のタグに日本語を書き込む場合、そのまま書き込むと文字化けします。
これまた難解で解決には時間がかかると思いましたが、ネットに情報がありました。
https://dicom.nema.org/medical/Dicom/2017c/output/chtml/part05/sect_H.3.2.html

以下のように文字コードをセットすれば文字化けが治ります。


ds.SpecificCharacterSet = 'ISO 2022 IR 13\ISO 2022 IR 87'

他のネット情報では半角カタカナはダメというのもありましたが、私の実験では半角カタカナも文字化けしません。

プログラム

実際のプログラムは以下のような感じです。


import glob
import os,shutil 
import sqlite3
import pydicom
import numpy as np
from PIL import Image

class ToDcm:

    def __init__( self ):
        self.sql3 = '/home/user/MMR_PKG/mmr.sql3'
        self.bdir = '/home/user/MMR_PKG/'
        self.dcmdir = '/home/user/ES_DCM/'
        self.basedcm = '/home/user/ES_DCM/base/test.dcm'
        self.newdir = '/var/www/html/weasis/flask/dcmtemp'
        self.StudyID = ''
        self.PatientID = ''
        self.PatientBirthDate = ''
        self.PatientName = ''
        self.PatientSex = ''
        self.StudyDate = ''
        self.AcquisitionTime = ''
   
    def getMaxID(self):
        conn = sqlite3.connect( self.sql3 )
        cur = conn.cursor()
        cur.execute('select max(examID) from convert')
        return cur.fetchone()
        cur.close()
        conn.close()
        
    def getMinID(self):
        conn = sqlite3.connect( self.sql3 )
        cur = conn.cursor()
        cur.execute('select min(examID) from examine')
        return cur.fetchone()
        cur.close()
        conn.close()
    
    def convInfo( self, convertID ):
        conn = sqlite3.connect( self.sql3 )
        cur = conn.cursor()
        cur.execute("select * from examine where examID=:id", {"id": convertID})        
        return cur.fetchone()
        taginfo = cur.fetchone()
        cur.close()
        conn.close()
        
    def createDir( self, convInfo ):
        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.StudyDate = convInfo[9].replace('-','')
        ndir = self.dcmdir + self.StudyDate.replace('-', '/') + '/' + self.StudyID
        self.newdir = ndir
        os.makedirs(self.newdir, exist_ok=True)

    def imgArr( self, convInfo ):
        imgArr = []
        imgDir = self.bdir + convInfo[1] + '/' + convInfo[8]
        for dir_path, dir_names, file_names in os.walk(imgDir):
            for file_name in file_names:
                if "snap" in file_name:                   
                    org = dir_path + '/' + file_name
                    destination = self.newdir
                    imgArr.append( dir_path + '/' + file_name ) 
        return imgArr

    def createDcm(self, imgarr):
        ds = pydicom.dcmread(self.basedcm)
        ds.SpecificCharacterSet = 'ISO 2022 IR 13\ISO 2022 IR 87'
        acn = 0
        for eimg in imgarr:
            print(eimg)   
            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 書き込み --- データベースに記録されている検査データ """
            ds.StudyID = self.StudyID
            ds.PatientID = self.PatientID
            ds.PatientBirthDate = self.PatientBirthDate
            ds.PatientName = self.PatientName
            ds.PatientSex = self.PatientSex
            ds.AcquisitionDate = self.StudyDate
            """  tag 書き込み --- その他の情報"""   
            fn = os.path.splitext(os.path.basename(eimg))[0]
            tm = fn.split('_')[2]
            ds.AcquisitionTime = tm
            ds.InstanceNumber = str( acn )
            """ 保存する """
            dcmfile = '/var/www/html/weasis/flask/dcmtemp/' + fn + '.dcm'
            ds.save_as(dcmfile)
            acn+= 1

if __name__ == "__main__":
    dcm = ToDcm()
    maxID = dcm.getMaxID()
    if maxID[0] is None :
        convertID = dcm.getMinID()[0]
    else:
        convertID = maxID[0]
    convInfo = dcm.convInfo( convertID )
    dcm.createDir( convInfo ) 
    imgarr=dcm.imgArr( convInfo )
    imgarr.sort()
    dcm.createDcm(imgarr)

動画にしてみました。

「ヘノヘノ モヘノ」が文字化けしておらず、カルテ番号・生年月日・性別・検査番号・撮影時間がきちんと表示されています。

これで、jpg から dicom ファイルを作成してタグに情報を書き込むことはできました。

次は、これらの dicom ファイルを dcm4chee にインポートします。