C-MOVE from ARIA database to ORTHANC


Let’s say that you have an installation of VARIAN ARIA database at your premises and you would like to push, over DICOM network protocol, patient’s DICOM data (e.g. imaging modalities, structures, plans, doses file) to PACS or any other DICOM network-enabled application.

network_diagram_aria_orthanc_eclipse

 

DICOM service configuration (on ARIA server):

Before we are able to send DICOM requests to ARIA DB, we need to install and setup the DICOM Service Daemon.  You should be able to start the DICOM Service Daemon Wizard from the windows “start” button. If the wizard is not there, you need to contact your local VARIAN service office, to install it for you.

When starting the wizard, you will see this:

dicom_service_daemon_1

Then you need to set-up a DICOM service daemon. This is the daemon that will receive the DICOM requests. My daemon is called “VARIDB” (AET: VARIDB) and listens on port: 58347.

dicom_service_daemon_2

Details of the services:

dicom_service_daemon_3a

Next, you have to define the trusted application entities. In another word, which application VARIDB daemon should talk to (sending or receiving data).

dicom_service_daemon_5a

 

ORTHANC installation/configuration:

Next, we need to install ORTHANC DICOM server.Please, go to https://www.orthanc-server.com/download.php and download the version matches your operating system. I am downloading ORTHANC for Windows x64 (https://www.orthanc-server.com/download-windows.php)

After finishing with the installation, we need to update the configuaration file (a common position is under: C:\Program Files\Orthanc Server\Configuration). The file is called: orthanc.json

Make sure that the foolowing options exist:

// The DICOM Application Entity Title
"DicomAet" : "ORTHANC",

// The DICOM port
"DicomPort" : 4242,

 

Eclipse workstation

From Eclipse workstation, we are going to run an ESAPI – Eclipse script – for transfering patient DICOM data from ARIA to ORTHANC.

 

The script is called GetDicomCollection.cs:

////////////////////////////////////////////////////////////////////////////////
// GetDicomCollection.cs
//
//  A ESAPI v11/v13 Script that generates a DCMTK script that is then executed
//  to enact a C-MOVE of a plan, related structure set and CT series, and
//  calculated dose.
//
//  See the article "Scripting the Varian DICOM DB Daemon with ESAPI + DCMTK"
//  for details on configuring your system to use this code.
//
//  Change the configuration items starting on Line 54 to those for your local
//  configuration.
//  
// Copyright (c) 2014 Varian Medical Systems, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy 
// of this software and associated documentation files (the "Software"), to deal 
// in the Software without restriction, including without limitation the rights 
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
// copies of the Software, and to permit persons to whom the Software is 
// furnished to do so, subject to the following conditions:
//
//  The above copyright notice and this permission notice shall be included in 
//  all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 
// THE SOFTWARE.
////////////////////////////////////////////////////////////////////////////////
using System;
using System.Linq;
using System.Text;
using System.Windows;
using System.Collections.Generic;
using VMS.TPS.Common.Model.API;
using VMS.TPS.Common.Model.Types;
using System.Reflection;
using System.IO;
using System.Diagnostics;


namespace VMS.TPS
{
  public class Script
  {
    public Script()
    {
    }

    public const string DCMTK_BIN_PATH= @"C:\variandeveloper\tools\dcmtk-3.6.0-win32-i386\bin"; // path to DCMTK binaries
    public const string AET = @"DCMTK";                 // local AE title
    public const string AEC = @"VARIDB";               // AE title of VMS DB Daemon
    public const string AEM = @"ORTHANC";                 // AE title of ORTHANC DICOM server
    public const string IP_PORT = @" 10.10.100.1 58347";// IP address of server hosting the DB Daemon, port daemon is listening to
    public const string CMD_FILE_FMT = @"move-{0}({1})-{2}.cmd";

    // holds standard error output collected during run of the DCMTK script
    private static StringBuilder stdErr = new StringBuilder("");

    public void Execute(ScriptContext context /*, System.Windows.Window window*/)
    {
      if (context.PlanSetup == null)
      {
        MessageBox.Show("please select a plan"); return;
      }

      string temp = System.Environment.GetEnvironmentVariable("TEMP");
      string filename = MakeFilenameValid(
          string.Format(CMD_FILE_FMT, context.Patient.LastName, context.Patient.Id, context.PlanSetup.Id)
        );
      filename = temp + @"\" + filename;
      GenerateDicomMoveScript(context.Patient, context.PlanSetup, filename);

      string stdErr1;
      string logFile = filename + "-log.txt";
      using (Process process = new Process())
      {

        // this powershell command allows us to see the standard output and also log it.
        string command = string.Format(@"&'{0}' | tee-object -filepath '{1}'", filename, logFile);
        // Configure the process using the StartInfo properties.
        process.StartInfo.FileName = "PowerShell.exe";
        process.StartInfo.Arguments = command;
        process.StartInfo.UseShellExecute = false;
        process.StartInfo.RedirectStandardError = true;

        // Set our event handler to asynchronously accumulate std err
        process.ErrorDataReceived += new DataReceivedEventHandler(stdErrHandler);

        process.Start();

        // Read the error stream first and then wait.
        stdErr1 = process.StandardError.ReadToEnd();
//        process.BeginErrorReadLine();
        process.WaitForExit();
        process.Close();
      }

      // dump out the standard error file, show them to user if they exist, and exit with a nice message.
      string stdErrFile = "";

      if (stdErr1.Length > 0)
      {
        stdErrFile = filename + "-err.txt";
        System.IO.File.WriteAllText(stdErrFile, stdErr1);
      }

      string message = string.Format("Done processing. \n\nCommand File = {0}\n\nLog file: {1}\nStandard error log: {2}", filename, logFile, stdErrFile);
      MessageBox.Show(message, "Varian Developer");

      // 'Start' generated log file to launch Notepad
      System.Diagnostics.Process.Start(logFile);
      // 'Start' generated text file to launch Notepad
      if (stdErr1.Length > 0)
        System.Diagnostics.Process.Start(stdErrFile);
      // Sleep for a few seconds to let notepad start
      System.Threading.Thread.Sleep(TimeSpan.FromSeconds(2));
    }

    //---------------------------------------------------------------------------------------------
    /// <summary>
    /// Callback for accumulating standard error while the DCMTK command script is running.
    /// </summary>
    //---------------------------------------------------------------------------------------------
    private static void stdErrHandler(object sendingProcess, 
        DataReceivedEventArgs outLine)
    {
      if (!String.IsNullOrEmpty(outLine.Data))
      {
          stdErr.Append(Environment.NewLine + outLine.Data);
      }
    }

    string MakeFilenameValid(string s)
    {
      char[] invalidChars = System.IO.Path.GetInvalidFileNameChars();
      foreach (char ch in invalidChars)
      {
        s = s.Replace(ch, '_');
      }
      return s;
    }
    //---------------------------------------------------------------------------------------------
    /// <summary>
    /// This method generates a DCMTK DOS Command script that when run will execute a C-MOVE on 
    /// the DB Daemon configured above on line 54+.  The move will include the DICOM objects for
    /// the passed plan and its related CT image, Structure Set, and Dose.
    /// </summary>
    /// <param name="patient">Active Eclipse patient</param>
    /// <param name="plan">Active Eclipse RT Plan</param>
    /// <param name="filename">Active Eclipse patient</param>
    //---------------------------------------------------------------------------------------------
    public void GenerateDicomMoveScript(Patient patient, PlanSetup plan, string filename)
    {
            string move = "movescu -v -aet " + AET + " -aec " + AEC + " -aem " + AEM + " -S -k ";
            //string move = "movescu -v -aet " + AET + " -aec " + AEC + " -S -k ";

            StreamWriter sw = new StreamWriter(filename, false, Encoding.ASCII);

      sw.WriteLine(@"@set PATH=%PATH%;" + DCMTK_BIN_PATH);

      // write the command to move the 3D image data set
      if (plan.StructureSet != null && plan.StructureSet.Image != null)
      {
        sw.WriteLine("rem move 3D image " + plan.StructureSet.Image.Id);
        string cmd = move + '"' + "0008,0052=SERIES" + '"' + " -k " + '"' + "0020,000E=" + plan.StructureSet.Image.Series.UID + '"' + IP_PORT;
        sw.WriteLine(cmd);
      }

      // write the command to move the structure set
      if (plan.StructureSet != null)
      {
        sw.WriteLine("rem move StructureSet " + plan.StructureSet.Id);
        string cmd = move + '"' + "0008,0052=IMAGE" + '"' + " -k " + '"' + "0008,0018=" + plan.StructureSet.UID + '"' + IP_PORT;
        sw.WriteLine(cmd);
      }

      // write the command to move the plan
      {
        sw.WriteLine("rem move RTPlan " + plan.Id);
        string cmd = move + '"' + "0008,0052=IMAGE" + '"' + " -k " + '"' + "0008,0018=" + plan.UID + '"' + IP_PORT;
        sw.WriteLine(cmd);
      }

      // write the command to move all RT Dose objects (we can't tell from the scripting API which RTDose to use, send them all).
      foreach (Study study in patient.Studies)
      {
        if ((from s in study.Series where (s.Modality == SeriesModality.RTDOSE) select s).Count() > 0)
        {
          sw.WriteLine("rem move all RTDose in study " + study.Id);
          // Study instance UID and RTDoseStorage SOP Class UID
          string cmd = move + "\"0008,0052=IMAGE\" -k \"0008,0016=1.2.840.10008.5.1.4.1.1.481.2\" -k \"0020,000D=" + study.UID + '"' + IP_PORT;
          sw.WriteLine(cmd);
        }
      }
      sw.Flush();
      sw.Close();
    }
  }
}

The script, above, makes use of the DCMTK – DICOM Toolkit. It uses the “movescu.exe” command to move DICOM data from one server to another.

Download DCMTK 3.6.3 – executable binaries: ftp://dicom.offis.de/pub/dicom/offis/software/dcmtk/dcmtk363/bin/dcmtk-3.6.3-win64-dynamic.zip and un zip the files under: C:\variandeveloper\tools\

If you install DCMTK in a different path, please take care that you update the DCMTK_BIN_PATH, accordingly.

DCMTK_BIN_PATH= @”C:\variandeveloper\tools\dcmtk-3.6.0-win32-i386\bin”; // path to DCMTK binaries

 

Now, we are ready to transfer DICOM data from ARIA to ORTHANC. Just, start Eclipse workstation, load a patient, and call the GetDicomCollection.cs script.

On a successful execution, the following similar message(s) can be found in the log files:

C:\scripts>movescu -v -aet DCMTK -aec VARIDB -aem ORTHANC -S -k "0008,0052=IMAGE" -k "0008,0018=1.2.246.352.71.5.1072637823.63278.2011021407533456314" 10.10.100.1 58347 
I: Requesting Association
I: Association Accepted (Max Send PDV: 16372)
I: Sending Move Request (MsgID 1)
I: Request Identifiers:
I: 
I: # Dicom-Data-Set
I: # Used TransferSyntax: Little Endian Explicit
I: (0008,0018) UI [1.2.246.352.71.5.1072637823.63278.2011021407533456314] #  48, 1 SOPInstanceUID
I: (0008,0052) CS [IMAGE]                                  #   6, 1 QueryRetrieveLevel
I: 
I: Received Move Response 1 (Pending)
I: Received Final Move Response (Success)
I: Releasing Association

 

Disclaimer:

The information contained in this website is for general information purposes only. The information is provided by sachpazidis.com and while we endeavour to keep the information up to date and correct, we make no representations or warranties of any kind, express or implied, about the completeness, accuracy, reliability, suitability or availability with respect to the website or the information, products, services, or related graphics contained on the website for any purpose. Any reliance you place on such information is therefore strictly at your own risk.
In no event will we be liable for any loss or damage including without limitation, indirect or consequential loss or damage, or any loss or damage whatsoever arising from loss of data or profits arising out of, or in connection with, the use of this website.
Through this website you are able to link to other websites which are not under the control of sachpazidis.com. We have no control over the nature, content and availability of those sites. The inclusion of any links does not necessarily imply a recommendation or endorse the views expressed within them.
Every effort is made to keep the website up and running smoothly. However, sachpazidis.com takes no responsibility for, and will not be liable for, the website being temporarily unavailable due to technical issues beyond our control.