Tuesday, December 9, 2008

Converting JPEG to DICOM using dcm4che 2

Hi Folks!

At last my small quiz has finished. It was just a few votes (15), however, we’ve got an interesting choice: the Jpeg to Dicom tutorial. So let’s see how we can encapsulate a Jpeg image into a Dicom dataset. This tutorial is based on jpg2dcm utility from dcm4che2 toolkit and you are supposed to know basic java programming, the Eclipse environment, and some Dicom stuff.

Open your Eclipse IDE and choose File > New > Java Project. Name it myJpegToDicom. Next we have to create a new class, so right-click the src package and select New > Class. Enter JpegToDicom for class name and select the main method option. You'll get something like this:


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

I strongly recommend that you have a look at my previous posts for reviewing some configuration steps. Inside the main’s body let’s code the following lines:


File jpgSource = new File("c:/picture.jpg");
File dcmDestination = new File("c:/picture.dcm");

The first line creates a new file which is our Jpeg image, and the second one creates our future Dicom file which will keep Jpeg data within. Then, we have to extract some header information from our Jpeg file. To make it easy to understand I decided to use other means than search for Jpeg header markers and hexadecimal numbers. So, let’s do the following:


try {
BufferedImage jpegImage = ImageIO.read(jpgSource);
if (jpegImage == null)
throw new Exception("Invalid file.");

We open a try block and then read our Jpeg into a BufferedImage through ImageIO.read() method. If this results in an invalid image so we may throw a new exception. Else, we’ve got a valid image and therefore valuable information about it. The BufferedImage class has a lot of useful methods for retrieving image data, then let’s save the number of color components (samples per pixel) of our image, the bits per pixel and the bits allocated:


int colorComponents = jpegImage.getColorModel().getNumColorComponents();
int bitsPerPixel = jpegImage.getColorModel().getPixelSize();
int bitsAllocated = (bitsPerPixel / colorComponents);
int samplesPerPixel = colorComponents;

It’s time to start building our Dicom dataset:


DicomObject dicom = new BasicDicomObject();
dicom.putString(Tag.SpecificCharacterSet, VR.CS, "ISO_IR 100");
dicom.putString(Tag.PhotometricInterpretation, VR.CS, samplesPerPixel == 3 ? "YBR_FULL_422" : "MONOCHROME2");

The first line creates a new basic Dicom object defined by dcm4che2 toolkit. The next one puts header information for Specific Character Set: ISO_IR 100 – it’s the same for ISO-8859-1 – the code for Latin alphabet. Finally, the last line puts header information for photometric interpretation (read with or without colors). So if our image has samples per pixel equals to 3, it has colors (YBR_FULL_422), else it’s a grayscale image (MONOCHROME2).

The following lines add integer values to our Dicom header. Note that all of them comes from BufferedImage methods. These values are mandatory when encapsulating. For more information you can check Part 3.5 of Dicom Standard.

dicom.putInt(Tag.SamplesPerPixel, VR.US, samplesPerPixel);
dicom.putInt(Tag.Rows, VR.US, jpegImage.getHeight());
dicom.putInt(Tag.Columns, VR.US, jpegImage.getWidth());
dicom.putInt(Tag.BitsAllocated, VR.US, bitsAllocated);
dicom.putInt(Tag.BitsStored, VR.US, bitsAllocated);
dicom.putInt(Tag.HighBit, VR.US, bitsAllocated-1);
dicom.putInt(Tag.PixelRepresentation, VR.US, 0);

Also, our Dicom header needs information about date and time of creation:

dicom.putDate(Tag.InstanceCreationDate, VR.DA, new Date());
dicom.putDate(Tag.InstanceCreationTime, VR.TM, new Date());

Every Dicom file has a unique identifier. Here we’re generating study, series and Sop instances UIDs. You may want to modify these values, but you should to care about their uniqueness.

dicom.putString(Tag.StudyInstanceUID, VR.UI, UIDUtils.createUID());
dicom.putString(Tag.SeriesInstanceUID, VR.UI, UIDUtils.createUID());
dicom.putString(Tag.SOPInstanceUID, VR.UI, UIDUtils.createUID());

Our Dicom header is almost done. The following command initiates Dicom metafile information considering JPEGBaseline1 as transfer syntax. This means this file has Jpeg data encapsulated instead common medical image pixel data. The most common Jpeg files use a subset of the Jpeg standard called baseline Jpeg. A baseline Jpeg file contains a single image compressed with the baseline discrete cosine transformation (DCT) and Huffman encoding.

dicom.initFileMetaInformation(UID.JPEGBaseline1);

After initiate the header we can open an output stream for saving our Dicom dataset as follows:

FileOutputStream fos = new FileOutputStream(dcmDestination);
BufferedOutputStream bos = new BufferedOutputStream(fos);
DicomOutputStream dos = new DicomOutputStream(bos);
dos.writeDicomFile(dicom);

The next three lines are the most important ones. According to Dicom Standard the data element PixelData (7FE0,0010), if encapsulated, has the value representation OB (Other Byte String) and its length shall be set to an undefined value (in this case -1). It also contains the encoded pixel data stream fragmented into one or more item(s):

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

The Item Tag (FFFE,E000) is followed by a 4 byte item length field encoding the explicit number of bytes of the item. The first item in the sequence of items before the encoded pixel data stream shall be a basic item with length equals to zero:

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

The next Item then keeps the length of our Jpeg file.

/*
According to Gunter from dcm4che team we have to take care that
the pixel data fragment length containing the JPEG stream has
an even length.
*/
int jpgLen = (int) jpgSource.length();
dos.writeHeader(Tag.Item, null, (jpgLen+1)&~1);

Now all we have to do is to fill this item with bytes taken from our Jpeg file:

FileInputStream fis = new FileInputStream(jpgSource);
BufferedInputStream bis = new BufferedInputStream(fis);
DataInputStream dis = new DataInputStream(bis);

byte[] buffer = new byte[65536];
int b;
while ((b = dis.read(buffer)) > 0) {
dos.write(buffer, 0, b);
}

Finally, the Dicom Standard tells that we have to put a last Tag: a Sequence Delimiter Item (FFFE,E0DD) with length equals to zero.

/*
According to Gunter from dcm4che team we have to take care that
the pixel data fragment length containing the JPEG stream has
an even length. So if needed the line below pads JPEG stream with
odd length with 0 byte.
*/
if ((jpgLen&1) != 0) dos.write(0);
dos.writeHeader(Tag.SequenceDelimitationItem, null, 0);
dos.close();
} catch(Exception e) {
System.out.println("ERROR: "+ e.getMessage());
}

That’s it! Now we have a Dicom file with Jpeg data encapsulated. The print screen below was taken from Sante Free Dicom Viwer and shows a Windows Vista sample Jpeg picture encapsulated into a Dicom file.



References:

Dicom Standard Part 3.5 - Annex A (Normative) Transfer Syntax Specifications - A4 Transfer Syntaxes for Encapsulation of Encoded Pixel Data.

Jpeg Huffman Coding Tutorial

I hope it helps!

Regards,

Samuel.

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.

Tuesday, March 25, 2008

Converting DICOM to JPEG using dcm4che 2

Hi Folks,

Sorry, I was quite busy these days, weeks, months :) Now, I can share with you how to convert any Dicom image to Jpeg using the amazing dcm4che 2.0 toolkit. I've spent a lot of time trying to figure out the magic of dcm4che and I've got some stuff that will be posted later.However, the best way to learn dcm4che is studying its utilities apps, so dcm2jpg is the one you must have a look. From now on let's just see how to perform the conversion.

First, you have to download the latest version of dcm4che libraries. Also, you'll need the JAI ImageIO Toolkit in order to quickly read pixel data from Dicom images. And if you don't have any java editor get the best one: Eclipse. You are 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 myDicomToJpeg. The next step is to create a new class, so right-click the src package and select New > Class. Enter DicomToJpeg for class name and select the main method option. You'll get something like this:

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

In order to use dcm4che classes we have to configure the project's build path. Right-click the project's main folder and select Properties. Select the Java Build Path option. Under Libraries tab we'll find the Add External Jars button. Click on it to select all dcm4che jar files and all JAI ImageIO jar files. Now our project is able to work with dcm4che.

Write the following line inside the main's body. This is our Dicom file:

File myDicomFile = new File("c:/dicomImage.dcm");

Then let's declare what will be our Jpeg image:

BufferedImage myJpegImage = null;

The following line returns an Iterator containing all currently registered ImageReaders that claim to be able to decode the named format (e.g., "DICOM", "jpeg", "tiff").

Iterator<ImageReader> iter = ImageIO.getImageReadersByFormatName("DICOM");

The java documentation says an ImageReader object are normally instantiated by the service provider interface (SPI) class for the specific format. Service provider classes (e.g., instances of ImageReaderSpi) are registered with the IIORegistry, which uses them for format recognition and presentation of available format readers and writers. So let's get our ImageReader object.

ImageReader reader = (ImageReader) iter.next();

If you check carefully dcm4che libraries you'll find some specific imageio packages. That's where it keeps the secret of reading Dicom pixel data. Therefore, our ImageReader object is now an instance of a specific SPI class for the Dicom format.

In the following line we get parameters for reading the Dicom image. The java documentation says an ImageReadParam class describe how a stream is to be decoded. Instances of this class or its subclasses are used to supply prescriptive "how-to" information to instances of ImageReader.

DicomImageReadParam param = (DicomImageReadParam) reader.getDefaultReadParam();

Before read all pixel data we have to create an ImageInputStream object for use by our ImageReader object. Note that this input stream has our Dicom file as a parameter. As this process throws an IOException it must be between a try-catch block.

Now all we have to do is to call the read() method from our ImageReader object together with our prescriptive Dicom parameters. The result is a new BufferedImage filled with Dicom pixel data. Pretty easy, hun :)

try {
ImageInputStream iis = ImageIO.createImageInputStream(myDicomFile);
reader.setInput(iis, false);
myJpegImage = reader.read(0, param);
iis.close();

That's it! We read the Dicom image into our myJpegImage object. The following lines just test if we really succeeded. If not, we just print a message.

if (myJpegImage == null) {
System.out.println("\nError: couldn't read dicom image!");
return;
}

You may be asking: ok, how do I get my Jpeg file? The answer is in the following lines. We firstly need to create a Jpeg file that will be sent to an OutputStream to be saved. Then, the JPEGImageEncoder object is responsible for encoding our Jpeg image into this output. When we close the OutputStream our Jpeg file is saved. We're done!!

File myJpegFile = new File("c:/jpegImage.jpg");
OutputStream output = new BufferedOutputStream(new FileOutputStream(myJpegFile));
JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(output);
encoder.encode(myJpegImage);
output.close();

Finally, if our try to read the image failed, we probably got an exception. So let's handle it printing a message.

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

Have a nice day! Hope it helps :)

Samuel.

Wednesday, January 16, 2008

Setting Up dcm4chee for Oracle Database

Hi Folks!

I'm starting my masters degree this year and I wrote an interesting research project to build a complete medical image processing server which works together with dcm4chee: a DICOM clinical data manager system. In this post I will show you how to setup dcm4chee properly using the great and free Oracle Express Database. This tutorial is for Windows.

Firstly, download OracleXE and launch it. The installation is quite simple and intuitive. When asked create the system account that will manage the database, so type system for username and manager for password. Wait copying its files and then finish the wizard. On the Windows command prompt access the directory:

oraclexe\app\oracle\product\10.2.0\server\BIN

...and execute the sqlplus.exe program. You will be asked for entering an username and password, which is system and manager. If no problems occurred you are now connected to Oracle and will get the C:\SQL> command line.




The Oracle Express brings a clear web interface where you may perform the database management, however, it uses the Jetty server that works on HTTP port 8080. If you try to run any other server like JBoss you'll get stuck at errors because port 8080 wont't be available. To overcome this issue you must change the HTTP port for Oracle. On SqlPlus type the following SQL commands:

SQL> begin
2 dbms_xdb.sethttpport('18080');
3 end;
4 /

After execute these lines you'll change Oracle HTTP port from 8080 to 18080. We must do it because dcm4chee works under JBoss server that also uses port 8080 for web access. Meanwhile, we're done with Oracle!

Download dcm4chee binaries for Oracle and also get the amazing JBoss application server. I chose version 2.13.0 of dcm4chee and the JBoss 4.2.2.GA release. Extract dcm4chee files at the root directory "C:\". Avoid using a directory that has a name which contains spaces as installation directory. So make it simple, just rename the extracted folder
to dcm4chee.

The next step is to extract JBoss files to the root directory and then rename the new folder to jboss. When finished, you have to copy some files from JBoss to dcm4chee by executing install_jboss.bat with the path of your JBoss installation directory as parameter. Access the Windows command prompt to execute the script. Eg. C:\dcm4chee\bin>intall_jboss.bat C:\jboss.




That's it, dcm4chee is almost done! Come back to Oracle for creating your PACS database. Again, access the SqlPlus with your system account and execute the commands below. They create a new Oracle tablespace named pacsdb and a new database user pacs. Every dcm4chee data will be stored based on these configurations.

CREATE TABLESPACE pacsdb
DATAFILE 'C:\xxx\pacsdb.ORA' SIZE 5M
AUTOEXTEND ON NEXT 5120K
DEFAULT STORAGE (INITIAL 10K NEXT 50K MINEXTENTS 1 MAXEXTENTS 121 PCTINCREASE 10) ONLINE;

CREATE USER pacs IDENTIFIED BY pacs
DEFAULT TABLESPACE pacsdb TEMPORARY TABLESPACE TEMP
QUOTA UNLIMITED ON pacsdb;

GRANT DBA TO pacs WITH ADMIN OPTION;

Reopen the Windows command prompt and execute SqlPlus. Now you have to establish connection with Oracle through your new user pacs and password pacs for generating all tables, primary keys, indexes, and so on. Therefore, type the following command and wait:

SQL> @C:\dcm4chee\sql\create.ora;

If no errors occurred your dcm4chee database is complete!

Important Notes:

1) dcm4chee doesn't contain an appropriate Jdbc driver for Oracle, so you need to download this driver and copy it to dcm4chee\server\default\lib\.

2)Use OCI connection for OracleXE. Change de Jdbc url connection in dcm4chee\server\default\deploy\pacs-oracle-ds.xml. Locate the tag <connection-url> and change its content to jdbc:oracle:oci:@xe.

3) Search Oracle installation directory for ocijdbc10g.dll and copy this file to dcm4chee\bin\native\.

Mission accomplished! Open the Windows command prompt, access dcm4chee\bin directory and execute run.bat to startup JBoss server. Finally, open your preffered web browser and go to localhost:8080/dcm4chee-web to see dcm4chee in action!




If you have any problems with this tutorial just let me know.
See you on the next post :)