Monday, September 29, 2008

Processing DICOM Images using dcm4che 2

Hi All,

First of all I want to thank all of you visitors! This post shows how we can use dcm4che2 toolkit for performing image processing. Most of articles and tutorials on the web present only pixel data accessing and manipulation, but not Dicom to Dicom image processing. Here we will manipulate an original Dicom image and then save it as a new Dicom processed image. You're supposed to know basic Java programming, the Eclipse environment and some Dicom stuff.


Lets start. Open your Eclipse IDE and choose File > New > Java Project. Name it myDicomImageProcessing. The next step is to create a new class, so right-click the src package and select New > Class. Enter DicomImageProcessing for class name and select the main method option. You'll get something like this:

public class DicomImageProcessing {
public static void main(String[] args) {
// TODO Auto-generated method stub
}
}

I encourage you to have a look at my last post Converting DICOM to JPEG because there are some repeated code here which there is no need to be explained again. Inside the main's body let's code the following line:

File sourceFile = new File("c:/original.dcm");

This will be our original Dicom file. Next we will open this file and get its pixel data:

try {
Iterator iter = ImageIO.getImageReadersByFormatName("DICOM");
ImageReader reader = (ImageReader) iter.next();
DicomImageReadParam param = (DicomImageReadParam) reader.getDefaultReadParam();
ImageInputStream iis = ImageIO.createImageInputStream(sourceFile);

reader.setInput(iis, false);
Raster raster = reader.readRaster(0, param);

Our ImageReader is able to return a complete Raster from our Dicom image. According to Java documentation a Raster represents values for pixels occupying a particular rectangular area (read pixel array). Then, if our raster object is null we got some errors opening the file, so we may display an error message:

if (raster == null) {
System.out.println("Error: couldn't read Dicom image!");
return;
}
iis.close();

Ok, we read the file and our raster object holds all pixel data information. Now comes the image processing tour. For this example I chose Java ConvolveOp class for performing blurring, embossing and sharpening operations. It implements a convolution from the source to the destination.

Convolution using a convolution kernel is a spatial operation that computes the output pixel from an input pixel by multiplying the kernel with the surround of the input pixel. This allows the output pixel to be affected by the immediate neighborhood in a way that can be mathematically specified with a kernel.

Great, we also need to use the Kernel class. This class defines a matrix that describes how a specified pixel and its surrounding pixels affect the value computed for the pixel's position in the output image of a filtering operation.

Now let's create a new kernel for applying an emboss filter to our Dicom image:

float[] emboss = new float[] { -2,0,0, 0,1,0, 0,0,2 };
Kernel kernel = new Kernel(3, 3, emboss);

See that our kernel is a simple matrix. You can change this values further and check the results, you are the man :) Next let's apply the filter through our ConvolveOp class.

ConvolveOp op = new ConvolveOp(kernel);
Raster newRaster = op.filter(raster, null);

Note that, we just pass our kernel as a parameter and create a new ConvolveOp object. The filter(src,dst) method can return a BufferedImage or a new Raster object containing all the manipulated pixel data. So we have to choose the second option. See that we set null to the raster destination parameter, this force the creation of a new Raster as a result. That's it! Our Dicom image was processed!

In order to save a new Dicom file that contains our processed image, we have to do the following:
1) Extract the array element from the raster DataBuffer;
2) Create a Dicom object without pixel data;
3) Add to the Dicom object the pixel data extracted from the DataBuffer;
4) Write the modified Dicom object to a file;

So let's go! In this example I use an 16Bit MR Dicom image and its raster holds a DataBufferUShort object, so we have 16Bit pixels stored on short type arrays. Depending on the file you may get a DataBufferByte object inside the raster and you'll have to adapt my example code. To extract the array just code the following lines:

DataBufferUShort buffer = (DataBufferUShort) newRaster.getDataBuffer();
short[] pixelData = buffer.getData();

The next step is to create a copy of our Dicom file without pixel data. This can be done by calling the getDicomObject() method from the DicomStreamMetaData class.

DicomStreamMetaData dsmd = (DicomStreamMetaData) reader.getStreamMetadata();
DicomObject dicom = dsmd.getDicomObject();

Allright, we got the Dicom object! All we have to do so is to add our modified pixel data inside this object. We need to access the PixelData tag and insert our pixelData short array like the code below:

dicom.putShorts(Tag.PixelData, dicom.vrOf(Tag.PixelData), pixelData);

Finally, lets save our new Dicom file containing the processed image:

File f = new File("C:/processed.dcm");
FileOutputStream fos = new FileOutputStream(f);
BufferedOutputStream bos = new BufferedOutputStream(fos);
DicomOutputStream dos = new DicomOutputStream(bos);
dos.writeDicomFile(dicom);
dos.close();

And, if we get any errors let's display a simple message:

} catch(Exception e) {
System.out.println("Error: couldn't read dicom image! "+ e.getMessage());
return;
}
}

It should work! I successfully opened the processed.dcm file on ImageJ. In addition, you may want to test other processing kernels and save different sets of processed images. You can try the following:

float[] blurring = new float[] { 1f/9f,1f/9f,1f/9f, 1f/9f,1f/9f,1f/9f, 1f/9f,1f/9f,1f/9f };
float[] sharpening = new float[] { -1,-1,-1, -1,9,-1, -1,-1,-1 };

If you have any problems running my examples just let me know!

Best regards,

Samuel.

41 comments:

Anonymous said...

Found code very use full for use in my final year project. Thanks for all your time and help Samcus.

Anil Kumar said...

Sorry to say i am not able to get DicomImageReadParam and DicomStreamMetaData classes.

Can u please tell me where i will get those classes, right now i am using dcm4che2.0.17

Samuel Covas Salomão said...

Hello Anil,

Thanks for visiting!

Please check if you have the package "org.dcm4che2.imageio.plugins.dcm". The classes you need belong to this package.

Any questions, just let me know.
Regards..

Hans said...

Hey Samuel,

Just what I was looking for... some very useful examples to get started with the DCM4CHE DICOM toolkit.
Currently refreshing a bit my Java knowledge (learned Java but haven't touched it in 5 years).

Have just installed the Eclipse IDE and will try get going with this tomorrow.

Thanks !!
Hans

Anonymous said...

hey sam, you have a great wealth of info available. i am trying to implement the code you have there however i get the following error when the program runs.

log4j:WARN No appenders could be found for logger (org.dcm4che3.image.LookupTable).
log4j:WARN Please initialize the log4j system properly

Thanks for any help you can provide

Samuel Covas Salomão said...

Hello Anonymous,

This is not an error, but a warning telling you that log4j was not properly configured. Your program should run, however you'll not be albe to see the log info provided by log4j labrary. Try to add log4j to you Java Build Path.

Regards,

Samuel.

Anonymous said...

Hi Sam,
First, thanks for making this tutorial page available. I have a greyscale BufferedImage which I would like to save as a dicom file.

I am quite new to Dicom and I have followed your tutorial on converting jpg->dicom. My problem is when I come to setting up the transfer syntax since I dont have encapsulated data:

dicom.initFileMetaInformation(UID.JPEGBaseline1);

dos.writeHeader(Tag.PixelData, VR.OB, -1);

dos.writeHeader(Tag.Item, null, 0);

int jpgLen = (int) jpgSource.length();
dos.writeHeader(Tag.Item, null, (jpgLen+1)&~1);


How do i modify the preceding lines to describe an array of greyscale pixeldata? I have tried the following without success:

dicom.initFileMetaInformation(UID.ImplicitVRLittleEndian);

dos.setTransferSyntax(TransferSyntax.ImplicitVRLittleEndian);
dos.writeHeader(Tag.Item, null, 0);
dos.writeHeader(Tag.SequenceDelimitationItem, null, 0);

dicom.putBytes(Tag.PixelData, dicom.vrOf(Tag.PixelData), pixelData);


Thank you in advance for advice

Samuel Covas Salomão said...

Hello Anonymous,

Try to follow my Dicom Image Processing tutorial. Then try to create your own Dicom file with compatible header for BitsStored, PhotometricInterpretation, SamplesPerPixel, and so on. Finally create a new grayscale array to fill in PixelData tag.

Regards :)

flokki said...

hi sam!
thanks for answering my mail that fast! i checked this tutorial now, very helpful!
now i would like to insert some data into tags that are not predefined and not accessable over the Tag.xxx notation. somehow i can't find the correct syntax for getting to tags by their number. do you have any clue how to solve this problem or maybe where to get information?
the dcm4che classdefinitions are not really helpful to me :(

cheers ;)

Alex said...

Hi Samuel,

Thank you for the very helpful guides. Would this processing tutorial also allow for adjusting the Window Level when displaying images? If so, is it possible for you to provide guidance on accomplishing that?

For example, I know I want to display "lung" levels, at about -500 level and 1500 window (a preset I've seen in Santesoft's Free Viewer), but I am lost in actually implementing it.

Keith said...

Hi Sam, I am having a problem with using this code: DicomImageReadParam param = (DicomImageReadParam)reader.getDefaultReadParam();
occasionally it throws this exception:
Exception in thread java.lang.ClassCastException: org.dcm4cheri.imageio.plugins.DcmImageReadParamImpl cannot be cast to org.dcm4che2.imageio.plugins.dcm.DicomImageReadParam
at dicomwithpixspace.LoadImageApp.main(LoadImageApp.java:123)
any ideas? thanks in advance
Keith

Anonymous said...

one question, how get the information of pacient, and the coordinate z from image thanks

Abder-Rahman said...

Thank you for telling me how to add "dcm4che" to Eclipse.

Abder-Rahman

Anonymous said...

hi, i run it but i have this pb how i resolde it?
log4j:WARN No appenders could be found for logger (org.dcm4che2.imageioimpl.plugins.dcm.DicomImageReader).
log4j:WARN Please initialize the log4j system properly.
Error: couldn't read dicom image! No Image Reader of class com.sun.media.imageioimpl.plugins.jpeg.CLibJPEGImageReader available for format:jpeg

Anonymous said...

Hello Samuel I am trying to implement this example, but the code return the next error.

"Error: couldn't read dicom image! java.awt.image.DataBufferByte cannot be cast to java.awt.image.DataBufferUShort"

I still looking to try to understand the DataBufferUShort but havent found any solution, also looking for the DataBufferByte that you mention in the step by step guide.

I will appreciatte any help
Thank you very much...

selva316 said...

HI I'm Selva, I used your source and chage the JPEG image to DICOM. When i retrieve to display the dicom image in my text it was not displaying.
Kindly Can anyone help me to solve this problem.

Anonymous said...

Your Code is more useful for me. I downloaded the Dicom Viewer Software.
I used your jar file in my project but the compress image is not displaying in the panel. But it shown in Preview. I didn't used XML file. It need use ah? If means Where i need to put the XML File.

prabhu g said...

hi I'm prabhu

please add the source file also in your blog for dimcom image processing and all.

Anonymous said...

Hey, kann someone tel me how to create a DICOM image and than save it in a DICOM file with the dcm4che api?

Anonymous said...

I downloaded the program and there is a .war file. Whwt the fuch is this .war? And how I can execute the program?

Samuel Covas Salomão said...

Hi,

A WAR file is a web application to be deployed into a web server or Servlet container. I recommend you have a look at JBoss or Tomcat to learn how to run the program.

Best,

Samuel.

Anonymous said...

Than this file is no good for me. Is there a version, which I can execute on a normal client? Or just the sourcecode, because the snipets hear are not realy helpful for understending DICOM.

Anonymous said...

Hi all !

I got next error, what is that ?

Multiple markers at this line
- imagereader cannot be resolved to a type
- The type HTMLDocument.Iterator is not generic; it cannot be parameterized with arguments

Anonymous said...

Hi Samuel

I have seen very good articles on dicom image processing using dcm4che and java
If you can share any code samples for dicom image compression(lossy & lossless) and decompression, It will be very helpful

Thanks

Anonymous said...

First of all thanks for this nice explanation.

But I have encountered a problem as explained below:

Iterator iter = ImageIO.getImageReadersByFormatName("DICOM");

I always get no value in the "iter" variable .It always gives false for "iter.next();"

Can you suggest me any solution or cause behind this problem.

Thanks in advance.

Samuel Covas Salomão said...

Hi,

You need JAI ImageIO and dcm4che-imageio* libraries in order to process your images. Check your classpath and see if you have them.

Regards,

Samuel.

Tomas said...

Thanks you for this great article.I would like to ask you -Is there an easy way ,how to recognize a text inside a picture?I expect that dcm4che2 has no such a functionality.
OCR software is necessary for this task,isn't?
Thank you

Samuel Covas Salomão said...

Hi Tomas,

I think you need something like a CAPTCHA reader/decoder. Try to look for it, there is a lot of examples in the Web.

Regards,

Samuel.

Maneesh said...
This comment has been removed by the author.
Maneesh said...
This comment has been removed by the author.
Maneesh said...

Hello Sir,

I knew that, this program is very useful for created a dicom file which can be viewed in any dicom viewer,

I fallowed every steps given above but i got some exception i.e.

Error 2: couldn't read dicom image! java.awt.image.DataBufferByte cannot be cast to java.awt.image.DataBufferUShort
java.lang.ClassCastException: java.awt.image.DataBufferByte cannot be cast to java.awt.image.DataBufferUShort
at DicomImageProcessing.main(DicomImageProcessing.java:38)
.
.

it indicated to the fallowing lines in program i.e.

DataBufferUShort buffer = (DataBufferUShort) newRaster.getDataBuffer
();
short[] pixelData = buffer.getData();
DicomStreamMetaData dsmd = (DicomStreamMetaData) reader.getStreamMetad
ata();
DicomObject dicom = dsmd.getDicomObject();
dicom.putShorts(Tag.PixelData, dicom.vrOf(Tag.PixelData), pixelData);


Above lines give an error, now i added some other code as,

DataBufferByte buffer = (DataBufferByte) newRaster.getDataBuffer();
byte[] pixelData = buffer.getData();
DicomStreamMetaData dsmd = (DicomStreamMetaData) reader.getStreamMetad
ata();
DicomObject dicom = dsmd.getDicomObject();
dicom.putBytes(Tag.PixelData, dicom.vrOf(Tag.PixelData), pixelData);

Now, the code is running well, but the out put file i.e. processed.dcm cann't be viewed on ImageJ viewer,

it gives error like,

"cann't be open Compressed dicom image"

Please Help Me Sir regarding this problem,

Thanking you Sir.

Samuel Covas Salomão said...

Hello Maneesh,

I see you managed to solve the problem using a byte buffer. It's right in your case :)

See that a byte buffer usually means compressed images like JPEG. ImageJ does not support compressed DICOM images, that's why you get that error.

Try to use another viewer like Sante DICOM Viewer or use another tool to decompress your file, such as: dcm2dcm (dcm4che toolkit) and dcmdjpeg (offis dcmtk).

Regards,

Samuel.

Maneesh said...

Thanks Sir,

But i want to create such a dicom file which can be viewed by any dicom viewer,

I have used dcm2dcm (dcm4che) toolkit, but with this i couldn't success to view the dicom file on ImageJ viewer.

Sorry for delay,

Fábio Tiago Rodrigues said...
This comment has been removed by the author.
johan said...

Hi sam, thank you for the tutorial.

could you give some example on how to create a new dicom file with existing dicom tag value.

thank you very much.

deepti pawar said...
This comment has been removed by the author.
deepti pawar said...

Hello,
I got error, it is not accepting DicomStreamMetaData
DicomOutputStream in following code

DicomStreamMetaData dsmd = (DicomStreamMetaData) reader.getStreamMetadata();
DcmObject dicom = dsmd.getDicomObject();
dicom.putShorts(Tag.PixelData, ((Object) dicom).vrOf(Tag.PixelData), pixelData);
File f = new File("C:/processed.dcm");
FileOutputStream fos = new FileOutputStream(f);
BufferedOutputStream bos = new BufferedOutputStream(fos);
DicomOutputStream dos = new DicomOutputStream(bos);

plz help..

Anonymous said...

Very informative tutorial. Thank you very much.

Anonymous said...

I want to add private tag in dicom which is having binary data using dcm4chee.please any one can share the snippet to do so.

Thanks
Kiran

Sagar Gopale said...

Hi Samuel, I have my code as:
DicomInputStream dicomInputStream = new DicomInputStream(dicomFile);
DicomObject dicomObject = dicomInputStream.readDicomObject();
I am getting the dicomObject as above. Can you please help me out to save the dicom object as jpeg?

Sagar Gopale said...

Hi Samuel, I want to display the dicom images in my web application. What are the steps to do it? I mean do I have to convert the dicom images to jpeg and display or is there any other way?