DICOM Basics using Java - Viewing DICOM Images


Introduction

This is part of my series of articles on the DICOM standard. If you are totally new to DICOM, please have a quick look at my earlier article titled “Introduction to the DICOM Standard” for a quick introduction to the standard. You may also want to look at my other three tutorials listed below to understand what is inside a DICOM file:

  1. DICOM Basics using Java - Making Sense of the DICOM File

  2. DICOM Basics using Java - Creating a DICOM File

  3. DICOM Basics using Java - Extracting Image Data

This tutorial also assumes that you know the basics of Java or any equivalent object-oriented language such as C# or C++.

This tutorial that will illustrate how to implement some basic operations such as viewing (single and multi-frame images) as well as window width and level operations. Radiologists and other users of DICOM images do far more than these operations. I will cover some of these more advanced image manipulation operations such as zooming, rotation, measurements, overlays/annotations, etc after I have covered some additional background necessary to understand these operations better in this series. So let us proceed to understand the basics.

The PixelMed Java DICOM Toolkit - Quick Overview

For the purposes of illustrating many aspects of DICOM that I plan to cover in this tutorial series, I will be using a freely available and powerful DICOM toolkit called PixelMed Java DICOM Toolkit. This is a completely stand-alone DICOM toolkit that provides functionality for DICOM file and directory processing, image viewing as well as DICOM networking-related operations. This toolkit is completely free for both commercial or non-profit use. It is well documented and also has a small discussion forum and mailing list for users. The list of features contained within this toolkit is quite comprehensive. Please keep in mind that the use of this toolkit in my tutorial does not in anyway imply my official endorsement of it for implementing a production application. Every situation is unique, and only you are ultimately in the best position to decide that. This article is also not meant to be a tutorial on this toolkit, and my focus here is simply to tie DICOM theory to what a practical (although simple) implementation might look like. So, if your goal is to learn how to use the PixelMed library, I would encourage you to visit its website or check out the discussion forum or StackOverflow discussion pages for any assistance.

Before We Get Started…

Much like my previous programming examples, I will use the most bare minimum code and approach to help illustrate the concepts that I cover in this tutorial. This means that the code I write here is best suited to simply show the concept that I am trying to explain and is not necessarily the most efficient code to deploy in real life and in your production application.

To get started, you will need to configure a few things on your machine including a Java development environment as well as the PixelMed toolkit before you can run the example if you want to try this out yourself.

  • Download and install the Eclipse Java IDE from here (or use any other IDE you prefer)
  • Download the PixelMed toolkit library from here
  • Ensure that the PixelMed.jar library is included in your Java project’s class path (some examples may require additonal runtime dependencies such as JAI Image IO Tools that can be found on PixelMed software download. Look for a tar compressed file called pixelmedjavadicom_dependencyrelease.YYYYMMDD.tar.bz2 or something similar)
  • You can find the source code and images used in this tutorial on GitHub
  • You can download more DICOM images from this site if you want as well

Example of Viewing DICOM Images

The PixelMed library provides a number of classes to enable us to display DICOM images. These classes are all located in the com.pixelmed.display package as you might expect. The SingleImagePanel class is the simplest one that we will require to implement DICOM image-related viewing, frame navigation and window width/level operations. This class has a constructor that take an instance of the SourceImage class (also from the com.pixelmed.display package) as a parameter. To make this class easier to demonstrate, I have overridden the SingleImagePanel class with some additional behavior. This new overridden class OverriddenSingleImagePanelForDemo has behavior to listen to both key and mouse events to perform both frame navigation and as well as update window width/level information on the screen. The SingleImagePanel class already has an implementation to perform frame navigation listening for mouse wheel scroll operation, but I am providing another implementation here to make the visualization easier for beginners. A JFrame class from the Java swing library is used as a container for the SingleImagePanel class. This is all we need. The main class that runs this entire demo is shown below.

    package com.saravanansubramanian.dicom.pixelmedtutorial;

    import java.awt.Color;
    import javax.swing.JFrame;
    import com.pixelmed.display.SourceImage;

    public class ViewScrollAndWindowWidthAndLevelingDemo {

        public static void main(String[] args) {
            String dicomInputFile = "D:\\JavaProjects\\Sample Images\\XA-MONO2-8-12x-catheter";
            try {
                JFrame frame = new JFrame();
                SourceImage sImg = new SourceImage(dicomInputFile);
                System.out.println("Number of frames: " + sImg.getNumberOfFrames());
                ImagePanelDemo singleImagePanel = new OverriddenSingleImagePanelForDemo(sImg);
                frame.add(singleImagePanel);
                frame.setBackground(Color.BLACK);
                frame.setSize(sImg.getWidth(),sImg.getHeight());
                frame.setTitle("Demo for view, scroll and window width/level operations");
                frame.setVisible(true);

            } catch (Exception e) {
                e.printStackTrace(); //in real life, do something about this exception
            }
        }
    }

“The greatest part of a writer’s time is spent in reading, in order to write: a man will turn over half a library to make one book.” ~ Samuel Johnson

Run the ViewImageWithWindowWidthAndLeveling class below (making adjustments to the code to point to any multi-frame DICOM image as necessary, or use the one that I uploaded along with the source code). This will launch a window. Highlight this window, and press the left or right keyboard button for instance and hold that button down and you will be able to navigate through the frames one at a time or continuously depending on how long you hold it down. This will give you an idea of what a cine-loop might look like. This is how one can scroll through a multi-frame DICOM image.

Now, click and drag the mouse on this window from left to right to control window width. Or, click and drag the mouse on this window from top to bottom for window level or centering. The screen capture below shows the code needed to achieve this behavior. As you can a lot of functionality is already provided by the SingleImagePanel class. The screen capture of the window showing the image as well as additional information regarding the image such as window width, level as well as the frame number are shown below the code illustration below as well.

    package com.saravanansubramanian.dicom.pixelmedtutorial;

    import java.awt.Color;
    import java.awt.Font;
    import java.awt.event.KeyEvent;
    import java.awt.event.MouseEvent;
    import com.pixelmed.display.SingleImagePanel;
    import com.pixelmed.display.SourceImage;
    import com.pixelmed.display.event.FrameSelectionChangeEvent;
    import com.pixelmed.event.ApplicationEventDispatcher;
    import com.pixelmed.event.EventContext;

    public class OverriddenSingleImagePanelForDemo extends SingleImagePanel {

        //initialize these to some default values
        private static final long serialVersionUID = 1L;
        private int frameIndex = 0;
        private int MaxFrames = 1;

        public OverriddenSingleImagePanelForDemo(SourceImage sImg) {
            super(sImg);
            MaxFrames = sImg.getNumberOfFrames();
            this.setSideAndViewAnnotationString(getTextToDisplay(1),30, "SansSerif",Font.BOLD, 14, Color.WHITE,true);
        }

        private String getTextToDisplay(int frameIndexNumber) {
            return " Window Width->" + (int) this.windowWidth
                    + " Level(or Center)->" + (int) this.windowCenter
                    + " Frame Index->" + frameIndexNumber;
        }

        @Override
        public void keyPressed(KeyEvent e) {
            super.keyPressed(e);
            if(frameIndex == MaxFrames){ //this is to reset the frame loop
                frameIndex = 0;
            }
            ApplicationEventDispatcher.getApplicationEventDispatcher()
            .processEvent(new FrameSelectionChangeEvent(new EventContext("Pass info here"), frameIndex++));
            UpdateDisplayInformation();

        }

        private void UpdateDisplayInformation() {
            this.sideAndViewAnnotationString = getTextToDisplay(frameIndex);
        }

        @Override
        public void mouseDragged(MouseEvent e) {
            super.mouseDragged(e);
            UpdateDisplayInformation();
        }

    }

DICOM Window Width/Level Demo

Conclusion

This concludes this short but hopefully useful tutorial on implementing simple view operations on a DICOM image file. Like I said earlier, I will be coming back to this topic to cover more advanced scenarios such as zoom, rotate, measure and annotate operations in a future tutorial. However, in the next tutorial in this series, I will show how to read as well as create DICOM directories as these enable us to manage large numbers of images that are often generated by most modalities such as CT, MR or ultrasound. If you have any questions or comments regarding this tutorial, please feel free to send me an email. Please note that I may not get back to you right away due to work and other commitments.

Footnote: The DICOM standard restricts the file names/identifiers contained within to 8 characters (either uppercase alphabetic characters and numbers only) to keep in conformity with legacy/historical requirements. It also states that no information must be inferred/extracted from these names. The file names usually don’t have a .dcm extension when they are stored as part of a media such as CD or DVD. I use longer names to keep these details from being a distraction right now, but I still want to mention what the standard states here so that no confusion arises as a result.