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):
  try:
    tags[tag]
  except:
    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';
  else:
    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'
    else:
      switch = ''
      new_ts = 'JPEG Lossless'

    f = os.popen('dcmtk/bin/dcmcjpeg.exe ' + switch + ' ' + file + ' ' + tmp_file)
    out = f.read()
    if(out):
      print out
      new_ts = ''
    else:
      os.remove(file)
      os.rename(tmp_file, file)
    
  if(new_ts):
    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 = '192.168.1.216'
  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:
    os.remove(file)
    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
    else:
      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
    else:
      print 'Echoscu NG'
      bad_echoscus += 1

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

  x += 1 
  time.sleep(5);

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.

<?PHP
#
# Prints out the DICOM tags in a file specified on the command line
#

require_once('../class_dicom.php');

$file = 'dean.dcm';

if(!file_exists($file)) {
  print "$file: does not exist\n";
  exit;
}

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

print "<pre>";

print_r($d->tags);

$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!