Pages

DICOM Modality Worklist

Modality worklist (MWL) is one of DICOM’s workflow services that really make a difference. It’s the difference between grocery store workflow with notes on little pieces of paper and a true modern accountable workflow.

Technically speaking, DICOM Modality Worklist is a task manager just like a piece of paper with short text and a check box or the tasks application on your iPhone (or Android). But for the imaging center or RAD department the advantages are enormous. The most obvious benefit is that there’s no need to reconcile all kind of miss spelled names in the PACS because the patient name is no longer keyed in on the modality workstation but received electronically via the MWL query. The fact that the requested procedure is also received electronically reduces the chance for doing the wrong procedure to the wrong patient. Combined with Modality Performed Procedure step (MPPS), that allows the modality to report the task status, take ownership over the task and checkmark it as done when completed, the up side is obvious. No wonder then, that many HMO’s require Modality Worklist as a mandatory feature for every imaging device they purchase. 

The most basic abstraction of a task is a short description of what should be done and a checkbox. That’s all it takes. The MWL data model is a bit more complicated and has two levels.
The top, parent, level is called “Requested Procedure” (RP) and holds the information about the patient (name, id), the study (accession number, study instance UID) and the procedure. The procedure can be described as text using attribute (0032,1060) – “Requested Procedure Description” or in a more sophisticated manner using the (0032,1064) – “Requested Procedure Code Sequence” where static tables of codes and meanings can be used to configure and maintain procedures in the RIS or HIS.
The child level is called “Scheduled Procedure Step” (SPS) and holds attributes relevant to the modality and the actual procedure to be made. A single requested procedure may hold more than one SPS if the request is for a multi-modality study, for example a chest X-Ray and a CT or whatever combination, or if for example two protocols should be applied (e.g. Chest and Abdomen). As a modality, we will use the data in the RP to identify the patient and eliminate re-typing of the name and ID and the SPS to determine what exactly to do.

HL7Kit Users Forum

Just wanted to inform everyone that there's a new hangout - HL7Kit Users Forum.
So, if you're already using HL7Kit or just evaluating it or even just downloaded it or never even heard about it, that's an opportunity here! Go to http://forum.hl7kit.com, register and get more from your software.
Thanks,
Roni

Low (Resolution) and Order (of applying transformations)

I'm doing a project that involves taking huge images of 400,000,000 pixels, 20000 X 20000, 700 MB on disk, scaling them down and cutting them into reasonably sized tiles (512x512 pixels).


In this post I want to present how simple code re factoring involving merely changing the order of applying the same transformations rewarded an enormous X20 performance gain.


As a first step, I had to order extra 8GB RAM for my workstation. Once thay have arrived (eBay, 100$, 1 week) I could finally click the input file and display it on windows image preview. Before that, my workstation, initially having only 2GB, hang for 20 minutes every time I accidentally hovered over the file.

The next step was to realize that .NET 2.0 bitmap classes won't do the trick. But than I was surprised to discover that WPF BitmapSource and its sub-classes (System.Windows.Media.Imaging) are capable of handling these very large images so I built the code around them avoiding a native C++ bite-cruncher completely. This was a very good start. It made me very happy.

The third step was to wrap the cropped images in DICOM. This is a subject for a dedicated, yet to come, post about the new multi-frame objects and concatenations. Then came the last two steps that proved out to be challenging.

My initial code looked like this:

  1. Use ScaleTransform to down-scale the original image to the required resolution
  2. Loop over the down-scaled image on X and Y to do the tiling (The Cut function)
  3. Use CroppedBitmap to do the cropping (in the loop)

Here it is:

Documentation Update for version 2.0.1.9

The online documentation is now updated for version 2.0.1.9. In the new documentation there's new page with information for DICOM conformance that is useful for writing the DICOM Conformance Statement.

Query/Retrieve part II - C-MOVE


In part I of this post, I was in a meeting with a customer reviewing their workstation code and while sitting there I was thinking to myself, why should my customers have to deal with so many details of the DICOM Q/R Service when all they really want is to retrieve a study just like they would have downloaded a zip file from a web site. And thus, later, back in my office I decided to extended the DICOM Toolkit API to include a C-MOVE method that will take care of everything including the incoming association. In today’s post I’m going to use the new MoveAndStore method to talk about the DICOM Query/Retrieve service. We’ll start at the end and then work our way backwards.

C-MOVE is a DICOM command that means this: The calling AE (we) ask the called AE (the PACS) to send all the DICOM Instances that match the identifier to the target AE. 
Here’s how you ask a PACS to send you the DICOM images with RZDCX (version 2.0.1.9).

        public void MoveAndStore()
        {
            // Create an object with the query matching criteria (Identifier)
            DCXOBJ query = new DCXOBJ();
            DCXELM e = new DCXELM();
            e.Init((int)DICOM_TAGS_ENUM.patientName);
            e.Value = DOE^JOHN";
            query.insertElement(e);
            e.Init((int)DICOM_TAGS_ENUM.patientID);
            e.Value = @"123456789";
query.insertElement(e);
            // Create an accepter to handle the incomming association
DCXACC accepter = new DCXACC();
            accepter.StoreDirectory = @".\MoveAndStore";
Directory.CreateDirectory(accepter.StoreDirectory);
            // Create a requester and run the query
DCXREQ requester = new DCXREQ();
            requester.MoveAndStore(
                MyAETitle, // The AE title that issue the C-MOVE
                IS_AE,     // The PACS AE title
                IS_Host,   // The PACS IP address
                IS_port,   // The PACS listener port
                MyAETitle, // The AE title to send the
                query,     // The matching criteria
                104,       // The port to receive the results
                accepter); // The accepter to handle the results
        }

Behind this rather short function hides a lot of DICOM networking and when it returns we should have all the matching objects stored in the directory “.\MoveAndStore”. Readers with some practical DICOM experience probably expect me to say that it can also fail. In that case MoveAndStore throws an exception with the error code and description. Sometimes you would have to set the detailed logging on and start reading logs like we did in chapter 5 of this tutorial on DICOM networking and in some later post we will look together at a DICOM log of a Q/R transaction.

The following diagram, taken from part 2 of the DICOM standard, is commonly seen in DICOM Conformance Statements as the Data Flow diagram of the Q/R Service. These diagrams and their notation are defined by the standard in part 2 that specify the DICOM Conformance Statement – a standard document that every application vendor should provide and that describes how they implemented the standard in their product. At some point we will get to how to read and write these documents.




The vertical dashed line represents the DICOM Protocol Interface between the two applications (it is usually a single dashed line but in this example it got a bit messed up). The arrows accros the interface represents DICOM associations. The arrow points from the application that initiates the association (the requester) to the application that responds to it (the responder or accepter). The upper part of the diagram shows the control chanel where the C-MOVE request is sent and statuses are reported back by the PACS. The lower part of the diagram shows the data chanel where the DICOM instances are sent to the client.

RZDCX 2.0.1.9

A new release of ZRDCX, 2.0.1.9, was released today. This release fixes the following issues:


#TypeStatusCreatedChangedVersionTitle 
Description
 
291codefixedJan 30Jan 30 DICOMDIR with strict = false fails on file names with .'sedit
Create filenames like 1.2.3.4.dcm ScanAndCreate(..., false) -> Exception Reported by SBX
 
290newfixedJan 19Jan 192.0.1.8MoveAndStore - Single threaded C-MOVE duplexing control and dataedit
Add a method MoveAndStore to DCXREQ to allow one command that performs both the C-MOVE and the C-STORE commands on separate but semi-synced associations.
 
287newfixedDec 12Dec 122.0.1.8Minor logging additions to RZDCXedit
Fix log messages when pres ctx id not found in store


Issue 291 addresses a very popular feature request to enable creation of DICOMDIR even when the filenames are not according to the standard. For example filenames with the instance uid like 1.2.3.4.5.6.dcm are not valid reference file id's according to the standard. Nevertheless, sometimes you would like to create a DICOMDIR file for other purposes other than exporting data on a CD. The ScanAndCreate method of DCXDICOMDIR takes a "strict" parameter that when set to false, will generate a DICOMDIR regardless of the validity of it's content (as long of course that the files are DICOM files).

Issue 290 is a new feature of RZDCX that enable to run a complete C-MOVE SCP with a single command including the incomming association. More about this feature in the upcoming chapter of the DICOM Tutorial.
Here's a short example


        public void MoveAndStore()
        {
            // Create an object with the query matching criteria (Identifier)
            DCXOBJ query = new DCXOBJ();
            DCXELM e = new DCXELM();
            e.Init((int)DICOM_TAGS_ENUM.patientName);
            e.Value = DOE^JOHN";
            query.insertElement(e);
            e.Init((int)DICOM_TAGS_ENUM.patientID);
            e.Value = @"123456789";
query.insertElement(e);
            // Create an accepter to handle the incomming association
DCXACC accepter = new DCXACC();
            accepter.StoreDirectory = @".\MoveAndStore";
Directory.CreateDirectory(accepter.StoreDirectory);
            // Create a requester and run the query
DCXREQ requester = new DCXREQ();
            requester.MoveAndStore(
                MyAETitle, // The AE title that issue the C-MOVE
                IS_AE,     // The PACS AE title
                IS_Host,   // The PACS IP address
                IS_port,   // The PACS listener port
                MyAETitle, // The AE title to send the
                query,     // The matching criteria
                104,       // The port to receive the results
                accepter); // The accepter to handle the results
        }

This single command takes care of all the details of a C-MOVE transaction. Instead of running an accepter on another thread to wait for the C-MOVE results, we pass the accepter as a parameter to the MoveAndStore method of the requester. Note that there's a new set property in DCXACC that enables setting the directory to store the incoming files. All the callbacks of DCXACC and DCXREQ can be used as well just as before.

Issue 287 is a small log enhancement that maxes it easier to to diagnose association problems. The log now shows very clearly when a presentation context for a command was not negotiated.

DICOM Query/Retrieve Part I


It all started when I was sitting in a cubicle with a customer, looking at the code of their workstation performing a Query/Retrieve cycle and though everything did look familiar and pretty much straight forward something bothered me.

Query/Retrieve, or Q/R for short, is the DICOM service for searching images on the PACS and getting a copy of them to the workstation where they can be displayed.

Q/R is a fundamental service and every workstation implements it. This sounds like a trivial task, just like downloading a zip file from a web site but there are a lot of details to take care of and while writing this post I realized that I will have to split it to a little sub-series. Today's post will be about the Query part and in the next post I'll get to the Retrieve.

To search the PACS we use the DICOM command C-FIND. This command takes as an argument a DICOM object that represent a query. The PACS transforms the object that we send to a query, probably to SQL, runs it and then transform every result record back into a DICOM object and send it back to us in a C-FIND response. The PACS sends one C-FIND response for every result record. While still running, the status field of the C-FIND response command is pending (0xFF00). The last response has a status success. It may of course fail and then RZDCX will throw an exception with the failure reason and status. It may also succeed but with no matches (empty results set).

Let's do some examples. This code constructs a query for searching patients:

            // Fill the query object
            DCXOBJ obj = new DCXOBJ();
            DCXELM  el = new DCXELM();

            el.Init((int)DICOM_TAGS_ENUM.QueryRetrieveLevel);
            el.Value = "PATIENT";
            obj.insertElement(el);

            el.Init(0x00100010);
            el.Value = "R*";
            obj.insertElement(el);

            el.Init(0x00100020);
            obj.insertElement(el);

            el.Init((int)DICOM_TAGS_ENUM.PatientsSex);
            obj.insertElement(el);

            el.Init((int)DICOM_TAGS_ENUM.PatientsBirthDate);
            obj.insertElement(el);