Sunday, October 21, 2012

The DICOM Server API

The very long Holidays season here delayed a bit the upcoming release of version 2.0.2.7 of RZDCX DICOM Toolkit and DSRSVC DICOM Server. This year the Jewish holidays fell just one after another on Tuesdays and Wednesdays and everyone were busy bridging complete weeks and going abroad for long vacations that didn't take much of their leave credits. It was almost impossible to work this way.

But, finally we're passed that and until the end-of-year Christian Holidays season we can make some progress and release new products and features.

This post is a very quick pass over the DICOM Server API. We'll do it by going over the full source code of the INI File Plugin DLL. but before that here's the release notes of version 2.0.2.7 that is now available for download from the Tech-Zone download page. The packages are divided to RZDCX and DSRSVC and for each one there's a Win32 and x64 packages.

DICOM Server and DICOM Toolkit Release Notes - 2.0.2.7

#
Subsys
Title
Description
76
DSRSVC
Add Q/R SCP Support to DSRSVC DicomServer
1. Implement calls to DicomNet framework. 2. Create plugin for using database.
312
RZDCX
If port is taken, accepter may throw access violation
The problem is in DicomNetwork::Start There's a assert there on the cond but than no release error handling
329
DSRSVC
Add Dynamic filenaming to DICOM Server Ini FIle Plugin
Add a INI entry FILENAME_TEMPLATE that uses DICOM Attributes values to name the incoming files
332
DSRSVC
DICOM Server crash when port is taken
If the port (104) is taken by another process, DICOM server crashes
333
DSRSVC
Add logging to DICOM Server
Add logging and service controller to start and stop the logging
334
RZDCX
DCXIMG.SaveBitmap creates gray file on attached
See attached files crated using DICOMImageExample
336
RZDCX
Set default window center/width when creating new DCXIMG.LoadFile
When opening a new DCXIMG from file using LoadFile no window center/window width is selected Check if a wc/ww is present use the default (first) one.
337
RZDCX
If port is taken, accepter may throw access violation
Same as in DSRSVC
338
RZDCX
RZDCX ReadBuffer API is not suitable for 64 bit
there seems to be an error in the interface for 64 bit OS, because ReadBuffer is defined as HRESULT ReadBuffer(int buffer, long size). If the address of the buffer is above 4GBytes, which can happen in our application, the function will crash. So it should be HRESULT ReadBuffer(size_t buffer, size_t size) because size_t is 64 bit on 64bit OS. Even better would be HRESULT ReadBuffer(void *buffer, size_t size). Now buffer is a pointer which has the correct size automatically.
339
DSRSVC
DICOM DB Plugin fails on nvarchar(max) fields
When the DB column is of type nvarchar(max) the ResultsIterator ctor sets the iterator to an error state (at dataaccess.cpp line 1754)


The DICOM Server API

The DICOM Server API let you control the behavior of the server without really dealing with the details of DICOM. Instead you implement your application logic and respond to events from the DICOM network.
The source code can be downloaded from this link: Source Code - DICOM INI File Plugin. This plugin implementation uses RZDCX for opening the DICOM files and extracting the data out of them.

Building the Plugin

To build the plugin, please use Microsoft Visual Studio 2008 (or later, the upgrade works) and build the solution Debug-DLL or Release-DLL configuration either Win32 or x64 platoform, depending on the DICOM server you are running. Once the build passes (it should take couple of seconds) copy the IniFilePlugin.DLL to the server install folder, rename it to dsrsvcp.dll and copy the DicomServer.ini.IniFilePlugin to the same folder and rename it to DicomServer.ini
After that restart the DICOM Server (you can also run it from command line with the -r option instead of as a service).
It's very important to use the *-DLL configuration together with the latest version of the server (2.0.2.7) otherwise string API parameters may not be interchangeable between the server and the plugin.

The INI File Plugin Configuration

Here's the content of the INI File plugin configuration file DicomServer.ini

[DICOM_SERVER]
AE_TITLE=DSRSVC
PORT=104
STORAGE_ROOT=C:\YOURDIR
FILENAME_TEMPLATE="StudyInstanceUID\SeriesInstanceUID\SOPInstanceUID.dcm"

You can configure the AE title, the port, the root directory and the file naming template. Use valid DICOM tag names separated by \'s from the DICOM_TAG_ENUM of RZDCX.

If we look at the code, you can see that these values are loaded by the software in the ctor DicomServerIniFilePlugin::DicomServerIniFilePlugin() at line 78 of DicomServerIniFilePlugin.cpp

The Plugin Architecture and Process Flow

The plugin is loaded by the DICOM server on startup using the dynamically exposed function GetDicomServerConfiguration defined in the file IniFilePlugin.def
This is standard dynamic dll loading process. 

The GetDicomServerConfiguration returns a pointer to a singleton object that exposes the abstract interface IDicomServerConfiguration defined in IDicomServerConfiguration.h

DICOM File Processing

The INI file plugin implements only the Storage SCP functionality using the ProcessFile function (line 174 in DicomServerIniFilePlugin.cpp). As an example, one can enhance this function just a bit and save not only the DICOM file but also a bitmap version of it. You can do that by loading the DICOM file into a DCXIMG object and save a bitmap out of it.
Just add the following code at line 208:

                  IDCXIMGPtr img(__uuidof(DCXIMG));
                  img->LoadFile(newFile.c_str());
                  string bmpFile(newFile);
                  bmpFile.append(".bmp");
                  img->SaveBitmap(0, bmpFile.c_str());

Here's the complete ProcessFile function with this change:

bool DicomServerIniFilePlugin::ProcessFile(
    const string& filename, ///< The full path of the file
    const string& source_ae_title ///< The AE title of the DICOM application that sent the file
    )
{

      if (hasRzdcx)
      {
            string sPath(StorageRoot());

            string s = buildFnameFromTemplate(filename);
            if (s.length() > 0)
            {
                  string newFile = sPath + "\\" + s;

                  size_t l = newFile.find_last_of("\\");
                  string folder = newFile.substr(0, l);
                  VerifyCreatePath(folder);

                  BOOL res = DeleteFile(newFile.c_str());
                  if (!res) {
                        DWORD err = GetLastError();
                  }
                  res = MoveFile(filename.c_str(), newFile.c_str());
                  if (!res)
                  {
                        DWORD err = GetLastError();
                        res = DeleteFile(filename.c_str());
                        if (!res)
                        {
                              err = GetLastError();
                        }
                        return false;
                  }

                  IDCXIMGPtr img(__uuidof(DCXIMG));
                  img->LoadFile(newFile.c_str());
                  string bmpFile(newFile);
                  bmpFile.append(".bmp");
                  img->SaveBitmap(0, bmpFile.c_str());

                  return true;
            }
            else
            {
                  return false;
            }
      }
      else
      {
            return true; // Just leave it where it was
      }
}

Controlling Association Security

Another interesting method of the API is:
bool DicomServerIniFilePlugin::OnAssocStarted(
        const char* calling_title, ///< The calling AE title
            const char* called_title, ///< The called AE title
            const char* calling_host)

If you return false from the function, the association will be rejected. You can check the values with some configuration and if found matching entries, return true. The implementation in this example always returns true.

For other calls have a look at the API definition header file IDicomServerConfiguration.h that has detailed doxygen style documentation for every method.

No comments:

Post a Comment