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.