2012/11/26(月)DICOM通信プロトコルのまとめ

DICOMという医用画像のフォーマットおよび医療画像通信規約があり、その通信規約の部分についてのまとめです。DICOMがなんであるか知ってる人向けです。

個人的に必要な最低限Query/Retrieveする範囲のみの情報で、網羅はしていません*1。しかし、この情報があればあの厄介かつ難解な規格書を読むのが相当楽になるかと思います。

*1 : 必要な範囲で、メモ代わりに加筆はしていきます

はじめに

DICOMの規格書はJIRA(日本画像医療システム工業会)のページより日本語訳をダウンロードすることができますが、公開の仕様でありながらなぜこのようなまとめが必要かというのが頭の痛い問題です。

  • TCP/IPではなく元々RS-232などのシリアル通信用に作られた規格。
  • 時代の変化と共にTCP/IP専用になってはいるが、古い規格をただ乗せただけ。
  • ネットやPCなどのエンジニアが作った規格ではないと思われる。
  • おそらくエンジニアではない人が翻訳している。*2
  • 記述がOSI 7層モデル準拠である。
  • 資料として検索することなど微塵も考えてない全角英字のオンパレード*3

つまりエンジニアの視点から見ると規格書が極めてまどろっこしい上に分かりにくいのです。

*2 : プロトコル関係の専門用語の翻訳が無茶苦茶

*3 : バカとしか言いようがありません

資料

DICOMの規格書本家)はPS 3.1からPS 3.20までの膨大な分冊にわかれていて、まずこの時点で頭痛がしてきます。タイトルをみても何を説明しているのかさっぱり不明です。

DICOM通信のクエリーリトリーブによりデータを取り出したい場合、メインとなるのは以下3冊(上から3冊)のようです。

PS 3.8(第8巻)メッセージ交換のためのネットワーク通信サポート
DICOM通信をTCP/IP上で行う方法について述べられています。
PS 3.7(第7巻)メッセージ交換
確立されたTCP/IP通信路で行う内部通信形式のコマンド部分がまとめられています。
PS 3.4(第4巻)サービスクラス仕様
上記コマンドに付随して使用する「データ部分」がまとめられています。
PS 3.10(第10巻)媒体相互交換のための媒体保存とファイルフォーマット
DICOM画像ファイルフォーマット

通信の概要

  • PDU……プロトコルデータユニット(プロトコルデータ単位と訳されている(汗))。通信において1回に送ったり受け取ったりする単位。HTTPで言えば、リクエスト全体がひとつのPDU、それに対する応答全体が1つのPDU。
  • ビッグエンディアン。
  • 長さの数値は符号なし。

PDUの種類

  • A-ASSOCIATE-RQ …… Request
  • A-ASSOCIATE-AC …… Acknowledge
  • A-ASSOCIATE-RJ …… Reject
  • P-DATA-TF
  • A-RELEASE-RQ …… Request
  • A-RELEASE-RP …… Response
  • A-ABORT

A-ASSOCIATE

通信路(アソシエイション)の確立のためのやり取り。クライアント側から通信路確立要求を出し、サーバ側がそれを承認もしくは拒絶する。

TCP/IPが接続されている時点でもう通信路はできていることなど、もちろん気にもしない。

A-RELEASE

通信路(アソシエイション)開放のためのやり取り。クライアント側から通信路の開放を要求し、サーバ側がそれに応答する。

TCPのソケットをクローズすればいいだけではないかと突っ込んではいけない。

A-ABORT

通信路の異常開放。クライアント側から一方的に通信の中断を通知する。

P-DATA

確立された通信路上での「DICOMメッセージ交換」に使用する。

クライアント側からみた実際の流れ

  1. 指定のTCPポートに接続
  2. A-ASSOCIATE-RQの送信
  3. A-ASSOCIATE-ACもしくはA-ASSOCIATE-RJの受信。後者ならexit
  4. P-DATA-TFによるDICOMメッセージ交換
  5. A-RELEASE-RQの送信
  6. A-RELEASE-RPの受信
  7. TCPセッションクローズ

A-ASSOCIATE-RQ, A-ASSOCIATE-AC共通

オフセット名前概要
00hPDU TypeA-ASSOCIATE-RQならば01h, A-ASSOCIATE-ACならば02h
01hReserved00h
02-05hlengthPDU全体のバイト数-6(次フィールド以降の長さ)
06-07hVersionProtocolバージョン。1固定。
08-09hReserved00h 00h
0A-19hCalled AEサーバ側のAEタイトル。前詰め。後ろを20hで埋める
1A-29hCalling AEクライアント側のAEタイトル。前詰め。後ろを20hで埋める
2A-49hReserved00hで埋める。
4Ah~可変アイテム可変長のデータ。後述

A-ASSOCIATE-RQ

可変アイテムには以下のすべてを含む。

  • 1つのアプリケーションコンテキスト(応用コンテキストと訳されている(汗))
  • 1つ以上のプレゼンテーションコンテキスト
  • 1つのユーザー情報

アプリケーションコンテキスト

オフセット名前概要
00hItem Type10h
01hReserved00h
02-03hlengthこのアイテムのバイト数-4(次フィールド以降の長さ)
04h~コンテキスト名-

コンテキスト名はISO 8824およびISO 9834-3で定義される、世界中でユニークなアプリケーションオブジェクト識別子みたいです。このバージョンのDICOM通信アプリケーションでは以下を使えばよいようです。

1.2.840.10008.3.1.1.1

プレゼンテーションコンテキスト

プレゼンテーションコンテキストは複数存在することがあります。異なる操作を一度に行う場合や、異なる転送構文での転送を一度に行う場合が該当します。その場合、A-ASSOCIATE-ACにおいてそれぞれのコンテキストIDを示し「許可、不許可」を個別に応答します。

オフセット名前概要
00hItem Type20h
01hReserved00h
02-03hlengthこのアイテムのバイト数-4(次フィールド以降の長さ)
04hコンテキストID1-255までの奇数
05-07hReserved00hで埋める
08h~sub-items1つの抽象構文および1つ以上の転送構文

抽象構文(Abstract Syntax)

オフセット名前概要
00hItem Type30h
01hReserved00h
02-03hlengthこのアイテムのバイト数-4(次フィールド以降の長さ)
04h~抽象構文名-

この詳細はQuery/Retrieveの説明で述べます。

転送構文(Transfer Syntax)。

オフセット名前概要
00hItem Type40h
01hReserved00h
02-03hlengthこのアイテムのバイト数-4(次フィールド以降の長さ)
04h~転送構文名-

PS 3.5によればこの構文名はDICOM暗黙VRリトルエンディアン転送構文(これはすべてのDICOM実装でサポートされるとある)を指定すれば良いようです。

1.2.840.10008.1.2

ユーザー情報

オフセット名前概要
00hItem Type50h
01hReserved00h
02-03hlengthこのアイテムのバイト数-4(次フィールド以降の長さ)
04h~user-dataユーザーデータのサブアイテム。通常下記を含む。

最大データ長ネゴシエーション。

オフセット名前概要
00hItem Type51h
01hReserved00h
02-03hlengthこのアイテムのバイト数-4。04h固定
04-07h最大受信長P-DATA-TFの最大長をサーバに対し制限する。0は無制限。

最大受信長を0や64KB以上に設定したら失敗するアホな実装(これがお高い売り物なんだから信じられない……)がありましたので、16KBに設定するのが無難なようです。ここで設定したPDUよりも大きなサイズのP-DATA PDU(ヘッダのlength基準)は送られなくなります(逆に相手が指定してきた場合は、そのサイズよりも大きなP-DATA PDUは送ってはいけない)。

その他、PS3.7によれば「Item Type 52h(実装UID)」および「Item Type 55h(実装名)」が通知・交換される。これらは特定のDICOM実装同士が互いを認識しあうために使われますが、実装UIDの通知は必須になっています。

K-PACSでは以下の値が使われてるようで、UIDを登録・取得できない場合はISO 8824およびISO 9834-3準拠で被らなそうな値を適当にもじって使えばよさそう*4。実装名は半角英数字および「.」「_」で構成すればよさそうです。

[52h] Their Implementation Class UID: 1.2.826.0.1.3680043.2.1396.999
[55h] Their Implementation Version Name: CharruaVista

またその他、アイテムタイプ51h-FFhは利用者情報サブアイテム用として割り当てられ、その詳細はPS 3.7 D3.3.2に定義されている。

*4 : 本当はあまりよろしくはないだろうけども

A-ASSOCIATE-AC

可変アイテムには以下のすべてを含む。

  • 1つのアプリケーションコンテキスト
  • 1つ以上のプレゼンテーションコンテキスト
  • 1つのユーザー情報

アプリケーションコンテキスト(10h)、ユーザー情報(50h)は、A-ASSOCIATE-RQと同一である(同じ識別子を返す模様)。

プレゼンテーションコンテキスト

オフセット名前概要
00hItem Type21h
01hReserved00h
02-03hlengthこのアイテムのバイト数-4(次フィールド以降の長さ)
04hコンテキストID1-255までの奇数
05hReserved00hで埋める
06hResult応答結果。0=Accppt, 0以外=Reject
07hReserved00hで埋める
08h~sub-items1つの転送構文サブアイテム

リザルトについて。

  • 0 : アクセプト
  • 1 : ユーザーリジェクト
  • 2 : 理由なしリジェクト
  • 3 : 抽象構文がサポートされていない
  • 4 : 転送構文がサポートされていない

A-ASSOCIATE-RQと異なりサブアイテムとして転送構文のみ持つことに注意してください。

A-ASSOCIATE-RQにプレゼンテーションコンテキストが複数存在した場合は、それぞれ個別のコンテキスト(抽象構文=操作指示および転送構文)についての「許可、不許可」を個別に応答します。応答しないものがある場合、不許可とみなされるようですが、リクエストにあったすべてのプレゼンテーションコンテキストについて応答するほうが望ましいと言えます。

またあるメッセージIDで非対応の転送構文が指定されたときは、対応している転送構文に書き換えてACを返信してあげれば、その形式で送ってくるようです。

A-ASSOCIATE-RJ

オフセット名前概要
00hPDU Type03h
01hReserved00h
02-05hlengthPDU全体のバイト数-6(次フィールド以降の長さ) 04h
06hReserved00h
07hResult1=拒絶, 2=一時的に拒絶
08hSource1=サービス利用者、
2,3=サービス提供者(2=ACSE, 3=プレゼンテーション関連)
09hReason拒絶理由

拒絶理由について。Source=1のとき。

  • Source=1のとき
    • 1 : 理由なし
    • 2 : アプリケーションコンテキストがサポートされない。
    • 3 : Calling AEが認識できない(正しくない)
    • 7 : Called AEが認識できない(正しくない)
    • 4-6,8-10 : 予約済
  • Souce=2のとき
    • 1 : 理由なし
    • 2 : サポートされないプロトコルバージョン
  • Souce=3のとき
    • 1 : 一時的輻輳
    • 2 : ローカル制限超過
    • 0,3-7 : 予約済

P-DATA-TF

オフセット名前概要
00hPDU Type04h
01hReserved00h
02-05hlengthPDU全体のバイト数-6(次フィールド以降の長さ)
06h~Data-items1つ以上のプレゼンテーションデータアイテム

プレゼンテーションデータアイテム

オフセット名前概要
00-03hlengthこのアイテムのバイト数-4(次フィールド以降の長さ)
04hコンテキストID1-255までの奇数
05h~dataデータ値(PDV)

コンテキストIDは「A-ASSOCIATE」によりネゴシエーションされたコンテキストIDを示します。これにより、どんな操作か(抽象構文)、どの形式で転送するか(転送構文)を示しています。

A-RELEASE-RQ

オフセット名前概要
00hPDU Type05h
01hReserved00h
02-05hlengthPDU全体のバイト数-6(次フィールド以降の長さ)。04h固定。
06-09hReserved00000000h

A-RELEASE-RP

オフセット名前概要
00hPDU Type06h
01hReserved00h
02-05hlengthPDU全体のバイト数-6(次フィールド以降の長さ)。04h固定。
06-09hReserved00000000h

A-ABORT

オフセット名前概要
00hPDU Type07h
01hReserved00h
02-05hlengthPDU全体のバイト数-6(次フィールド以降の長さ)。04h固定。
06hReserved00h
07hReserved00h
08hSource0=サービス利用者,2=サービス提供者,1=予約済
09hReason理由。Source=0のときは00h固定

Source=1のときの理由は以下の通り。

  • 0 : 理由なし
  • 1 : 認識できないPDU
  • 2 : 予期しないPDU
  • 3 : 予約済
  • 4 : 認識できないPDUパラメタ
  • 5 : 予期しないPDUパラメタ
  • 6 : 無効なPDUパラメタ

PDV, DIMSEメッセージ

P-DATAによりやりとりされる実体がDIMSEメッセージです。

プレゼーテーションアイテム(PDV)が1つであるとき、P-DATA PDUは次のようになります。

オフセット名前概要
00hPDU Type04h
01hReserved00h
02-05hlengthPDU全体のバイト数-6(次フィールド以降の長さ)
06-09hlengthこのアイテムのバイト数-4(上記値-4)
0AhコンテキストID1-255までの奇数
0BhPDV headerデータヘッダ
0Ch~dataデータ値(PDV)

PDVは「コマンド集合」もしくは「データ集合」のどちらかです。両方同時に持つことは許されず、ひとつのPDUは必ずどちらかのPDVを持ちます。これを示すのがデータヘッダです。(PS 3.8付属書Eより)

  • Headerのbit 0
    • 1 : 続くフラグメントがコマンドである
    • 0 : 続くフラグメントがデータである
  • Headerのbit 1
    • 1 : このPDV中に最後の要素を含む
    • 0 : このPDV中に最後の要素を含まない

Headerのbit 1が0の場合、続いて送られてくるP-DATAの中のPDVが後ろに続いているとみなして処理します。例えば、256byteの大きさを持つPDV*5中のとある要素が512byteのサイズをもつ場合、1つめのPDVからあふれた分のサイズは続いてのPDVから読み込みます。

  • P-DATA PDU #1
    • PDUヘッダ
    • PDVヘッダ = 0
    • PDV (256byte)
      • 要素X ヘッダ8byte(データ長さ120) + データ120byte
      • 要素Y ヘッダ8byte(データ長さ240) + データの先頭120byte
  • P-DATA PDU #2
    • PDUヘッダ
    • PDVヘッダ = 2
    • PDV (256byte以下)
      • 要素Y データの後半120byte
      • 要素Z ヘッダ8byte + データ

要素ヘッダの途中で複数のP-DATA PDUに分割されることも考えられるため、柔軟な実装が必要になります。(後に具体例で説明しています)


コマンド要素、データ要素の形式

コマンド集合やデータ集合はそれぞれ、複数の「コマンド要素」や「データ要素」から構成されます。要素はそれぞれ独自のタグ(4byte)を持ち、各要素にはVRと呼ばれるデータ形式が予め決められています。(PS3.5の7.1.3)

オフセット名前概要
00-03h要素タグ(1234,5678)ならば 34h 12h 78h 56h
04-07h要素サイズ要素データの長さ。要素全体の長さ-8
08h~要素データ偶数byteの要素データ。中身の形式はVRによる。

これまでと異なり、各要素中の値(数値)はリトルエンディアンで表現されます。A-ASSOCIATE-RQによって「暗黙VRリトルエンディアン転送構文(1.2.840.10008.1.2)」を指定しているためです。*6

VRの形式(一部抜粋)

VRに対応する値の形式はPS 3.5の6.2で定義されています。サイズはすべて偶数である(偶数でなければならない)。

VRサイズ意味
UL4byte符号なし32bit整数値
US2byte符号なし16bit整数値
IS12byte(max)"+","-","0-9"からなる10進数文字列。32bit符号付整数の範囲
DA8byte日付文字列。YYYYMMDD。範囲照合の場合最大18byte。
DA18byte(max)日付範囲照合の場合。(例)YYYYMMDD-YYYYMMDD
TM16byte(max)時刻文字列。HHMMSS.FFFFFF。MM以降は省略可*7
DT26byte(max)YYYYMMDDHHMMSS.FFFFFF&ZZXX。&ZZXXはUTCからのずれ。+0900等
AS4byte年齢列。nnnD,nnnW,nnnM,nnnY。018Dは18日、020Yは20年
AE16byte(max)先頭/末尾に SPACE(20h) を持つエンティティ識別文字列
CS16byte(max)先頭/末尾に SPACE(20h) を持つことのある文字列。Code string
SH16文字(max)先頭/末尾に SPACE(20h) を持つことのある文字列。Short string
LO64文字(max)先頭/末尾に SPACE(20h) を持つことのある文字列。Long string
UI64byte(max)固有(Unique)識別子。「0-9」と「.」の集合体文字列。奇数の際00hを付加。
SQ--シーケンス(後述)

範囲照合の詳細はPS 3.4のC.2.2.2.5参照。サイズが「文字」となっているものはワイドキャラクター(日本語等)においては、byte数と一致しないことがあるらしい(未確認)。

シーケンス要素の特殊処理

DICOM通信においてデータを取得する際、シーケンスを適切に処理する必要があります。

シーケンスの詳細は「PS3.5 7.5」にありますが、要するに一つの項目に複数のデータが列挙された形式です。ここではデータの切り出しについてのみ述べます。中身を詳細に処理したい場合は仕様書を参照してください。

(VR非明示リトルエンディアン転送では)シーケンスデータのアイテムは、「正しい長さ」か「FFFFFFFFhの長さ」を持ちます。後者は長さが未定義であることを示します。鵜呑みにして処理するとその後のアイテムがパースできず痛い目を見ます。シーケンスデータは「FE FF DD E0 00 00 00 00」(FFFE, E0DD)で終わります。この部分まで含めて切り出して上げれば良いわけです。*8

シーケンスとして登録されているVRをすべて処理プログラム側で持つのが本来の正しい処理かもしれませんが、そうも言ってられないので、次の方法で処理をしました。

  • シーケンスデータの長さが「FFFFFFFFh」でなければ通常のとおり処理する。
  • データ長さが「FFFFFFFFh」でデータの先頭が「FEh FFh」で始まるデータをシーケンスデータとみなす。
  • シーケンスデータの終わりは単純に8byteの文字列マッチングで「FEh FFh DDh E0h 00h 00h 00h 00h」の手前までを切り出す。

(追記)シーケンスは入れ子になることがあります。その場合の処理はより複雑になります。入れ子構造を保って切り出さないと、dcmj2pnmなどのコマンドで弾かれることがあります。

*5 : 相手から送られる最大PDUパケット長で、A-ASSOCIATEでこちらが申し出た最大転送長に制約されます。このときの制約はPDVではなくPDUへの制約であることに注意。

*6 : そうでない場合、データ要素はVRを示すヘッダを持つことがあります

*7 : うるう秒の際、SS=60となることがある

*8 : 中身が空のシーケンスはいきなり「FE FF DD E0 00 00 00 00」で終わります

Query/Retrieveの実際

以上を踏まえて、クエリー・リトリーブにより情報を取得する方法について述べます。

規格書PS 3.4およびPS 3.7 E.1によれば、Query/Retrieveには次のSOPクラスとコマンドコード(タグ(0000,0100)の値)が割り当てられています。

DIMSE操作名コマンドSOPクラス名SOPクラス
C-FIND-RQ0020hRoot Query/Retrieve - FIND1.2.840.10008.5.1.4.1.2.2.1
C-FIND-RSP8020h
C-MOVE-RQ0021hRoot Query/Retrieve - MOVE1.2.840.10008.5.1.4.1.2.2.2
C-MOVE-RSP8021h
C-GET-RQ0010hRoot Query/Retrieve - GET1.2.840.10008.5.1.4.1.2.2.3
C-GET-RSP8010h

このSOPクラスはA-ASSOCIATE-RQの抽象構文として送信されます。なおC-GETはサポートされないことが多く、C-FINDとC-MOVEを使うのが定石のようです。

C-FIND-RQ

これらのコマンドは、P-DATA PDUにおけるPDVの中身としてコマンド集合とデータ集合により表現されます。ダンプしてみてると2つのPDVに分けるようです。

  • P-DATA (集合ヘッダ + コマンド集合)
  • P-DATA(集合ヘッダ + データ集合)

PS 3.7に9.3.2.1に、C-FINDリクエストコマンドのコマンド要素の構成が書かれています。要素は必ず偶数バイトであることに注意してください。文字列ならば後ろに SPACE(20h) を付加します。

タグVR説明
(0000,0000)ULグループの長さ(全体-12=次のタグ以降の長さ)
(0000,0002)UISOPクラスID。"1.2.840.10008.5.1.4.1.2.2.1"
(0000,0100)USコマンド領域。0020h固定。
(0000,0110)USメッセージID。識別するための固有の値。"3"とかの数字で構わない
(0000,0700)US優先度。LOW=0002h, MEDIUM=0000h, HIGH=0001h。MIDでok。
(0000,0800)USデータ集合タイプ。0101h(Null)以外の値。後方互換性のため 0102h を設定。

この後ろにデータ集合が続きます。PS 3.4のC.6.1.1.3にどのようなデータを付属させるか説明があります。各タグのVRについてはPS 3.6に説明があります。*9

※データ集合タイプが0101h以外のときは後ろにデータ集合が続きます。データ集合タイプが0101hのときはデータ集合は続きません。

タグ患者検査SEIMGVRメモ書き説明
(0008,0016)----UIsop_class_uidSOPクラスUID
(0008,0018)---UUIsop_uidSOPインスタンスUID
(0008,0020)-R--DAstudy_date検査日付
(0008,0030)-R--TMstudy_time検査時刻
(0008,0050)-R--SHaccession_number受付番号
(0008,0052)xxxxCSqr_levelQuery/Retrieveレベル
(0008,0058)----UIfailid_uid_list失敗SOPリスト。\ 区切り文字列
(0008,0060)-OR-CSmodality検査のモダリティ(SERIESが持つ)
(0008,0061)----CSmodalities検査のモダリティ(STUDYが持つ)
(0008,0070)----LOmanufacture検査装製造元
(0008,0080)----LOinstitution_name検査装置製造者名
(0008,0090)-O--PNphysician_name照会医師の名前
(0008,1030)-O--LOstudy_description検査の解説
(0010,0010)R---PNpatient_name患者の名前
(0010,0020)U---LOpatient_id患者ID
(0010,0030)O---DApatient_birth_date患者の誕生日
(0010,0040)O---CSpatient_sex患者の性別
(0010,1010)OO--ASpatient_age患者の年齢
(0020,000D)-U--UIstudy_uid検査インスタンスUID
(0020,000E)--U-UIseries_uidシリーズインスタンスUID
(0020,0010)-R--SHstudy_id検査ID
(0020,0011)----SHseries_numberシリーズID
(0020,1200)O---ISpatient_studies患者に関係した検査の数
(0020,1202)O---ISpatient_series患者に関係したシリーズの数
(0020,1204)O---ISpatient_instances患者に関係したインスタンスの数
(0020,1206)-O--ISstudy_series検査に関係したシリーズの数
(0020,1208)-O--ISstudy_instances検査に関係したインスタンスの数
(0020,1209)--O-ISseries_instancesシリーズに関係したインスタンスの数

Query/Retrieveはその取得する情報によって、レベルが定義されています(PS 3.4 C6.2)。です。

  • PATIENT : 患者情報
  • STUDY : 検査情報
  • SERIES : シリーズ情報(※上記表では「SE」表記)
  • IMAGE : 複合オブジェクトインスタンス情報(※上記表では「IMG」表記)

1人の患者は1つ以上の検査情報を持ち、1つの検査情報は1つ以上のシリーズ情報を持ちます。1つのシリーズには1つ以上のIMAGEが含まれます。つまり、上から包含関係になっていることに注意してください。

例えば、CTならこんな感じです。

  • [STUDY]1回のCTの検査
    • [SERIES]足先から足の付根までの撮影データ
      • 画像1
      • 画像2
      • 画像3
      •  :
    • [SERIES]頭部撮影データ
      • 画像1
      • 画像2
      • 画像3
      •  :

患者情報のタグについてはかなり省略しました。

  • U 固有キー属性
  • R 必要キー属性
  • O 任意選択キー属性

属性は、Rは多分情報として必ず返せ(持て)、Uはその情報を一意に決めるためのIDという意味だと思われます。しかしながら、とある実装ではRを必ず返すわけでもなく、よく分かりません。とりあえず、例えばモダリティなど特定の情報を取得したい場合はデータサイズ0で送りつけると返してくれます。どうやら「どの情報によって絞り込むか」を決めると同時に、「どの情報が欲しいかC-FIND時のデータ集合の要素があるかないかによって指定する」ようです。

なお、例えば検査情報を取得するのにシリーズ番号を指定するなど不正な項目を指定するとエラーが返ってきます。また表では「O」になっていませんが、検査情報やシリーズ情報を取得する際に、患者名等を取得することもできるようです。

C-FIND-RSP(C-FIND-RQの応答)

これものP-DATA PDUにおけるPDV(集合ヘッダ + コマンド集合)の中身として送られてきます。

タグVR説明
(0000,0000)ULグループの長さ(全体-12=次のタグ以降の長さ)
(0000,0002)UISOPクラスID。"1.2.840.10008.5.1.4.1.2.2.1"
(0000,0100)USコマンド領域。8020h固定。
(0000,0120)US応答メッセージID。C-FIND-RQのメッセージIDに対応する。
(0000,0800)USデータ集合タイプ。0101h(Null)以外の値。後方互換性のため 0102h を設定。
(0000,0900)USステータス(Status)

ステータスの詳細(PS 3.4付属書K 4.1.1)。

Status詳細
0000h成功
A700h資源不足
A900h識別子とSOPに一致しない
B000h副操作(C-STORE)が1つ以上失敗した
Cxxxh処理することができない
FE00h取り消し
FF00h一致(データ)が続いている
FF01h一致(データ)が続いている。1つ以上の警告あり

クエリーに対して複数のマッチデータがある場合、P-DATA(集合ヘッダ + データ集合)がその数だけ存在する。データ要素についてはC-FIND-RQ参照。

受信側のフローは以下のとおり。

  1. コマンド集合のP-DATA PDUを送信する。
  2. データ集合のP-DATA PDUを送信する
  3. コマンド集合のP-DATA PDUを受信する。
    • Statusが0000hならば正常終了(これ以上処理しない)
    • StatusがFF00h, FF01hのどちらでもなければ異常終了(これ以上処理しない)
  4. データ集合のP-DATA PDUを受信する。(データ集合タイプ != 0101h の時)
  5. 3に戻る
  6. (C-MOVE/C-GET時)最後のStatusがB000hの場合、続けてデータ集合P-DATA PDUが送られてくる。これには失敗インスタンスリスト(0008,0058)が入っている。*10

受信したデータ集合の数だけ、要求した検索条件(C-FIND)にマッチしたデータが存在したことになります。

P-DATAは続けて送られてきますので、初回のコマンド受信以外は socket に対して select しないほうが無難です。

検査画像の情報を取得するまで

  1. QR Levelを「STUDY」にして検査データ(複数)を取得する。条件として、患者IDを指定したり、検査時期を指定したりといった検索条件を付加することができる(K-PACS等の実装を参照のこと)。この際「検査インスタンスUID(study_uid)」を忘れず取得すること。
  2. 取得した検査データに含まれるシリーズデータを取得する。検査データごとに「検査インスタンスUID(study_uid)」を指定して、その検査に含まれるシリーズ(複数)を取得する。この際「シリーズインスタンスUID(series_uid)」を忘れず取得すること。

このような手順で画像情報までは取得できますが、C-FINDでは画像そのものを取得することはできません。

なお、検査インスタンスUIDは世界で唯一であることが保証されています。*11

*9 : なんで説明がここまで分散してるのやら……

*10 : これを受信し忘れるとやり取りに矛盾が起こる。

*11 : DICOM準拠すると、そのように実装することが求められる

C-MOVEによる画像データの取得

C-FINDによって得られた情報から、実際に画像を取得する方法を述べます。

実際に画像データを取得するためには C-MOVE-RQ(PS3.7 - 9.3.4)を発行し、指定したDICOMサーバ(通常、自分自身)に対してデータを転送するように要求します。要求時はAEタイトルのみしか指定できませんので、DICOM/PACSサーバに予め自分自身が登録されいる必要があります。

仕様書を読むとC-GETが適当な気もするのですが、C-GETを実装しているPACSサーバはほとんどありません

具体的な手順は以下のようになります。

  1. 【自分 → 相手:通信路1】C-MOVE-RQを発行
  2. 【自分 ← 相手:通信路2】C-STORE-RQにより画像データが送られてくる
  3. 【自分 → 相手:通信路2】C-STORE-RSPを発行。画像がまだあれば続けて2に戻る。
  4. 【自分 ← 相手:通信路2】A-RELEASE-RQ
  5. 【自分 → 相手:通信路2】A-RELEASE-RP
  6. 【自分 ← 相手:通信路1】C-MOVE-RSPによりC-STOREの結果が送られてくる
  7. 【自分 → 相手:通信路1】A-RELEASE-RQ
  8. 【自分 ← 相手:通信路1】A-RELEASE-RP

通信路2は相手から自分に対して接続してきますので、こちら側にC-STOREに対応したDICOMサーバが必要になります。非常に厄介な仕様です……。

C-MOVE-RQ

以下詳細はPS3.7 9.3のプロトコルをみてください。

タグVR説明
(0000,0000)ULグループの長さ(全体-12=次のタグ以降の長さ)
(0000,0002)UISOPクラスID。"1.2.840.10008.5.1.4.1.2.2.2"
(0000,0100)USコマンド領域。0021h
(0000,0110)USメッセージID。"3"とかの数字
(0000,0110)USメッセージID。識別するための固有の値。"3"とかの数字で構わない
(0000,0600)AEC-STOREを実行する「DICOM AE」の名称
(0000,0700)US優先度。LOW=0002h, MEDIUM=0000h, HIGH=0001h。MIDでok。
(0000,0800)USデータ集合タイプ。0102h を設定。

C-MOVE-RSP

タグVR説明
(0000,0000)ULグループの長さ(全体-12=次のタグ以降の長さ)
(0000,0002)UISOPクラスID。"1.2.840.10008.5.1.4.1.2.2.2"
(0000,0100)USコマンド領域。8021h
(0000,0120)US応答メッセージID。対応するRQのメッセージIDの数字
(0000,0800)USデータ集合タイプ。データ集合なしなので 0101h に設定される。
(0000,0900)USステータス(Status)。C-FIND-RSP参照。
(0000,1020)USC-STORE副操作の残り数。
(0000,1021)USC-STORE副操作の完了数。
(0000,1022)USC-STORE副操作の失敗数。
(0000,1023)USC-STORE副操作の警告数。

C-STORE-RQ

タグVR説明
(0000,0000)ULグループの長さ(全体-12=次のタグ以降の長さ)
(0000,0002)UISOPクラスID。"1.2.840.10008.5.1.4.1.1.x" .x.y のことも。
(0000,0100)USコマンド領域。0001h
(0000,0110)USメッセージID。"3"とかの数字
(0000,0700)US優先度。LOW=0002h, MEDIUM=0000h, HIGH=0001h。MIDでok。
(0000,0800)USデータ集合が存在することを示す。0101h 以外を設定。"1"とか。
(0000,1000)UI保存(送信)されるデータのインスタンスUID。
(0000,1031)AEMOVE発行AEタイトル
(0000,1032)USMOVE発行AEのメッセージID

これに続けて、データ集合を含むP-DATA PDUが送られてきます。

このときのコマンド集合とデータ集合を一体としたものが、1つのDICOM画像形式になります。

C-STORE-RSP

タグVR説明
(0000,0000)ULグループの長さ(全体-12=次のタグ以降の長さ)
(0000,0002)UISOPクラスID。"1.2.840.10008.5.1.4.1.1.x" .x.y のことも。
(0000,0100)USコマンド領域。8021h
(0000,0120)US応答メッセージID。対応するRQのメッセージIDの数字
(0000,0800)USデータ集合が存在しないことを示す。0101h を設定。
(0000,0900)USステータス(Status)。C-FIND-RSP参照。
(0000,1000)US保存(送信)されるデータのインスタンスUID。

DICOM画像ファイルの復元

C-MOVE(C-STORE)で得られた画像からDICOM画像ファイル(.dcm)を復元することができます。PS3.10にヘッダの形式が書かれています。

位置/タグVRメモ書き内容
000-07fh--00h で埋める
080-083h--文字列 "DICM"
(0002,0000)ULgroup_lengthメタ情報のグループ長さ。
(0002,0001)OBformat_version"\x00\x01"の2byte固定
(0002,0002)UIsop_classC-STOREの該当クラス。"1.2.840.10008.5.1.4.1.1.x"
(0002,0003)UIinstance_uid続く画像データに対するUID
(0002,0010)UItransfer_class転送構文UID。ここでは"1.2.840.10008.1.2"の暗黙VR。
(0002,0012)UIimplementation_classこのファイルを生成したアプリ実装UID
(0002,0013)SHimplementation_nameこのファイルを生成したアプリ実装名

これだけです。メタ情報の内容はDICOM通信のA_ASSOCIATEに相当し、明示VRリトルエンディアンでエンコードされます。このエンコードは、暗黙VRとはアイテムヘッダの構成が異なり、ヘッダ中にVRを明示する必要があります。

メタ情報内のタグフォーマット

VRが「OB,OW,OF,SQ,UT,UN,VR」のどれかの場合。(PS3.5の7.1.2)

オフセット名前概要
00-03h要素タグ(1234,5678)ならば 34h 12h 78h 56h
04-05hVR文字列VRを示す2byte文字列。
06-07hreserved00h 00h
08-0bh要素サイズ要素データの長さ。要素全体の長さ-12
0ch~要素データ偶数byteの要素データ。中身の形式はVRによる。

VRが上記以外の場合。

オフセット名前概要
00-03h要素タグ(1234,5678)ならば 34h 12h 78h 56h
04-05hVR文字列VRを示す2byte文字列。
06-07h要素サイズ要素データの長さ。要素全体の長さ-8
08h~要素データ偶数byteの要素データ。中身の形式はVRによる。

これだけ注意すればヘッダの構成は簡単です。ヘッダに続けて画像データを書き出します。

  • なお転送構文UID集合はメタ情報の中にのみ存在し、他の場所で書くことは禁止されています。
  • メタ情報以外の部分では「VR」部分は付きません。

メタ情報以外のタグフォーマット

オフセット名前概要
00-03h要素タグ(1234,5678)ならば 34h 12h 78h 56h
04-07h要素サイズ要素データの長さ。要素全体の長さ-8

基本的にはC-MOVE(C-STORE)で得られたデータ集合をそのまま書き出すだけです。メタ情報の転送構文UIDにて「1.2.840.10008.1.2」を指定しておけば、基本的に暗黙VRリトルエンディアン(同じく1.2.840.10008.1.2)により得られたデータをそのまま書き出せば良いことなります。

なお、VRがSQであるシーケンスデータは未定義長さ(0xFFFFFFFF)を持ちますが、ファイルに書き出すときに正しいシーケンスの長さ(シーケンス要素の長さ)を指定してあげます。*12

作成した画像ファイルはDICOM ViewerIrfanViewなどで表示できます。これらの閲覧ツールは作成したファイルが正しいか確認するのに使用できます。ただコントラスト(WC/WW)を本来の値で表示したり変更したいときなど、前者のようなDICOM専用ビュアーで見るほうがよいでしょう。

*12 : K-PACSはそのように実装されていましたし、未定義長さのままファイル出力すると、まともに表示できるビュワーは存在しませんでした。

その他

Perlmagick(ImageMagick)で処理する際のメモ。

	$image->Set( 'dcm:display-range' => 'reset' );
	$image->Read( 'file.dcm' );
	$image = $image->[0];
	$image->AutoLevel();

DICOM関連の開発のご相談がありましたら、連絡ください。Windowsアプリでも、Webアプリでも何でも。