A basic DICOM worklist provider

Recently we were asked to write a PHP DICOM Worklist server for an overseas customer. DICOM Multi-tool seemed like overkill, and so we put together a very basic project that:

  • Generates DCMTk-compatible dump files from a MySQL schema
  • Converts those dump files to .wl data files
  • Launches wlmscpfs (DCMTk’s worklist daemon) and points it to the records the app creates

This application has no database of its own. But it’s now available under an MIT license. Feel free to comment, contribute and enjoy.

Automating DICOM Printer 2

This article is intended for IT staff. It includes samples of XML files that might be difficult to understand for the non-initiated. If you would like to learn about automated DICOM storage without the technical jargon, please send us an email.

One of the consequences of implementing a PACS, is that everything has to be sent there.  This has produced a world of patchwork solutions that often involve generating paper records for the sole purpose of digitizing them back into electronic form.

I truly can’t think of a greater offense to the purpose of electronic health records than to make them complicit in the needless use of paper.  Not to mention the tragedy of entire human working lives spent scanning things that have just come out of a printer.

Attaching Paper to PACS

DICOM Printer 2 was originally designed to address this problem.  The XML-based configuration, regular expression matching, plug-in interfaces and capability to operate according to data-driven logic gives it a natural ability to fit into whatever part of patient workflows are most wasteful of human time.

In this post, I go through a short example of a common configuration, broadly applicable to the following workflow:

  1. A document is generated; perhaps by a fax server, a medical instrument (such as BMD), or a staff member who transcribes a dictated report.
  2. The document is printed to paper.
  3. The document is prepared into a scanning batch, and scanned.
  4. Office staff open the scanned document and manually upload, link, or otherwise select a patient record to which it should be attached.
  5. The document arrives in PACS for permanent storage.

Any reasonable person can see that steps 2 and 3 are unnecessary, and step 4 often involves visual data searching that can quite often be automatically performed by a computer.

One of our most common configurations, results in the following workflow:

  1. A document is generated.
  2. DP2 automatically reads patient and study information from the contents.
  3. DP2 queries PACS and/or Worklist for the patient record, associates it, and,
  4. The document arrives in PACS.

No paper, no shameless waste of human life, still the same result in PACS.  Here is the configuration that is needed to achieve this:

<DicomPrinterConfig>
  ...
  <ActionsList>
    <ParseJobTextFile name="ParseContents">
      <DcmTag tag="(8,50)">^(.*)\-.*$</DcmTag>
      <DcmTag tag="(8,60)">^.*\-(.*)$</DcmTag>
    </ParseJobFileName>
    <Query name="Query" type="Worklist">
      <DcmTag tag="(0008,0050)" />
      <DcmTag tag="(0008,0060)" />
      <DcmTag tag="(0010,0020)" />
      <DcmTag tag="(0020,000D)" />
      <ConnectionParameters>
        <MyAeTitle>DICOMPRINTER2</MyAeTitle>
        <PeerAeTitle>DVT</PeerAeTitle>
        <Host>localhost</Host>
        <Port>104</Port>
      </ConnectionParameters>
    </Query>
    <Store name="Store">
      <Compression type="RLE" />
      <ConnectionParameters>
        <PeerAeTitle>CONQUESTSRV1</PeerAeTitle>
        <Host>192.168.0.41</Host>
        <Port>5678</Port>
      </ConnectionParameters>
    </Store>
  </ActionsList>
  <Workflow>
    <Perform action="ParseContents" onError="Hold" />
    <Perform action="Query" onError="Suspend" />
    <If field="QUERY_FOUND" value="1">
      <Statements>
        <Perform action="Store" />
      </Statements>
      <Else>
        <Suspend resumeAction="Query" />
      </Else>
    </If>
  </Workflow>
</DicomPrinterConfig>

The parts of this file seem complex, but it is really comprised of only a few action definitions and a workflow that implements them.

The first action parses the contents of the printed document, looking for accession and modality values:

    <ParseJobTextFile name="ParseContents">
      <DcmTag tag="(8,50)">Accession: (.*)$</DcmTag>
      <DcmTag tag="(8,60)">Modality: (.*)$</DcmTag>
    </ParseJobFileName>

The two DcmTag elements describe regular expressions used to extract the values, and the tag attributes define where the data is to reside. The next action is:

<Query name="Query" type="Worklist">
      <DcmTag tag="(0008,0050)" />
      <DcmTag tag="(0008,0060)" />
      <DcmTag tag="(0010,0020)" />
      <DcmTag tag="(0020,000D)" />
      <ConnectionParameters>
        <MyAeTitle>DICOMPRINTER2</MyAeTitle>
        <PeerAeTitle>DVT</PeerAeTitle>
        <Host>localhost</Host>
        <Port>104</Port>
      </ConnectionParameters>
    </Query>

Pretty self-explanatory: query worklist with accession and Modality, and retrieve Patient ID and Study UID. Note that if DICOM header attributes have previously been set in the workflow, then including them in a query automatically makes them part of the query parameters. In this case, because Accession and Modality will have already been extracted from the document, then the query would be based on these two parameters, and retrieve two more. The ConnectionParameters group is self-explanatory.

The next action stores the document to PACS:

    <Store name="Store">
      <Compression type="RLE" />
      <ConnectionParameters>
        <PeerAeTitle>CONQUESTSRV1</PeerAeTitle>
        <Host>192.168.0.41</Host>
        <Port>5678</Port>
      </ConnectionParameters>
    </Store>

Once these actions have been defined, the magic can be performed. The following piece of the configuration file defines how these actions will be composed into an automated workflow:

  <Workflow>
    <Perform action="ParseContents" onError="Hold" />    #1
    <Perform action="Query" onError="Suspend" />         #2
    <If field="QUERY_FOUND" value="1">                   #3
      <Statements>
        <Perform action="Store" />                       #4
      </Statements>
      <Else>
        <Suspend resumeAction="Query" />                 #5
      </Else>
    </If>
  </Workflow>

The process is straightforward in this case:

  1. Extract document contents, according to previously defined ParseContents action, if nothing is found, stop processing and hold the document in queue indefinitely.
  2. Query worklist using retrieved values, if the query fails, suspend the job for a pre-determined amount of time, and then retry.
  3. If query completes successfully, and a record is found then,
  4. Store to PACS.
  5. If the query returns no records, suspend and retry the query later.

The last step helps if the timing of data in worklist (or PACS) does not precede document generation.  A situation like this often arises when requisitions, which are the first documents to arrive, need to be attached to studies after they have been performed.

There are actually a multitude of variants of this workflow, sometimes involving more than one query, sometimes requiring a little bit of user input to augment the record, but the general process is a very common application of DICOM Printer 2.

We hope you too can save a few forests’ worth of trees and an FTE or two, by implementing DICOM Printer 2 in your practise.

Adding a logo to DICOM Printer 2 output

Please note that adding these custom elements to DICOM Printer 2 workflows is a task that requires a little knowledge of XML. If you feel that the post is too technical for you, then ask someone with IT experience to help.

Recap

In our previous post, we went through installation and configuration of basic film output. Although this is great for basic use, most facilities we’ve come across like to see their identity expressed on their product.

So, we’ll want to go from this:

Ceph image without logo

To this!

Ceph image with facility and logo

First Things First

In the previous post, we used the Configuration Wizard. The Wizard does the basics, but to add a logo we need to dig into the actual configuration file. To see it, launch the DICOM Printer Control Center. It can be found via the Start Menu, or by searching in Windows 8.

Accessing DICOM Printer 2 control from the start menu

You will then see the main control window.

DICOM Printer 2 Control Center main window

Switch to the Configuration tab. If you followed the previous tutorial, you will see the following text:

<?xml version="1.0" encoding="UTF-8"?>
<DicomPrinterConfig>
    <General>
        <CheckingInterval>1</CheckingInterval>
        <SuspensionTime>1</SuspensionTime>
        <Verbosity>30</Verbosity>
    </General>
    <ActionsList>
        <Print name="PrintOnDevice">
            <Resolution>320 x 320</Resolution>
            <PrintMode>Grayscale12</PrintMode>
            <ConnectionParameters>
                <MyAeTitle>DICOM_PRINTER</MyAeTitle>
                <PeerAeTitle>DS5302</PeerAeTitle>
                <Host>192.168.1.100</Host>
                <Port>5040</Port>
                <AssociationTimeout>10</AssociationTimeout>
                <DimseTimeout>10</DimseTimeout>
            </ConnectionParameters>
        </Print>
        <Trim name="TrimImages">
            <Left>auto</Left>
            <Right>auto</Right>
            <Top>auto</Top>
            <Bottom>auto</Bottom>
        </Trim>
    </ActionsList>
    <Workflow>
        <Perform action="TrimImages" onError="Ignore"/>
        <Perform action="PrintOnDevice" onError="Discard"/>
    </Workflow>
</DicomPrinterConfig>

The configuration is comprised of three parts. General settings, the ActionsList, which is the list of things DP2 will be doing, and the Workflow, which is the order in which they will be performed.

In this example, any printed document pages are “trimmed”, i.e., margins are completely removed:

<Perform action="TrimImages" onError="Ignore"/>

and then they are sent to the DS5302 dry imager at IP address 192.168.1.100 and port 5040:

<Perform action="PrintOnDevice" onError="Discard"/>

If the imager happens to be unavailable, or communication fails, the document is “Discarded”. This is so we don’t create a long queue of documents.

Since we want our logo to appear at the top left, and not overlay the image, we will:

  1. Remove the Trim action, and
  2. Replace it with an action that puts the logo into place.

Remove Trim

Simply get rid of the parts of the configuration that relate to Trim. Leaving only:

<?xml version="1.0" encoding="UTF-8"?>
<DicomPrinterConfig>
    <General>
        <CheckingInterval>1</CheckingInterval>
        <SuspensionTime>1</SuspensionTime>
        <Verbosity>30</Verbosity>
    </General>
    <ActionsList>
        <Print name="PrintOnDevice">
            <Resolution>320 x 320</Resolution>
            <PrintMode>Grayscale12</PrintMode>
            <ConnectionParameters>
                <MyAeTitle>DICOM_PRINTER</MyAeTitle>
                <PeerAeTitle>DS5302</PeerAeTitle>
                <Host>192.168.1.100</Host>
                <Port>5040</Port>
                <AssociationTimeout>10</AssociationTimeout>
                <DimseTimeout>10</DimseTimeout>
            </ConnectionParameters>
        </Print>
    </ActionsList>
    <Workflow>
        <Perform action="PrintOnDevice" onError="Discard"/>
    </Workflow>
</DicomPrinterConfig>

Add the Logo

First, we need to save the logo image to a location where DICOM Printer can find it. The best place for this is the DICOM Printer config folder:

Open up the DICOM Printer Control center and click the shortcut to the config folder.

Save the logo here:

When making your logo image, please note the image type (PNG in this case) and the dimensions. We are using 200 x 100 px. The full path of the logo is:

%ProgramData%\Flux Inc\DICOM Printer 2\config\logo.png

Which, on English machines, is actually the same as:

C:\ProgramData\Flux Inc\DICOM Printer 2\config\logo.png

Create the Action

The logo can be placed onto every page using a PrintImage action, like so:

<PrintImage name="addLogo">
    <ImagePath>C:\ProgramData\Flux Inc\DICOM Printer 2\config\logo.png</ImagePath>
    <X>1</X>
    <Y>1</Y>
    <Width>16</Width>
    <Height>16</Height>
    <Aspect>keep</Aspect>
</PrintImage>

Note that:

  • We’ve called the action addLogo. We’ll use this name later to call the action in the workflow.
  • We’ve also instructed DICOM Printer 2 to place the logo 1% from the top (Y) and 1% from the left (X) of the page, and,
  • The logo is going to be placed into a box that is 16% of the page wide and 16% of the page tall. If you are unsure of your logo dimensions, then we recommend this size.
  • DP2 will fit the image into the box, and keep the original aspect ratio.

When included as part of the original configuration, the addLogo action looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<DicomPrinterConfig>
    <General>
        <CheckingInterval>1</CheckingInterval>
        <SuspensionTime>1</SuspensionTime>
        <Verbosity>30</Verbosity>
    </General>
    <ActionsList>
        <Print name="PrintOnDevice">
            <Resolution>320 x 320</Resolution>
            <PrintMode>Grayscale12</PrintMode>
            <ConnectionParameters>
                <MyAeTitle>DICOM_PRINTER</MyAeTitle>
                <PeerAeTitle>DS5302</PeerAeTitle>
                <Host>192.168.1.100</Host>
                <Port>5040</Port>
                <AssociationTimeout>10</AssociationTimeout>
                <DimseTimeout>10</DimseTimeout>
            </ConnectionParameters>
        </Print>
        <PrintImage name="addLogo">
          <ImagePath>C:\ProgramData\Flux Inc\DICOM Printer 2\config\logo.png</ImagePath>
          <X>1</X>
          <Y>1</Y>
          <Width>16</Width>
          <Height>16</Height>
          <Aspect>keep</Aspect>
        </PrintImage>
    </ActionsList>
    <Workflow>
        <Perform action="addLogo" onError="Ignore"/>
        <Perform action="PrintOnDevice" onError="Discard"/>
    </Workflow>
</DicomPrinterConfig>

Adding the Facility name

Next up, the facility name at the bottom of the page. It requires a separate action, which looks something like this:

<PrintText name="addFooter">
  <Text>Clinica Odontologica – R. Odontologica 131 – São Paulo, Brasil</Text>
  <X>10</X>
  <Y>97</Y>
  <Width>80</Width>
  <Height>3</Height>
  <Color>0xFFFFFF</Color>
  <BackgroundColor>0x80000000</BackgroundColor>
</PrintText>

This is a PrintText action called addFooter, and it will:

  • Place the text “Clinica Odontologica – R. Odontologica 131 – São Paulo, Brasil”.
  • 10% from the left (X), and 97% from the top (Y) of the page.
  • The text box will be 80% wide and 3% tall, with the font automatically adjusted to fit.
  • The foreground colour will be white (0xFFFFFF) and the background will be a semi-transparent (0x80), black (000000) rectangle. This will allow the text to be read even when overlaying part of the image.

The completed configuration, with the addFooter action added to the workflow, looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<DicomPrinterConfig>
    <General>
        <CheckingInterval>1</CheckingInterval>
        <SuspensionTime>1</SuspensionTime>
        <Verbosity>30</Verbosity>
    </General>
    <ActionsList>
        <Print name="PrintOnDevice">
            <Resolution>320 x 320</Resolution>
            <PrintMode>Grayscale12</PrintMode>
            <ConnectionParameters>
                <MyAeTitle>DICOM_PRINTER</MyAeTitle>
                <PeerAeTitle>DS5302</PeerAeTitle>
                <Host>192.168.1.100</Host>
                <Port>5040</Port>
                <AssociationTimeout>10</AssociationTimeout>
                <DimseTimeout>10</DimseTimeout>
            </ConnectionParameters>
        </Print>
        <PrintImage name="addLogo">
          <ImagePath>C:\ProgramData\Flux Inc\DICOM Printer 2\config\logo.png</ImagePath>
          <X>1</X>
          <Y>1</Y>
          <Width>16</Width>
          <Height>16</Height>
          <Aspect>keep</Aspect>
        </PrintImage>
        <PrintText name="addFooter">
          <Text>Clinica Odontologica – R. Odontologica 131 – São Paulo, Brasil</Text>
          <X>10</X>
          <Y>97</Y>
          <Width>80</Width>
          <Height>3</Height>
          <Color>0xFFFFFF</Color>
          <BackgroundColor>0x80000000</BackgroundColor>
        </PrintText>
    </ActionsList>
    <Workflow>
        <Perform action="addLogo" onError="Ignore"/>
        <Perform action="addFooter" onError="Ignore"/>
        <Perform action="PrintOnDevice" onError="Discard"/>
    </Workflow>
</DicomPrinterConfig>

And That’s It!

Now, save and restart the DP2 service, like so:

And then,

Your next print job will have your logo at the top right and facility name at the bottom!

Ceph image with facility and logo

Thanks for reading! Be sure to check out our downloads page, where you can download fully functional 60 day evaluations.

At 495 USD, DICOM Printer is a stupendously inexpensive application. We’re excited that so many people find it useful.