DICOM Worklist Server

Mithilfe des Toolkits DCMTK lässt sich ein einfacher DICOM Worklist Server realisieren. Hierbei wird der in dem Toolkit enthaltene wlmscpfs Server verwendet. Für jeden Worklisteintrag muss dazu in einem konfigurierbaren Verzeichnis eine binäre DICOM-Datei vorhanden sein. Mittels des ebenfalls in dem Toolkit enthaltenden Programms dump2dcm lassen sich diese aus einer Textrepräsentation des DICOM-Datensatzs erstellen:

(0008,0050) SH ${AN}      #   0, 0 AccessionNumber
(0010,0010) PN ${PN}      #  16, 1 PatientsName
(0010,0020) LO ${PID}     #   6, 1 PatientID
(0010,0030) DA ${GEBDAT}  #   8, 1 PatientsBirthDate
(0010,0040) CS ${SEX}     #   2, 1 PatientsSex
(0040,0100) SQ 	      	  # 118, 1 ScheduledProcedureStepSequence
  (fffe,e000) -           # 110, 1 Item
    (0008,0060) CS [US]   #   2, 1 Modality
  (fffe,e00d) -    	  #   0, 0 ItemDelimitationItem
(fffe,e0dd) - 	

Eine Erzeugung direkt aus Mirth Connect ist nicht möglich. Allerdings kann man den Mirth Connect verwenden, um obiges Text-Template mit Inhalt aus HL7-Nachrichten zu befüllen.

In einem Transformer werden die enstprechenden Variablen-Templates mit Inhalten gefüllt.

tHnoUsMwl

Die exportierten Textdateien werden dann über einen cron-Job periodisch in die DICOM-Binärdateien umgewandelt:

#!/bin/bash
#
# DICOM Worklist generierern

HISWEBWL=/var/opt/hisweb/dicom/HNOUSMWL
DUMPFILES=`ls -1 $HISWEBWL/*.txt 2>/dev/null`
if [ -n $DUMPFILRS ] 
then
  rm -f $HISWEBWL/lockfile
  for FILE in $DUMPFILES
  do
    BASENAME=`basename $FILE .txt` 
    dump2dcm $FILE $HISWEBWL/$BASENAME.wl >/dev/null 2>/dev/null
    rm -f $FILE	
  done 
  touch $HISWEBWL/lockfile
fi

Die Worklist kann man sich auch aus ADT-Nachrichten fingieren: Für jede A04-Nachricht mit Bewegungsdatum des aktuellen Tages wird eine Auftragsdatei mit O am Dateinamensanfang folgender der Fallnummer als Dateiname erstellt. Bei Nachrichten zu stationären Patienten erzeugt man eine Datei mit I am Dateinamensanfang und dann folgend der Fallnummer. Der entsprechende Filter sieht dann so aus:

// Filter für Ultraschall Modality Worklist
//
// $Id$


// Eventtypen, die kommuniziert werden sollen
var valid_Events = new RegExp("A01|A02|A04|A06|A07|A12|A13");
var todayTs = new Date();
var eventDate = new Date();


var process = false;

if (valid_Events.test(msg['EVN']['EVN.1']['EVN.1.1'].toString()))
{
	eventDate.hl7parse(msg['ZBE']['ZBE.2']['ZBE.2.1'].toString());
	if ( todayTs.toGermanDateString() == eventDate.toGermanDateString() ) {	
		process = true;
	} 	
} 

return process;

Hier in diesem Beispiel kommen Prototyphilfsfunktionen zum Einsatz.

Über einen weiteren cron-Job, der täglich um 20:00 Uhr ausgeführt wird, werden dann die nicht mehr benötigten Worklisteinträge gelöscht:

#!/bin/bash
#
# alte DICOM Worklist Einträge löschen

HISWEBWL=/var/opt/hisweb/dicom/HNOUSMWL
NDAYS=15

rm -f $HISWEBWL/lockfile
rm -f $HISWEBWL/O*.wl $HISWEBWL/AO*.wl	
FILES=`find $HISWEBWL -name "I*.wl" -type f -mtime +$NDAYS -print`
for FILE in $FILES
do
  rm -f $FILE >/dev/null 2>/dev/null
done
touch $HISWEBWL/lockfile

Zum Starten des wlmscpfs-Servers wird folgendes runlevel-skript in /etc/init.d verwendet:

#! /bin/sh
### BEGIN INIT INFO
# Provides:          wlmscpfs
# Required-Start:    $remote_fs $syslog
# Required-Stop:     $remote_fs $syslog
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: DICOM Basic Worklist Management SCP
# Description:       DICOM Basic Worklist Management SCP
### END INIT INFO

# Author: Stefan Langenberg 
# Do NOT "set -e"

# PATH should only include /usr/* if it runs after the mountnfs.sh script
PATH=/sbin:/usr/sbin:/bin:/usr/bin
DESC="DICOM Basic Worklist Management SCP"
NAME=wlmscpfs
DAEMON=/usr/bin/$NAME
DATAPATH=/var/opt/hisweb/dicom
LOG=/var/log/wlmscpfs.log
DAEMON_ARGS="+ac -v --disable-file-reject -dfp $DATAPATH 16999"
PIDFILE=/var/run/$NAME.pid
SCRIPTNAME=/etc/init.d/$NAME

# Exit if the package is not installed
[ -x "$DAEMON" ] || exit 0

# Read configuration variable file if it is present
[ -r /etc/default/$NAME ] && . /etc/default/$NAME

# Load the VERBOSE setting and other rcS variables
. /lib/init/vars.sh

# Define LSB log_* functions.
# Depend on lsb-base (>= 3.2-14) to ensure that this file is present
# and status_of_proc is working.
. /lib/lsb/init-functions

#
# Function that starts the daemon/service
#
do_start()
{
        # Return
        #   0 if daemon has been started
        #   1 if daemon was already running
        #   2 if daemon could not be started
        #start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \
        #       || return 1
        start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- \
                $DAEMON_ARGS >$LOG 2>$LOG &
        return 0                
        # Add code here, if necessary, that waits for the process to be ready
        # to handle requests from services started subsequently which depend
        # on this one.  As a last resort, sleep for some time.
}

#
# Function that stops the daemon/service
#
do_stop()
{
        # Return
        #   0 if daemon has been stopped
        #   1 if daemon was already stopped
        #   2 if daemon could not be stopped
        #   other if a failure occurred
        start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $NAME
        RETVAL="$?"
        [ "$RETVAL" = 2 ] && return 2
        # Wait for children to finish too if this is a daemon that forks
        # and if the daemon is only ever run from this initscript.
        # If the above conditions are not satisfied then add some other code
        # that waits for the process to drop all resources that could be
        # needed by services started subsequently.  A last resort is to
        # sleep for some time.
        start-stop-daemon --stop --quiet --oknodo --retry=0/1/KILL/5 --exec $DAEMON
        [ "$?" = 2 ] && return 2
        # Many daemons don't delete their pidfiles when they exit.
        rm -f $PIDFILE
        return "$RETVAL"
}

#
# Function that sends a SIGHUP to the daemon/service
#
do_reload() {
        #
        # If the daemon can reload its configuration without
        # restarting (for example, when it is sent a SIGHUP),
        # then implement that here.
        #
        start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME
        return 0
}

case "$1" in
  start)
        log_daemon_msg "Starting $DESC" "$NAME"
        do_start
        case "$?" in
                0|1) log_end_msg 0 ;;
                2) log_end_msg 1 ;;
        esac
        ;;
  stop)
        log_daemon_msg "Stopping $DESC" "$NAME"
        do_stop
        case "$?" in
                0|1) log_end_msg 0 ;;
                2) log_end_msg 1 ;;
        esac
        ;;
  status)
       status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
       ;;
  #reload|force-reload)
        #
        # If do_reload() is not implemented then leave this commented out
        # and leave 'force-reload' as an alias for 'restart'.
        #
        #log_daemon_msg "Reloading $DESC" "$NAME"
        #do_reload
        #log_end_msg $?
        #;;
  restart|force-reload)
        #
        # If the "reload" option is implemented then remove the
        # 'force-reload' alias
        #
        log_daemon_msg "Restarting $DESC" "$NAME"
        do_stop
        case "$?" in
          0|1)
                do_start
                case "$?" in
                        0) log_end_msg 0 ;;
                        1) log_end_msg 1 ;; # Old process is still running
                        *) log_end_msg 1 ;; # Failed to start
                esac
                ;;
          *)
                # Failed to stop
                log_end_msg 1
                ;;
        esac
        ;;
  *)
        #echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2
        echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
        exit 3
        ;;
esac

: