Carino DICOM マニュアル

Carino DICOMはDICOMゲートウェイであり、継続運用のための アプライアンスです。検査を受信し、振り分けて転送し、保持しているものをクエリ/取得と DICOMwebで公開し、PACSやRISが応答しなくなっても画像診断部門の仕事を止めません。本マニュアルは、 導入、セキュリティモデル、すべてのサービス、そしてこのソフトウェアの実際の限界を扱います。

ここに載せているスクリーンショットはすべて、このソフトウェアが実際に動いているところで、掲載のために作った架空の検査を保持しています。本マニュアルに患者は一人も登場しません。

目次

  1. これは何で、どんな役割を担うのか
  2. はじめかた
  3. セキュリティモデルとトークンのルール
  4. サービス、ひとつずつ
  5. これは医療機器ではありません
  6. ライセンスと相談先

1. これは何で、どんな役割を担うのか

Carino DICOMは、画像を生み出す機器と、それを保存したり読影したりするシステムとのあいだに 立ちます。あなたのアーカイブを置き換えようとしているのではありません。それらをつなぐ部品であり、 そのどれかが倒れたときにも立っている部品であろうとしています。

役割は二つあり、この区別はどのサービスを有効にするかを決めるので重要です。

すべては一つのconfig.jsonで動き、すべてヘッドレスで動きます。Webダッシュボードは 便利ですが、必須ではありません。

既定のポート

サービスポートプロトコル
受信(Storage SCP)11112DICOM / DIMSE
仮想プリント11113DICOM Print
Modality Worklist11114DICOM C-FIND
クエリ/取得11115DICOM C-FIND/C-MOVE/C-GET
緊急RIS2575MLLPで運ぶHL7
ダッシュボード + DICOMweb8042HTTP

なぜ104ではなく11112なのか。DICOMの登録済みポートは104ですが、これはLinuxと macOSでは特権ポートでrootを必要とします。既定値はそれを避けています。機器の設定をこれに合わせるか、 コンテナかファイアウォールで10411112へ公開してください。

2. はじめかた

動かす形を選ぶ

向いている場面停電のあとに自分で戻るか
デスクトップアプリ(トレイ)人が前に座っているPC。検査室ひとつ、診療所ひとつ、 お試しの評価。「ログイン時に起動」を設定した場合のみ
Docker / Podmanサーバー、あるいは隔離してまるごと移せる状態にしておきたい PC。戻る(restart: unless-stopped
systemdサービス画像診断部門の隅に据え置く常設のPCで、誰もログインして いないもの。戻る

デスクトップアプリ

お使いのOS向けのパッケージを リリースの ページからダウンロードして開いてください。システムトレイに収まり、クリックするとダッシュボードが 開きます。まだコード署名をしていないため、WindowsとmacOSは初回起動時に警告を出します。それでも開くための 正確な手順はトップページにあります。

初回起動時に、新しいバージョンが出ていないかをGitHubに確認してよいか、一度だけ尋ねます。「いいえ」と 答えても、質問を閉じても無効のままで、それが既定です。有効にしていると、新しいリリースはトレイのメニュー の一行として、また概要のパネルのバージョンの隣に現れます。どちらをクリックしても、ブラウザで リリースのページが開きます。何もダウンロードされませんし、勝手に入るものもありません。トレイのメニュー にある更新を確認のチェックでいつでも切り替えられます。これがあるのはデスクトップ版だけで、 コンテナやsystemdでの運用にはありません。

Docker

git clone https://github.com/MiguelCarino/Carino-DICOM
cd Carino-PACS
mkdir -p data && sudo chown -R $(id -u):$(id -g) data
docker compose up -d
docker compose logs -f pacs

初回起動でアクセストークンを生成し、ログに出力しますhttp://127.0.0.1:8042/を開き、ダッシュボードに尋ねられたら貼り付けてください。設定、 検査、ログ、索引 — すべては./dataにあり、バックアップすべきなのはここです。

初回起動でいちばん多い失敗は、./dataの所有者がrootになっていることです。 コンテナは決してrootでは動かないので、EACCESで終了します。お使いのアカウントが uid 1000でない場合は、PACS_UIDPACS_GID.envファイルに 書いて再ビルドしてください。SELinuxのホスト(Fedora、RHEL、Rocky)では、ボリュームの :zという接尾辞は飾りではありません。これがないと、所有権は完全に正しく見えるのに バインドマウントに書き込めません。

systemdサービス(Linux)

git clone https://github.com/MiguelCarino/Carino-DICOM.git
cd Carino-PACS
sudo packaging/systemd/install.sh

インストーラはcarino-pacsというシステムユーザーを作り、コードを /opt/carino-pacsへコピーし、/var/lib/carino-pacsを用意し、ユニットを 設置します… そして起動はしません。これは手落ちではありません。PACSを起動することは 患者データを受け付ける待ち受けを開くことであり、その判断は設定を読んだあとに下すものです。読む前ではありません。 /var/lib/carino-pacs/config.jsonを編集してから、次を実行してください。

sudo systemctl enable --now carino-pacs
systemctl status carino-pacs
journalctl -u carino-pacs -f

インストーラを再実行すると、コードはその場で更新され、既存のconfig.jsonには決して 触れません。

データの置き場所

ディレクトリ
デスクトップ / CLI~/CarinoDICOM/(既存のインストールは ~/CarinoPACS のままです)
Dockerコンテナ内の/data → ホストの./data
systemd/var/lib/carino-pacs/(モード0750)

config.json/etcではなくデータと一緒に置かれます。設定の中の 相対パス("./received""./logs""./index.db")は、設定 ファイル自身が置かれているディレクトリを基準に解決されるからです。/etcに設定を置けば、 患者の検査が/etcじゅうに散らばることになります。

初回起動、順番どおりに

  1. サービスを選びます。すべてオフの状態で出荷されます。ダッシュボードの初期設定の 画面が、このPCは何を動かすべきかを尋ねるので、それだけを有効にしてください。
  2. 送信先を追加します(名前、ホスト、ポート、AE title)。場所は設定一式送信先タブで、その行を開くと最初に出るタブです。
  3. C-ECHOで疎通を確かめます。画像を1枚も動かす前に: ./run.sh echo --name "Hospital PACS"。C-ECHOが通らないなら、問題はCarinoでは ありません。ネットワークか、ファイアウォールか、AE titleです。
  4. 機器を1台こちらへ向けます。受信のAE title、IP、ポートを使って設定し、テストの 検査を送ってください。
  5. アクティビティのログを見ます。アクティビティログタブです。 何かが失敗すればそこに出ます。このソフトウェアは、黙って失敗するより声を上げるほうを選びます。
ダッシュボードの初期設定の画面。「このPCで動かすサービスを選んでください」という見出しの下に、サービスごとのカード — 受信、自動送信、プリント受信、緊急RIS、ワークリスト、クエリ/取得 — が並び、それぞれに「使う」のチェックボックスがあり、どれも未チェックです。下部には「何も選ばれていません。このPCは受信も送信もしません。」という表示と、「今はしない」「オンにする」のボタンがあります。
手順1。何もチェックされておらず、何も動いていません。下部の表示がそう言っていて、適用するまでポートは一つも開きません。各カードにはそのサービスが何のためのものかが書かれているので、選択はプロトコル名ではなく説明を読んで決められます。
アクティビティのログタブ。時刻付きの流れです。起動時の行が待ち受けごとにその名前とポートを挙げ、赤いブロックがルーティングルールは匿名化を要求しているのにプロファイルがオフだと説明し、橙色の行が拒否されたアソシエーションと次の再試行の時刻を報告しています。
手順5。アクティビティの流れです。意味のある行はすべて、それについて何をしてほしいのかを述べます。ここの赤いブロックは対処を名指しする匿名化の保留で、橙色の行は接続を拒否された送信先が次の再試行を告げているものです。

ヘッドレスで動かす

./run.sh init            # config.json とそのフォルダを作る
./run.sh init --token    # あわせて web.auth_token を生成する
./run.sh serve           # ダッシュボードを http://127.0.0.1:8042 で
./run.sh receive         # 受信だけ
./run.sh send            # フォルダ監視 / 転送だけ
./run.sh qr              # クエリ/取得だけ
./run.sh mwl             # ワークリストだけ
./run.sh ris             # HL7オーダの受信だけ
./run.sh print           # 仮想プリンタだけ
./run.sh echo --host 10.0.0.5 --port 104 --aet REMOTEPACS

どのコマンドも-c / --config <path>を受け付けます。Windowsでは run.ps1です。

3. セキュリティモデルとトークンのルール

サインインの入口。「このPACSにはアクセストークンが必要です」と題したカードに、パスワードの入力欄がひとつ、サインインのボタン、そしてトークンは設定ファイルの web.auth_token であること、保存済みのトークンをダッシュボードが表示することはない旨の注記があります。
ダッシュボードの代わりにこれが出ます。あとから401になったときにも、また出ます。再起動すると設計上すべてのセッションが切れるので、もう一度サインインするのは不具合ではなく通常のことです。

まず要となる点から。ダッシュボードはアーカイブの鍵です。これを開ける人は、保存されて いるすべての検査を読み、DICOMファイルをダウンロードし、あらゆる設定を変更し、サービスを開始・停止し、 検査を削除し、サーバーを停止できます。初期状態では共有の秘密がひとつあるだけで、それを持つ人は何でも できます。プロファイルは任意で、有効にするまではオフです。有効にすれば、各自が自分と してサインインし、自分の権限、自分に見える患者の識別情報、そして監査証跡に残る自分の名前を持ちます。 共有トークンは管理者として動作し続けるので、すでにそれを使っている仕組みが壊れることはありません。

設定一式のスタッフタブ。管理者、IT、放射線科医、受付のプロファイルがあることを示す行の下に、管理者のカードが短く置かれています。「管理者(今後追加される権限も含めてすべて)」にチェックが入っているため、権限の表がまったくありません。その下のITのカードには「できる操作」のチェックボックスの表が並び、スクリーンショットは「見られる情報」の行の途中で切れています。右下には「追加」ボタンが固定されています。
プロファイルを有効にしたあとの姿です。権限は人ごとで、ブラウザの中に隠すのではなく、すべてのエンドポイントで強制されます。そして見られる情報できる操作とは別です。患者の氏名を一度も読むことなく、アクセッション番号で検査を振り直せるプロファイルを作れます。チェックの入っていない項目は、本来表示される場所すべてで***と表示されます。

権限はダッシュボードが何を描くかも決めます。プロファイルに見えるのは、その権限に見合うサイドバーの 行と、その中のタブだけです。routing.readを持ちconfig.readを持たない放射線科医 には、送信先ルーティングのある設定一式が見え、設定タブはありません。 そのどれも持たない受付には、設定一式の行そのものがありません。上部ヘッダのサービスチップは 意図的な例外です。下のWebダッシュボードを参照してください。

プロファイルを使うアプライアンスでの同じ入口。管理者、IT、放射線科医、受付のボタンがそれぞれ南京錠付きで並び、その下に「アクセストークンでサインインする」というリンクがあります。
プロファイルを使うアプライアンスが出す入口です。この選択画面が描かれるのはusers.list_profilesがオンのあいだだけです。そのポートに到達できる人へスタッフの一覧を公開するのは本物の情報開示であり、判断はあなたに委ねられています。トークンは下に畳まれて今も使えます。誰かが自分を締め出したときの、戻り道だからです。

ルール

web.auth_tokenが空であってよいのは、web.hostがループバックのあいだ だけです。web.hostがそれ以外 — 0.0.0.0、LANのアドレス、ホスト名 — で トークンが空なら、サーバーは起動を拒否します。この拒否は、迂回すべきエラーでは なく、機能です。

理由は具体的です。127.0.0.1で認証なしのとき、アクセス制御を担っているのはOSです。この PC上のプロセスだけがAPIに到達できます。それは筋が通ります。しかしweb.hostは運用者が 変更できる値で、誰かが「向こうのPCから入れるように」と0.0.0.0に書き換えた瞬間 — 急いで いて、火曜日で、セキュリティのことなど考えずに — その同じAPIは、LANの隣人の誰にでも検査の一覧、保存先の パス、DICOMのバイト列、そして/api/shutdownを差し出します。この変更が10秒で済み、その結果が 何年も続くからこそ、このルールがあります。

コンテナは常にその状況にあります。コンテナは仕組み上0.0.0.0で公開する からです。そのためイメージは、こちらがトークンを渡さなければ初回起動で256ビットのトークンを生成し、 それを出力します。決してしないのは、黙って生成することです。誰も見ていない秘密は、誰も更新 しない秘密です。

トークンの生成と渡し方

./run.sh init --token                                  # config.json に書き込む
python3 -c "import secrets; print(secrets.token_urlsafe(32))"
openssl rand -base64 32

サーバーは次のいずれの形でも認証情報を受け付けます。

クッキーがあるのは、ダッシュボードがトークンを尋ねるのを一度きりにするためです。 JavaScriptの中に持ち続ければ、あらゆるXSSとあらゆるブラウザ拡張がそれを読めます。クッキーがトークンを 運ぶことは決してありません。運ぶのは、起動時に生成してメモリ上だけに置いた秘密によるHMACです。だから 再起動すると全員がサインアウトします。運用者が一人のアプライアンスには、これが妥当な割り切りです。ディスク上に セッションの保管場所はなく、漏れるものもなく、最悪でも1シフトに一度トークンを打ち直すだけです。

ダッシュボードは平文のHTTPで通信します。Web層には組み込みのTLSがありません。ループバックの 外へ公開するなら、HTTPSを終端するリバースプロキシの後ろに置いてください。共有ネットワークで平文の HTTPに載せて送ったトークンは、渡してしまったトークンです。

DICOM側

このモデルが守らないもの

バックアップ。sqliteの索引はキャッシュで、自分で作り直せます。画像はそうではありません。 保存先のディレクトリをバックアップし、そして少なくとも一度は復元を試してください。

テレメトリはありません。ただし、あなた自身が入れる例外がひとつだけあります。エンジンは どこへも何も送りません。解析も、クラッシュレポートも、利用回数の集計も、実行時に取ってくる第三者の スクリプトもありません。外向きの接続は、あなたが設定したDICOMのアソシエーションとHL7の応答だけで、 接続先はあなたが名指しした相手だけです。そしてDocker、Podman、systemdでの運用はそれだけであり、 それ以上は何もしません。ここが肝心なところで、患者データを載せたまま検査室に置かれるのはその形だから です。デスクトップアプリだけは、これに加えて新しいバージョンが出ていないかを確認できます。ただし初回 起動時に一度だけ出る質問に「はい」と答えた場合に限られ、既定では無効で、トレイのメニューからいつでも また無効にできます。この確認はGitHubのリリース一覧に対する一日一回のHTTPSリクエストひとつだけで、 User-Agentのほかには何も付きません。識別子も、カウンタも、設定も、患者データもありません。したがって GitHubに分かるのはこのPCのIPアドレスと、アプリを起動したおおよその時刻だけで、それが全部です。何も ダウンロードされませんし、勝手に入るものもありません。これを超えるものを送る変更、あるいは誰も同意 していないものを送る変更は、脆弱性として扱われます。

アクティビティの監査タブ。パネルのヘッダにはタブの帯の上に「表示をクリア」「整合性を検証」「エクスポート」のボタンがあり、タブの下にはファイル数、ディスク上のサイズ、チェーン先端のダイジェストを示す行、その下に時刻付きの行が並び、どれもシステムがapiトークンでサインインした記録で、いずれもokです。
監査証跡と、その上に印字されたチェーンの先端です。このダイジェストこそ、このPCが到達できない場所へ写しておく値打ちのある値です。三つのボタンはタブではなくパネルのものなので、表示をクリア整合性を検証エクスポートログでも同じように手元にあります。拒否は成功と同じ忠実さで記録されます。断られた要求も、ここでは他と変わらない一行です。

4. サービス、ひとつずつ

ポートを開くものはすべてオフの状態で出荷されます。正しい問いは「何ができるか」ではなく「このPCは何をする必要があるか」です。オンにしたサービスが増えれば、開いたポートもその分だけ増えます。下記の索引だけが唯一の例外で、これは何も待ち受けないローカルのsqliteキャッシュなので、自動的に起動します。

サービスのパネル。サービスごとにカードが並びます — 受信、自動送信、プリント受信、緊急RIS、ワークリスト、クエリ/取得 — それぞれに緑のドット、停止ボタン、AE title、待ち受けの内容、カウンタが表示されています。
各サービスはそれぞれ個別に開始・停止し、どのカードにも見ておくべき数字が載っています。処理した件数と、失敗した件数です。ダッシュボードはエンジンそのものではありません。ここで止めれば止まるのはその待ち受けだけで、アプライアンスではありません。
概要のパネル。稼働中のサービス、受信数、送信数、滞留、保留、未処理オーダ、空き容量を数えるタイルが並び、その下にこのPCのアドレス、受信、設定ファイルと保存先のパス、各送信先への到達可否、最後に受信した検査を示すカードがあります。
概要は「このPCは動いているか」にひと画面で答えます。行き先のあるものを数えるタイルはすべて、それを担当するパネルへのボタンになっていて — 滞留保留はそれぞれ検査の自分のタブを開きます — 思いがけない数字はその詳細までワンクリックです。空き容量だけは例外です。開く先がないので、何もしないボタンにはせず、そのままの表示にとどめています。

受信 — Storage SCP

ポート11112 · C-STORE、C-ECHO

できること
モダリティから送られてきた検査を受け取り、ディスクに保存します。患者 / 検査 / シリーズ別に整理することもできます。すべての転送構文を受け付け、圧縮されたオブジェクトはそのまま保存します。トランスコードは行わないため、入ってくる途中で何かが変えられることはありません。
オンにする場面
ここへ画像を送信するものがある場合です。モダリティ、他のPACS、ワークステーションなど。これが中心となるサービスです。
オフのままでよい場面
このPCが、他の何かがフォルダに置いたものを転送するだけの場合です。
検査の履歴タブ、📥受信の表示。検査ごとに患者、ID、日付、説明、モダリティ、画像数の行が並び、その下にシリーズが一覧され、行ごとに送信、添付、タグ編集、フォルダを開く、削除の操作が付いています。
届いたものが新しい順に、各検査を構成するシリーズとともに並びます。フォルダを開くは、実際にディスク上で落ち着いた場所を開きます。整理されているのは本物のフォルダツリーであり、誰かがエクスポートしなければならないデータベースではありません。

自動送信 — Storage SCUとルーティングルール

外向きクライアント · C-STORE

できること
フォルダを監視し、新しいファイルを該当するすべての送信先へ転送します。ファイルが送られるのは安定している(2回のスキャンでサイズが変わらない)と判断されてからなので、書き込み途中のファイルが転送されることはありません。進捗は送信先ごとに管理され、有効なすべての送信先が受け取って初めて完了として数えられ、失敗したホストは次のスキャンで再試行されます。成功したら、元のファイルはそのまま残す・移動する・削除するのいずれかを選べます。
ルール
ルーティングを有効にすると、ルールがモダリティ、発信側 AE title、ステーション、患者ID、検査説明で送信先を選びます(*? のワイルドカードは大文字小文字を区別しません)。例: ER_* からのCTを匿名化して教育用アーカイブへ。
保証すること
検査がどこにも送られずに終わることは絶対にありません。ルーティングがオフ、一致するルールがない、ヘッダが読めない、もう存在しない送信先を名指ししているルール — そのどれもが、有効なすべての送信先へ送るという動作に戻ります。送りすぎは運用者を煩わせますが、送り足りなければ画像が失われます。
唯一の例外
ある送信先について匿名化を要求しているルールがあり、その除去を実際には実行できない場合、その送信先は保留されます。識別可能なまま送るのではなく、そこへは一切送信しません。除去を止める原因は、プロファイルがoffであること、またはプロファイルはオンでも現在の設定から匿名化処理を構築できないことで、対処はそれぞれ異なります。どちらの場合も失われるものはありません。下の転送時の匿名化を参照してください。
オンにする場面
このPCが画像を先へ届ける必要がある場合です。中央のPACSへ、読影用のワークステーションへ、あるいは教育用アーカイブへのコピーとして。
設定一式の送信先タブ。Main archive、Reading room、Teaching archiveの行が並び、それぞれにホスト、ポート、AE title、TLS・RISなし・緊急時のプライマリのチェックボックス、Echoボタンがあります。
送信先はここで名前を付け、他のあらゆる場所ではその名前で参照されるので、ノードの名前を変えても、それを指しているルールが迷子になりません。Echoは、実際の画像をそこに託す前にアソシエーションが成立するかを確かめます。
設定一式のルーティングタブ。「ルールで振り分ける」のチェックボックスがあり、その下に番号の付いたルールが並びます。各ルールにはモダリティ、発信側AE、ステーション、患者ID、検査説明の照合欄と、送信先のチェックボックスが並ぶ「送信先」の行、そして「匿名化」と「ここで停止」があります。
ルールは上から順に読まれ、空欄はすべてを意味します。ルールは、すべて埋めるフォームではなくフィルタです。順序が効くので、各ルールには並べ替えるための矢印が付いています。
検査の滞留タブ。対応が必要なファイルがあることを知らせるバナーの下に、「自動で再試行中」と「保留中 — 何も送信していません」が並びます。前者にはTeaching archiveの行があり、待機中のインスタンス、赤字の直近のエラー、「今すぐ再試行」ボタン、次の試行までのカウントダウンが出ています。後者には「プロファイルがオフ」と付いたMain archiveの行があり、待機中のインスタンス、該当する.dcmファイルの名前、それらを解放する設定変更を説明する段落、そして「匿名化の設定」と「それを要求しているルール」のボタンがあります。
ここに並ぶ上下は別々の問題で、タブはそれらを分けたままにします。上は落ちているノードで、これは自然に解消します。下は匿名化の保留で、これをタイマーが解除することは絶対にありません。だから各行には、それを解除する設定変更が書き出され、その場所へ行って実行するためのボタンが添えられています。再試行できる送信先がありませんというセクションは、そこに入れるものがあるときだけ描かれます。有効な送信先ではなくなった名前に振り分けられたファイルです。接続できるノードが残っていないため、それらを再試行するものはありません。対処は、その送信先を復旧させるか、失われることを受け入れるかで、どちらかが起きるまで、それらはアーカイブも削除もされずに送信フォルダに残ります。

転送時の匿名化

PS3.15 Annex Eのプロファイル · 出ていくコピーにのみ適用

できること
送信されるオブジェクトにBasic Application Level Confidentiality Profileを適用し、どの保持オプションを使ったかを(0012,0064)に明記するので、受け取った側は何が残されたかを確認できます。アーカイブされた原本が書き換えられることは絶対にありません。この非対称こそが要点です。
プロファイル
basicは日付(そのまま、またはシフト)、患者の身体的特性、機器の識別情報、施設の識別情報を保持します。strictは機器と施設の識別情報を落とし、保持を指定していてもプライベートタグを削除します。
オンにする場面
画像が臨床環境の外へ出る場合です。教育、研究、ベンダー、外部へのセカンドオピニオンなど。
オフにするとき
そもそも除去を行うかどうかを決めるのはプロファイルで、どの送信先が除去済みのコピーを受け取るかを決めるのはルールの匿名化のチェックです。ルールがまだ匿名化を要求しているのにプロファイルをoffにすると、Carinoはその送信先を保留し、匿名化されたものしか受け取らないと伝えられているノードへ識別可能なコピーを送ることはしません。

保留されているのであって、送信されたのではありません — そして検査が動かなくなったときの答えはこれです。ルールが匿名化を指定している送信先は、その除去を実際に実行できない限り、何も受け取りません。配信は先送りされますが、識別情報は開示されません。ディスクで待っている検査は設定を直せば解放されますが、外部のノードに届いてしまった氏名は、どんな設定変更でも取り戻せません。

除去が起きない理由には種類があり、直し方は同じではありません。どの保留にもそのどちらであるかが記録され、検査滞留タブは、推測ではなく記録された原因に対応する対処を表示します。

  • プロファイルがoffなのに、ルールがまだ除去を要求している場合です。プロファイルをbasicstrictにすれば、次の自動送信で解放されます。
  • プロファイルはオンでも、現在の設定から匿名化処理を構築できなかった場合です。この場合も除去に使えるものがありません。それを止めた失敗はアクティビティログタブの送信のチャンネルに出ているので、そちらを直してください。プロファイルをオフにしてもこちらは解放されません。何も解放されず、除去を止めている側が入れ替わるだけです。

ルールの匿名化のチェックを外せばどちらの保留も解放されますが、検査は識別可能なまま解放されます。それは、この保留が防ぐために存在している、まさにその結果です。その送信先が本当にもう匿名化されたデータを受け取る対象ではない場合にのみ、この変更を行ってください。

どの変更が正しいにせよ、それは保留を報告している行からワンクリックです。保留された各行には、設定一式設定タブを開く匿名化の設定ボタンと、ルーティングタブを開くそれを要求しているルールボタンが付いています。どちらもタブを開くだけで、除去を要求しているルールまでスクロールすることも、それを強調表示することもありません。それでも、問題を示している行が、それを直す両方の場所への入口になっているので、それぞれをサイドバーの行から探していたころより、直すまでの手数は少なくなっています。

失われるものはなく、黙って起きることもありません。検査は送信フォルダに残り、アーカイブも削除もされません。同じ検査の他の送信先には引き続き届きます。アクティビティログタブは、検査、送らなかった送信先、その原因を挙げたエラーを出します。設定タブの匿名化のフィールドセットもそれを示し、検査の横の⚠バッジがそれらのファイルを数えます。そしてそのバッジ自体がボタンで、滞留タブを開きます。保留が独りでに解除されることはありません。時間切れを進めるタイマーもなければ、再試行するものもありません。

ピクセルはきれいにしません。画像に焼き込まれた患者情報 — 超音波やSecondary Captureがフレームの中に印字するバナー — は、ここのどのプロファイルを使っても残ります。そしてそれこそが、「匿名化済み」のデータが氏名を付けたまま病院の外へ出ていく、最も多い経路です。Clean Pixel Dataのオプション(113101)は、実際に行っていないので意図的に宣言していません。この漏えいをプログラムの内部から検出することはできません。画像は人が見る必要があります。構造化レポート(Structured Report)の中の記述文も同様に読み取りません。

索引

sqlite · index.db

できること
保存したファイルごとに1行を保持し、そこから患者、検査、シリーズの答えを導き出すので、検査の要約が、それがまとめているインスタンスとずれることは決してありません。クエリ/取得(Query/Retrieve)とDICOMwebの裏側にある問い合わせ層です。
オンにする場面
クエリ/取得やDICOMwebを使う場合です。どちらもこれだけに依存しています。
保証すること
索引はキャッシュであり、正ではありません。失っても再スキャンで済み、画像が失われることはありません。

クエリ/取得 — C-FIND、C-MOVE、C-GET

ポート11115 · Patient RootとStudy Root

できること
他のシステムから「この患者の検査は何がありますか」と尋ね、続いて「こちらへ / あのワークステーションへ送ってください」と頼めるようにします。古い機器が実際に話せるのは、PACSのこちら側です。2009年の超音波装置やCRリーダーがDICOMwebを話すことは決してありませんが、DIMSEなら話せます。
オンにする場面
ワークステーションやモダリティがここから検査を取得する必要がある場合、あるいはプライマリPACSの障害の間、このPCが一時的なアーカイブとして働く場合です。
保証すること
C-MOVEがインスタンスの一覧を勝手に作ることはありません。必ず索引を通して解決します。索引は知っているのにディスクから読み出せなかったものは失敗したサブオペレーションとして数えられ、Failed SOP Instance UID Listに名前が載ります。黙って落とすことは絶対にありません。一致した数より少ない画像しか送っていないのに成功を報告するC-MOVEは、このソフトウェアが起こしうる最悪の失敗です。

DICOMweb — QIDO-RS、WADO-RS、STOW-RS

HTTP、ダッシュボードのポートの/dicom-web配下

できること
最近のビューア(OHIF、Weasisなど)が、DICOMのアソシエーションを交渉することなくHTTPで検索・取得・保存できるようにします。STOW-RSのアップロードはC-STOREと同じ保存処理を通るため、ビューアから投稿された検査は、モダリティが送ってきたものと区別がつきません。
オンにする場面
アーカイブに対してWebビューアを使いたい場合です。これらの経路もトークンが守っていることを忘れないでください。
CORS
cors_origins完全一致で照合され(スキーム + ホスト + ポート)、既定では空なので、一覧にないオリジンへ何かが返されることはありません。パターン構文はありません。ただしリテラルの*有効で、それは運用者が自分でそう入力したからです。それ以降はすべてのオリジンに応答が返り、運用者が開いたどのページからでも、このPCのアーカイブを読めてしまいます。ビューアを名指しで指定してください。
できないこと
/rendered/thumbnail、bulkdataのURI、転送構文どうしのトランスコードは実装しておらず、406を返します。中途半端に動くビューアは、機能がないことより悪いです。作れないものを偽ることは絶対にありません。

Modality Worklist(MWL)

ポート11114 · ワークリストのC-FIND

できること
技師が患者情報を手入力しなくて済むよう、モダリティへオーダを提供します。各オーダには事前に生成されたStudy Instance UIDが入っていて、それが検査に焼き込まれるため、モダリティが返してくる検査はそのオーダと正確に突き合わせられます。オーダの対象装置のAEを設定すると特定のステーションだけに送られ、空欄ならすべてのステーションに表示されます。
オンにする場面
RISがモダリティに届いていない場合です。RISが止まっているから、その場所にそもそもRISがないから、あるいは実際のRISなしでRIS→PACSの流れを試すためです。

登録済みのモダリティ

ポートなし · 設定一式 → モダリティ

できること
この装置が面倒を見る検査室の一覧です。人が見て分かる名前、ワークリストが完全一致で照合するステーションのAE title、モダリティのコード、必要なら装置名。これは許可リストではありません。ここに登録がなくても画像は送れますし、登録があるから送れるようになるわけでもありません。得られるのは、オーダの宛先が手入力ではなく一覧から選ぶものになること、そしてワークリスト確認がAE titleを借りる先がこの一覧になることです。
オンにする場面
ワークリストを配信するとき、あるいはオーダを手入力するとき。ステーションが一つも登録されていない間は、オーダの宛先は手で打つしかなく、打ち間違いを捕まえる手立てがありません。

ワークリスト確認 — 相手のRISに尋ねる

送信側クライアント · ワークリストのC-FIND · 設定一式 → モダリティ

できること
「なぜこの装置に自分のリストが出ないのか」を、装置に触らずに切り分けます。病院の本物のRIS(またはそのブローカ)のアドレスを設定で与え、モダリティの行のワークリスト確認を押すと、Carinoはそのモダリティ自身のAE titleを使い、そのモダリティが投げるはずの問いを相手のRISに投げ、返ってきたものをすべてアクティビティ取得結果に記録します。一度の実行で同じ問いを鍵を一つずつ外しながら五回投げます — ステーションと日付とモダリティ、次にモダリティなし、日付なし、ステーションなし。障害の位置を教えてくれるのは、答えどうしのだからです。どれにも何も返らなければ相手のRISに予定が入っていないということですし、いちばん狭い問いだけが空で返るなら、間違っているのはステーションのAE titleです。
押す前に
先にそのモダリティをネットワークから外してください。この確認はそのモダリティとして接続します。一つのRISに対して同じAE titleで二つが応答する状態は、あとから人が行って解かなければならない混乱です。
できないこと
サービスでも、予定表でも、キューでもありません。取得結果にあるのは他人が答えた内容の記録です。それらのオーダがこの装置自身のワークリストで配信されることはなく、この装置はどれも自分のものだと主張しません。クリアはその記録を空にするだけです。

緊急RIS — HL7オーダ

ポート2575 · MLLPで運ぶHL7のORM^O01

できること
MLLPでHL7オーダを受信します。上流が何も生きていないときには、ダッシュボードから手入力することもできます。検査がC-STOREで戻ってくるとアクセッション番号(なければ患者ID)でオーダと照合され、オーダは完了し、証跡のためにアーカイブされます。消されることは絶対にありません。
オンにする場面
RISが止まっているとき、または連携をテストしているときです。
保証すること
画像の配信がオーダとの照合を条件にすることは絶対にありません。一致するオーダのない検査も、保存され転送されます。オーダは手作業で突き合わせるために未処理のまま残るだけです。
RISオーダのパネル。「➕ 新規オーダ(手入力)」の開閉欄と、「🟢 未処理」「✓ 完了」のタブがあり、未処理オーダにはアクセッション番号、患者、モダリティ、依頼検査、HL7の送信元、登録した時刻が表示され、「📎 検査を取り込む」「取り消し」「削除」の操作が付いています。
HL7で届いたオーダと、上流が何も生きていないときに手で入力するための、上部の開閉欄です。対応する検査が届くとそれぞれ自分で完了し、消されるのではなくアーカイブされます。

TLSも認証情報もありません。セキュリティの節を参照してください。そのポートにソケットを開ける人は、誰でもオーダを注入できます。信頼できる臨床ネットワークとファイアウォールを用意するか、さもなければ使わないでください。

緊急フェイルオーバー

プライマリに指定した送信先のC-ECHO監視

できること
送信先をプライマリに指定して監視を有効化すると、Carinoが定期的にそこへC-ECHOを送り、転送の失敗も見ます。しきい値を超えて到達できないままなら、緊急RISを有効化するかどうかの確認が出ます。有効化すると、技師が検査を続けられるようローカルのワークリストを起動し、障害の間に受信した検査をすべて保留し、プライマリが戻ったら転送します。通常運用に戻すタイミングは運用者が決めます。
オンにする場面
このPCが、業務が依存しているPACSへのゲートウェイである場合です。本マニュアルの冒頭に「継続運用」という言葉があるのは、このためです。

通知 — WebhookとE-mail

送信のみ · HTTP Webhook、SMTP

できること
ダッシュボードを開いていない人に届かせます。画面上のバナーは転送を見ている人には十分で、これが存在する理由である場面には無力です。午前3時に主系が落ち、当直の放射線科医はどの画面の前にもいません。二つの経路は互いに独立していて、大元のスイッチが入るまでは両方とも動きません。
送るもの
フェイルオーバーの出来事だけです — 検知、切替、復旧。装置のほかのどの部分も通知を出しません。これは汎用の警報システムではありませんし、オンにしてもそうはなりません。
メールが届く相手
アドレスではなくプロファイルです。フェイルオーバーが通知先に指定していて、かつメールアドレスが入っている有効なプロファイルすべてに届きます。SMTPを設定してもどのプロファイルにもアドレスが無ければ、何も送られず、音も立てません。「メールはオンなのに誰も知らされていない」ときに最初に見るのはそこです。文面は役割によって変わり、落ちた送信先のアドレスは、送信先の表をすでに見てよい相手にしか含まれません。メールは建物の外へ出ていき、取り消せないからです。
絶対にしないこと
報告している当のものを遅らせたり壊したりすること。送信はすべてワーカースレッドに渡され、キューには上限があり — あふれれば古いものから捨て、捨てた回数を数えます。復旧後に届く「主系が落ちています」は沈黙より悪いからです — 失敗は例外ではなくカウンタとログ行になります。送信数、失敗数、破棄数、待ち数、最後のエラーはいずれも読めるので、「オンなのに一度も実際には届いていない」が、それに頼る障害のに見えます。試すのは静かな午後であって、障害の最中ではありません。

仮想プリント(ダッシュボードのカード名はプリント受信)

ポート11113 · DICOM Print、PDF出力

できること
DICOMプリンタとして振る舞い、送られてきたものをPDFとして取り込みます。出力がフィルムしかない機器から、保管できるものを残すための手段です。
オンにする場面
印刷しかできないモダリティがあり、その出力を救い出したい場合です。
検査の📎保留タブ。PDFとJPEGが順番待ちしており、それぞれ隣に届いた検査から患者名、患者ID、アクセッション番号、検査日、検査説明が入力済みで、「プレビュー」のリンクと「承認して送信」「破棄」のボタンがあります。
取り込んだフィルム — そして検査の隣で見つかったPDFや画像 — は、推測でDICOMに変換されるのではなく、その検査自身の情報が入った状態でここで待ちます。患者情報を確認してから承認してください。

Webダッシュボード

ポート8042 · HTTP、既定は127.0.0.1

できること
サービスごとの状態、送信先、オーダ、リアルタイムのアクティビティのログ、設定、そして同梱のDICOMエディタです。ダッシュボードでできることはすべてCLIにもあります。
画面の構成
サイドバーの行は概要サービス検査オーダ設定一式アクティビティの六つで、--dev-peerを付けて起動したプロセスにだけ七つ目のテスト用ノードが加わります。同じファイルの山に対して投げる問いが検査のタブで、履歴保留滞留です。機器を立ち上げるときに決めるものが設定一式のタブで、送信先ルーティング設定モダリティスタッフです。開くのは設定ではなく送信先です。そして記録がアクティビティのタブで、ログ監査取得結果です。最後の一つは、この装置があなたのモダリティの一つとして尋ねたときに相手のRISが答えた内容です。オーダは未処理と完了の独自の切り替えを持ちます。検査の行には、それ自体がボタンになっているカウンタ、📎保留と⚠滞留が付いていて、それぞれ自分のタブを開きます。つまり何かが滞留している間は滞留までワンクリック、何もないときは行、次にタブの二段階です。0という件数は自分を隠すからです。ずっと0を表示しているカウンタは鳴りっぱなしの警報と同じで、誰も見なくなります。オーダも同じ条件で自分の件数を持ちます。どのパネルにもタブにも固有のアドレスがあり — #studies/stuck#configuration/routing#activity/logs — ダッシュボードの中の場所をブックマークでき、障害の間に電話で読み上げられ、ブラウザの戻るボタンで離れられます。古い#dlgStuck形式のリンクも今も解決します。概要だけは例外です。#overviewと入力すれば応じますが、アドレスバーに書き込まれることはなく、履歴も残しません。患者名を表示するため、無人の画面が再読み込みのあとそこへ戻ってはならないからです。
ヘッダのチップ
上部に並ぶサービスチップは、サービスを開始・停止できる人にとってはこれまでどおりです。services.controlを持たないプロファイルからも指標としては見えますが、操作は無効になっています。サービスの状態は機密ではなく — 受付でも受信が落ちていることを見られる必要があります — サーバーが拒否するスイッチを差し出しても意味がないからです。
ループバック以外に公開する場面
具体的な理由があるときだけです。そのときはトークン(必須)とHTTPSを前段に置いてください。トークンのルールを参照してください。
設定一式の設定タブ。ワイド画面ではフィールドセットが列に分かれて並びます。受信のAE title、待ち受けアドレス、ポート、保存フォルダ、空き容量の下限、AEの許可リスト、TLSの各項目。自動送信の発信側AE、監視フォルダ、ポーリング間隔、ファイル送信後の動作、TLSの信頼設定。その横にプリント受信、フェイルオーバー、ワークリスト、RIS、クエリ/取得、DICOMweb、索引、匿名化、APIアクセス、連携のフィールドセットがあります。
config.jsonにあるものすべてを、フォームにしたものです。正はファイルのままです。ここでの編集はファイルに書き込まれ、ダッシュボードが保存を拒否するような設定は理由とともに拒否され、黙って書き換えられることはありません。
同梱のDICOMエディタでサンプルの検査を開いたところ。左に画像とウィンドウ中心・ウィンドウ幅のスライダ、右に患者、モダリティ、検査説明を並べた主要情報(臨床)のパネルがあり、患者名は「表示」ボタンの裏に隠されています。その下の技術情報のパネルにSOP Instance UID、転送構文、Photometric Interpretation、ファイルのSHA-256が出ています。
同梱のエディタは、履歴タブの検査から、または単独で開けます。すべてブラウザの中で動作し — どこにも何もアップロードされません — 患者名は最初は隠された状態です。ヘッダを開く最も多い理由が、UIDを読むことだからです。
エディタの編集タブ。すべて匿名化、すべてランダム化、+タグ、JSON、CSV、印刷、すべてダウンロードのツールバー、読み込んだシリーズの帯、患者・検査・シリーズ・画像・機器・UID・プライベートタグの絞り込みチップ、属性ごとに1行、グループ番号と要素番号、説明、VR、編集できる値が並ぶタグ表、そして右三分の一には画像編集パネル(回転・反転・階調反転・焼き込まれた文字の塗りつぶし。いずれも保存画素に書き込まれます)と、その下にプレビュー、折りたたまれたウィンドウ/レベルの値、ファイルのドロップ領域があります。
タグ表は、検査を再送する前にヘッダを直す場所です。誤ったアクセッション番号や打ち間違えた患者IDを、すでに次の作業へ移ってしまったモダリティではなく、ゲートウェイで直します。

テスト用ノード — 使い捨ての第二アーカイブ

ループバックのみ · pacs serve --dev-peer · 配布ビルドには決して入りません

できること
同じプロセスの中に、まるごと第二のアーカイブを作ります。127.0.0.1のみで待ち受け、自分のAE title、自分の受信ポートとQuery/Retrieveポート、一時フォルダの中の自分の保存領域を持ち、この装置側にはそこを指す送信先が最初から用意されます。転送、ルーティングルール、転送時の匿名化、C-MOVEが思ったとおりに動くかを確かめるあいだ、検査が実際に行く先になります。併せて、無効状態の「ブラックホール」送信先も作られます。こちらを有効にすると送信はわざと失敗し、滞留タブを好きなときに埋めて、本番で起きる前に滞留とはどう見えるものかを確かめられます。
開き方
pacs serve --dev-peerでエンジンを起動し(ダッシュボードのURLの下の行にその旨が出ます)、サイドバーの🧪 テスト用ノードを選んでノードを作成を押します。あとはパネルがAE title、二つのポート、フォルダ、受信数を表示します。このフラグが無ければ行そのものが存在せず、APIは404を返し、config.jsonのどの設定でも呼び戻せません。これは意図した設計です。設定ならダッシュボードのトークンを持つ者なら誰でも変えられますが、起動時の引数はHTTPからはまったく届きません。
操作できる人
使い捨てのテスト用アーカイブを作成・破棄する権限を持つプロファイルです。既定のプロファイルでは情報システムと管理者がこれにあたります。転送がどこで死んでいるのかを示すのは検証台の仕事だからです。受付と読影医には与えられません。第二のアーカイブは検証台の道具であって、臨床の道具ではありません。作成も破棄も、どちらも監査証跡に記録されます。
保存された中身の行方
破棄を押したとき、プロセスが止まったとき、そして強制終了や停電のあとの次回起動時に、すべて消えます — フォルダも、この装置側の二つの送信先の行も。何も残りません。第二の設定を手で書いてフォルダごと忘れるかわりにこれを使う理由は、まさにそこにあります。一つだけ心づもりを。まだ送信待ちが残っているまま破棄すると、その送信は再試行する先を失います。検査滞留で片付けてください。

これは検証台の道具であり、それを保っているのがこのフラグです。ノードはループバック限定で、例外はありません。ネットワーク上のモダリティから見つけられるテスト用アーカイブは、誰かの画像の、監査されない第二の保管庫になってしまい、しかもそこにあることに誰も気づきません。実際の検査を扱っている装置でこのフラグを渡さないでください。デスクトップ版とコンテナ版が代わりに渡すことは決してありません。

5. これは医療機器ではありません

Carino DICOMは医療機器ではありません。どの国のどの規制当局からも、認証・承認・登録を受けて いません。医療機器としてのCEマーキングも、FDAのクリアランスも、ANVISA、COFEPRIS、その他同等の機関 への登録もありません。臨床使用のバリデーションを実施した者もいません。

一次診断のためのものではありません。ここに診断用ビューアはありません。ウィンドウ調整も、 計測も、較正された描画経路も、表示に使うディスプレイの制御もありません。検査は、すでにお持ちの バリデーション済みのワークステーションで読影してください。

注釈にするより率直に書くほうが役に立つので、はっきり述べます。

これは悲観ではありません。このプロジェクトは黙って届かない画像は、クラッシュより悪いという ことを真剣に受け止めています。だからこそ、起動を拒否し、失敗したサブオペレーションを明示的に数え、 送り足りないより送りすぎるほうを選びます。配信を差し止めるのはただ一つの場合だけで、それも声を上げ、 取り消せる形で行います。ルールが匿名化を要求している送信先で、約束された除去を実行できないとき — プロファイルがオフであるか、プロファイルはオンでも匿名化処理を構築できなかったときです。そのどちらで あるかを述べます。二つは直し方が違うからです。しかしソフトウェアが答えられるのは自分がすることまでで、 残りはあなたのものです。

6. ライセンスと相談先

Carino DICOMはAGPL-3.0-or-laterで公開されています。ネットワークサーバーであるため §13が適用されます。改変版をサービスとして運用するなら、それを使う人々にソースを提供しなければなりません。 改変した配備にはLICENSEファイルと、ソースへの参照を残してください。

本マニュアルの誤り — あるいは放射線科医なら決して言わない訳 — を見つけたら、issueを立ててください。 日本語をはじめとする各言語のドキュメントは、おまけではなくプロジェクトの一部です。

Carino DICOM · トップページ · carino.systemsの工房の一部 · AGPL-3.0-or-later.