First of all I'd like to thank you all for every email, tips, comments and mainly the valuable knowledge we have been sharing since I started this simple blog. This time I want to write a tutorial that takes a multiframe DICOM image and then playback it frame by frame. The final result is a Swing Java application where we can watch such file as it was a video file. If you feel it sounds nice so let's go!
If it's your first time here, please I strongly recommend that you read my DICOM to JPEG Conversion post so you can learn how to setup Eclipse and dcm4che toolkit. Then come back to this post and continue to this example tutorial. Let's start! Open your Eclipse IDE and choose
File > New > Java Project
. Name it DicomExamples
. The next step is to create a new class, so right-click the src
package and select New > Class
. Enter DicomMultiframePlayer
for class name and select the main method option. You'll get something like this:public class DicomMultiframePlayer { public static void main(String[] args) { // TODO Auto-generated method stub } }
To make this tutorial easy to understand I will first build the Java Swing Graphic User Interface (GUI). In order to get a Java Swing application our class must be a
JFrame
class instance. Then since it's a Desktop program we need to provide some interaction to the user. We can do this by creating some JButton
components where the user can open and playback the DICOM file. Thus, we should think how to control the playback, here we will use a specific Java Thread
. Finally to accomplish all these requirements we must change our code a little.public class DicomMultiframePlayer extends JFrame implements ActionListener, Runnable { /** * Class constructor. */ public DicomMultiframePlayer() { // TODO Auto-generated method stub } /** * This class implements the Runnable interface. * It will handle thread execution. */ @Override public void run() { // TODO Auto-generated method stub } /** * This class implements the ActionListener interface. * It will handle action events performed by buttons. * @param e ActionEvent object source. */ @Override public void actionPerformed(ActionEvent e) { // TODO Auto-generated method stub } /** * This is a private class to render images onto a JPanel component. */ private class ImagePanel extends JPanel { // TODO Auto-generated method stub }; /** * Run the program. * @param args Program arguments. */ public static void main(String[] args) { new DicomMultiframePlayer(); } }
As you can see our simple class now has additional Java interfaces implementations:
ActionListener
and Runnable
. The first will handle JButton
actions and the second handles Java thread execution. Also, I started to code a private class that will be responsible for rendering our images onto a JPanel
component. For now our GUI class needs some Swing components and new class attributes, so let's declare them.// These Swing components will be our file chooser private JLabel fileLabel; private JTextField fileField; private JButton btnChoose; // These Swing components will be our playback controls private JButton btnPlay; private JButton btnPause; private JButton btnStop; private JButton btnExit; // This vector stores all image frames for playback private Vector<BufferedImage> images; // This is a Swing JPanel control for image display private ImagePanel imagePanel; // Used for simple playback control private boolean stop; private int currentFrame;
The next step is to instantiate evey class attribute inside the class constructor. But before that let's code a helper method for
JButton
creation. Since we have more than 3 buttons it's useful to code a creation method, this way we save several lines of code. I called this method createJButton()
./** * Create a new Java Swing JButton. * @param width button width. * @param height button height. * @param text button text. * @return a new JButton. */ private JButton createJButton(int width, int height, String text) { JButton b = new JButton(text); b.setMinimumSize(new Dimension(width, height)); b.setMaximumSize(new Dimension(width, height)); b.setPreferredSize(new Dimension(width, height)); // button action will be handled by 'this' class b.addActionListener(this); return b; }
Then let's instantiate our class members and attributes inside the class constructor as follows:
// Call the parent constructor and pass the window frame title. // Tell the application to terminate when the user close the window. // Configure a new BorderLyout for our application frame. super("XA Player using dcm4che - by samucs-dev"); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.getContentPane().setLayout(new BorderLayout()); // Initialize our frame vector. // Create our panel to render Dicom images. images = new Vector<BufferedImage>(); imagePanel = new ImagePanel(); // Configure the file chooser component. fileLabel = new JLabel("File:"); fileField = new JTextField(30); btnChoose = this.createJButton(25, 25, "..."); // Configure our JButton components for playback control. btnPlay = this.createJButton(80,25,"Play"); btnPause = this.createJButton(80,25,"Pause"); btnStop = this.createJButton(80,25,"Stop"); btnExit = this.createJButton(80,25,"Exit"); btnPause.setEnabled(false); btnStop.setEnabled(false); // Add the file chooser to the top of the frame window. JPanel panelNorth = new JPanel(); panelNorth.add(fileLabel); panelNorth.add(fileField); panelNorth.add(btnChoose); this.getContentPane().add(panelNorth, BorderLayout.NORTH); // Add the image render panel to the center of the frame window. this.getContentPane().add(imagePanel, BorderLayout.CENTER); // Add the buttons to the bottom of the frame window. JPanel panelSouth = new JPanel(); panelSouth.add(btnPlay); panelSouth.add(btnPause); panelSouth.add(btnStop); panelSouth.add(btnExit); this.getContentPane().add(panelSouth, BorderLayout.SOUTH); // Configure frame window size and then show it. this.setSize(new Dimension(500,500)); this.setLocationRelativeTo(null); this.setVisible(true);
Well, that was a lot of code. Don't be afraind this is Java GUI done by hand :) Your reward to get here is to execute the program and see your great job! Right click the
DicomMultiframePlayer
class and choose Run As > Java Application
. You will see something like the picture below.The GUI is almost done. Now we must implement the
ImagePanel
class. This is a key part for us to playback the multiframe Dicom file. The ImagePanel
class extends the Swing JPanel
component. This way we can use its paint()
method for rendering our images and then display it to the user through the main GUI. We have to implement it as a private class for our application as follows:private class ImagePanel extends JPanel { // The image to be rendered onto the panel. */ private BufferedImage image; // Class constructor. */ public ImagePanel() { super(); this.setPreferredSize(new Dimension(1024,1024)); this.setBackground(Color.black); } // Set the image to be rendered. */ public void setImage(BufferedImage image) { this.image = image; this.updateUI(); } // Render the image. */ @Override public void paint(Graphics g) { if (this.image != null) { g.drawImage(this.image, 0, 0, image.getWidth(), image.getHeight(), null); } } };
That's it! Our GUI is done! Now all we have to do is to code a method for opening our DICOM file and implement the
ActionListener
and Runnable
interfaces. First let's see how openFile()
method shoulde be. That's where we use the amazing dcm4che toolkit for extracting DICOM image frames.private void openFile(File file) { // Clear the image vector */ images.clear(); try { // Here we get a new image reader for our Dicom file. */ System.out.println("Reading DICOM image..."); ImageReader reader = new DicomImageReaderSpi().createReaderInstance(); FileImageInputStream input = new FileImageInputStream(file); reader.setInput(input); // Just to show the number of frames. */ int numFrames = reader.getNumImages(true); System.out.println("DICOM image has "+ numFrames +" frames..."); // For each frame we read its image and then add it to the image vector. */ System.out.println("Extracting frames..."); for (int i=0; i < numFrames; i++) { BufferedImage img = reader.read(i); images.add(img); System.out.println(" > Frame "+ (i+1)); } // That's it we read all frames. */ System.out.println("Finished."); } catch(Exception e) { e.printStackTrace(); imagePanel.setImage(null); return; } // This is the first playback control. // After reading we always display the first frame. */ stop = false; currentFrame = 0; imagePanel.setImage(images.get(0)); }
Now the
actionPerformed()
method implemented from the ActionListener
interface handles all action events from our control buttons. The code below shows how this should be. See that depending on the button that was pressed other buttons become disabled. This is just to certify the final user will use the GUI properly.@Override public void actionPerformed(ActionEvent e) { // The user is opening a file. */ if (e.getSource().equals(btnChoose)) { JFileChooser chooser = new JFileChooser(); int action = chooser.showOpenDialog(this); switch(action) { case JFileChooser.APPROVE_OPTION: this.openFile(chooser.getSelectedFile()); break; case JFileChooser.CANCEL_OPTION: return; } } // Start playback of Dicom frames. */ if (e.getSource().equals(btnPlay)) { btnPlay.setEnabled(false); btnPause.setEnabled(true); btnStop.setEnabled(true); stop = false; new Thread(this).start(); } // Pause playback. */ if (e.getSource().equals(btnPause)) { btnPlay.setEnabled(true); btnPause.setEnabled(false); btnStop.setEnabled(true); stop = false; } // Stop. Return to the first frame (0). */ if (e.getSource().equals(btnStop)) { btnPlay.setEnabled(true); btnPause.setEnabled(false); btnStop.setEnabled(false); stop = true; currentFrame = 0; imagePanel.setImage(images.get(0)); } // Exit the application. */ if (e.getSource().equals(btnExit)) { System.exit(0); } }
Finally we have the implementation of Runnable interface through the
run()
method. That's where we handle the playback and give the user the illusion of watching a video file. The run()
method is an infinite loop and it is always checking if it has to stop. If the stop flag is false so it increments the frame number and update the display image causing the application to playback the DICOM file.@Override public void run() { while(true) { if (!btnPlay.isEnabled()) { // Check if it has to stop. */ if (stop) break; // Increment the current frame and update the display image. */ currentFrame++; if (currentFrame == images.size()) currentFrame = 0; imagePanel.setImage( images.get(currentFrame)); // Cause our thread to sleep 70 milliseconds. */ // Increase this value to slow playback speed. */ 1try { Thread.sleep(70); } catch (InterruptedException e) { e.printStackTrace(); } } } }
Mission accomplished! I hope you have enjoyed all this tutorial. This is a simple application and I expect you to develop it as you wish. Please, get the complete source code and an example multiframe DICOM image to test yourself. Many thanks to Marcio Colunas from University of Aveiro, Portugal, who gave me the permission to use the multiframe image. See you on the next post!
Kinldy regards,
Samuel.
20 comments:
hallo; when i compile the programm of DicomMultiframePlayer I get an 2 error:
C:\Program Files\Xinox Software\JCreatorV4\MyProjects\DicomMultiframePlayer\src\DicomMultiframePlayer.java:34: expected
private Vector images;
^
C:\Program Files\Xinox Software\JCreatorV4\MyProjects\DicomMultiframePlayer\src\DicomMultiframePlayer.java:44: '(' or '[' expected
images = new Vector ();
^
2 errors
how Resolve this errors?
Hello,
Thanks for visiting!
The source code is ok. Be sure you are using the latest JAVA version 6. Maybe your version does not understand "Generics" for Vector objects.
Regards,
Samuel.
i am try java version 6 the build and run of source code is ok but the picture of dicom does't displayed why?
Hi,
Please, be sure you have "dcm4che-image" libraries. Also you need JAI Imageio (https://jai-imageio.dev.java.net) in your Java build path to work with Dicom images using dcm4che.
Regards,
Samuel.
i am sure
IDE get:
Reading DICOM image...
Exception in thread "AWT-EventQueue-0" java.lang.NoClassDefFoundError: com/sun/media/imageio/stream/StreamSegmentMapper
at org.dcm4che2.imageioimpl.plugins.dcm.DicomImageReaderSpi.createReaderInstance(DicomImageReaderSpi.java:146)
at javax.imageio.spi.ImageReaderSpi.createReaderInstance(ImageReaderSpi.java:296)
at DicomMultiframePlayer.openFile(DicomMultiframePlayer.java:146)
at DicomMultiframePlayer.actionPerformed(DicomMultiframePlayer.java:101)
at javax.swing.AbstractButton.fireActionPerformed(AbstractButton.java:1995)
at javax.swing.AbstractButton$Handler.actionPerformed(AbstractButton.java:2318)
at javax.swing.DefaultButtonModel.fireActionPerformed(DefaultButtonModel.java:387)
at javax.swing.DefaultButtonModel.setPressed(DefaultButtonModel.java:242)
at javax.swing.plaf.basic.BasicButtonListener.mouseReleased(BasicButtonListener.java:236)
at java.awt.Component.processMouseEvent(Component.java:6038)
at javax.swing.JComponent.processMouseEvent(JComponent.java:3260)
at java.awt.Component.processEvent(Component.java:5803)
at java.awt.Container.processEvent(Container.java:2058)
at java.awt.Component.dispatchEventImpl(Component.java:4410)
at java.awt.Container.dispatchEventImpl(Container.java:2116)
at java.awt.Component.dispatchEvent(Component.java:4240)
at java.awt.LightweightDispatcher.retargetMouseEvent(Container.java:4322)
at java.awt.LightweightDispatcher.processMouseEvent(Container.java:3986)
at java.awt.LightweightDispatcher.dispatchEvent(Container.java:3916)
at java.awt.Container.dispatchEventImpl(Container.java:2102)
at java.awt.Window.dispatchEventImpl(Window.java:2429)
at java.awt.Component.dispatchEvent(Component.java:4240)
at java.awt.EventQueue.dispatchEvent(EventQueue.java:599)
at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:273)
at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:183)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:173)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:168)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:160)
at java.awt.EventDispatchThread.run(EventDispatchThread.java:121)
Process completed.
Hello,
This message "java.lang.NoClassDefFoundError" says that the program could not find your imageio classes. You need to add the following to you Java build path:
dcm4che-image-2.0.20.jar
dcm4che-imageio-2.0.20.jar
dcm4che-imageio-rle-2.0.20.jar
clibwrapper_jiio.jar
jai_imageio.jar
Also, don't forget to add the others dcm4che core libraries.
Regards,
Samuel.
Hi,
This is great work. Can I use this module in MATLAB. We can call routines of DCM4CHE from MATLAB.
If you have any idea please help me...
Thanks
Hi,
I am Sorry, but I got following errors.
java.lang.NoClassDefFoundError: DicomMultiframePlayer
Caused by: java.lang.ClassNotFoundException: DicomMultiframePlayer
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)
Exception in thread "main"
Hi Anonymous,
This error falls in java.net package. I believe it's not DicomMultiframePlayer.java error. Are you using applet or something on a network?
Regards,
Samuel.
Reading DICOM image...
DICOM image has 76 frames...
Extracting frames...
org.dcm4che2.data.ConfigurationError: No Image Reader of class com.sun.media.imageioimpl.plugins.jpeg.CLibJPEGImageReader available for format:jpeg
at org.dcm4che2.imageio.ImageReaderFactory.getReaderForTransferSyntax(ImageReaderFactory.java:99)
at org.dcm4che2.imageioimpl.plugins.dcm.DicomImageReader.initCompressedImageReader(DicomImageReader.java:337)
at org.dcm4che2.imageioimpl.plugins.dcm.DicomImageReader.initImageReader(DicomImageReader.java:322)
at org.dcm4che2.imageioimpl.plugins.dcm.DicomImageReader.read(DicomImageReader.java:484)
at javax.imageio.ImageReader.read(ImageReader.java:923)
at DicomMultiframePlayer.openFile(DicomMultiframePlayer.java:167)
at DicomMultiframePlayer.actionPerformed(DicomMultiframePlayer.java:115)
at javax.swing.AbstractButton.fireActionPerformed(AbstractButton.java:1995)
at javax.swing.AbstractButton$Handler.actionPerformed(AbstractButton.java:2318)
at javax.swing.DefaultButtonModel.fireActionPerformed(DefaultButtonModel.java:387)
at javax.swing.DefaultButtonModel.setPressed(DefaultButtonModel.java:242)
at javax.swing.plaf.basic.BasicButtonListener.mouseReleased(BasicButtonListener.java:236)
at java.awt.Component.processMouseEvent(Component.java:6267)
at javax.swing.JComponent.processMouseEvent(JComponent.java:3267)
at java.awt.Component.processEvent(Component.java:6032)
at java.awt.Container.processEvent(Container.java:2041)
at java.awt.Component.dispatchEventImpl(Component.java:4630)
at java.awt.Container.dispatchEventImpl(Container.java:2099)
at java.awt.Component.dispatchEvent(Component.java:4460)
at java.awt.LightweightDispatcher.retargetMouseEvent(Container.java:4577)
at java.awt.LightweightDispatcher.processMouseEvent(Container.java:4238)
at java.awt.LightweightDispatcher.dispatchEvent(Container.java:4168)
at java.awt.Container.dispatchEventImpl(Container.java:2085)
at java.awt.Window.dispatchEventImpl(Window.java:2478)
at java.awt.Component.dispatchEvent(Component.java:4460)
at java.awt.EventQueue.dispatchEvent(EventQueue.java:599)
at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:269)
at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:184)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:174)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:169)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:161)
at java.awt.EventDispatchThread.run(EventDispatchThread.java:122)
hi
while i am executing your code.it show errors like this
Reading DICOM image...
DICOM image has 76 frames...
Extracting frames...
org.dcm4che2.data.ConfigurationError: No Image Reader of class com.sun.media.imageioimpl.plugins.jpeg.CLibJPEGImageReader available for format:jpeg
at org.dcm4che2.imageio.ImageReaderFactory.getReaderForTransferSyntax(ImageReaderFactory.java:99)
at org.dcm4che2.imageioimpl.plugins.dcm.DicomImageReader.initCompressedImageReader(DicomImageReader.java:337)
at org.dcm4che2.imageioimpl.plugins.dcm.DicomImageReader.initImageReader(DicomImageReader.java:322)
at org.dcm4che2.imageioimpl.plugins.dcm.DicomImageReader.read(DicomImageReader.java:484)
at javax.imageio.ImageReader.read(ImageReader.java:923)
at DicomMultiframePlayer.openFile(DicomMultiframePlayer.java:167)
at DicomMultiframePlayer.actionPerformed(DicomMultiframePlayer.java:115)
at javax.swing.AbstractButton.fireActionPerformed(AbstractButton.java:1995)
at javax.swing.AbstractButton$Handler.actionPerformed(AbstractButton.java:2318)
at javax.swing.DefaultButtonModel.fireActionPerformed(DefaultButtonModel.java:387)
at javax.swing.DefaultButtonModel.setPressed(DefaultButtonModel.java:242)
at javax.swing.plaf.basic.BasicButtonListener.mouseReleased(BasicButtonListener.java:236)
at java.awt.Component.processMouseEvent(Component.java:6267)
at javax.swing.JComponent.processMouseEvent(JComponent.java:3267)
at java.awt.Component.processEvent(Component.java:6032)
at java.awt.Container.processEvent(Container.java:2041)
at java.awt.Component.dispatchEventImpl(Component.java:4630)
at java.awt.Container.dispatchEventImpl(Container.java:2099)
at java.awt.Component.dispatchEvent(Component.java:4460)
at java.awt.LightweightDispatcher.retargetMouseEvent(Container.java:4577)
at java.awt.LightweightDispatcher.processMouseEvent(Container.java:4238)
at java.awt.LightweightDispatcher.dispatchEvent(Container.java:4168)
at java.awt.Container.dispatchEventImpl(Container.java:2085)
at java.awt.Window.dispatchEventImpl(Window.java:2478)
at java.awt.Component.dispatchEvent(Component.java:4460)
at java.awt.EventQueue.dispatchEvent(EventQueue.java:599)
at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:269)
at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:184)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:174)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:169)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:161)
at java.awt.EventDispatchThread.run(EventDispatchThread.java:122)
help me
Hello,
Don't forget to install JAI ImageIO package before running this code.
Kindly regards,
Samuel.
Hi i need to convert a mpg video file into dcm file. I'm not able to find a right solution.
Gunter says that he used jpg2dcm Utility :
jpg2dcm --mpeg -C ../etc/jpg2dcm/mpg2dcm.cfg img.mpg video.dcm
But i need to write a program by creating dicom objects with dataset for converting a mpg video file to dcm video file. How to write a program for this as u wrote for JPEG to DICOM and DICOM to JPEG.
Great example Samuel, the best in the net, it help me a lot especially the dcm4che libraries integration part... I hope you post an example of sending and receiving images from the server in the future... excellent work
But hoe multiframe dcm files can be created?
Hi Samuel,
My name's Samuel too. I've been trying to setup samucs-web on my jboss server where I'm also running an instance of dcm4chee. I'm getting the same error when I try to navigate to look at images in a series that others are mentioning about jai but it's unclear to me how to go about installing jai. I believe it's already installed as part of dcm4chee. This is the error I'm getting:
org.dcm4cheri.imageio.plugins.DcmImageReadParamImpl cannot be cast to org.dcm4che2.imageio.plugins.dcm.DicomImageReadParam
Thanks for any help!
Hello,
Oh my God
that is really very very helpful :)
thank you a lot for your explications
with my best regards Samuel
Hi
I have set up the DCM4CHE jars as indicated,
dcm4che-image-2.0.20.jar
dcm4che-imageio-2.0.20.jar
dcm4che-imageio-rle-2.0.20.jar
clibwrapper_jiio.jar
jai_imageio.jar
but the the last 2 jars of 1.1 version . Now the latest 1.1.3 version has these jar's ,
jai_codex.jar, jai_core.jar, mlibwrapper_jai.jar.
I have added the dmc4che jar's to the classpath, but still not able to compile the source.
- pg
I tried your source code but this is what I got:
Exception in thread "AWT-EventQueue-0" java.lang.NoClassDefFoundError: com/sun/media/imageio/stream/StreamSegmentMapper
at org.dcm4che2.imageioimpl.plugins.dcm.DicomImageReaderSpi.createReaderInstance(DicomImageReaderSpi.java:146)
at javax.imageio.spi.ImageReaderSpi.createReaderInstance(Unknown Source)
I added all the dicom4che2 jar/ and jai imageio.jar
hii... i tried your code and add all required jar files- image,imageio,jai,core,slf4j,logback-core,logback-classic,clipwraperjii etc. but still i am unable to run successfully these code. and this time i am getting eror like:
Failed to instantiate [ch.qos.logback.classic.LoggerContext]
Reported exception:
java.lang.NoSuchMethodError: ch.qos.logback.classic.util.ContextInitializer.(Lch/qos/logback/classic/LoggerContext;)V
----------------------------******
and also.........
Exception in thread "AWT-EventQueue-0" java.lang.NoClassDefFoundError: org/dcm4che2/util/GenericNumericArray.
-----------------------------****
and
Caused by: java.lang.ClassNotFoundException: org.dcm4che2.util.GenericNumericArray
and many so please help me how can i fix all these erors and bugs. Thanks in advanced. reply please.
Post a Comment