マイブログ リスト

医療言語処理講座

ラベル RDSR の投稿を表示しています。 すべての投稿を表示
ラベル RDSR の投稿を表示しています。 すべての投稿を表示

2022年5月2日月曜日

Pyゼミ1.0 番外編 DICOM RDSRを読む

 Pydicomを使ってRDSRを読んで必要な情報を取り出してみる

RDSRはRadiation Dose Structured Reportの略で、CTや血管造影などX線を用いた検査でどのくらX線量(Radiation dose)を照射したのか、構造化された書式(Structured report)にて出力したものです。

RDSRはDICOM規格で規定されています。詳細は規格書を読みましょう。

今回は時間がなく規格書を理解せず、帰納的にSRの解析に取り組みました。
解析結果が正しいかどうか、医療現場で使用しているソフトウェアの結果で検証しています。
検証した範囲では問題なさそうですが、利用は自己責任でお願いします。

プログラムはこちらから
RDSRファイルはこちらから

(この記事は2022年4月24日に日本放射線技術学会 北海道支部 第78回春季学術大会 医療情報セミナーで発表したものです)


PydicomはDICOM画像だけでなくSRも読むことができます。RDSRファイルを読み込んで表示するだけであれば数行で書くことができます。


import pydicom
fnm = "./CT_rdsr.SR"
ds = pydicom.dcmread(fnm)   
print(ds)


このたった4行でRDSRのDICOMファイル"rdsr.sr"の内容をpydicomを用いて変数dsに読み込み、それをプリントすることでRDSRの内容を確認することができる。


DICOM RDSRの内容を見てみる


DICOMのファイルはデータセット呼ばれるデータエレメントの集合でできています。
データエレメントは、タグと呼ばれる16進数のグループ番号とエレメント番号から始まり、キーワード、値表現(VR)、値の長さ(VL)と値(VF, Value field)で構成されています。

print(ds)で出力された内容を見てみしょう。
表示された各行はデータエレメントを表していて次のように表示されています。

                (グループ番号, エレメント番号) キーワード VR:値

(0002,0002)と(0008,0016)のSOP Class UIDが”X-Ray Radiation Dose SR Storage"と記述されています。
また、(0008,0060)のModalityは”SR”とあり、このファイルがStructured reportであることがわかります。

Dataset.file_meta -------------------------------
(0002, 0000) File Meta Information Group Length  UL: 196
(0002, 0001) File Meta Information Version       OB: b'\x00\x01'
(0002, 0002) Media Storage SOP Class UID         UI: X-Ray Radiation Dose SR Storage
(0002, 0003) Media Storage SOP Instance UID      UI: 1.3.12.2.1107.5.1.4.65939.30000022022322414137700000249
(0002, 0010) Transfer Syntax UID                 UI: Explicit VR Little Endian
(0002, 0012) Implementation Class UID            UI: 1.3.12.2.1107.5.4.7
(0002, 0013) Implementation Version Name         SH: 'SIEMENSXLEOVB21C'
-------------------------------------------------
(0008, 0005) Specific Character Set              CS: 'ISO_IR 100'
(0008, 0016) SOP Class UID                       UI: X-Ray Radiation Dose SR Storage
(0008, 0018) SOP Instance UID                    UI: 1.3.12.2.1107.5.1.4.65939.30000022022322414137700000249
(0008, 0020) Study Date                          DA: '20220224'
(0008, 0021) Series Date                         DA: '20220224'
(0008, 0023) Content Date                        DA: '20220224'
(0008, 0030) Study Time                          TM: '074907.240000'
(0008, 0031) Series Time                         TM: '075458.275000'
(0008, 0033) Content Time                        TM: '075458.315000'
(0008, 0050) Accession Number                    SH: '6010016637053'
(0008, 0060) Modality                            CS: 'SR'



上記以外の情報は通常のDICOM画像とそう変わりありません。

しかし、もう少し下のほうを見てみると、(0040,0275)のRequest Attributes Sequence以下で字下げ(インデント)が見られ、
さらに(0040,0008) Scheduled Protocol Code Sequence以下でインデントが発生しています。
これはVRが"SQ"に設定されているためです。
”SQ”は「ある意味のある集合のシーケンス(連続した項目)を含み、このシーケンスは入れ子状態をとることができる」ものです。
詳しくはDICOM Part5(http://www.jira-net.or.jp/dicom/dicom_data_02_01.html)を参照してください。

(0029, 1008) [syngo Report Type]                 CS: 'CTDOSEREPORT'
(0029, 1009) [syngo Report]                      LO: '1.0'
(0029, 1015) [SR Variant]                        US: 6
(0029, 1017) [SC SOP Instance UID]               UI: 1.3.12.2.1107.5.1.4.65939.30000022022322414137700000247
(0040, 0275)  Request Attributes Sequence  1 item(s) ---- 
   (0040, 0008)  Scheduled Protocol Code Sequence  1 item(s) ---- 
      (0008, 0100) Code Value                          SH: '3003100550020000'
      (0008, 0102) Coding Scheme Designator            SH: '99'
      (0008, 0103) Coding Scheme Version               SH: '6010016637053'
      (0008, 0104) Code Meaning                        LO: 'AG?F?~???ETAE?F?S?g'
      ---------
   (0040, 0009) Scheduled Procedure Step ID         SH: '6010016637053'
   (0040, 1001) Requested Procedure ID              SH: '6010016637053'
   ---------


この"SQ"をさらに追いかけてみます。
すると、(0040, a730) Content Sequence が20項目あると表示されています。
つまり、これ以下に何らかの意味ある項目が20項目あることを示しています。

(0040, a050) Continuity Of Content               CS: 'SEPARATE'
(0040, a372)  Performed Procedure Code Sequence  0 item(s) ---- 
(0040, a491) Completion Flag                     CS: 'COMPLETE'
(0040, a493) Verification Flag                   CS: 'UNVERIFIED'
(0040, a504)  Content Template Sequence  1 item(s) ---- 
   (0008, 0105) Mapping Resource                    CS: 'DCMR'
   (0040, db00) Template Identifier                 CS: '10011'
   ---------
(0040, a730)  Content Sequence  20 item(s) ---- 
   (0040, a010) Relationship Type                   CS: 'HAS CONCEPT MOD'
   (0040, a040) Value Type                          CS: 'CODE'
   (0040, a043)  Concept Name Code Sequence  1 item(s) ---- 
      (0008, 0100) Code Value                          SH: '121058'
      (0008, 0102) Coding Scheme Designator            SH: 'DCM'
      (0008, 0104) Code Meaning                        LO: 'Procedure reported'
      ---------
   (0040, a168)  Concept Code Sequence  1 item(s) ---- 
      (0008, 0100) Code Value                          SH: 'P5-08000'
      (0008, 0102) Coding Scheme Designator            SH: 'SRT'
      (0008, 0104) Code Meaning                        LO: 'Computed Tomography X-Ray'
      ---------
   (0040, a730)  Content Sequence  1 item(s) ---- 
      (0040, a010) Relationship Type                   CS: 'HAS CONCEPT MOD'
      (0040, a040) Value Type                          CS: 'CODE'
      (0040, a043)  Concept Name Code Sequence  1 item(s) ---- 
         (0008, 0100) Code Value                          SH: 'G-C0E8'
         (0008, 0102) Coding Scheme Designator            SH: 'SRT'
         (0008, 0104) Code Meaning                        LO: 'Has Intent'
         ---------

さらに、下を見てみると次のように表示されています。
(0008, 0104)  Code Meaningには'CT Accumulated Dose Data'とあり、
その下に
(0008, 0104)  Code Meaningには'Total Number of Irradiation Events'とあり、
またその下に
(0008, 0104)  Code Meaningには'events'とあり、
次に
(0040, a30a) Numeric Valueには"7.0"とあります。

さらに、その下には
(0008, 0104) Code Meaning には'CT Dose Length Product Total'とあり、
その下に
(0008, 0104) Code Meaningには'mGy.cm'と単位があり、
次に
(0040, a30a) Numeric Valueには"377.94"と数値があります。

   (0040, a010) Relationship Type                   CS: 'CONTAINS'
   (0040, a040) Value Type                          CS: 'CONTAINER'
   (0040, a043)  Concept Name Code Sequence  1 item(s) ---- 
      (0008, 0100) Code Value                          SH: '113811'
      (0008, 0102) Coding Scheme Designator            SH: 'DCM'
      (0008, 0104) Code Meaning                        LO: 'CT Accumulated Dose Data'
      ---------
   (0040, a050) Continuity Of Content               CS: 'SEPARATE'
   (0040, a730)  Content Sequence  2 item(s) ---- 
      (0040, a010) Relationship Type                   CS: 'CONTAINS'
      (0040, a040) Value Type                          CS: 'NUM'
      (0040, a043)  Concept Name Code Sequence  1 item(s) ---- 
         (0008, 0100) Code Value                          SH: '113812'
         (0008, 0102) Coding Scheme Designator            SH: 'DCM'
         (0008, 0104) Code Meaning                        LO: 'Total Number of Irradiation Events'
         ---------
      (0040, a300)  Measured Value Sequence  1 item(s) ---- 
         (0040, 08ea)  Measurement Units Code Sequence  1 item(s) ---- 
            (0008, 0100) Code Value                          SH: '{events}'
            (0008, 0102) Coding Scheme Designator            SH: 'UCUM'
            (0008, 0103) Coding Scheme Version               SH: '1.4'
            (0008, 0104) Code Meaning                        LO: 'events'
            ---------
         (0040, a30a) Numeric Value                       DS: "7.0"
         ---------
      ---------
      (0040, a010) Relationship Type                   CS: 'CONTAINS'
      (0040, a040) Value Type                          CS: 'NUM'
      (0040, a043)  Concept Name Code Sequence  1 item(s) ---- 
         (0008, 0100) Code Value                          SH: '113813'
         (0008, 0102) Coding Scheme Designator            SH: 'DCM'
         (0008, 0104) Code Meaning                        LO: 'CT Dose Length Product Total'
         ---------
      (0040, a300)  Measured Value Sequence  1 item(s) ---- 
         (0040, 08ea)  Measurement Units Code Sequence  1 item(s) ---- 
            (0008, 0100) Code Value                          SH: 'mGy.cm'
            (0008, 0102) Coding Scheme Designator            SH: 'UCUM'
            (0008, 0103) Coding Scheme Version               SH: '1.4'
            (0008, 0104) Code Meaning                        LO: 'mGy.cm'
            ---------
         (0040, a30a) Numeric Value                       DS: "377.94"
         ---------
      ---------
   ---------

このように、Code Meaningの値やNumeric Valueの値を抽出することで必要な情報を得ることができるのではないかと考えました。

少し解析のフローをまとめると

  1. データエレメントのKeywordが“Content Sequence”が見つかったら解析を始める
  2. データエレメントのVRが“SQ“のとき、KeywordとValueを取得する
  3. データエレメントのkeywordが“Code Meaning”のとき、Valueの値を出力する
  4. 取得したValue(データエレメント)について2.の解析を繰り返す。
つまり、VRに"SQ"が見つかったらその下の階の項目を取り出し、その中のVRに"SQ"が見つかったら、さらにその下の。。。と繰り返すことで各階のCode Meaningの情報を取り出すこと考えました。

しかし、階層が3階層より下が上手く取り出すことができませんでした。
これはPydicomの仕様(バグ?)かわかりません。
そこで、タグ、キーワード、VRと値を取り出すメソッド(getInfoDCM)を作成して対応しました。



RDSRを読むPythonプログラム


RDSRを読み込むための準備を最初に書きます。
インポートするのPydicomだけです。
RDSRのファイル名を変数fnmに代入します。
rstxt, dstxtとsmryはSRの処理結果を格納する変数です。
nLvは"SQ"による階層の深さ、インデントの深さを代入する変数です。
prevLは前の"SQ"の行(データエレメント)の深さを格納する変数です。
inkeywordsには、抽出すべき値のキーワードを格納しています。


import pydicom

fnm = "./CT_rdsr.SR"    # RDSR DICOM file name
rstxt = ""      # For result text using getInfoDCM method
dstxt = ""      # For DICOM dataset
smry  = ""      # For target text
nLv   = 0       # Depth Level
prevL = 0       # For depth level of previous SQ line

# For extraction keyword
inkeywords = "CodeMeaning,TextValue,NumericValue,UID,DateTime"  


データエレメントの深さ求めるメソッドとデータエレメントの各要素求めるメソッドについて


1.データエレメントの深さを求めるgetDepthメソッド

 インデントは各行(データエレメント)の前の空白の数を数えて返します。
 引数のdataはひとつのデータエレメントです。

def getDepth(data):
    cnt = 0         
    for i in range(len(data)):
        if data[i] == ' ':
            cnt += 1
        else:
            break
    return cnt


2.データエレメントの各要素求めるgetInfoDCMメソッド
 このメソッドはデータエレメント中のタグ、キーワード、VRと値を抽出して返します。
 引数のdataには (grp,elm) Key word VR: 'value'  のようにひとつのデータエレメントが格納されています。

Pydicomでは"SQ"のシーケンスの終わりに'---------'が表示されます。
メソッドの最初にこの処理を記述します。
与えれたdataの内容が'---------'であった場合、
        tagとVRには何も値を入れず、
        キーワードには、”NoKeyword”と
        値には、'---------'をそのまま入れて返します。


def getInfoDCM(data):
    # ex) data is "(grp,elm) Key word VR: 'value' "
    data = data.strip(" ")  # Remove blanks on both sides of string
    if data == '---------':
        tag = ["",""]
        kwd = "NoKeyword"
        vr  = ""
        val = '---------'
        return tag, kwd, vr, val
        
    # Tag
    tag = data.split(")")[0]    # "(grp, elm"
    tag = tag.replace("(", "").replace(" ","")  # "grp, elm"
    tag = tag.split(",")    # [group, elemnt]
    
    # vr
    vr = ""
    if "item(s)" in data:   # ex) (0040, a043)  Concept Name Code Sequence  1 item(s) ----
        vr = "SQ"
    else:
        vr = data.split(":")[0][-2:]    # ex) "(grp,elm) Key word VR"->"VR"
        
    # value
    val = ""
    if ":" in data:
        val = data.split(":")[-1]       # [・・・ VR:value] -> value
    elif "item(s)" in data:
        val = ' '.join(data.split(" ")[-3:-1])  # -> n item(s) 
    val = val.replace("\'","").replace("\"","") # remove " and '
    
    # keyword
    kwd  = ""
    stat = data.find(")")+1 # Start position in keyword
    end  = data.find(vr)    # End position in keyword
    kwd  = data[stat:end]   # Extract keyword (remove (g,e))
    if "item(s)" in kwd:    # Case of VR="SQ"
        kwd = kwd.split("item(s)")[0]   # [kwd n,----] -> (g,e) kwd n 
        kwd = kwd.strip(" ").split(" ")[0:-1] # Extract the string before "n items"
        kwd = ''.join(kwd).strip(" ")  # Joint keyword and remove blanks      
    else:
        kwd = kwd.replace(" ","")   # Remove blank in keyword
    
    return tag, kwd, vr, val


はじめにTagの処理を見ます。
        (grp,elm) Key word VR: 'value'
グループ番号とエレメント番号は()の中にありますのでこれを頼りに2つの番号を抽出し、リストtagに代入します。

次に、VRです。
        (grp,elm) Key word VR: 'value'
        (grp,elm) Key word 99 item(s) ----
変数dataの中に"item(s)"があればVRは”SQ”です。
"SQ"以外の場合(else)、コロン(:)で変数dataの値を2つ分割し、前半[0]の最後の2文字[-2:]を抽出し変数vrに代入します。

次に、値です。
        (grp,elm) Key word VR: 'value'
        (grp,elm) Key word 99 item(s) ----
変数data中にコロン(:)があれば2つに分割し後半[1]を値として変数valに代入します。
コロンがなく、”item(s)”があれば、変数dataを空白で分割し”99 items(s)"のみ抽出しvalに代入します。
最後にvalの値の前後のシングル/ダブルクォーテーション( ’ / " )を削除します。

最後にキーワードです。
        (grp,elm) Key word VR: 'value'
        (grp,elm) Key word 99 item(s) ----
データエレメントの”)”の後ろの文字(stat番目)からVRの前(end)の文字列を抽出data[stat:end]して変数kwdに代入します。
しかし、この処理では、VRが"SQ"のときキーワードに"n item(s)"が存在します。
これを除く処理を行いました。
また、抽出したキーワード中の空白は除くようにしました。つまり”Study Date”は”StudyDate”になります。
これはPydicomのキーワードの表現に揃えるためです。



メインのプログラムを読む


はじめにpydicomを使ってRDSRのファイルを読み込みデータセットを変数dsに代入します。
SOPClassUIDなど必要な基本情報を表示します。
smryは必要な抽出項目の要約を代入する変数です。

変数dsは'pydicom.dataset.FileDataset'の型を取りますが、これを文字列型に変換して変数txdsに代入します。
txdsを改行(\n)で分割して行単位に分割してリストlndsに代入します。
読み込んだ情報などを表示していったん処理が停止(pause)します。
Enterキーで処理が進みます。

for文でlndsから1行(1データエレメント)ずつ取り出して処理します。

valueに'ContentSequence'が見つかったら、smryflagをTrueにして必要情報を抽出するように処理します。

変数rstxtには行番号、深さのレベル数とともに抽出した結果を追加代入(+=)しています。
一方、dstxtにはデータセットに行番号とデータエレメントを追加代入しています。
この2つの出力ファイルを比較することにより、間違いなく抽出できているか確認できます。
 
変数smryには注目するキーワードのデータエレメントの値を追加代入しています。

# ===== MAIN =====        
ds = pydicom.dcmread(fnm)   # Read DICOM SR

#print("ds=\n",ds)
print("SOP Class UID:", ds.SOPClassUID)
print("Modality     :", ds.Modality)
print("Manufacturer :", ds.Manufacturer)
print("Study Date   :", ds.StudyDate)
print("Pt Name      :", ds.PatientName)
print("Pt ID        :", ds.PatientID)
print("Modality     :", ds.Modality)
print("Content Sequence:", len(ds.ContentSequence))

input("pause. Type ENTER key")

smry  = "Study Date," + str(ds.StudyDate) + "\n"
smry += "Study Time," + str(ds.StudyTime) + "\n"

txds = str(ds)      # Convert pydicom.dataset to String

print(txds)
print("\nData type :",type(ds),"\n\t\t-->",type(txds))
lnds = txds.split("\n")     # Split text to lines
print("len(ds)=",len(lnds)) 
input("pause. Type ENTER key")

for i, ln in enumerate(lnds):
    tag, keyword, vr, value = getInfoDCM(ln)
    nLv = getDepth(ln)//3

    if value in 'ContentSequence':  # Start anlysis from this line
        smryflag = True

    sIndent = " "*(nLv*3)           # Indent
    rstxt += str(i)+",L:"+str(nLv)+sIndent+"("+str(tag)+"),"+keyword+","+vr+","+value+"\n"    
    dstxt += str(i)+","+ln + "\n"
    
    print("value = >",value,"<")
        
    # CR flag
    if prevL > nLv or nLv <= 1:
        crflag = True
    else:
        crflag = False
    
    if smryflag and keyword in inkeywords:
        #smry += "["+str(i)+"L"+str(+nLv)+"]"+str(value)+", "
        smry += str(value)+", "

    if vr == "SQ" and crflag:   # VR is SQ, crflag is True
        smry += "\n"            # Then add CR in smry

    if vr == "SQ":              # VR is SQ,
        prevL = nLv             # Then assign now level to prevL

smry += rmCRline(smry)

with open("rdsrResult.txt","w") as f:
    f.write(rstxt)
with open("rdsrDataset.txt","w") as f:
    f.write(dstxt)
with open("rdsrSummary.csv","w") as f:
    f.write(smry)



さて、ここで問題です。変数smryに値を追加していくだけですと、抽出したい情報がたった1行で出力されてしまいます。
そこで、意味ある情報の塊の境界で改行を入れる必要があります。

改行するかどうかを変数crflagを用いて制御しています。
    if prevL > nLv or nLv <= 1:
        crflag = True
    else:
        crflag = False

これは、1つ前の"SQ"のデータエレメントの階層レベルprevLが現在の階層レベルnLvより大きい(つまり現在の階層は前の階層より上がるとき)、または現在の階層が1以下のとき(つまり階層が0または1の深さにある)ときはcrflagをTrueにしています。
それ以外はcrflagをFalseに設定します。

そして、VRの値とcrflagの状態を見て変数smryに改行を入れています。
    if vr == "SQ" and crflag:   # VR is SQ, crflag is True
        smry += "\n"            # Then add CR in smry


このままでは改行が至る所に入ってしまうため、無駄な改行を削除するためrmCRlineメソッドを作成しました。
    smry += rmCRline(smry)



実行してみる


プログラム実行してみましょう
初めに基本的な情報を表示して停止します。


SOP Class UID: 1.2.840.10008.5.1.4.1.1.88.67
Modality     : SR
Manufacturer : SIEMENS
Study Date   : 20220224
Pt Name      : NO^Name
Pt ID        : 12345678
Modality     : SR
Content Sequence: 20

pause. Type ENTER key


ここで、「ENTER」キーを打つと、処理が次に進みます。

                ・・・・・・・
      (0008, 0104) Code Meaning                        LO: 'Source of Dose Information'
      ---------
   (0040, a168)  Concept Code Sequence  1 item(s) ---- 
      (0008, 0100) Code Value                          SH: '113856'
      (0008, 0102) Coding Scheme Designator            SH: 'DCM'
      (0008, 0104) Code Meaning                        LO: 'Automated Data Collection'
      ---------
   ---------

Data type : <class 'pydicom.dataset.FileDataset'
                --> <class 'str'>
len(ds)= 3159

pause. Type ENTER key


読み込んだデータセットのデータの型が'pydicom.dataset.FileDataset'から'str'に変換されました。
「ENTER」キーを打つと、処理が次に進みます。

・・・・・・・
value = >  CONTAINS <
value = >  CODE <
value = > 1 item(s) <
value = >  113854 <
value = >  DCM <
value = >  Source of Dose Information <
value = > --------- <
value = > 1 item(s) <
value = >  113856 <
value = >  DCM <
value = >  Automated Data Collection <
value = > --------- <
value = > --------- <


最後に3つのファイルが出力されます。
rdsrDataset.txt        データエレメントに行番後を付加して出力します。
rdsrResult.txt          getInfoDCMメソッドで抽出した結果を行番号と一緒に出力します。
rdsrSummary.txt     必要な情報を行単位で出力します。


summayの結果を見てみる


rdsrSummary.csvファイルの出力結果は次のようになります。

最初に基本的な情報が表示されます。


以下ではCTのX線量に関する情報が単位とともに表示されています。


























おわりに


今回、2022年4月24日に日本放射線技術学会 北海道支部 第78回春季学術大会 医療情報セミナーで発表したものを掲載しました。

DICOM SRの読み込みはPydicomのサンプルrtplan.srで試していましたが、今回RDSRは初めての経験でした。
読み込むまでは良かったのですが、階層が6階層まで下りて行く必要があり、Pydicomの標準機能ではうまく情報を取り込むことができませんでした。
CTのほかにAG(血管造影)のRDSRも解析しましたが、同じ結果でした。

本来はDICOM規格をしっかり理解したうえでプログラミングすべきでしたが、短期間に結果を出す必要があり、力ずくの対応でした。
したがってまだ気づかぬバグなどが潜んでいるかもしれません。プログラムの使用には注意をお願いします。


2019年9月13日金曜日

医用画像使ったPythonゼミナールを始めまーす


修正履歴:各Pyゼミのリンクを作成しました。また,一部採番が重複していたので修正しました。2019.09.13

年も明けて2019年

ブログを立ち上げてかなり放置していたので,そろそろなんかやってみようかとPCに向かっているところです。
放置している間にBloggerもだいぶ変わった。
医療情報に関連するテーマで少し持続するテーマ,”Pythonで医用画像AI”を考えてみました。

人工知能の流行もやや一段落

人工知能の流行も一時よりはだいぶ熱が冷めた感じですが,医療においてはこれからでしょう。
研究室では,これまでJavaを使ってAIを作ってきましたが,あるきっかけらPythonに移行することにしました。
それれは医療画像規格であるDICOM(Digital Imaging and COmmunication in Medicine)が非常に簡単にPythonで扱えるからでした。

本学,北海道情報大学医療情報学部ではJavaプログラミングが1年生(診療情報管理専攻)で必修科目です。
プログラミングの実習中の学生との雑談でpython勉強中です,という学生もいたりして,彼らにも画像処理結果が見れるので良いテーマでしょう。

PythonでAIやってみる?

本研究室の3,4年生のゼミナールでこれまで勉強してきた内容や作成物を整理して,1年生でもわかるようにブログの中で公開してみようかと考えたわけです。

もちろんここらでちょっと自分もAIに触れてみようと思っている人や学生などを対象に連載を組んでみたいと思っています。読んでもらいたいターゲットとしては

  • python勉強したいけど興味ある分野が見つからない人々
  • とりあえず人工知能に触れてみた大学生,高校生,ひょっとしたら小中高生
  • 修士のテーマにしたいけどどうする,という大学院生
  • 画像がいっぱいあって解析してみたい診療放射線技師や医師など医療スタッフ
  • 会社でAI推進の指示があったが何から手を付けて良いかわからない社会人
  • ネットで色々調べたけど具体的にAIとDICOMをどうくっつけるか迷える人々

環境構築編,DICOM編,GUI編とAI編をはじめます

具体的には以下の内容で進めていきたいと考えています。どこから始めても良いように並行して公開していきます。

0 環境構築編

本ゼミナールではUbuntu上で開発することを前提に進めまていますが,Windows上でのAI開発環境構築やノートPC上でのDual boot環境構築をまとめました。

Pyゼミ0.01 Pythonで医用画像のAIを試すための環境構築 
              
Pyゼミ0.02 Windows10AIしたい
              
Pyゼミ0.03 MSI ノートPCPS42 8RC-009JP)にAI環境を構築する
             
Pyゼミ0.04 MSI P65Dual bootにしてみた

Pyゼミ0.05 備忘録 Ubuntu18.04にTensorFlowとChainerをインストールする 

Pyゼミ0.06 備忘録 Ubuntu20.04にTensorFlowとPyTorchをインストールする New 20221019

Ⅰ DICOM編

医用画像は標準化が進み,DICOM(Digital Imaging and COmmunication in Medicine)が一般的に用いられています。Pydicomを使った簡単な画像の表示,ウィンドニングと呼ばれる最適画像濃度変換処理とJPEG,PNG画像変換や画像ポジションによるソート,画像拡張について紹介します。

Pyゼミ1.01 DICOM画像表示
              
Pyゼミ1.02 DICOMからJPEGなどへの変換
              
Pyゼミ1.03 DICOMのタグ情報を読む
              
Pyゼミ1.04 DICOMのタグ情報(ImagePosition)で画像ファイルをソートする
              
Pyゼミ1.05 DICOM JPEG圧縮画像を解凍する

Pyゼミ1.06 DICOM CT画像をLUTで疑似カラーを付けてみる


Pyゼミ1.07 CT画像にDICOM Color LUTを使ってヒートマップ画像などを重ねてFusion画像を作ってみる

Pyゼミ1.0 番外編 DICOM RDSRを読む 20220502

Pyゼミ1.08 PET画像を読む  20220506 


Ⅱ GUI(Graphical User Interface)編

PyQt5を使ったGUIプログラムを紹介します。
DICOM画像表示,複数DICOM画像のページングとマウスにより任意画像の抽出,DICOM画像上から任意のROIの抽出など画像加工プログラムを紹介します。

Pyゼミ2.01 PyQt5によるDICOM画像表示.画像ファイル渡し
              
Pyゼミ2.02 PyQt5によるDICOM画像表示とページング
              
Pyゼミ2.03 PyQt5によるDICOM画像のセグメンテーション
              
Pyゼミ2.04 PyQt5によるDICOM画像の拡大表示と保存

Pyゼミ2.05 PyQt5によるラジオボタンとチェックボックス new2020.02.16

             

Ⅲ 人工知能編

人工知能のフレームワークであるChainerを使います。
手書き数字認識MNISTのデータから医用画像データへの拡張方法,pythonを使ったChainerの訓練プログラムをGPU対応にする方法などを紹介します。

Pyゼミ3.01 手書き認識のニューラルネットワーク
              
Pyゼミ3.02 手書き画像からDICOM画像へ
              
Pyゼミ3.03 DICOM画像を訓練用とテスト用に分けてみる
              
Pyゼミ3.04 手書き文字認識のニューラルネットワークをDICOMに拡張する
              
Pyゼミ3.05 MNISTのネットワークをAlexnetに対応させる
              
Pyゼミ3.06 ChainerTrainer Extensionを使ってみる
              
Pyゼミ3.07 ChainerGPUを使って学習してみる



プログラムの方針

プログラムのサンプルを探すのであればGitHubに大量のソースコードが保存されています。しかし,初心者がチャレンジしてするには敷居が高いように思います。
そこでPyゼミでは次の点に留意してプログラムを作成しています。

  • 100行程度の長さで,アルゴリズムを容易に理解できる
  • ひとつのプログラム内でアルゴリズムが完結している
  • 実行した結果が複雑ではなく容易に理解が得られる


おわりに

本研究室のゼミナールだけでなく,外部の他の勉強会やセミナーにも参加してみて,周囲の人たちがいろいろな問題をもっていることが分かってきました。
おおよそ図書などで人工知能が使えるようになっても,最初にデータをどのように加工してAIに与えるべきなのか,つまりMNISTのデータで上手くいくのは分かったけどその次どうしたらいいの...という問題にすこしでも参考になればと考えています。

ちょっとお願い

なお本ソースコードの使用にあたって,何らかの具都合や不利益が生じたとしても本研究室で責任を負いかねます.どうぞご了承ください。
本ゼミナールで配布するDICOM画像は私のCT全身像です。実験以外の利用はしないようお願いいたします。
また,ご意見などありましたらメッセージをいただけますでしょうか。