DICOM in Python?

I do most of my software development these days using Python under Windows. I had the idea of rewriting my DICOM PHP class in Python with the mind of ease of use under Windows and including any needed DCMTK binaries in a nice package. From past experience with other languages I knew it could be difficult to start other programs in the background on Windows and then keep track of them. I set out to write a simple store and forward application using Python and the DCMTK to see what I was getting myself into.

Turns out Python has some really nice modules for running programs in the background. In a very short period of time I had the beginnings of pretty slick DICOM application. Even as a quick and dirty test application it is able to:

  • Read DICOM tags from files.
  • Start promiscuous DICOM storage services on port 4343
  • Write any images it receives into a directory.
  • Watch that directory for files and detect if those files are DICOM.
  • Compress DICOM files, using a compress type suitable to the file’s modality
  • Send DICOM files to a remote host
  • Perform a DICOM ping in order to tell if a DICOM host is up

You can download all of my files here. Just extract the zip, the good stuff is in snf.py. Take note that this is just proof of an idea and by no means safe or production worthy code.

If you don’t feel like downloading it, here is the complete source:

import subprocess
import time
import os  
import xml.etree.ElementTree as ET

# Is the file a DICOM file?
def is_dcm(file):
  f = os.popen('dcmtk/bin/dcm2xml.exe ' + file)
  out = f.read()
  if not "TransferSyntaxUID" in out:
    return False
  return True

def echoscu(host, port):
  f = os.popen('dcmtk/bin/echoscu.exe ' + host + ' ' + port)
  out = f.read()
  if not out:
    return True
  return False

# Dump out the DICOM header into an array indexed by the tag tags['0010,0010'] = some value
def dcm_dump(file):
  # Run dcm2xml to get some XML of the DICOM headers
  f = os.popen('dcmtk/bin/dcm2xml.exe ' + file)
  out = f.read()
  root = ET.fromstring(out)

  # The XML we get doesn't make it easy to look up tags. Lets reorganize it
  tags = {}

  # Get the meta tags  
  for child in root:
    tag = child.tag
    value = child.attrib['name']
    name = child.attrib['name']
    tags[tag] = value 

  # Get the real tags
  for child in root.iter('element'):
    tag = child.attrib['tag']
    value = child.text
    name = child.attrib['name']
    tags[tag] = value

  return tags

# pull the value from the array generated by dcm_dump()
def get_tag(tags, tag):
    return ''
  val = tags[tag]
  return val

def storescu_switch(ts):
  if 'JPEG Baseline' in ts:
    return '-xy'
  elif 'JPEG Extended' in ts:
    return '-xx'
  elif 'JPEG Lossless' in ts:
    return '-xs';
    return ''


# Compress a DICOM file if need be
def compress_dcm(modality, ts, file):
  switch = ''
  new_ts = ''
  if not 'JPEG' in ts: # we need to compress
    tmp_file = 'temp.dcm'
    if modality == 'US':
      switch = '+eb'
      new_ts = 'JPEG Baseline'
    elif modality == 'CR' or modality == 'DR' or modality == 'DX' or modality == 'SC' or modality == 'RG' or modality == 'OT':
      swtich = '+ee'
      ts = 'JPEG Extended'
      switch = ''
      new_ts = 'JPEG Lossless'

    f = os.popen('dcmtk/bin/dcmcjpeg.exe ' + switch + ' ' + file + ' ' + tmp_file)
    out = f.read()
      print out
      new_ts = ''
      os.rename(tmp_file, file)
    ts = new_ts
  return ts

# Send a DICOM file, compress if needed
def send_dcm(file):
  print file
  tags = dcm_dump(file)
  ts = compress_dcm(get_tag(tags, '0008,0060'), get_tag(tags, 'data-set'), file)
  switch = storescu_switch(ts)
  my_ae = 'PYTHON'
  remote_ae = 'DEANO'
  host = ''
  port = '105'

  print ts
  print switch

  f = os.popen('dcmtk/bin/storescu.exe -ta 10 -td 10 -to 10 ' + switch + ' -aet ' + my_ae + ' -aec ' + remote_ae + ' ' + host + ' ' + port + ' ' + file)
  out = f.read()
  print out
  if not out:
    print file + " sent OK"
# Program flow
# Start storescp
# Loop through temp_images directory
# Detect dicom file
# Figure out if compressed
# If not, compress based on modality
# Send File in another thread
# Send OK: Delete File
# Check on storescp... echoscu myself... if no good for five tries... kill and restart storescp

# Directories and defines
base_dir = "C:\\rrad\\r"
temp_images = base_dir + '\\temp_images'

listen_port = '4343' # Hopefully always free

# Start storescp
storescp_args = "-dhl -td 20 -ta 20 -xf " + base_dir + "\\storescp.cfg Default -od " + temp_images + ' ' + listen_port
subprocess.Popen("dcmtk/bin/storescp.exe " + storescp_args)

x = 0 # count runs
bad_echoscus = 0 # Count failed echoscus

# Main loop
while True:
#  print temp_images + " : " + storescp_args + "\n"
  print "Run: ", x

  # check out the temp_images dir for DICOM files
  for fn in os.listdir(temp_images):
    if is_dcm(temp_images + '\\' + fn):
      send_dcm(temp_images + '\\' + fn) # Found one, send it
      os.remove(temp_images + '\\' + fn)
      print "Removed non-DICOM file: " + fn

  # Every 5 runs, echoscu myself   
  if x == 5:
    if echoscu('localhost', '4343'):
      print "ECHOSCU OK"
      bad_echoscus = 0
      print 'Echoscu NG'
      bad_echoscus += 1

    x = 0
    if bad_echoscus == 3:
      print "Too many bad echoscus"

  x += 1 

DICOM Sample Images

I get asked, and am often in need myself, of sample DICOM files in various formats. By sample I mean no weird private tags and all of the identifying patient information has been removed. The files below are part of the collection I have built up over the years.

These files can be used to test applications you write using my PHP DICOM Class and can be opened in any image viewer, such as MicroDICOM.

Click on the thumbnail to get the DICOM file.

How to batch convert DICOM to JPEG

This method will allow you to batch convert DICOM images into JPEG images.

  1. Install IrfanView
  2. Install the IrfanView plugins (This gives Irfan View DICOM support)
  3. Now you have the prerequisite programs installed.
  4. Open IrfanView
  5. Go to File->Batch Conversion/Rename
  6. Find and select the DICOM you wish to convert and click the Add button. (You’ll most likely have to change ‘Files of Type’ to All Files (*.*)
  7. Select your Output Directory, this is where the JPEGs will be created.
  8. Check ‘Batch Conversion’
  9. Under Output Format, select JPG – JPEG Format
  10. Click Start
  11. There should now be a JPEG of each of the DICOM files you added in the Output Directory you selected.

IrfanView Screen Shot


How to Convert Multi-Frame Cine Loops to AVI or MP4 Format Using PHP

In this tutorial I’m going to post some example code and walk you through converting a DICOM multi-frame image (CINE Loop) into an mp4 video file. I chose MP4 because it works well with Apple and Microsoft products.

You’ll need ffmpeg installed with the ability to do x264. The ffmpeg site has many guides on how to do this. I used this guide for CentOS 6 for this tutorial.

You’ll need a copy of my PHP DICOM class installed.

You’ll need a copy of the example files.

Once you’ve got the prerequisites out of the way, run the commands below to download and run a copy of the example files.

wget http://www.deanvaughan.org/projects/multi-frame-to-mp4.tar.gz
tar zxvf multi-frame-to-mp4.tar.gz
cd Mutli-Frame_to_MP4/
./dcm2mp4.php sample.dcm

Your freshly created MP4 file is ./video_temp/sample.dcm.mp4, a copy is playing below:

Now take a look at dcm2mp4.php, it should be commented well enough to make it easy to understand what it is doing.

How to Write Your Own PACS: Receiving Images

This article will cover writing a storage server that is able to receive images, store them on your hard drive, create thumbnails of the images, parse out important header information, and store that header information in a database.

In my continuing effort to show off how handy my PHP DICOM class and the OFFIS DCMTK are, I’ve decided to write a series of articles on how to implement a PACS in PHP.

These articles will be focused on Linux, though I may amend them in the future to cover Windows. They will assume that you have some knowledge of PHP, mySQL, and the Linux command line.

You will need my PHP DICOM class installed. You will also need mySQL installed and have access to add users and databases.

One of the things I dislike about programming books is that they’ll spend pages upon pages showing snippets of code and explaining everything line by line. I’m not going to use that approach, I’m going to walk you through getting the example code running on your computer, testing it, and explaining how it works in a high level fashion. I tried to write the code plainly, simply, and use verbose comments through out explaining what is going on.

Lets get the sample code up and running!

Step 1:

wget http://www.deanvaughan.org/projects/PACS_Tutorial.tar.gz
tar xvf PACS_Tutorial.tar.gz
cd PACS_Tutorial/part1
chmod 755 import.php store_server.php send_test_image.php
chmod -R 777 received_images temp

Download all of the sample code and set permissions.

Step 2:

mysql -uroot -p < db.sql

We need to create a database and user for our PACS. The file db.sql will create a user, 'pacs', with password 'pacspassword', and grant it all privileges to a new database called 'pacs'. It will then create two tables, studies and images.

If at all possible, don't change any of this, it'll make the tutorial a lot less of a headache. If you do change it, open up import.php, and look for mysql_connect. Change the values there as needed.

Step 3:


With that command we've launched our storage server on port 1104. Notice that the script didn't launch in the background? Don't worry, I'll cover that later on.

If you don't want your server running on port 1104, edit store_server.php and its easy enough to change.

The storage server as is doesn't care about AE titles, its promiscuous and will accept images from anyone.

Step 4:

Since our storage server is using our terminal, leave it running and open a new terminal for the next steps.

store_server.php is going to be receiving your images, after an image is received it will run another script, import.php. import.php will be responsible for moving the image to a safe place on the hard drive, parsing the image's header information, placing that information into a database, and creating a JPEG thumbnail of the image for later use.

You can test to see if all of this works by sending an image to your computer's IP address using port 1104. You can make something up for the AE title.

If you don't have a way to send yourself images, I've included a small script that will send your new server one image.


That will send dean.dcm from class_dicom.php's examples over to your new server.

Once you have sent an image you can switch back to your terminal running store_server.php and see the log entries regarding the image transmission. Among many things the log will show you where the image was saved.

Step 5:

mysql -uroot -p < view_db.sql

Let's take a look at the database. If all went well the command above should show you the contents of the database; information about the images sent to the server.

There are two tables.

The first table is studies. This table contains information obtained from the image pertaining to the study. Each study can have many images, in example, a chest study could have both a PA and lateral view; one study, two images.

The second table is images. This table contains information specific to an image.

You'll see there isn't a field for storing the file name of the image, this is because the file name is easily inferred from database information.

Take a look at this snippet from import.php:

$store_dir = "./received_images/$sent_from_ae/" . $img['year'] . "/" . $img['month'] . "/" . $img['day'] . "/" .

The image is stored in a directory based on the AE title it came from, the appointment date, and the study uid. The image itself uses the SOP instance plus '.dcm' for its file name. Since all of this information is in the database, we can find the file. Later tutorials will go into more detail on this.

Step 6:

Right now, the storage server is stuck running in its own terminal window, if you close the window the storage server stops running, not cool. You'll want to get it running in the background and you'll want it to start at boot.

This gets tricky as different flavors of Linux, and even different versions of those flavors, handle this differently. I'll discuss the three most common approaches. If you have not done so already, control-c in the storage_server.php terminal to stop it.

CentOS 5.6+, Fedora, Red Hat, Ubuntu:

You should have a /etc/init directory. You can place script in this directory that init will use to start/stop and keep running your programs.

start on runlevel [3]
stop on runlevel [!$RUNLEVEL]
console output
exec /home/dean/PACS_Tutorial/part1/store_server.php

Create /init/store_server.conf and add all of that. Be sure to change the path to store_server.php to match your own setup.

'initctl start store_server' to start your server


If you don't have a /etc/init directory, you can always fall back to editing the inittab itself.


Edit /etc/inittab and add this line to the bottom of the file. Be sure to change the path so it points to your copy of store_server.php. Once you're back at the command line, type 'init q'. This will reload the inittab and start up store_server.php.

All else fails:

nohup store_server.php

nohup will start store_server.php in the background for you. It will not restart the process if it crashes as the methods above will.

The End?

Now you have a multi-threaded DICOM storage server capable of receiving thousands of studies worth of images everyday. The information it receives is placed into a database for easy access by other programs.

Stay tuned for part two where in I'll show you how to write a user interface to your PACS.

Using class_dicom.php on Linux

I develop primarily on Linux and wrote class_dicom.php with Linux in mind. This tutorial will be geared towards CentOS or Fedora as that is what I use on a day to day basis. I’ll try to include notes for Ubuntu/Debian when possible.

I’m going to assume you have at minimum, Linux installed on a PC and you can access the command line as root.

1. Let’s get your environment ready

We’re going to install all of the needed development tools, a web server, and MySQL. A web server and MySQL is not needed by class_dicom.php, but since most folks are going to want to use PHP from a web server, we might as well get it installed.


yum -y groupinstall "Web Server" 'Development Tools' "MySQL Database"

yum -y install wget php php-gd ImageMagick libjpeg zlib libpng libtiff libxml2 libpng-devel zlib-devel libjpeg-devel libtiff-devel libxml-devel libxml2-devel libwrap-devel libpng10-devel  php-mysql


sudo apt-get install lamp-server^

sudo apt-get install build-essential wget ImageMagick libjpeg zlib libpng libtiff libxml2 libpng-devel zlib-devel libjpeg-devel libtiff-devel libxml-devel libxml2-devel libwrap-devel libpng10-devel

All of the above will probably take a while to finish out. Note that the ^ in the Ubuntu command is not a typo and needs to be present.

2. Build and install DCMTK

DCMTK is what class_dicom.php uses to actually work with DICOM files. Among many other things, it is a collect of command line tools for working with DICOM files.

The process of building and installing is essentially the same between versions of Linux.

wget ftp://dicom.offis.de/pub/dicom/offis/software/dcmtk/dcmtk360/dcmtk-3.6.0.tar.gz 
tar zxvf dcmtk-3.6.0.tar.gz
cd dcmtk-3.6.0
./configure;make;make install

Depending on your computer, the DCMTK can take a long time to build.

3. Install class_dicom.php

Download a copy:

wget --no-check-certificate http://github.com/vedicveko/class_dicom.php/zipball/master
unzip master
mv vedicveko-class_dicom.php* class_dicom_php

and…. you’re done.

Lets make sure it works!

cd class_dicom_php/examples
./get_tags.php dean.dcm

You should see a header dump of dean.dcm printed to the screen.

class_dicom.php Example: Send all DICOM files in a directory

The script below will look in the directory you specify in $temp_dir and send any images it finds to a DICOM host. When it is done sending it will move the DICOM file to a back up directory.

file = "$temp_dir/$file";
      $ret = $d->send_dcm($target_host, $target_port, $my_ae, $target_ae);
      if($ret) {
        print "Send Error: $ret\n";
      else {
        print "Good Send\n";
        print "Moving $temp_dir/$file\n";
        rename("$temp_dir/$file", "bk/$file");



Using class_dicom.php in Windows

I had the opportunity today to help someone get class_dicom.php working under windows. I was pretty surprised to learn that you do not have to modify the class much, but it does take some time to install the other prerequisite software.

If you’re using IIS, you can skip step #1. You will need to get a copy of PHP installed under IIS. PHP on IIS (http://php.iis.net/) will allow you to do that easily. If you are using IIS, change the file locations below as appropriate for your enviroment.

1. You’ll need a copy of PHP.

Since you’ll probably want to run your PHP scripts under a web server you might as well kill two (three) birds with one stone.  The Wamp (http://www.wampserver.com/)  package is an easy as pie installer that will install and configure Apache, mySQL, PHP, and several other handy tools at once.

Once installed, be sure to find it in the system tray, left click it, and then select Put Online to start it up.

You can then go to http://localhost to test your install out.

The files you’re serving are all under C:\wamp\www\

2. You’ll need the Windows binaries of the DCMTK.

Head on over to the DCMTK website (http://dicom.offis.de/dcmtk.php.en/) and under executable binaries, grab the zip file for Windows.

Unzip it an copy the result to C:\dcmtk so it’s easy to find.

3. Get a copy class_dicom.php.

You can download a zipped version from GitHub (https://github.com/vedicveko/class_dicom.php/zipball/master). Once downloaded unzip it to C:\wamp\www\class_dicom.

You should be able to visit your web server at http://localhost/class_dicom and see a directory listing.

We’re going to need to make some modifications to the class. Open C:/wamp/www/class_dicom/class_dicom.php in an editor.

Near the top of a file is a line that looks like this:

define('TOOLKIT_DIR', '/usr/local/bin');

We need to change that to point to where we installed DCMTK. Change the line to look like this:

define('TOOLKIT_DIR', 'C:/dcmtk/bin');

Save class_dicom.php and you’re done with it.

4. Finishing up

All of the scripts in the examples directory for class_dicom.php are meant to run on the UNIX command line. This won’t work out too well when you try to run them under a web server on Windows. We need to change up one of the examples to run in this environment so you can see that it works. Take the code below and copy it into the C:\wamp\www\class_dicom\examples\get_tags.php file.

file = $file;
print "TEST: " . $d->load_tags();

print "


$name = $d->get_tag('0010', '0010');
print "Name: $name\n";


Once that is done go to http://localhost/class_dicom/examples/get_tags.php. You'll see a header dump of the dean.dcm sample DICOM file.

(Or skip all of that and open get_tags_webbased.php in your browser instead.)

You now have a functional install of class_dicom.php to work with!

Class DICOM PHP Updates!

For the first time ten months I’ve decided to update my DICOM PHP class (class_dicom.php).

I’ve added two new functions:

is_dcm(): This function will return true if the provided file is a DICOM file, false otherwise.

multiframe_to_video():  Convert a DICOM multi-frame file into a video (.mp4, .avi, .mpg, ect)

multiframe_to_video() is the big one. This functionality is driven by ffmpeg and is easily extendable and modifiable. Think of it as DICOM to AVI or DICOM to MPG.

Check it out!