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

Pixel extraction using DCM4CHE

Pixel extraction using DCM4CHE

take time 1 or 2 hours.

What you can do?

1: load dicom data(2D image only, this do not explain about video format.) as Attributes objects
2: Extract pixel array (in this case, only use LEE, JPEG2000, JPEG Baseline that are major transfer-syntax and 8 or 16 bit grayscale.)
3: show pixels as images

What prerequisite?

1: have java programming skills (need not high skill)
2: can use eclipse 2018 version or higher
3: can use maven project in eclipse
4: download sample dicom dataset from weasis projects(https://github.com/nroduit/dcm-samples)
5: use jdk 1.8 or higher (and set to maven projects it version)

Project Set-Up

1. create dcm4che_tutorial maven projects

- File > New > Other > Maven Projects
- check Simple archtype and setup on your need.

2. edit pom.xml

- add dcm4che Repository.
- add three libraries (dcm4che-core, jai_imageio, ij) to dependencies

in this case, for instance,

Example my pom.xml

<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" 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.22.1</version>
</dependency>
<dependency>
<groupid>com.sun.media</groupid>
<artifactid>jai_imageio</artifactid>
<version>1.2-pre-dr-b04</version>
</dependency>
<!-- https://mvnrepository.com/artifact/net.imagej/ij -->
<dependency>
<groupid>net.imagej</groupid>
<artifactid>ij</artifactid>
<version>1.52p</version>
</dependency>
</dependencies>
</project>

3. copy sample dicom dataset to project

- in this case, create  "sample" folder in project then copy dcm-samples-master folder (this file is weasis sample dicom dataset)

Extract pixels

At first, create new class "tutorial_opendcm.java" in project.

Case 1 : No compressed/decompressed images

These Transfer syntax are ;
  • 1.2.840.10008.1.2 Implicit VR Endian: Default Transfer Syntax for DICOM
  • 1.2.840.10008.1.2.1 Explicit VR Little Endian
  • 1.2.840.10008.1.2.1.99 Deflated Explicit VR Little Endian
  • 1.2.840.10008.1.2.2 Explicit VR Big Endian

Typing code in main method

1.Set sample dicom file path

//8 bits gray
String gray8 = "sample/dcm-samples-master/archive/1.2.276.0.7230010.3.200.1.1.1";


//16 bits gray
String gray16 = "sample/dcm-samples-master/archive/2.16.840.1.113662.2.2344286425913116080603163842909806166139";
//32 bits gray (important for parametric images, e.g, pet, spect ...) sorry, future work...
//...

//COLOR
//RGB

String rgb = "sample/dcm-samples-master/archive/1.2.40.0.13.1.1.75699434012420281431899874274436428076";

//set image
String img_path = gray8;//in this case, set gray8 image.


2.Load dcm file

File f = new File(img_path);
Attributes dcm = null;
String tsuid = "";
     
try( DicomInputStream dis = new DicomInputStream(f); ){
        dcm = dis.readDataset(-1, -1);
        tsuid = dis.getTransferSyntax();
        SafeClose.close(dis);
}


3.get pixel data as Objects

Object pixelData = dcm.getValue(Tag.PixelData);


4.check null for pixel data

//if pixelData is null, this dicom file is not dicom "image" objects.
if(pixelData == null) {
        System.out.println("this dicom file dose not have pixelData. return.");
        return;
}


5.check pixel data type(compress or not).

//no compressed
if(pixelData instanceof byte[]) {
        System.out.println("this dcm file is raw image (no compressed).");
//compressed
}else {
        //check Fragments for compressed pixel data.
        //in this tutorial, skip this topics to keep simplicity.
        //hint
        //Fragments fragmObj = (Fragments)pixelData;
        //BulkData pixelsBulk = frageObj.get(1);//get(0) is just Item tag, so start 1.
        //bulk to byte ... and read using ImageReader specified format e.g, JPEG2000, JPEG, RLE etc.
        System.out.println("this dcm file is compressed. return.");
        return;
}


6.cast to byte array from pixel data

byte[] pixelDataByte = (byte[])pixelData;

7. collect image construction info

int w = dcm.getInt(Tag.Columns, 0);
int h= dcm.getInt(Tag.Rows, 0);
int channel = dcm.getInt(Tag.SamplesPerPixel, -1);
int bitsAllocated = dcm.getInt(Tag.BitsAllocated,-1);
     
//check meta info
System.out.println("img width:"+w+" img height:"+h);
System.out.println("BitsAllocated:"+bitsAllocated);
System.out.println("Tag.SamplesPerPixel:"+channel);
System.out.println("SOPClassUID:"+dcm.getString(Tag.SOPClassUID));
System.out.println("TransferSyntaxUID:"+tsuid);

//get bytebuffer for correspond byte order(littele endian, big endian)
ByteBuffer buffer = ByteBuffer.wrap(pixelDataByte).order(ByteOrder.LITTLE_ENDIAN);
byte[] sortedPixel = buffer.array();

8.should mention about gray or color

//grayscale
if(channel == 1 && bitsAllocated == 8) {
        ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_GRAY);
        ColorModel cm = new ComponentColorModel(cs, false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE);
        SampleModel sm = cm.createCompatibleSampleModel(w, h);
        //construct buffer as byte
        DataBufferByte db = new DataBufferByte(sortedPixel, w*h);
        WritableRaster raster = Raster.createWritableRaster(sm, db, null);
        BufferedImage bi = new BufferedImage(cm, raster, false, null);
        ImagePlus imp = new ImagePlus("",bi);
        imp.show();
}else if(channel == 1 && bitsAllocated == 16) {
        //reconstruct pixel array as short.
        short[] shortArray = new short[sortedPixel.length/2];
        buffer.asShortBuffer().get(shortArray);
        ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_GRAY);
        ColorModel cm = new ComponentColorModel(cs, false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE);
        ShortProcessor sp = new ShortProcessor(w, h, shortArray, cm);
        ImagePlus imp = new ImagePlus("",sp);
        imp.show();
//color(rgb)
}else if(channel == 3) {
        //please check RGB byte order.
        //should consider which order is RGBRGBRGB... or RRRGGGBBB...
        //in this case, use RRRGGGBBB
        byte[] alpha = new byte[sortedPixel.length/3];
        byte[] R = new byte[sortedPixel.length/3];
        byte[] G = new byte[sortedPixel.length/3];
        byte[] B = new byte[sortedPixel.length/3];
    for (int i = 0; i < R.length; i++) { 
    alpha[i] = (byte)0;
    R[i] = pixelDataByte[i];
    G[i] = pixelDataByte[R.length + i];
    B[i] = pixelDataByte[(R.length*2) + i];
    }
    ColorProcessor cp = new ColorProcessor(w, h);
    //0=alpha, 1=red, 2=green, 3=blue
    cp.setChannel(0, new ByteProcessor(w, h, alpha));
    cp.setChannel(1, new ByteProcessor(w, h, R));
    cp.setChannel(2, new ByteProcessor(w, h, G));
    cp.setChannel(3, new ByteProcessor(w, h, B));
    ImagePlus imp = new ImagePlus("RGB DICOM",cp);
        imp.show();
         
        //if image pixels constructed RGBRGBRGB order,
        // Convert interleaved byte RGB to packed int ARGB
//            int[] rgb_array = new int[sortedPixel.length/3];
//            //https://stackoverflow.com/questions/29748021/properly-load-24-bit-bitmap-data-into-a-32-bit-bitmap-object
//            //https://www.dyclassroom.com/image-processing-project/how-to-create-a-random-pixel-image-in-java
//            for (int i = 0; i < rgb_array.length; i++) {
//        rgb_array[i] = 0xff << 24 // Alpha (all opaque)
//                      | ((pixelDataByte[i * 3] & 0xff) << 16)
//                      | ((pixelDataByte[i * 3 + 1] & 0xff) << 8)
//                      | ((pixelDataByte[i * 3 + 2] & 0xff));
//    }
//            ColorProcessor cp = new ColorProcessor(w, h,rgb_array);
            //then, show image.
            //and also you can use,
//            ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);
//            ColorModel cm = new ComponentColorModel(cs, false, false, Transparency.TRANSLUCENT, DataBuffer.TYPE_BYTE);
//            SampleModel sm = cm.createCompatibleSampleModel(w, h);
//            DataBufferByte db = new DataBufferByte(sortedPixel, w*h);
//            WritableRaster raster = Raster.createWritableRaster(sm, db, null);
//            BufferedImage bi = new BufferedImage(cm, raster, false, null);
//            ImagePlus imp = new ImagePlus("RGB DICOM",bi);
//            imp.show();

}else {
        //out of range channels in this tutorial...
        System.out.println("channel is not 1 or 3... return.");
        return;
}


Case 2 : compressed images

sorry, this topics is skipped in this tutorial.

2020/04/23
tatsuaki kobayashi
with best regards.

Appendix:whole source code

import java.awt.Transparency;
import java.awt.color.ColorSpace;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.ComponentColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.DataBufferInt;
import java.awt.image.DataBufferShort;
import java.awt.image.IndexColorModel;
import java.awt.image.Raster;
import java.awt.image.SampleModel;
import java.awt.image.WritableRaster;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.dcm4che3.data.Attributes;
import org.dcm4che3.data.Tag;
import org.dcm4che3.io.DicomInputStream;
import org.dcm4che3.util.SafeClose;

import ij.ImagePlus;
import ij.process.ByteProcessor;
import ij.process.ColorProcessor;
import ij.process.ImageProcessor;
import ij.process.ShortProcessor;

public class tutorial_opendcm {

public static void main(String[] args) throws IOException {

//you can choose some pixels type...

//GRAYSCALE
//8 bits gray
String gray8 = "sample/dcm-samples-master/archive/1.2.276.0.7230010.3.200.1.1.1";
//16 bits gray
String gray16 = "sample/dcm-samples-master/archive/2.16.840.1.113662.2.2344286425913116080603163842909806166139";
//32 bits gray (important for parametric images, e.g, pet, spect ...) sorry, future work...
//...

//COLOR
//RGB
String rgb = "sample/dcm-samples-master/archive/1.2.40.0.13.1.1.75699434012420281431899874274436428076";

String img_path = rgb;
File f = new File(img_path);
        Attributes dcm = null;
        String tsuid = "";
        
        try( DicomInputStream dis = new DicomInputStream(f); ){
        dcm = dis.readDataset(-1, -1);
        tsuid = dis.getTransferSyntax();
        SafeClose.close(dis);
        }
        
        Object pixelData = dcm.getValue(Tag.PixelData);
        
        //if pixelData is null, this dicom file is not dicom "image" objects.
        if(pixelData == null) {
        System.out.println("this dicom file dose not have pixelData. return.");
        return;
        }
        
        //check pixel data type.
        //no compressed
        if(pixelData instanceof byte[]) {
        System.out.println("this dcm file is raw image (no compressed).");
        //compressed
        }else {
        //check Fragments for compressed pixel data.
        //in this tutorial, skip this topics to keep simplicity.
        //hint 
        //Fragments fragmObj = (Fragments)pixelData;
        //BulkData pixelsBulk = frageObj.get(1);//get(0) is just Item tag, so start 1.
        // bulk to byte ... and read using ImageReader specified format e.g, JPEG2000.
        System.out.println("this dcm file is compressed. return.");
        return;
        }
        
        //cast to byte array from pixel data
        byte[] pixelDataByte = (byte[])pixelData;
        
        int w = dcm.getInt(Tag.Columns, 0);
        int h= dcm.getInt(Tag.Rows, 0);
        int channel = dcm.getInt(Tag.SamplesPerPixel, -1);
        int bitsAllocated = dcm.getInt(Tag.BitsAllocated,-1);
        
        //check meta info
        System.out.println("img width:"+w+" img height:"+h);
        System.out.println("BitsAllocated:"+bitsAllocated);
    System.out.println("Tag.SamplesPerPixel:"+channel);
    System.out.println("SOPClassUID:"+dcm.getString(Tag.SOPClassUID));
    System.out.println("TransferSyntaxUID:"+tsuid);
        
    //get bytebuffer for correspond byte order(littele endian, big endian)
        ByteBuffer buffer = ByteBuffer.wrap(pixelDataByte).order(ByteOrder.LITTLE_ENDIAN);
        byte[] sortedPixel = buffer.array();
        
        //you should mention about gray or color,
        //grayscale
        if(channel == 1 &amp;&amp; bitsAllocated == 8) {
            ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_GRAY);
            ColorModel cm = new ComponentColorModel(cs, false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE);
            SampleModel sm = cm.createCompatibleSampleModel(w, h);
            //construct buffer as byte
            DataBufferByte db = new DataBufferByte(sortedPixel, w*h);
            WritableRaster raster = Raster.createWritableRaster(sm, db, null);
            BufferedImage bi = new BufferedImage(cm, raster, false, null);
            ImagePlus imp = new ImagePlus("",bi);
            imp.show();
        }else if(channel == 1 &amp;&amp; bitsAllocated == 16) {
        //reconstruct pixel array as short.
        short[] shortArray = new short[sortedPixel.length/2];
        buffer.asShortBuffer().get(shortArray);
        ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_GRAY);
            ColorModel cm = new ComponentColorModel(cs, false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE);
            ShortProcessor sp = new ShortProcessor(w, h, shortArray, cm);
            ImagePlus imp = new ImagePlus("",sp);
            imp.show();
        //color(rgb)
        }else if(channel == 3) {
        //please check RGB byte order.
        //should consider which order is RGBRGBRGB... or RRRGGGBBB...
        //in this case, use RRRGGGBBB
        byte[] alpha = new byte[sortedPixel.length/3];
            byte[] R = new byte[sortedPixel.length/3];
            byte[] G = new byte[sortedPixel.length/3];
            byte[] B = new byte[sortedPixel.length/3];
    for (int i = 0; i &lt; R.length; i++) { 
    alpha[i] = (byte)0;
    R[i] = pixelDataByte[i];
    G[i] = pixelDataByte[R.length + i];
    B[i] = pixelDataByte[(R.length*2) + i];
    }
    ColorProcessor cp = new ColorProcessor(w, h);
    //0=alpha, 1=red, 2=green, 3=blue
    cp.setChannel(0, new ByteProcessor(w, h, alpha));
    cp.setChannel(1, new ByteProcessor(w, h, R));
    cp.setChannel(2, new ByteProcessor(w, h, G));
    cp.setChannel(3, new ByteProcessor(w, h, B));
    ImagePlus imp = new ImagePlus("RGB DICOM",cp);
            imp.show();
            
            //if image pixels constructed RGBRGBRGB order,
            // Convert interleaved byte RGB to packed int ARGB
//            int[] rgb_array = new int[sortedPixel.length/3];
//            //https://stackoverflow.com/questions/29748021/properly-load-24-bit-bitmap-data-into-a-32-bit-bitmap-object
//            //https://www.dyclassroom.com/image-processing-project/how-to-create-a-random-pixel-image-in-java
//            for (int i = 0; i &lt; rgb_array.length; i++) {
//        rgb_array[i] = 0xff &lt;&lt; 24 // Alpha (all opaque)
//                      | ((pixelDataByte[i * 3] &amp; 0xff) &lt;&lt; 16) 
//                      | ((pixelDataByte[i * 3 + 1] &amp; 0xff) &lt;&lt; 8) 
//                      | ((pixelDataByte[i * 3 + 2] &amp; 0xff)); 
//    }
//            ColorProcessor cp = new ColorProcessor(w, h,rgb_array);
            //then, show image.
            //and also you can use,
//            ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);
//            ColorModel cm = new ComponentColorModel(cs, false, false, Transparency.TRANSLUCENT, DataBuffer.TYPE_BYTE);
//            SampleModel sm = cm.createCompatibleSampleModel(w, h);
//            DataBufferByte db = new DataBufferByte(sortedPixel, w*h);
//            WritableRaster raster = Raster.createWritableRaster(sm, db, null);
//            BufferedImage bi = new BufferedImage(cm, raster, false, null);
//            ImagePlus imp = new ImagePlus("RGB DICOM",bi);
//            imp.show();
        }else {
        //out of range channels in this tutorial...
        System.out.println("channel is not 1 or 3... return.");
        return;
        }
}
}

コメント

このブログの人気の投稿

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は、医用画像の研究および画像診断レポートを管理するための堅牢で安定したプラッ