週刊 DICOMデコーダーを創る (2)

##本日はtag, VR, lengthを読み取ってみる
需要の程は不明ですが，知識の整理も兼ねて今日も作っていきます．

今日はtag, VR, 値長さを読み取るところまでやってみます．
前回はViewController.swiftにまとめていましたが，本格的に読み取り始めることにするので，新たにDicomDataクラスを作成します．

```DicomData.swift
class DicomData{
    
    var dicomData : Data!
    
    init(withData data:Data)  {
        dicomData = data
        // dicomデータかチェック
        // ヘッダは128バイトのファイルプリアンブル
        // 続く4バイトにプレフィックスが続く
        if dicomData.count < 132{
            print("file size error")
            return
        }
        if dicomData.getStringWithRange(start: 128, length: 4) != "DICM"{
            print("file type error")
            return
        }
        print("dicom file loaded")
    }
}
```

##データ要素の読み取り

###VR一覧
PS3.5 p23に一覧が記載されています．
各VRには対応するデータ内容がどのような値型で格納されているかを示しています．
DICOM規格ではかなり厳密に区別されていますが，実際には値として入っているのは
数字・文字列・バイト列（バイナリ）だけです．

| VR | 内容 | 備考|
|:-:|:-:|:-:|
| AE  | 文字列  |   |
| AS  | 年齢を表す文字列  |   |
| AT  | tag, elementを表す16bit符号なし整数  |   |
| CS  | 文字列  |   |
|  DA | 日付を表す文字列  |   |
| DS  | 固定小数点か浮動小数点を表す文字列  |   |
| DT  |  日時を示す文字列 |   |
| FL  | 32bit浮動小数点数  |   |
|  FD | 64bit浮動小数点数  |   |
| IS  |  整数列　整数を表す文字列 |   |
| LO| 文字列| |
| LT|文字列 | |
| OB|byte列　ここにdataが入る |* |
| OF|浮動小数点ワードの列 |* |
| OW|その他のワード列　dataが入る | *|
| PN|人名が入る文字列 | |
|SH |文字列 | |
|SL |32bit符号付き整数 | |
|SQ |項目のシーケンス | *|
|SS |16bit符号付き整数 | |
|ST |文字列 | |
|TM |時間を表す文字列 | |
|UI |文字列 | |
|UL |32bit符号なし整数 | |
|UN |内容が不明のバイト列 | *|
|US |16bit符号なし整数 | |
|UT |文字列 | *|

<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1465632/7fae3319-4f52-8e2a-f1b2-ea752f41602a.png">

各データ要素は，

| group | element | VR | 値長さ | 値
|:-:|:-:|:-:|:-:|:-:|:-:|
|1byte|1byte|2byte|2byte = UInt16|値長さで指定された分だけ|

で構成されているが，*をつけたOB, OW, OF, SQ, UT, UNは

| group | element | VR | 予約領域(使用しない)| 値長さ | 値
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
|1byte|1byte|2byte|2byte|4byte = UInt32|値長さで指定された分だけ|
で構成される点に注意が必要です．
VRに続く2byteに値長さは格納されておらず，2byteあけて4byte分使って値が格納されています．

これはdicomの各tagに対応するデータは日時であったり人名であったり，基本的に小さなデータを格納しているが，例えばOBやOWには16bit高解像度CTの画素値が入ったりするので，その容量を示すためには2byte長で表すことができる最大数(=16bit符号なし整数 max 65,535)となるので，データとして60KB程度のものしか格納できなくなります．
そのため，OBやOWなど画素を収納する部分は4byteでデータ容量を表すことができ，4,294,967,295byte = 4GB程度のデータが格納可能となります．

##関数作成
新たにDicomDataクラスを作ったので，そちらに各読み取り関数を実装していきます．
今回実装するのは，

* 指定した長さ分のバイト列→文字列へasciiテーブルで変換するクラス
* 16bit符号なし整数
* 32bit符号なし整数
* 現在読み取りしているファイルアドレスを移動させるseek関数

です．

```DicomData.swift
    var currentPosition : Int = 0
    
    func readUInt16() -> UInt16{
        let data = dicomData[currentPosition ... currentPosition+1].map{$0}
        currentPosition += 2
        return UInt16(data[1]) << 8 + UInt16(data[0])
    }
    
    func readUInt32() -> UInt32{
        let data = dicomData[currentPosition ... currentPosition+3].map{$0}
        currentPosition += 4
        return UInt32(UInt16(data[3])) << 24 + UInt32(UInt16(data[2])) << 16 + UInt32(UInt16(data[1])) << 8 + UInt32(UInt16(data[0]))
    }
    
    func readChar(length:Int) -> String{
        let pos = currentPosition
        currentPosition += 2
        return dicomData[pos...pos+length-1].map{String(Unicode.Scalar($0))}.joined()
    }
    
    func seek(offset : Int){
        currentPosition += offset
    }
    
```

`readChar`関数は前回と同様に`String(Unicode.Scalar($0))`で実装します．
`readUInt16 readUInt32`はWindowsの.NET frameworkには存在するのですが，swiftでは見当たりませんでした（間違っていたらすいません）．

bit演算のシフトを用いて実装します．

例えば
`16 A2 15 DA`のバイナリが順に並んでいたとき，little endianであれば4byteとったときに
`DA 15 A2 16 = 3658850838`と読み取る必要があります
それぞれをbitで表すと
`DA(=218) 1101 1011`
`15(= 21) 0001 0101`
`A2(=162) 1010 0010`
`16(= 22) 0001 0110`
です．

これを左にそれぞれ24bit, 16bit, 8bitずらすことで
`1101 1011 0000 0000 0000 0000 0000 0000`
`0000 0000 0001 0101 0000 0000 0000 0000`
`0000 0000 0000 0000 1010 0010 0000 0000`
`0000 0000 0000 0000 0000 0000 0001 0110`
として，これらを足し合わせて（bit演算の四則演算は癖があるので確認してください）
（今回は単純に + でいいですが）
`1101 1010 0001 0101 1010 0010 0001 0110 = 0xDA15A216`
とすることで得られます．

##読み取り
```
    func analyzeData(){
        currentPosition = 128 + 4
        
        while currentPosition <= 1950 {
            let position = currentPosition
            
            let group = readUInt16()
            let element = readUInt16()
            let vr = readChar(length: 2)
        
            if ["OB", "OW", "OF", "SQ", "UT"].contains(vr){
                // VRの続きの2byteは意味をなさないので飛ばす
                currentPosition += 2
                let length = readUInt32()
                
                print("Address: 0x\(String(position, radix: 16))(\(position)), tag: (\(String(format: "%04x", group)), \(String(format: "%04x", element))), VR: \(vr), Length: \(length)")
                
                currentPosition += Int(length)
                
            }else{
                let length = readUInt16()
                
                print("Address: 0x\(String(position, radix: 16))(\(position)), tag: (\(String(format: "%04x", group)), \(String(format: "%04x", element))), VR: \(vr), Length: \(length)")
                
                currentPosition += Int( length)
                
            }
        }
    }
```

今回は試しに1950バイト目まで読み取ってみます
格納されたデータの内容は読み飛ばして，tag, VR, lengthのみ読んでいます

出力は

```
Address: 0x84(132), tag: (0002, 0000), VR: UL, Length: 4
Address: 0x90(144), tag: (0002, 0001), VR: OB, Length: 2
Address: 0x9e(158), tag: (0002, 0002), VR: UI, Length: 28
Address: 0xc2(194), tag: (0002, 0003), VR: UI, Length: 46
Address: 0xf8(248), tag: (0002, 0010), VR: UI, Length: 22
Address: 0x116(278), tag: (0002, 0012), VR: UI, Length: 8
Address: 0x126(294), tag: (0008, 0008), VR: CS, Length: 38
Address: 0x154(340), tag: (0008, 0016), VR: UI, Length: 28
Address: 0x178(376), tag: (0008, 0018), VR: UI, Length: 46
Address: 0x1ae(430), tag: (0008, 0020), VR: DA, Length: 8
Address: 0x1be(446), tag: (0008, 0030), VR: TM, Length: 6
Address: 0x1cc(460), tag: (0008, 0050), VR: SH, Length: 0
Address: 0x1d4(468), tag: (0008, 0060), VR: CS, Length: 2
Address: 0x1de(478), tag: (0008, 0070), VR: LO, Length: 0
Address: 0x1e6(486), tag: (0008, 0080), VR: LO, Length: 0
Address: 0x1ee(494), tag: (0008, 0081), VR: ST, Length: 0
Address: 0x1f6(502), tag: (0008, 0090), VR: PN, Length: 0
Address: 0x1fe(510), tag: (0008, 1030), VR: LO, Length: 0
Address: 0x206(518), tag: (0008, 1050), VR: PN, Length: 0
Address: 0x20e(526), tag: (0008, 2110), VR: CS, Length: 2
Address: 0x218(536), tag: (0008, 2112), VR: SQ, Length: 4294967295
```

です．
先程示したとおり，VRがOB, OW, OF, SQ, UTは挙動が違うので分けています．

現時点ではアドレスとVR，データ長が表示されているだけで，各tagが何を示すのかはわかりませんが，これは対応表があります．
またそのうち実装しますが，今見えている中で大事なのは
(0002, 0010)のUIが入っているデータで，UIなので文字列が入っています．
内容は，1.2.840.10008.1.2.4.50という文字列で，これも対応表がありますが，
JPEG Baseline (Process 1) で圧縮転送していることを示しています．
ここを読み取って，どのような方法で最終の画像データをデコードするのかを決めることになります．

|データ |転送形式 |
|:-:|:-:|
| 1.2.840.10008.1.2 | Implicit VR Little Endian Default Transfer Syntax |
| 1.2.840.10008.1.2.1 | Explicit VR Little Endian Transfer Syntax |
| 1.2.840.10008.1.2.1.99 | Deflated Explicit VR Little Endian |
| 1.2.840.10008.1.2.2 | Explicit VR Big Endian |
| **1.2.840.10008.1.2.4.50** | **JPEG Baseline (Process 1)** |
| 1.2.840.10008.1.2.4.70 | JPEG Lossless, Non-Hierarchical, First-Order Prediction (Process 14 [Selection Value 1]) |
| 1.2.840.10008.1.2.4.80 | JPEG-LS Lossless Image Compression |
| 1.2.840.10008.1.2.4.81 | JPEG-LS Lossy (Near-Lossless) Image Compression |
| 1.2.840.10008.1.2.4.90 | JPEG 2000 Image Compression (Lossless Only) |
| 1.2.840.10008.1.2.4.91 | JPEG 2000 Image Compression |
| 1.2.840.10008.1.2.4.100 | MPEG2 Main Profile |
| 1.2.840.10008.1.2.5 | RLE Lossless |

本来はすべての伝送方法に対応してデコードできるようにする必要がありますが，
現時点では上記から一部を実装する予定です．

今回のログですが，最初のほうは順調に読めていますが，最後のVRがSQの部分で変になってしまっています．
このSQが曲者で，正しく実装するのはなかなか難しそうなのですが，データを階層化して入れ子のようにまとめるためのVRです．

今後どのように実装していくかはまだ悩み中ですが，次回はSQを関して実装していこうと思います．

ではまた．