Handle Store Requests in a PACS Server - WinForms C#

This tutorial shows how to add an XML database to a PACS Server and allow a client to store DICOM datasets on the server in a C# WinForms application using the LEADTOOLS SDK.

Overview  
Summary This tutorial covers how to add a database and handle C-STORE requests for a PACS server in a WinForms C# Application.
Completion Time 60 minutes
Visual Studio Project Download tutorial project (18 KB)
Platform Windows WinForms C# Application
IDE Visual Studio 2019
Development License Download LEADTOOLS

Required Knowledge

Get familiar with the basic steps of creating a project and a PACS Server by reviewing the Add References and Set a License and the Create a Simple PACS Server tutorials, before working on the Handle Store Requests in a PACS Server - WinForms C# tutorial.

Create the Project and Add LEADTOOLS References

Start with a copy of the project created in the Create a Simple PACS Server tutorial. If you do not have that project, follow the steps in that tutorial to create it.

The references needed depend upon the purpose of the project. References can be added by one or the other of the following two methods (but not both).

If using NuGet references, this tutorial requires the following NuGet package:

If using local DLL references, the following DLLs are needed.

The DLLs are located at <INSTALL_DIR>\LEADTOOLS22\Bin\Dotnet4\x64:

For a complete list of which DLL files are required for your application, refer to Files to be Included With Your Application.

Set the License File

The License unlocks the features needed for the project. It must be set before any toolkit function is called. For details, including tutorials for different platforms, refer to Setting a Runtime License.

There are two types of runtime licenses:

Note

Adding LEADTOOLS NuGet and local references and setting a license are covered in more detail in the Add References and Set a License tutorial.

Create the Database Class

With the project created, the references added, and the license set, coding can begin.

In the Solution Explorer, Right-click on <Project>.csproj and select Add -> New Folder. Create a folder called Database. Right-click on this folder and select Add -> New Item.... Select the Class option and name the class DicomDB.cs, then click Add.

The DicomDB class holds the datasets found in the PACS server, allowing for the insertion of datasets as records after a C-STORE request and saving and loading the database into an external XML file.

Note

Make sure the namespace for the class is PacsServer.

Add the Database Class Constructor and Properties

Add the using statements below to the top of the Server class:

C#
// Using block at the top 
using Leadtools.Dicom; 
using System; 
using System.Data; 
using System.IO; 

Use the code below to define the public and private properties of the DicomDB class along with its constructor.

The constructor loads the database from an external XML file, if it already exists. If it does not exist, it creates the database using the DICOM datasets in the server's images folder.

C#
public class DicomDB 
{ 
   protected DataSet ds = new DataSet("DICOMSVR"); // System.Data.DataSet instance 
   protected string dbFileName; // External XML File Name 
   public bool NeedImport = false; 
   public string ImageDir; 
   private object adoDatasetLock = new object(); 
 
   // Get/Set Properties 
   public int Count { get { return ds.Tables[0].Rows.Count; } } 
   public DataSet DB { get { return ds; } } 
 
   public DicomDB(string dbFileName) 
   { 
      this.dbFileName = dbFileName; 
 
      if (!LoadDatabase()) 
      { 
         CreateDatabase(); 
         NeedImport = true; 
      } 
 
      lock (adoDatasetLock) 
      { 
         if (ds.Tables["Images"] != null) 
         { 
            ds.Tables["Images"].RowDeleting += new DataRowChangeEventHandler(DicomDB_RowDeleting); 
         } 
      } 
   } 
 
   void DicomDB_RowDeleting(object sender, DataRowChangeEventArgs e) 
   { 
      string file = e.Row["ReferencedFile"].ToString(); 
 
      try 
      { 
         File.Delete(file); 
      } 
      catch 
      { 
      } 
   } 
} 

Add the Code to Load and Save the Database

Add two new methods to the DicomDB class named LoadDatabase() and Save(). LoadDatabase() will be called inside the DicomDB() method as shown in the previous section. The Save() method will be called throughout the DicomDB class, as shown in the next sections.

Add the code below to the LoadDatabase method to load the database from an external XML file.

C#
protected bool LoadDatabase() 
{ 
   bool loaded = true; 
 
   try 
   { 
      ds.ReadXml(dbFileName); 
   } 
   catch 
   { 
      loaded = false; 
   } 
   return loaded; 
} 

Add the code to the Save() method to save the database to an external XML file.

C#
public bool Save() 
{ 
   bool ret = false; 
 
   try 
   { 
      ds.WriteXml(dbFileName, XmlWriteMode.WriteSchema); 
      ret = true; 
   } 
   catch 
   { 
   } 
   return ret; 
} 

Add the Code to Create the Database

Add a new method to the DicomDB class named CreateDatabase(). This method will be called inside the DicomDB() method, as shown in the first section. Use the following code to create the table structure for the DICOM dataset.

C#
private bool CreateDatabase() 
{ 
   bool created = true; 
   lock (adoDatasetLock) 
   { 
      try 
      { 
         DataTable table; 
         DataColumn Patients_PatientID, Studies_PatientID; 
         DataColumn Studies_InstUID, Series_InstUID; 
         DataColumn Series_ID, Images_ID; 
         DataColumn[] key = new DataColumn[1]; 
 
         table = ds.Tables.Add("Patients"); 
         Patients_PatientID = table.Columns.Add("PatientID", typeof(string)); 
         table.Columns.Add("PatientName", typeof(string)); 
         table.Columns.Add("PatientBirthDate", typeof(DateTime)); 
         table.Columns.Add("PatientBirthTime", typeof(DateTime)); 
         table.Columns.Add("PatientSex", typeof(string)); 
         table.Columns.Add("EthnicGroup", typeof(string)); 
         table.Columns.Add("PatientComments", typeof(string)); 
 
         key[0] = Patients_PatientID; 
         table.PrimaryKey = key; 
 
         table = ds.Tables.Add("Studies"); 
         Studies_InstUID = table.Columns.Add("StudyInstanceUID", typeof(string)); 
         table.Columns.Add("StudyDate", typeof(DateTime)); 
         table.Columns.Add("StudyTime", typeof(DateTime)); 
         table.Columns.Add("AccessionNumber", typeof(string)); 
         table.Columns.Add("StudyID", typeof(string)); 
         table.Columns.Add("PatientName", typeof(string)); 
         Studies_PatientID = table.Columns.Add("PatientID", typeof(string)); 
         table.Columns.Add("StudyDescription", typeof(string)); 
         table.Columns.Add("ReferringDrName", typeof(string)); 
 
         key[0] = Studies_InstUID; 
         table.PrimaryKey = key; 
         ds.Relations.Add("Studies", Patients_PatientID, Studies_PatientID); 
 
         table = ds.Tables.Add("Series"); 
         Series_ID = table.Columns.Add("SeriesInstanceUID", typeof(string)); 
         Series_InstUID = table.Columns.Add("StudyInstanceUID", typeof(string)); 
         table.Columns.Add("Modality", typeof(string)); 
         table.Columns.Add("SeriesNumber", typeof(int)); 
         table.Columns.Add("PatientID", typeof(string)); 
         table.Columns.Add("SeriesDate", typeof(DateTime)); 
 
         key[0] = Series_ID; 
         table.PrimaryKey = key; 
         ds.Relations.Add("Series", Studies_InstUID, Series_InstUID); 
 
         table = ds.Tables.Add("Images"); 
         table.Columns.Add("SOPInstanceUID", typeof(string)); 
         Images_ID = table.Columns.Add("SeriesInstanceUID", typeof(string)); 
         table.Columns.Add("StudyInstanceUID", typeof(string)); 
         table.Columns.Add("InstanceNumber", typeof(int)); 
         table.Columns.Add("ReferencedFile", typeof(string)); 
         table.Columns.Add("PatientID", typeof(string)); 
         table.Columns.Add("SOPClassUID", typeof(string)); 
         table.Columns.Add("TransferSyntaxUID", typeof(string)); 
 
         ds.Relations.Add("Images", Series_ID, Images_ID); 
 
         Save(); 
      } 
      catch 
      { 
         created = false; 
      } 
   } 
   return created; 
} 

Add the Code to Insert DICOM Files to the Database

Add the enumeration below directly after the namespace declaration.

C#
public enum InsertReturn 
{ 
   Exists, 
   Error, 
   Success 
} 

Create a new Insert(DicomDataSet dcm, string filename) method and use the following code to parse a DICOM dataset file and add it to the table structure and the external XML file.

C#
public InsertReturn Insert(DicomDataSet dcm, string filename) 
{ 
   string patientID; 
   string studyInstanceUID; 
   string seriesInstanceUID; 
   string sopInstanceUID; 
   InsertReturn ret = InsertReturn.Success; 
 
   patientID = AddPatient(dcm, ref ret); 
   if (ret != InsertReturn.Success && ret != InsertReturn.Exists) 
   { 
      return ret; 
   } 
 
   studyInstanceUID = AddStudy(dcm, patientID, ref ret); 
   if (ret != InsertReturn.Success && ret != InsertReturn.Exists) 
   { 
      return ret; 
   } 
 
   seriesInstanceUID = AddSeries(dcm, studyInstanceUID, patientID, ref ret); 
   if (ret != InsertReturn.Success && ret != InsertReturn.Exists) 
   { 
      return ret; 
   } 
 
   sopInstanceUID = AddImage(dcm, seriesInstanceUID, studyInstanceUID, patientID, filename, ref ret); 
   if (ret != InsertReturn.Success && ret != InsertReturn.Exists) 
   { 
      return ret; 
   } 
 
   if (ret == InsertReturn.Success) 
   { 
      Save(); 
   } 
 
   return ret; 
} 

Create a new Insert(DicomDataSet dcm, string filename) method and add the following code to check if a record already exists in the table.

C#
private bool RecordExists(DataTable table, string filter) 
{ 
   DataView dv = new DataView(table); 
 
   if (dv != null) 
   { 
      dv.RowFilter = filter; 
      return dv.Count > 0; 
   } 
   return false; 
} 

Add the Utility Method GetStringValue()

Right-click on the <Project>.csproj in the Solution Explorer and select Add -> New Folder. Create a folder called DicomCommon. Right-click on this folder and select Add -> New Item.... Select the Class option and name the class Utils.cs, then click Add.

Note

For this and other related PACS tutorials, the Utils class holds a number of utility methods for parsing data from DICOM dataset files.

Use the code below to add a GetStringValue() method, which returns the string value of a DICOM tag:

C#
using Leadtools.Dicom; 
 
namespace PacsServer 
{ 
   class Utils 
   { 
      public static string GetStringValue(DicomDataSet dcm, long tag) 
      { 
         DicomElement element; 
 
         element = dcm.FindFirstElement(null, tag, true); 
         if (element != null) 
         { 
            if (dcm.GetElementValueCount(element) > 0) 
            { 
               return dcm.GetConvertValue(element); 
            } 
         } 
 
         return ""; 
      } 
   } 
} 

Insert Patient Data

In the DicomDB.cs file, use the following code to parse and add the Patients table information from a DICOM Dataset to the database.

C#
private string AddPatient(DicomDataSet dcm, ref InsertReturn ret) 
{ 
   string patientID = ""; 
 
   ret = InsertReturn.Success; 
 
   patientID = Utils.GetStringValue(dcm, DicomTag.PatientID); 
   if (patientID.Length == 0) 
   { 
      ret = InsertReturn.Error; 
      return ""; 
   } 
 
   lock (adoDatasetLock) 
   { 
      if (!RecordExists(ds.Tables["Patients"], "PatientID = '" + patientID + "'")) 
      { 
         DataRow dr; 
 
         dr = ds.Tables["Patients"].NewRow(); 
         if (dr != null) 
         { 
            dr["PatientID"] = patientID; 
            dr["PatientName"] = Utils.GetStringValue(dcm, DicomTag.PatientName); 
            dr["PatientSex"] = Utils.GetStringValue(dcm, DicomTag.PatientSex); 
            dr["EthnicGroup"] = Utils.GetStringValue(dcm, DicomTag.EthnicGroup); 
            dr["PatientComments"] = Utils.GetStringValue(dcm, DicomTag.PatientComments); 
 
            try 
            { 
               dr["PatientBirthDate"] = DateTime.Parse(Utils.GetStringValue(dcm, DicomTag.PatientBirthDate)); 
               dr["PatientBirthTime"] = DateTime.Parse(Utils.GetStringValue(dcm, DicomTag.PatientBirthTime)); 
            } 
            catch 
            { 
            } 
 
            ds.Tables["Patients"].Rows.Add(dr); 
         } 
      } 
      else 
      { 
         ret = InsertReturn.Exists; 
      } 
   } 
 
   return patientID; 
} 

Insert Study Data

In the DicomDB.cs file, add a new AddStudy(DicomDataSet dcm, string patientID, ref InsertReturn ret) method to parse and add the Studies table information from a DICOM Dataset to the database.

C#
private string AddStudy(DicomDataSet dcm, string patientID, ref InsertReturn ret) 
{ 
   string studyInstanceUID; 
   string filter; 
 
   ret = InsertReturn.Success; 
   studyInstanceUID = Utils.GetStringValue(dcm, DicomTag.StudyInstanceUID); 
   if (studyInstanceUID.Length == 0) 
   { 
      ret = InsertReturn.Error; 
      return ""; 
   } 
 
   lock (adoDatasetLock) 
   { 
      filter = "StudyInstanceUID = '" + studyInstanceUID + "' AND PatientID = '" + patientID + "'"; 
      if (RecordExists(ds.Tables["Studies"], filter)) 
      { 
         ret = InsertReturn.Exists; 
         return studyInstanceUID; 
      } 
 
      filter = string.Format("StudyInstanceUID = '{0}'", studyInstanceUID); 
      if (RecordExists(ds.Tables["Studies"], filter)) 
      { 
         ret = InsertReturn.Error; 
         return studyInstanceUID; 
      } 
 
      // Add 
      try 
      { 
         DataRow dr = ds.Tables["Studies"].NewRow(); 
         if (dr != null) 
         { 
            dr["StudyInstanceUID"] = studyInstanceUID; 
            dr["StudyID"] = Utils.GetStringValue(dcm, DicomTag.StudyID); 
            dr["StudyDescription"] = Utils.GetStringValue(dcm, DicomTag.StudyDescription); 
            dr["AccessionNumber"] = Utils.GetStringValue(dcm, DicomTag.AccessionNumber); 
            dr["PatientID"] = patientID; 
            dr["PatientName"] = Utils.GetStringValue(dcm, DicomTag.PatientName); 
            dr["ReferringDrName"] = Utils.GetStringValue(dcm, DicomTag.ReferringPhysicianName); 
 
            try 
            { 
               dr["StudyDate"] = DateTime.Parse(Utils.GetStringValue(dcm, DicomTag.StudyDate)); 
               dr["StudyTime"] = DateTime.Parse(Utils.GetStringValue(dcm, DicomTag.StudyTime)); 
            } 
            catch 
            { 
            } 
 
            ds.Tables["Studies"].Rows.Add(dr); 
         } 
      } 
      catch (Exception) 
      { 
         ret = InsertReturn.Error; 
      } 
   } 
 
   return studyInstanceUID; 
} 

Insert Series Data

In the DicomDB.cs file, add a new AddSeries(DicomDataSet dcm, string studyInstanceUID, string patientID, ref InsertReturn ret) method to parse and add the Series table information from a DICOM Dataset to the database.

C#
private string AddSeries(DicomDataSet dcm, string studyInstanceUID, string patientID, ref InsertReturn ret) 
{ 
   string seriesInstanceUID; 
   string filter; 
 
   ret = InsertReturn.Success; 
   seriesInstanceUID = Utils.GetStringValue(dcm, DicomTag.SeriesInstanceUID); 
   if (seriesInstanceUID.Length == 0) 
   { 
      ret = InsertReturn.Error; 
      return ""; 
   } 
 
   filter = "StudyInstanceUID = '" + studyInstanceUID + "' AND SeriesInstanceUID = '" + seriesInstanceUID + "'"; 
   lock (adoDatasetLock) 
   { 
      if (RecordExists(ds.Tables["Series"], filter)) 
      { 
         ret = InsertReturn.Exists; 
         return seriesInstanceUID; 
      } 
 
      filter = string.Format("SeriesInstanceUID = '{0}'", seriesInstanceUID); 
      if (RecordExists(ds.Tables["Series"], filter)) 
      { 
         ret = InsertReturn.Error; 
         return seriesInstanceUID; 
      } 
 
      // Add 
      try 
      { 
         DataRow dr; 
 
         dr = ds.Tables["Series"].NewRow(); 
         if (dr != null) 
         { 
            string temp; 
 
            temp = Utils.GetStringValue(dcm, DicomTag.SeriesNumber); 
 
            dr["SeriesInstanceUID"] = seriesInstanceUID; 
            dr["StudyInstanceUID"] = studyInstanceUID; 
            dr["Modality"] = Utils.GetStringValue(dcm, DicomTag.Modality); 
            dr["PatientID"] = patientID; 
 
            try 
            { 
               dr["SeriesDate"] = DateTime.Parse(Utils.GetStringValue(dcm, DicomTag.SeriesDate)); 
            } 
            catch 
            { 
            } 
 
            try 
            { 
               if (temp.Length > 0) 
               { 
                  dr["SeriesNumber"] = Convert.ToInt32(temp); 
               } 
            } 
            catch 
            { 
            } 
 
            ds.Tables["Series"].Rows.Add(dr); 
         } 
      } 
      catch (Exception) 
      { 
         ret = InsertReturn.Error; 
      } 
   } 
 
   return seriesInstanceUID; 
} 

Insert Image Data

In the DicomDB.cs file, add a new AddImage(DicomDataSet dcm, string seriesInstanceUID, string studyInstanceUID, string patientID, string filename, ref InsertReturn ret) method to parse and add the Images table information from a DICOM Dataset to the database.

C#
private string AddImage(DicomDataSet dcm, string seriesInstanceUID, string studyInstanceUID, string patientID, string filename, ref InsertReturn ret) 
{ 
   string sopInstanceUID; 
   string filter; 
 
   ret = InsertReturn.Success; 
   sopInstanceUID = Utils.GetStringValue(dcm, DicomTag.SOPInstanceUID); 
   if (sopInstanceUID.Length == 0) 
   { 
      ret = InsertReturn.Error; 
      return ""; 
   } 
 
   filter = "StudyInstanceUID = '" + studyInstanceUID + "' AND SeriesInstanceUID = '" + seriesInstanceUID + "'"; 
   filter += " AND SOPInstanceUID = '" + sopInstanceUID + "'"; 
   lock (adoDatasetLock) 
   { 
      if (RecordExists(ds.Tables["Images"], filter)) 
      { 
         ret = InsertReturn.Exists; 
         return sopInstanceUID; 
      } 
 
      filter = string.Format("SOPInstanceUID = '{0}'", sopInstanceUID); 
      if (RecordExists(ds.Tables["Images"], filter)) 
      { 
         ret = InsertReturn.Error; 
         return sopInstanceUID; 
      } 
 
      // Add 
      try 
      { 
         DataRow dr; 
 
         dr = ds.Tables["Images"].NewRow(); 
         if (dr != null) 
         { 
            string temp; 
 
            dr["SOPInstanceUID"] = sopInstanceUID; 
            dr["SeriesInstanceUID"] = seriesInstanceUID; 
            dr["StudyInstanceUID"] = studyInstanceUID; 
            dr["PatientID"] = patientID; 
            dr["ReferencedFile"] = filename; 
 
            temp = Utils.GetStringValue(dcm, DicomTag.SOPClassUID); 
            if (temp.Length == 0) 
            { 
               temp = Utils.GetStringValue(dcm, DicomTag.MediaStorageSOPClassUID); 
               if (temp.Length == 0) 
               { 
                  temp = "1.1.1.1"; 
               } 
            } 
            dr["SOPClassUID"] = temp; 
 
            temp = Utils.GetStringValue(dcm, DicomTag.TransferSyntaxUID); 
            if (temp.Length == 0) 
            { 
               temp = DicomUidType.ImplicitVRLittleEndian; 
            } 
            dr["TransferSyntaxUID"] = temp; 
 
            temp = Utils.GetStringValue(dcm, DicomTag.InstanceNumber); 
            if (temp.Length > 0) 
            { 
               dr["InstanceNumber"] = Convert.ToInt32(temp); 
            } 
 
            ds.Tables["Images"].Rows.Add(dr); 
         } 
      } 
      catch (Exception) 
      { 
         ret = InsertReturn.Error; 
      } 
   } 
 
   return sopInstanceUID; 
} 

Add an Instance of the Database to the Server and Form

With the database configured, go to Utilities/Server.cs and add a DicomDB class to the properties:

C#
public DicomDB dicomDB; 

Add an instance of the DicomDB class to the form1.cs file:

C#
private DicomDB dicomDB = new DicomDB(Application.StartupPath + @"\DicomCS.xml"); 
public DicomDB DicomData { get { return dicomDB; } } 

Initialize the server's dicomDB property using this instance in the InitServer() method:

C#
dicomServer.dicomDB = dicomDB; // Add to InitServer() 

Add the Code to Handle a C-STORE Request

Modify PACS Server to Accept Datasets

Go to the Utilities\Server.cs file. Add the below using statement.

C#
using System.Windows.Forms; // Add to using block 

Add an ImageDir string value, which is the directory used by the server for storing local datasets.

C#
// Images Directory 
private string _ImageDir = Application.StartupPath + @"\ImagesCS\"; 

In the Get/Set Server Properties section in the Server.cs file add the ImageDir property below.

C#
// Add to Get/Set Server Properties 
public string ImageDir { get { return _ImageDir; } set { _ImageDir = value; } } 

Modify the InitAction() method to accept a DicomDataSet instance in its parameters and use it to initialize a DicomAction class.

C#
public DicomAction InitAction(string actionOp, ProcessType process, Client client, DicomDataSet ds) 
{ 
   DicomAction action = new DicomAction(process, this, client, ds); 
 
   action.AETitle = client.Association.Calling; 
   action.ipAddress = client.PeerAddress; 
   client.Timer.Start(); 
 
   MainForm.Log(actionOp + ": Received from " + action.AETitle); 
 
   return action; 
} 

Modify Client to Accept Store Requests

Go to the Utilities\Client.cs file and add the code below for the OnReceiveCStoreRequest() handler method which is called when a C-STORE request is sent:

C#
protected override void OnReceiveCStoreRequest(byte presentationID, int messageID, string affectedClass, string instance, DicomCommandPriorityType priority, string moveAE, int moveMessageID, DicomDataSet dataSet) 
{ 
   action = _server.InitAction("C-STORE-REQUEST", ProcessType.StoreRequest, this, dataSet); 
 
   action.PresentationID = presentationID; 
   action.MessageID = messageID; 
   action.Class = affectedClass; 
   action.Instance = instance; 
   action.Priority = priority; 
   action.MoveAETitle = moveAE; 
   action.MoveMessageID = moveMessageID; 
   action.DoAction(); 
   dataSet.Dispose(); 
} 

Modify the OnReceiveCEchoRequest to pass null to the modified _server.InitAction() method, since C-ECHO does not involve a dataset.

C#
protected override void OnReceiveCEchoRequest(byte presentationID, int messageID, string affectedClass) 
{ 
   action = _server.InitAction("C-ECHO-REQUEST", ProcessType.EchoRequest, this, null); 
   action.PresentationID = presentationID; 
   action.MessageID = messageID; 
   action.Class = affectedClass; 
   action.DoAction(); 
} 

Modify the Form to Create the Server's Images Directory

Navigate to the Form1.cs file and add the code below to the InitServer() method.

C#
// Create Images Folder 
if (!Directory.Exists(dicomServer.ImageDir)) 
   Directory.CreateDirectory(dicomServer.ImageDir); 

Add the Store Action to the DicomAction Class

Open the Utilities\DicomAction.cs file and add the store request to the enumeration directly after the namespace declaration.

C#
public enum ProcessType 
{ 
   EchoRequest, 
   StoreRequest 
} 

Use the following code to add the properties used for the store request to the DicomAction class.

C#
// Add to Request Properties 
private DicomCommandPriorityType _Priority; 
private string _MoveAETitle; 
private int _MoveMessageID; 
 
// DICOM DataSet Properties 
public string dsFileName; 
private DicomDataSet ds = new DicomDataSet(); 
C#
// Add to Get/Set Properties methods 
public DicomCommandPriorityType Priority { get { return _Priority; } set { _Priority = value; } } 
public string MoveAETitle { get { return _MoveAETitle; } set { _MoveAETitle = value; } } 
public int MoveMessageID { get { return _MoveMessageID; } set { _MoveMessageID = value; } } 
public DicomDataSet DS { get { return ds; } } 

Modify the DicomAction constructor to take a DataSet inside its parameters.

C#
public DicomAction(ProcessType process, Server server, Client client, DicomDataSet ds) 
{ 
   this.server = server; 
   this.client = client; 
   this.process = process; 
 
   if (ds != null) 
   { 
      this.ds.Copy(ds, null, null); 
   } 
} 

Modify the DoAction method to parse the StoreRequest process.

C#
public void DoAction() 
{ 
   if (client.Association != null) 
   { 
      switch (process) 
      { 
         // C-ECHO 
         case ProcessType.EchoRequest: 
            DoEchoRequest(); 
            break; 
         // C-STORE 
         case ProcessType.StoreRequest: 
            DoStoreRequest(); 
            break; 
      } 
   } 
} 

Add a new DoStoreRequest() method and add the following code.

C#
private void DoStoreRequest() 
{ 
   DicomCommandStatusType status = DicomCommandStatusType.ProcessingFailure; 
   string msg = "Error saving dataset received from: " + AETitle; 
   DicomElement element; 
 
   if (!IsActionSupported()) 
   { 
      string name = GetUIDName(); 
 
      server.MainForm.Log("C-STORE-REQUEST: Abstract syntax (" + name + ") not supported by association"); 
      client.SendCStoreResponse(_PresentationID, _MessageID, _Class, _Instance, DicomCommandStatusType.ClassNotSupported); 
      return; 
   } 
 
   element = ds.FindFirstElement(null, DicomTag.SOPInstanceUID, true); 
   if (element != null) 
   { 
      string value = ds.GetConvertValue(element); 
      string file; 
      InsertReturn ret; 
 
      file = server.ImageDir + value + ".dcm"; 
      ret = server.MainForm.DicomData.Insert(ds, file); 
      switch (ret) 
      { 
         case InsertReturn.Success: 
            DicomExceptionCode dret = SaveDataSet(file); 
            if (dret == DicomExceptionCode.Success) 
            { 
               status = DicomCommandStatusType.Success; 
            } 
            else 
            { 
               msg = "Error saving dicom file: " + dret.ToString(); 
               status = DicomCommandStatusType.ProcessingFailure; 
            } 
            server.MainForm.Log("C-STORE-REQUEST: New file imported: " + file); 
            break; 
         case InsertReturn.Exists: 
            msg = "File (" + file + ") not imported. Record already exists in database"; 
            status = DicomCommandStatusType.DuplicateInstance; 
            break; 
         case InsertReturn.Error: 
            msg = "Error importing file: " + file; 
            status = DicomCommandStatusType.ProcessingFailure; 
            break; 
      } 
 
   } 
 
   if (status != DicomCommandStatusType.Success) 
   { 
      server.MainForm.Log("C-STORE-REQUEST: " + msg); 
   } 
 
   client.SendCStoreResponse(_PresentationID, _MessageID, _Class, _Instance, status); 
   server.MainForm.Log("C-STORE-RESPONSE: Response sent to " + AETitle); 
} 

Add a new SaveDataSet(string filename) method and add the following code.

C#
private DicomExceptionCode SaveDataSet(string filename) 
{ 
   string temp; 
 
   Utils.SetTag(ds, DicomTag.FillerOrderNumberProcedure, "01"); 
 
   temp = Utils.GetStringValue(ds, DicomTag.SOPClassUID); 
   Utils.SetTag(ds, DicomTag.MediaStorageSOPClassUID, temp); 
 
   temp = Utils.GetStringValue(ds, DicomTag.SOPInstanceUID); 
   Utils.SetTag(ds, DicomTag.MediaStorageSOPInstanceUID, temp); 
 
   Utils.SetTag(ds, DicomTag.ImplementationClassUID, client.Association.ImplementClass); 
 
   Utils.SetTag(ds, DicomTag.ImplementationVersionName, client.Association.ImplementationVersionName); 
 
   try 
   { 
      ds.Save(filename, DicomDataSetSaveFlags.MetaHeaderPresent | DicomDataSetSaveFlags.GroupLengths); 
   } 
   catch (DicomException de) 
   { 
      return de.Code; 
   } 
 
   return DicomExceptionCode.Success; 
} 

Open the DicomCommon/Utils.cs file and add the using statement below.

C#
using System.Text; // Add to using block 

Add a new SetTag(DicomDataSet dcm, long tag, object tagValue) method and add the following code.

C#
public static DicomExceptionCode SetTag(DicomDataSet dcm, long tag, object tagValue) 
{ 
   DicomExceptionCode ret = DicomExceptionCode.Success; 
   DicomElement element; 
 
   if (tagValue == null) 
      return DicomExceptionCode.Parameter; 
 
   element = dcm.FindFirstElement(null, tag, true); 
   if (element == null) 
   { 
      element = dcm.InsertElement(null, false, tag, DicomVRType.UN, false, 0); 
   } 
 
   if (element == null) 
      return DicomExceptionCode.Parameter; 
 
   try 
   { 
      string s = tagValue.ToString(); 
      if (IsAscii(s)) 
         dcm.SetConvertValue(element, s, 1); 
      else 
         dcm.SetStringValue(element, s, DicomCharacterSetType.UnicodeInUtf8); 
   } 
   catch (DicomException de) 
   { 
      ret = de.Code; 
   } 
 
   return ret; 
} 

Add a new IsAscii(string value) method and add the following code.

C#
public static bool IsAscii(string value) 
{ 
   return Encoding.UTF8.GetByteCount(value) == value.Length; 
} 

Add the Transfer and Abstract Syntaxes to the Inclusion List

Before running the project, open the Utilities/Server.cs file and add the syntaxes to support storing the DICOM sample files found in <INSTALL_DIR>\LEADTOOLS22\Resources\Images\DICOM:

C#
private void BuildInclusionList() 
{ 
   _UidInclusionList.Add(DicomUidType.VerificationClass); 
          
   // Store Transfer Syntax 
   _UidInclusionList.Add(DicomUidType.JPEG2000LosslessOnly); // Image1.dcm 
   _UidInclusionList.Add(DicomUidType.JPEGLosslessNonhier14B); // Image2.dcm 
   _UidInclusionList.Add(DicomUidType.ImplicitVRLittleEndian); // Image3.dcm 
          
   // Store Abstract Syntax 
   _UidInclusionList.Add(DicomUidType.EnhancedMRImageStorage); // Image1.dcm 
   _UidInclusionList.Add(DicomUidType.DXImageStoragePresentation); // Image2.dcm 
   _UidInclusionList.Add(DicomUidType.MRImageStorage); // Image3.dcm 
} 

Run the Project

Run the project by pressing F5, or by selecting Debug -> Start Debugging.

If the steps were followed correctly, the application runs and allows a PACS client to send a C-STORE request to the server. Upon successful validation, the request is processed allowing the image to be added to the database.

Run the LEADTOOLS Dicom Storage SCU - C# demo to test the server, this demo is found here: <INSTALL_DIR>\LEADTOOLS22\Bin\DotNet4\x64\DicomStoreDemo_Original.exe

Use File -> Options to configure the connection then File -> Add Dicom... to add the sample DICOM datasets (Image1, Image2, and Image3) to the demo.

Then use File -> Store to send a C-STORE request for each of the datasets to the server.

Storage SCU Client
PACS Server C-STORE

Wrap-up

This tutorial showed how to add a database to a PACS Server and implement handling of C-STORE requests.

See Also

Help Version 22.0.2021.11.30
Products | Support | Contact Us | Intellectual Property Notices
© 1991-2021 LEAD Technologies, Inc. All Rights Reserved.

Products | Support | Contact Us | Intellectual Property Notices
© 1991-2021 LEAD Technologies, Inc. All Rights Reserved.