スキップしてメイン コンテンツに移動

dcm4cheを使ってDICOM画像を表示する

DICOM画像を開いてみる準備


Eclipseを使って、Mavenプロジェクトを作ります。
ImageJもdependencyに追加しておいてください。
JPEGなどで圧縮されている場合は、ページ一番下の読み込みを。

(pom.xmlの例)
<project xmlns="http://maven.apache.org/POM/4.0.0"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <groupId>com.visionary</groupId>
 <artifactId>dcm4che_tutorial</artifactId>
 <version>0.0.1-SNAPSHOT</version>

 <repositories>
  <repository>
   <id>www.dcm4che.org</id>
   <name>dcm4che Repository</name>
   <url>https://www.dcm4che.org/maven2/</url>
  </repository>
 </repositories>

 <dependencies>
  <!-- https://mvnrepository.com/artifact/org.dcm4che/dcm4che-core -->
  <dependency>
   <groupId>org.dcm4che</groupId>
   <artifactId>dcm4che-core</artifactId>
   <version>5.18.0</version>
  </dependency>
  <!-- https://mvnrepository.com/artifact/net.imagej/ij -->
  <dependency>
   <groupId>net.imagej</groupId>
   <artifactId>ij</artifactId>
   <version>1.52p</version>
  </dependency>
 </dependencies>
</project>


DICOM画像を開く


前提です。
DICOM画像であること。つまり、ピクセルが含まれていること。構造化レポートはピクセルがないので、以下のコードでは動きません。(ピクセルを持たないDICOMファイルの読み込みヒントはコードに。)
今回はRGB形式の画像読み込みです。この読み込みの仕組みを理解できれば、ShortでもFloatでもDoubleでも、それがグレースケールでもRGBでも、マルチチャンネルでも、読めるようになるのではないか、という淡い期待から。

DICOM画像を開く

ImageJを使って開いてみますが、Javaのオリジナルで開く方法と、ImageJで開く方法を紹介する。
ImageIO.read(File dicom);を使うと早いが、この方法では、画像が圧縮されていた場合に気づかずにそのまま処理してしまうので、DICOM属性を確認して、DICOM画像の性格を見越して、処理したほうがいい。
ピクセルをByteBufferという最小単位に分解してから、ピクセルを構築する。

まず、DICOMファイルを読み込みます。

 String img_path = "C:\\Users\\RT-Mii\\Desktop\\test-dcm";
 File f = new File(img_path);
 if (f == null) {
    return;
 }
 DicomInputStream dis = null;
 Attributes dcm = null;
 try {
   dis = new DicomInputStream(f);
   dcm = dis.readDataset(-1, -1);
   //if you want to read dcm file which not included pixeldata,
   //dcm = dis.readDataset(-1, Tag.PixelData);
  } catch (IOException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  } finally {
   SafeClose.close(dis);
  }

画像の種類と画像フォーマットを調べます。
(番号が取得できるので、詳細はDICOM標準規格から検索して下さい。)
(ここで、思うところがあれば、先に非圧縮したりなどの前処理をします。)

  System.out.println(dcm.getString(Tag.SOPClassUID));//種類
  System.out.println(dcm.getString(Tag.TransferSyntaxUID));//形式

ピクセルデータを取り出します。

  byte[] pixels = null;
  try {
    pixels = dcm.getBytes(Tag.PixelData);
  } catch (IOException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
  }

byteはCPUによって並び順が上下する(BigEndian/LittleEndian)ので、今回はIntel向けにリトルエンディアンにして、念のため整列させておきます。(おそらく、ビッグエンディアンでも大丈夫なはず。。)

  ByteBuffer buffer = ByteBuffer.wrap(pixels).order(ByteOrder.LITTLE_ENDIAN);
  pixels = buffer.array();

後から使うので、画像の高さと幅を取得しておきます。

  int w = dcm.getInt(Tag.Columns, 0);//0は、タグに値がなかった場合の用意
  int h= dcm.getInt(Tag.Rows, 0);

ピクセル当たりのデータ量を確認しておきます。
BitsAllocatedは1ピクセルあたりに割り振られる容量です(aRGBのカラーでも、この値はカラーチャンネルごとのピクセルという意味で8-bitになります)。
SamplesPerPixelはピクセルのもつチャンネル数です。例えば、RGBの場合、3などですね。

  System.out.println(dcm.getString(Tag.BitsAllocated));
  System.out.println(dcm.getString(Tag.SamplesPerPixel));//ここでは3

今回はRGBを対象としているので、SamplesPerPixelは3です。
では、pixelsとSamplePerPixelの整合性を確認します。
pixelsの中には、1ピクセル当たり3つのbyte(r,g,b)が整列しているはずです。
よって、pixels/SamplePerPixel == x*hとなるはずです。

  System.out.println(pixels.length/3);
  System.out.println(w*h);

ここまで確認出来たら、あとはピクセルを再構築して、画像を表示します。

2つ、やり方を紹介します。
先に、byteピクセルをintにまとめるために、ピクセルの受け皿を用意します。

  int[] rgb = new int[pixels.length/3];

1.ImageJで楽ちんする

javaの場合、通常、intは32bitですから、aRGBの4要素に、それぞれ8ビットずつ割り当ててやります。ビット演算でこのようにできます。
RGBの部分はもとのpixelsから3つずつ順に取得して、アルファ(透明)の設定は今回はないので、すべて透明になるようにそのまま値を入れます。

 for (int i = 0; i < pixels.length / 3; i++) {
     rgb[i] = 0xff << 24 // Alpha (all opaque)
              | ((pixels[i * 3] & 0xff) << 16)
              | ((pixels[i * 3 + 1] & 0xff) << 8)
              | ((pixels[i * 3 + 2] & 0xff));
 }

(補足)
  • あるピクセルのRED成分:(pixels[i * 3] & 0xff)
  • あるピクセルのGREEN成分:(pixels[i * 3 + 1] & 0xff)
  • あるピクセルのBLUE成分:(pixels[i * 3 + 2] & 0xff)
ImageJのColorProcessorというクラスを使ってみます。

   ColorProcessor cp = new ColorProcessor(w, h, rgb);

画像を確認します。

   ImagePlus imp = new ImagePlus("",cp);
   imp.show();

2.Javaのクラスをゴリゴリ使います。

今回、pixelsはbankというピクセル位置ごとにRGBが整列しているbyte[]になっています。これを使って画像を作ることもできます。
実際には、カラーの表示方法や、透明度の設定、ピクセルの性質(byte or short or float or double)など、画像にするまでに詳細な設定があります。

   ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);
   ColorModel cm = new ComponentColorModel(cs, false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE);
   SampleModel sm = cm.createCompatibleSampleModel(w, h);

ここで、バンクデータであるpixelsを使います。

   DataBufferByte db = new DataBufferByte(pixels, w*h);
   WritableRaster raster = Raster.createWritableRaster(sm, db, null);
   BufferedImage result = new BufferedImage(cm, raster, false, null);

画像を確認します。

   ImagePlus imp = new ImagePlus("",result);
   imp.show();

個人的には、1.の方法が分かりやすかったです。

グレースケールの読み込みも似たような方法でできます。DICOM画像はほとんど16bit-グレースケール(つまりピクセルごとの値はshortで、ピクセルごとのサンプル数は1)なので、ByteをShortにすることを意識して、上のコードを改変すればよいはずです。
aRGBは4つの配列を意識しますが、グレースケールは1つの配列なので、もっと楽でしょう。

(追記)16ビットグレースケール画像を開く

 void show16bit(byte[] pixels,int w,int h) {
  short[] shortArray = new short[pixels.length/2];
  ByteBuffer byteBuf = ByteBuffer.wrap(pixels).order(ByteOrder.LITTLE_ENDIAN);
  byteBuf.asShortBuffer().get(shortArray);
  System.out.println(shortArray.length);
  //https://forum.image.sc/t/how-to-initialize-the-shortprocessor/23689/2
  ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_GRAY);
  ColorModel cm = new ComponentColorModel(cs, false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE);//allways byte for colormodel!
  ShortProcessor sp = new ShortProcessor(w, h, shortArray, cm);
  ImagePlus imp = new ImagePlus("",sp);

  imp.show();

 }

(追記) 圧縮形式のDICOM画像の場合

BufferedImage bi = null;
File f = new File(path);
//  ImageIO.scanForPlugins();
//  Iterator<ImageReader> iter = ImageIO.getImageReadersByFormatName("DICOM");
//  ImageReader reader = (ImageReader) iter.next();
try {
 DicomImageReader reader = (DicomImageReader) new DicomImageReaderSpi().createReaderInstance();
 DicomImageReadParam param = (DicomImageReadParam) reader.getDefaultReadParam();
 FileImageInputStream fiis = new FileImageInputStream(f);
 reader.setInput(fiis,false);
 int numFrame = reader.getNumImages(true);
 if(numFrame > 1) {
   System.out.println("CanonTotalReportExtractor: this file is frame data, return.");
   return new Object[] {null, null};
 }

 /*
  * なぜだかEclipse上では動作するが、
  * リリースすると動かない。
  * bi = ImageIO.read(f);
  */

//   bi = ImageIO.read(f);//対応していない種類の画像だったときは例外は発生せず、nullが返る。

 bi = reader.read(0, param); //frameNoOfImage and Param.this case always index is 0.
 fiis.close();

 } catch (IOException e) {
  // TODO Auto-generated catch block
  e.printStackTrace();
}

Visionary Imaging Services, Inc.
2019/9/5


コメント

このブログの人気の投稿

DockerでDICOMサーバを使いまわす

DCM4CHEEがだいぶ前からDocker対応しているので、その手順をメモします。 基本的には、このページに記載の通りです。 https://github.com/dcm4che/dcm4chee-arc-light/wiki/Running-on-Docker 一番シンプルな構築方法でいきます。 https://github.com/dcm4che/dcm4chee-arc-light/wiki/Run-minimum-set-of-archive-services-on-a-single-host あくまでもテストケースですが、今回は仮想OSを立てて、その中にDockerを入れて、サーバーのホストにします。以下のようにしてみました。 大元のPCでの作業 VirtualBox(またはHyper-V)を用意する Ubuntu19.04を最小構成で仮想化する(メモリは4GB, 割り当て仮想保存容量127GBなど)。 (インストール時にイメージを再起動できない場合はPC本体を再起動するとよいことがあります) Ubuntu19.04を起動する。 Ubuntuで行う作業 Ubuntuをインストールしたら行うお決まりのコマンドを打つ sudo -i apt-get update apt-get upgrade vimを入れておく  apt install vim UbuntuにJavaを入れておく。(JDK9以上) apt install openjdk-12-jre-headless Dockerをインストールする apt install docker.io DCM4CHEE関連のイメージを取得する(一行ずつ実行) docker pull dcm4che/slapd-dcm4chee:2.4.44-19.0 docker pull dcm4che/postgres-dcm4chee:12.0-19 docker pull dcm4che/dcm4chee-arc-psql:5.19.0 docker pull dcm4che/keycloak:7.0.0 docker pull dcm4che/keycloak-gatekeeper:7.0.0 docker pull

dcm4cheをローカル環境にインストールする

DCM4CHEのインストール ここからダウンロードします。 執筆時点では(2019/11/5)、最新バージョンは5.19.0です。 https://sourceforge.net/projects/dcm4che/files/dcm4che3/ (リンクではdcm4che3となっていますが、バージョンでは5で、何か意図があるのかなと、思っています。) 環境変数パス ダウンロードができたら、解凍して、binフォルダまでの環境変数パスを通します。 Windows ならシステムの詳細設定からだったと思います。 MacやLinuxなら、.bashrcあたりにパスを追加します。 例えば、ubuntuで、localフォルダにコピーした場合ならば。 vim ~/.bashrc から、「i」でインサートモードにして、以下を追記して、 #dcm4che export PATH="$PATH:/usr/local/dcm4che/dcm4che-5.15.1/bin" 「esc」押して、「:」おして、wq!でエンター。 追記できたら、 $source ~/.bashrc パスが通せたか、dcmdumpコマンドで確認します。 > dcmdump dcmdump: missing file operand Try `dcmdump --help' for more information. このように表示されれば、インストールは完了です。 注意 DCMTKをインストールしている場合、コマンドが被ることがあります。 DICOMツールは何個もいらないので、DCM4CHEを使うときはDCM4CHEのみにパスを、DCMTKを使うときはDCMTKにパスを通すと競合しません。 Visionary Imaging Services, Inc.

はじめに

所信表明 VISブログ担当の小林です。 私の専門は医用画像情報です。 医用画像情報を扱う際に、DICOMデータの取り扱いは避けて通れません。 しかし、DICOMについて調べると、リソースが無くて困ることが多いです。 そこで、個人的に好きなツールである、dcm4cheを使ったDICOMのハウツーを備忘録としてこの技術ブログに残していくことにしました。 これまでにいろいろなオープンソースのDICOMツールを使ってきました。 DCMTK、GDCM、pixelmed.jarやpydicomなどです。 このブログではdcm4cheを使います。dcm4cheはこれらのツールに匹敵する強力なツールです。そして特筆すべき点として、堅牢なDICOMサーバ(dcm4chee)もこのツールを使ってオープンソースで提供されています。 dcm4che 2000年頃、Gunter Zeilinger氏は、商用のJava DICOM Toolkit(JDT)を使ってJDicomユーティリティを作成しました。 この後、彼は自身のDICOMツールキットを書くべきであると決めたそうです。 そして、dcm4che(d-c-m-for-chayと発音)が生まれました。 Gunter氏は、オープンソースの革命的な側面にインスパイアされて、有名な革命家にちなんで、ツールキットに名前を付けることにしたそうです。 当時、このツールキットのオリジナル版は、JavaベースのDICOM API用のJSR(Java Specification Request)としてSun(現在はOracle)に提出することを目的として設計されました。 そのことを念頭に置いて、ツールキットはインターフェース層と実装層に分けられました。 JSRには至りませんでしたが、プロジェクトはJava DICOM開発コミュニティの間で人気が高まり始めました。 そして、dcm4cheをDICOMバックエンドにしたdcm4jbossアーカイブが誕生したそうです。 このDICOMサーバアプリケーションを開発するサブプロジェクトは、IHE Image ManagerとImage Archiveアクターを実装しました。 dcm4jbossは、医用画像の研究および画像診断レポートを管理するための堅牢で安定したプラッ