Tuesday, December 25, 2012

C# Tricks


I did some UI work in C# lately and discovered all kind of cool new stuff. So cool, I couldn't resist sharing it with the world.

Lets say we have a multi-threaded application that throws tasks (TPL) in the back-end.

The tasks report back and we want to reflect it in the UI but we should update UI always in the main thread. We use Invokefor and in many places in the code we have chunks of the kind:

void SomeFunction()
{
  if (this.InvokeRequired)
    this.Invoke(new MethodInvoker(SomeFunction)
  else
  {
     // Here goes some UI functionality
  }
}

this is the form we are working in for example.

With .NET 3.0 we have Action so we can write one function like this

        void DoUiStuff(Action a)
        {
            if (this.InvokeRequired)
                this.Invoke(new MethodInvoker(a));
            else
                a();

        }

Then whenever we want a UI action we write a lambda expression for example:

DoUiStuff(() => progressBar.Visible = true);

or even a code block like this:

DoUiStuff(() =>
            {
              progressBar.Visible = false;
MessageBox.Show("Done!");
});

Fun ...



Tuesday, December 18, 2012

DICOM Server and DICOM Toolkit new Release 2.0.2.8

The last couple of weeks were very busy with many installations of HRZ DICOM Server all over the world. It turned out to be a product that many people were waiting for and we've been busy with installations and support work. Naturally, many issues were found and fixed. These fixes and improvements were gathered into the last release that is now available online. It was great fun to see that people liked the idea of the server and modified and customized the database mappings to meat their own requirements. For me this was the best indication that the product concept is really working.

Release 2.0.2.8

A new release is ready on the DICOM downloads page. This release addresses couple of bug fixes and improvements to the DICOM Server and DICOM Toolkit. Many of these changes were initiated by customers requests and I would like to thank you for the feedback and bug reports.

Wednesday, December 5, 2012

Part 19 of the DICOM Standard

DICOM Plugin Model
While preparing for a meeting with a new customer I took some time to review part 19 of the DICOM standard that I haven't red yet. Actually I should have done it before even it went out but being busy with other issues, it got ever delayed. Part 19 of the DICOM standard carries a prominent name, "Application Hosting". For me, Application Hosting immediately associates with Amazon EC2, Google App Engine, Cloud Computing and Grid. You can imagine my disappointment when I found out that behind the cover page I couldn't find anything of that kind. Instead, part 19 is a very detailed software specification document of a plugin model for two applications collaborating with one another over SOAP. It includes WSDL's and call sequencing diagrams that explains how the host application can invoke (or connect to) the plugin and exchange Web Services URL's that each application can then work with to collaborate with one another. The model is well defined and when I got few days ago an email Ad from Aunt Minnie about the Philips IntelliSpace PACS API I immediately went to look if it uses the DICOM plugin API Model. The Ad went to Philips Medical web site and as far as I could figure out their API is not based on DICOM's Application Hosting part. As of this moment I don't know of any vendor that does implement DICOM Application Hosting and if one of the reader does know such vendor, please comment and share this info with me.

Friday, November 30, 2012

COM is not Dead

Once in a while I get questioned why am I still using COM when there's newer technology like .NET that dominates Windows development. When I started working with DICOM the latest technology was COM and frankly speaking COM gave me harder time then DICOM back then, but after accommodating to the principals, COM had proven itself as a great way for module segregation setting clear interfaces and concrete boundaries between software components. Even now, after almost thirteen years and after countless declarations from Microsoft that COM is no longer to be supported, COM is integral part of Windows OS and actually using COM in .NET (C# or VB.NET) is much simpler then before. Even the annoyance of registering COM objects with regsvr32 no longer exists with the so simple option of side by side assemblies in .NET projects.
Eliminating regsvr32 using Side by Side assemblies Isolated flag

The reason I'm telling you all this is a little project I've been working on lately. In this project there's a department scheduler that provide the worklist through a web service but the Modalities need a DICOM Modality Worklist So I've been asked to bridge between the two. My solution was to create a plugin for the HRZ DICOM Server that calls the web service whenever a Modality Worklist Query is received. So the Modalities talk DICOM to the DICOM Server that translates their query to a SOAP call and then translates back the response to DICOM.
Implementing a web service client is so simple in C# and the DICOM Server plugin model already provide the Modality Worklist frameworks so all that is left to be done is to call the C# Web Service client from a DICOM Server plugin which is native C++. Ouch! How to use managed DLL from native executable? Exactly! COM! With a single check-box and couple of attributes you can turn any .NET class into a COM object and then use it in your native code.
Turning a C# Class into a COM Object
All that is left to do is to set "Register for COM interop" check-box in the .NET Class Library project properties page.
Register for COM interop flag
And now we have a .NET library ready to be called from native C++ using COM. To complete the integration we use RegAsm to add the registry entries for the COM and that's about it.

In the C++ code the TLB is imported using #import Pre-Processor directive and the method is called just like any other COM API



      USES_CONVERSION;
      _HospPatientByIDPtr api(__uuidof(HospPatientByID));
      BSTR first_name(0);
      BSTR last_name(0);
      VARIANT_BOOL res = api->GetPatientInfoByID(
             A2W(pid.c_str()), &first_name, &last_name);

Thursday, October 25, 2012

Patches to the Modality Worklist Database

It turns out that the Modality Worklist Server draws a lot of attention and that there were couple of glitches in the last release. The main issue comes from date and time columns in the database (i.e. datetime sql type). Until this is fixed in the DICOM Database Plugin, here's a little patch that maps the database date and time fields to valid DICOM date and time formats.

The DICOM date format is YYYYMMDD and the DICOM time format is HHMMSS.TTT or HHMMSS

Another issue that is going to be solved in the next release is the case sensitive column names. This means that in order to add more fields to your MWL C-FIND Response, you should add the columns to the view exactly as they are named in the toolkit dictionary with the correct case. This problem was in the last build modality field that should have been Modality with a capital M.

Wednesday, July 18, 2012

Wednesday, July 11, 2012

DSRSVC DICOM Modality Worklist Server - Update

I got a response that the Database plugin for the DICOM Server crashes. Where was a problem in DICOM Database Plugin that didn't export the configuration symbol and as a result the server didn't pick it up.
This problem is now fixed and the new build is available on the download page. If you've downloaded version 2.0.2.2, please download version 2.0.2.3 instead.
I would like to thank Igor that found this problem and reported it to me.

Sunday, July 8, 2012

DICOM Modality Worklist Server

HRZ DICOM Server is in use for many years in my custom software projects. It's a windows based service that is easily installed and configured. I've been using it since 2008 for various purposes and it is now a very stable and reliable software.
In this post I'm going to present its use as a Modality Worklist Server (or in short MWL SCP), explain how to install the software and how to configure the database to match your needs. Then, on the next post, I'm going to combine it together with processing HL7 order message to schedule a new work item. This will close the gap between HL7 and DICOM and we'll have most of the parts of IHE's radiology schedule workflow (SWF) integration profile.


What's the difference between a DICOM Server and a PACS?

Throughout the years, as PACS evolved, there were many debates about what is the feature set that makes a PACS? Does it have to have a Viewer? Does it have to provide all DICOM services? Should it have HL7? Many questions and even more answers. Calling it a DICOM Server is a laundry cleaning bypass of all that. It's a DICOM Server meaning it serves requests for DICOM Clients. What services does it provide? DSRSVC is a Storage SCP by default and you can add more services to it as you like using a plugin API. In the download page you'll find two plugins. The INI file plugin provides configuration for the storage service. The MS SQL Server DICOM Database Plugin adds:
  • Query/Retrieve SCP, 
  • Storage Commitment SCP and 
  • Modality Worklist SCP
Throughout the years it was used in all kind of applications, as an inbox for web viewer, as a CAD engine, as a departmental archive, as a proxy, as a bridge, and many more.

If you're looking to speed up development and use an off-the-shelf software product that you can easily extend, customize and integrate with your system, read on.

Saturday, June 30, 2012

Convert DICOM to Bitmap in 3 lines of code

In an earlier post I covered how to convert JPEG to DICOM and also PDF and Bitmap to DICOM. In this very short post I'm going to show the opposite direction, which is much simpler,  converting DICOM to Bitmap.
With MODALIZER-SDK this is very simple. Here's the C# code:


            DCXIMG img = new DCXIMG();
            img.LoadFile(dcmfile.Text);
            img.SaveBitmap(0, bmpFile.Text);


dcmfile and bmpFile in this example are text boxes.
SaveBitmap first parameter is the frame number (0 based index) as a DICOM file may have many frames in it.
The DCXIMG class takes care of all conversions and decoding of the image if the pixel data in the DICOM file is compressed.
That's it on this subject. Comments and questions are most welcome.

Tuesday, April 10, 2012

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:

Saturday, February 4, 2012

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.

Wednesday, February 1, 2012

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.

Monday, January 23, 2012

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);

Monday, January 9, 2012

Introduction to DICOM - Chapter 6 - Transfer Syntax

Transfer syntax defines how DICOM objects are serialized. When holding an object in memory, the only thing that matters is that your application can use it. The internal representation of the object is your own business. However, when sharing objects with other applications, everyone should be able to use the same object. The common solution for such problems is serialization.

Serialization is the process of writing a data structure or object state to wire i.e in a format that can be stored in a file or memory buffer, or transmitted across a network so it can be read on the other side of the wire or later by the same or by another process.

There's no shared memory in DICOM but it can be easily made using the same mechanism that is utilized for networking and files alike i.e. serializing the object into memory according to the rules dictated by the standard i.e. using transfer syntax.

In this post I'll cover the following issues:
  • Present the term Transfer Syntax, 
  • Why Transfer Syntax is required 
  • What is Transfer Syntax used for 
  • How Transfer Syntax is set when using 
    • DICOM files 
    • DICOM network
So, as I said, the serialization in DICOM is governed by a term called Transfer Syntax.

Transfer Syntax is defined at the object level and is the syntax for serializing a DICOM object. We have seen transfer syntaxes already in chapter 5 when dealing with association negotiation but did not discuss them. In order for an application to read a DICOM object from a network wire, it has to know the rules that were used to write the object into the wire. In the association request the calling AE sends a list of abstract syntaxes with SOP Class UID's. For every SOP Class, the calling AE sends a list of transfer syntax UID's. In the association response the called AE selects one of the transfer syntax UID's for every SOP class it accepts.