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.

41 comments:

Anonymous said...

Thanks. This is very helpful. I wanted to write this program myself, when I serached GOOGLE I found your write up. This will save me hours of time.

Sivagururaja said...

Thanks Samuel. Nice one. It's very helpful. Keep doing well. ;)

Neo said...

Sam,

awesome web application, I am lucky that I found your blog & it saves my so many man-days :)

Keep rocking as you already did.

Thanks,
Neo

AKS said...

Sir,
Love your code.
I just want to know how to change a camera photograph(maybe jpeg-exif format) to JPEG-LOSSLESS before converting into DICOM since the PACS does not want to support jpeg-baseline.

winsv said...

Thanks Samuel. This is excelent...just saved my life...jejeje

Your code is great!!

Bash said...

Thank you very much Samuel this is very helpful, like all the post you already did. I am starting to use dicom and your site its very very helpful in the process of understanding. Keep with the good work, Samucs is great.

Bihui (Will) Liu said...

Thanks for the code. It helped a lot. But can you tell me why does the pixel data=0 in the header information?

Unknown said...

Hello Bihui,

Thanks for your comments! The PixelData Dicom tag is set to -1 because we are encapsulating a whole Jpeg file into a Dicom dataset. So there is no pixel to be stored in that tag. All pixel information is kept untouched in the encapsulated Jpeg file.

Kindly regards,

Samuel.

Unknown said...

Thanks samucs. By using this code i converted jpg image to .dcm image. But while opening the converted dicom image in Imagej viewer ,im having the problem

"java.io.IOException.
Imagej cannot open compressed Dicom Image
Transfer syntax UID=1.2.840.10008.1.2.4.50"

I think dicom image is in Compressed format.

Help me to rectify it

Unknown said...

Hello NV,

Yes, ImageJ is not able to read DICOM with JPEG encapsulated yet. As far as I know ImageJ only opens "true" DICOM images that was generated by a medical acquisition device.

Kindly regards,

Samuel.

Unknown said...

Thank you samucs for the reply,

That dicom image was stored in mysql database by sql query. It stored succesfully. While im retrieving from mysql i need to be open that dicom image in a viewer or a java Panel.
Which viewer help me to open a dicom image from the database.

regards,
NV

Unknown said...

muito obrigado a vc samucs
I have a little problem
when I tried to run your program as an executable jar
it soesnt work
I've Isolated the problem and
it is that
it doesnt recognise ImagIO
can you help please
tnx again
ran

Unknown said...

Hi,

Try to install the JAI ImageIO package (https://jai-imageio.dev.java.net/binary-builds.html).

Kindly regards,

Samuel.

Prabhu Ganapathylingam said...

hi

i want to convert the jpeg video to the dicom video.

please help me

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...

I got folowin exception:

Exception in thread "main" java.lang.NoClassDefFoundError: org/slf4j/LoggerFactory
at org.dcm4che2.data.VR.(VR.java:62)
at JpegToDicom.main(JpegToDicom.java:39)
Caused by: java.lang.ClassNotFoundException: org.slf4j.LoggerFactory
at java.net.URLClassLoader$1.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
at java.lang.ClassLoader.loadClassInternal(Unknown Source)

and this is line 39:
dicom.putString(Tag.SpecificCharacterSet, VR.CS, "ISO_IR 100");

Unknown said...

Hi,

Be sure you have all these libraries in your classpath:

Class-Path:
lib/clibwrapper_jiio.jar
lib/dcm4che-core-2.0.20.jar
lib/dcm4che-image-2.0.20.jar
lib/dcm4che-imageio-2.0.20.jar
lib/dcm4che-imageio-rle-2.0.20.jar
lib/dcm4che-iod-2.0.20.jar
lib/dcm4che-net-2.0.20.jar
lib/jai_imageio.jar
lib/log4j-1.2.13.jar
lib/slf4j-api-1.5.0.jar
lib/slf4j-log4j12-1.5.0.jar

Note that you must have JAI ImageIO installed proprely in order to run the program successfully.

Best,

Samuel.

Anonymous said...

OK thanks, but what about this clibwrapper_jiio.jar? for what ith this file needed?

Unknown said...

Hi, it's part of JAI ImageIO install.

Anonymous said...

OK I includet all the mentioned libraries to the classpath and I was able to to conver my jpg file to dcm, OK I am not realy sure that the file was converted, but there were no exeptions. Now I tried to open the dcm file with "Sante Free Dicom Viwer", but the programm can´t open the dcm file also my imageJ with DICOMtools plugin can´t open the file. So what´s gone wrong?

Unknown said...

Hello,

I'm glad you could generate your Dicom file! ImageJ doesn't open such encapsulated file, but Sante Viewer does. Maybe you are missing some Dicom tags. I can have a look at your file in order to solve the problem. May you send both Dicom and Jpeg files to samucs@gmail.com?

Best,

Samuel.

Anonymous said...

OK I send you the images and the sourcecode.

Anonymous said...

Hi Samuel,

I want to thank you one more time for your help and for this great tutorial.

Greetz

Anonymous said...

Ok got an other question, can this code, with some modifications be used for exporting more then 1 .jpg file to a dcm file? And can this "Sante DICOM Viewer FREE" open such dcm files, with more than 1 image?

Anonymous said...

Hi, when I try to list the header of the dicom produced by this method;
I got an "java.lang.UnsupportedOperationException"

DicomInputStream diss = new DicomInputStream(
new FileInputStream(
new File(fname)));
DicomObject obj = diss.readDicomObject(); <-- triggers the exception


The exception is thrown in the dcm4che2.data.VR class

Unknown said...

Hi,

Something is missing there, check your libraries. Could you send me the Dicom image you are trying to list the header?

Kindly regards,

Samuel.

Anonymous said...

Hello,i imanaged to get rid of my UnsupportedOperationExceprtion problem ..

The probleme was in the produced Dicom.
I switched to VRExcplicitLittleEndiant transfert syntax, and used MultiframeGrayscaleWordSecondaryCaptureImageStorage SOP class.

Anup Dharangutti said...
This comment has been removed by the author.
Anup Dharangutti said...

Thanks a lot Samuel for the tutorial, excellent piece of code !!

Mauro said...

Hello
Thanks Samuel, This is an excelent job, however, I have a question: How can i do to add more frames to same DICOM file?
Thanks for the valuable help

Marcelo Porto said...

Hi, Samuel.
Great post. However I'm having some troubles when I try to upload the .dcm file into a PACS server. The file is ignored during the upload because there is a SOP Class UID missing. Do you know how could I solve this?

Thanks!

jyotsna ravikumar said...

Hi Samuel.

I am facing this issue :

In my code below , I keep getting
NosuchElementException, though I see no. of frames as 6 and all metadata as well

System.out.println("Reading DICOM image...");
ImageReader reader = new DicomImageReaderSpi().createReaderInstance();
System.out.println("Class of ImageReader is "
+ reader.getClass().getName());
FileImageInputStream input = new FileImageInputStream(dicomFile);
reader.setInput(input);

int numFrames = reader.getNumImages(true);
System.out.println("DICOM image has " + numFrames + " frames..."); // This returns 6 Frames

List images = new ArrayList();

try {
for (int i = 0; i < numFrames; i++) {
BufferedImage img = reader.read(i); // This throws NosuchElementException
images.add(img);
System.out.println(" > Frame " + (i + 1));
}

} catch (Exception e) {
e.printStackTrace();

}

System.out.println("Finished.");


Exception Details as:

DICOM image has 6 frames...
java.util.NoSuchElementException
at javax.imageio.spi.FilterIterator.next(ServiceRegistry.java:808)
at javax.imageio.ImageIO$ImageReaderIterator.next(ImageIO.java:502)
at javax.imageio.ImageIO$ImageReaderIterator.next(ImageIO.java:487)
at org.dcm4che2.imageioimpl.plugins.dcm.DicomImageReader.initRawImageReader(DicomImageReader.java:369)
at org.dcm4che2.imageioimpl.plugins.dcm.DicomImageReader.initImageReader(DicomImageReader.java:338)
at org.dcm4che2.imageioimpl.plugins.dcm.DicomImageReader.read(DicomImageReader.java:576)
at javax.imageio.ImageReader.read(ImageReader.java:923)
at com.ge.jyotsna.sample.hello.world.HelloWorldClass.start(HelloWorldClass.java:94)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)

Lucas Roberto Kochenborger said...

Hi. I'm trying load DICOM images, but I have some problem, can you help me?
This is my code?:

File file = new File("image.dcm");
ImageReader reader = new DicomImageReaderSpi().createReaderInstance();
FileImageInputStream inputStream = new FileImageInputStream(file);
reader.setInput(inputStream);
this.buffer = reader.read(0);

And this error occurs for me:

"No Image Reader of class com.sun.media.imageioimpl.plugins.jpeg2000.J2KImageReaderCodecLib available for format:jpeg2000"

Thanks

Unknown said...
This comment has been removed by the author.
Wahyudi Arifandi said...

Hello Samuel.
I think there might be something missing at your steps which i don't know as well.
I found that the generated dicom file is unable to be opened by Weasis v1.0.8.
Size of the generated dicom file was just 616 bytes which is not make sense. Then, when I opened the generated dicom file with notepad, it just contains a few number of bytes only.
Do you have any idea?

Wahyudi Arifandi said...

I found this one is working:
http://web-dicom.googlecode.com/svn/trunk/dicomweb/src/org/psystems/dicomweb/Jpg2Dcm.java

Unknown said...

Hi Samuel,

Please help me in this,

I'm using jdk8 and windows 8 environment and trying convert dicom (.dcm) to jpeg getting following issue.

Exception in thread "main" org.dcm4che2.data.ConfigurationError: No Image Reader of class com.sun.media.imageioimpl.plugins.jpeg2000.J2KImageReaderCodecLib available for format:jpeg2000
at org.dcm4che2.imageio.ImageReaderFactory.getReaderForTransferSyntax(ImageReaderFactory.java:99)
at org.dcm4che2.imageioimpl.plugins.dcm.DicomImageReader.initCompressedImageReader(DicomImageReader.java:410)
at org.dcm4che2.imageioimpl.plugins.dcm.DicomImageReader.initImageReader(DicomImageReader.java:395)
at org.dcm4che2.imageioimpl.plugins.dcm.DicomImageReader.read(DicomImageReader.java:636)
at dicom.Example1.main(Example1.java:34)


My code snippet here :

package dicom;

import java.awt.image.BufferedImage;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Iterator;

import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;

import org.dcm4che2.imageio.plugins.dcm.DicomImageReadParam;

import com.sun.media.jai.codec.ImageEncoder;
import com.sun.media.jai.codecimpl.JPEGCodec;
import com.sun.media.jai.codecimpl.JPEGImageEncoder;

public class Example1 {

static BufferedImage myJpegImage=null;

public static void main(String[] args) {
File file = new File("D:\\dicom_libs\\dcms\\IM-0001-0001.dcm");
Iterator iterator =ImageIO.getImageReadersByFormatName("DICOM");
while (iterator.hasNext()) {
ImageReader imageReader = (ImageReader) iterator.next();
DicomImageReadParam dicomImageReadParam = (DicomImageReadParam) imageReader.getDefaultReadParam();
try {
ImageInputStream iis = ImageIO.createImageInputStream(file);
imageReader.setInput(iis,false);
myJpegImage = imageReader.read(0, dicomImageReadParam);
iis.close();
if(myJpegImage == null){
System.out.println("Could not read image!!");
}
} catch (IOException e) {
e.printStackTrace();
}
File file2 = new File("D:\\dicom_libs\\test.jpg");
try {
OutputStream outputStream = new BufferedOutputStream(new FileOutputStream(file2));
ImageEncoder encoder = JPEGCodec.createImageEncoder("test", outputStream, null);
encoder.encode(myJpegImage);
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("Completed");
}

}
}

I'm using following jars :

dcm4che-core-2.0.28.jar

dcm4che-image-2.0.28.jar

dcm4che-imageio-2.0.28.jar

jai_imageio-1.1.jar

jai_windows-i586.jar

sun-jai_codec.jar

apache-logging-log4j.jar

slf4j-api-1.7.7.jar

slf4j-log4j12-1.7.7.jar

Unknown said...

Hello samuel,

I am trying a variation of your code and running into issue for several days now. I am loading up PPM format images (via twelvemonkeys lib) output by FFMPEG and then trying to write out Multiframe DICOM using dcm4che2 The code is creating the header (which I am printing out to verify), I have set the xfer syntax UID to be the simple 1.2.840.10008.1.2 (so not JPEG), and I have verified that at runtime the DICOMImageWriter is available.
List of supported Format Names of registered ImageWriters:
Found 6 format names: 'PAM', 'PNM', 'DICOM', 'PPM', 'PBM', 'PGM',
ImageWriters for format name:DICOM
Writer[0]: org.dcm4che2.imageioimpl.plugins.dcm.DicomImageWriter:
canWriteCompressed:false
canWriteProgressive:false
canWriteTiles:false
canOffsetTiles:false
But then when it tries to write out the actual image data as DICOM I get the following:
org.dcm4che2.data.ConfigurationError: No Image Writer of class com.sun.media.imageioimpl.plugins.jpeg.CLibJPEGImageWriter available for format:jpeg
at org.dcm4che2.imageio.ImageWriterFactory.getWriterForTransferSyntax(ImageWriterFactory.java:94)
at org.dcm4che2.imageioimpl.plugins.dcm.DicomImageWriter.setupWriter(DicomImageWriter.java:219)
at org.dcm4che2.imageioimpl.plugins.dcm.DicomImageWriter.prepareWriteSequence(DicomImageWriter.java:247)
at org.dcm4che2.imageioimpl.plugins.dcm.DicomImageWriter.write(DicomImageWriter.java:192)

I have read extensively about issues with that plugin, but unlike the others that have commented about it I do NOT want to use it ... I am trying not to default to it. Nonetheless in looking at what others did to get around issues with not finding the correct jpeg writer (particularly on Mac OS X like myself), I went ahead and created an entry for the xfer syntax UID and desired plugin writer in the org/dcm4che2/imageio/ImageWriterFactory.properties entry in the jar file. This did not help. Nor did my attempts to coax the org.dcm4che2.imageio.ImageWriterFactory into selecting the correct plugin by passing in an ImageWriteParam to the write() that should steer it clear of anything jpeg. Any ideas?? I am at a loss currently.

Anonymous said...

Hi Sam,

Sorry if this is not right place for my query.

I am trying to create my own PACS using C-STORE (StoreSCP.java) for dicom compliance using dcm4che I need to process dicom image and save it in my own database and mount location. I am not sure how exactly to carry out this requirement. Can you please throw some light in this area or point me to right resources.

LifeVoxel.AI said...
This comment has been removed by the author.
LifeVoxel.AI said...

LifeVoxel.AI platform offers a better alternative for the medical imaging community. Its unique approach is based on 12 international patents that encompass critical patient-centric delivery of care using intuitive, diagnostic and instantly accessible visualizations over the Internet. It works 100X faster and its cheaper by 42% -80% than other service providers.

Interactive Streaming and Artificial Intelligence Platform