Handle Find Requests in a PACS Server - WinForms C#

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

Overview  
Summary This tutorial covers how to handle C-FIND requests for a PACS server in a WinForms C# Application.
Completion Time 60 minutes
Visual Studio Project Download tutorial project (22 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 with an XML database by reviewing the Add References and Set a License, Create a Simple PACS Server, and Handle Store Requests in a PACS Server tutorials, before working on the Handle Find 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 Handle Store Requests in a 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 functionality 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.

Add the Find Records Code to the Database Class

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

Open the Database\DicomDB.cs file and add the code below to search the database for a record according to a specified string filter.

C#
public DataView FindRecords(string type, string filter) 
{ 
 
   DataView dv; 
   lock (adoDatasetLock) 
   { 
      dv = new DataView(ds.Tables[type]); 
 
      if (dv != null) 
      { 
         dv.RowFilter = filter; 
      } 
   } 
   return dv; 
} 

Modify Client to Accept Find Requests

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

C#
protected override void OnReceiveCFindRequest(byte presentationID, int messageID, string affectedClass, DicomCommandPriorityType priority, DicomDataSet dataSet) 
{ 
   action = _server.InitAction("C-FIND-REQUEST", ProcessType.FindRequest, this, dataSet); 
 
   action.PresentationID = presentationID; 
   action.MessageID = messageID; 
   action.Class = affectedClass; 
   action.Priority = priority; 
   action.DoAction(); 
   dataSet.Dispose(); 
} 

Add Utility Methods to Parse DICOM Dataset

Open the DicomCommon/Utils.cs file and add the following using statements.

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

Add the following methods for parsing and inserting different values from the DICOM dataset.

C#
public static bool IsTagPresent(DicomDataSet dcm, long tag) 
{ 
   DicomElement element; 
 
   element = dcm.FindFirstElement(null, tag, true); 
   return (element != null); 
} 
 
public static DicomExceptionCode InsertKeyElement(DicomDataSet dcmRsp, DicomDataSet dcmReq, long tag) 
{ 
   DicomExceptionCode ret = DicomExceptionCode.Success; 
   DicomElement element; 
 
   try 
   { 
      element = dcmReq.FindFirstElement(null, tag, true); 
      if (element != null) 
      { 
         dcmRsp.InsertElement(null, false, tag, DicomVRType.UN, false, 0); 
      } 
   } 
   catch (DicomException de) 
   { 
      ret = de.Code; 
   } 
 
   return ret; 
} 
 
public static DicomExceptionCode SetKeyElement(DicomDataSet dcmRsp, long tag, object tagValue) 
{ 
   DicomExceptionCode ret = DicomExceptionCode.Success; 
   DicomElement element; 
 
   if (tagValue == null) 
      return DicomExceptionCode.Parameter; 
 
   try 
   { 
      element = dcmRsp.FindFirstElement(null, tag, true); 
      if (element != null) 
      { 
         string s = tagValue.ToString(); 
         if (IsAscii(s)) 
            dcmRsp.SetConvertValue(element, s, 1); 
         else 
            dcmRsp.SetStringValue(element, s, DicomCharacterSetType.UnicodeInUtf8); 
      } 
   } 
   catch (DicomException de) 
   { 
      ret = de.Code; 
   } 
 
   return ret; 
} 
 
public static StringCollection GetStringValues(DicomDataSet dcm, long tag) 
{ 
   DicomElement element; 
   StringCollection sc = new StringCollection(); 
 
   element = dcm.FindFirstElement(null, tag, true); 
   if (element != null) 
   { 
      if (dcm.GetElementValueCount(element) > 0) 
      { 
         string s = dcm.GetConvertValue(element); 
         string[] items = s.Split('\\'); 
 
         foreach (string value in items) 
         { 
            sc.Add(value); 
         } 
      } 
   } 
 
   return sc; 
} 
 
public static byte[] GetBinaryValues(DicomDataSet dcm, long tag) 
{ 
   DicomElement element; 
 
   element = dcm.FindFirstElement(null, tag, true); 
   if (element != null) 
   { 
      if (element.Length > 0) 
      { 
         return dcm.GetBinaryValue(element, (int)element.Length); 
      } 
   } 
 
   return null; 
} 

Add the Find Action to the DicomAction Class

Open the Utilities\DicomAction.cs file and add the following to the using block.

C#
using System; 
using System.Collections.Specialized; 
using System.Data; 

Add the FindRequest to the enumeration directly after the namespace declaration.

C#
public enum ProcessType 
{ 
   EchoRequest, 
   StoreRequest, 
   FindRequest 
} 

Add a QueryLevel enumeration to specify the query level the find action occurs on.

C#
public enum QueryLevel 
{ 
   Patient, 
   Study, 
   Series, 
   Image 
} 

Modify the DoAction method to parse the FindRequest 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; 
         // C-FIND 
         case ProcessType.FindRequest: 
            DoFindRequest(); 
            break; 
      } 
   } 
} 

Use the following code for the DoFindRequest() method which validates the FIND request and calls the relevant find method according to the query level of the request.

C#
private void DoFindRequest() 
{ 
   string level; 
   string msgTag = ""; 
   DicomCommandStatusType status; 
 
   // Check if abstract syntax is supported 
   if (!IsActionSupported()) 
   { 
      string name = GetUIDName(); 
 
      server.MainForm.Log("C-FIND-REQUEST: Abstract syntax (" + name + ") not supported by association"); 
      client.SendCFindResponse(_PresentationID, _MessageID, _Class, DicomCommandStatusType.ClassNotSupported, null); 
      return; 
   } 
 
   // Check if FIND request passed a valid dataset 
   if (ds == null) 
   { 
      server.MainForm.Log("C-FIND-REQUEST: No dataset provided"); 
      client.SendCFindResponse(_PresentationID, _MessageID, _Class, DicomCommandStatusType.InvalidArgumentValue, null); 
      return; 
   } 
 
   // Retrieve query level and check if relevant tags are present in the provided dataset 
   level = Utils.GetStringValue(ds, DicomTag.QueryRetrieveLevel); 
   status = AttributeStatus(level, ref msgTag); 
   if (status != DicomCommandStatusType.Success) 
   { 
      server.MainForm.Log("C-FIND-REQUEST: " + msgTag); 
      client.SendCFindResponse(_PresentationID, _MessageID, _Class, status, null); 
      return; 
   } 
 
   // Do the find method according to the query level 
   try 
   { 
      switch (level) 
      { 
         case "PATIENT": 
            DoPatientFind(); 
            break; 
         case "STUDY": 
            DoStudyFind(); 
            break; 
         case "SERIES": 
            DoSeriesFind(); 
            break; 
         case "IMAGE": 
            DoFindImage(); 
            break; 
         default: 
            server.MainForm.Log("C-FIND-REQUEST: Invalid query retrieve level: " + level); 
            client.SendCFindResponse(_PresentationID, _MessageID, _Class, DicomCommandStatusType.InvalidAttributeValue, null); 
            break; 
      } 
   } 
   catch (Exception e) 
   { 
      server.MainForm.Log("C-FIND-REQUEST: Processing failure: " + e.Message); 
      if (null != client && client.IsConnected()) 
      { 
         client.SendCFindResponse(_PresentationID, _MessageID, _Class, DicomCommandStatusType.ProcessingFailure, null); 
      } 
   } 
} 

Use the following code for the AttributeStatus method, which validates the required DICOM tags for the specified query level.

C#
private DicomCommandStatusType AttributeStatus(string level, ref string msgTag) 
{ 
   if (level.Length == 0) 
   { 
      msgTag = "Query Retrieve Level"; 
      return DicomCommandStatusType.InvalidArgumentValue; 
   } 
 
   if (_Class == DicomUidType.PatientRootQueryFind && level != "PATIENT") 
   { 
      if (!Utils.IsTagPresent(ds, DicomTag.PatientID)) 
      { 
         msgTag = "Patient ID"; 
         return DicomCommandStatusType.MissingAttribute; 
      } 
 
      if (Utils.GetStringValue(ds, DicomTag.PatientID).Length == 0) 
      { 
         msgTag = "Patient ID missing value"; 
         return DicomCommandStatusType.MissingAttribute; 
      } 
   } 
 
   if (level == "STUDY" || level == "SERIES" || level == "IMAGE") 
   { 
      if (!Utils.IsTagPresent(ds, DicomTag.StudyInstanceUID)) 
      { 
         msgTag = "Study Instance UID"; 
         return DicomCommandStatusType.MissingAttribute; 
      } 
 
      if (level == "SERIES" || level == "IMAGE") 
      { 
         if (Utils.GetStringValue(ds, DicomTag.StudyInstanceUID).Length == 0) 
         { 
            msgTag = "Study Instance UID missing value"; 
            return DicomCommandStatusType.MissingAttribute; 
         } 
      } 
   } 
 
   if (level == "SERIES" || level == "IMAGE") 
   { 
      if (!Utils.IsTagPresent(ds, DicomTag.SeriesInstanceUID)) 
      { 
         msgTag = "Series Instance ID"; 
         return DicomCommandStatusType.MissingAttribute; 
      } 
   } 
 
   if (level == "IMAGE") 
   { 
      if (!Utils.IsTagPresent(ds, DicomTag.SOPInstanceUID)) 
      { 
         msgTag = "SOP Instance ID"; 
         return DicomCommandStatusType.MissingAttribute; 
      } 
   } 
 
   return DicomCommandStatusType.Success; 
} 

Add the Patient Level Find Code

Use the code below to implement the find action for the patient query level. This method will parse each retrieved record and send a DICOM dataset as a response to the client.

The response dataset contains the following key elements:

C#
private void DoPatientFind() 
{ 
   DataView dv; 
   string filter = "PATIENTID LIKE '*'"; 
   string patientID = Utils.GetStringValue(ds, DicomTag.PatientID); 
   string patientName = Utils.GetStringValue(ds, DicomTag.PatientName); 
   DicomDataSet rspDs; 
 
   // Build patient filter 
   if (patientID.Length > 0) 
      filter = "PATIENTID LIKE '" + patientID + "'"; 
   if (patientName.Length > 0) 
   { 
      if (filter.Length > 0) 
         filter += " AND "; 
      filter += "PATIENTNAME LIKE '" + patientName + "'"; 
   } 
 
   // Search database for matching records 
   server.MainForm.Log("DB QUERY: " + filter); 
   dv = server.MainForm.DicomData.FindRecords("Patients", filter); 
 
   // Populate the response dataset with data from found records 
   foreach (DataRowView drv in dv) 
   { 
      DataRow row = drv.Row; 
 
      rspDs = InitResponseDS(QueryLevel.Patient); 
 
      Utils.SetKeyElement(rspDs, DicomTag.PatientID, row["PatientID"]); 
      if (row["PatientName"] != null) 
         Utils.SetKeyElement(rspDs, DicomTag.PatientName, row["PatientName"]); 
      if (row["PatientBirthDate"] != null) 
         Utils.SetKeyElement(rspDs, DicomTag.PatientBirthDate, row["PatientBirthDate"]); 
      if (row["PatientBirthTime"] != null) 
         Utils.SetKeyElement(rspDs, DicomTag.PatientBirthTime, row["PatientBirthTime"]); 
      if (row["PatientSex"] != null) 
         Utils.SetKeyElement(rspDs, DicomTag.PatientSex, row["PatientSex"]); 
      if (row["EthnicGroup"] != null) 
         Utils.SetKeyElement(rspDs, DicomTag.EthnicGroup, row["EthnicGroup"]); 
      if (row["PatientComments"] != null) 
         Utils.SetKeyElement(rspDs, DicomTag.PatientComments, row["PatientComments"]); 
 
      int seriesCount = 0; 
      int imageCount = 0; 
 
      DataView dvStudies = server.MainForm.DicomData.FindRecords("Studies", "PatientID = '" + row["PatientID"].ToString() + "'"); 
      Utils.SetKeyElement(rspDs, DicomTag.NumberOfPatientRelatedStudies, dvStudies.Count); 
      foreach (DataRowView drvStudies in dvStudies) 
      { 
         DataRow rowStudy = drvStudies.Row; 
         DataView dvSeries; 
 
         dvSeries = server.MainForm.DicomData.FindRecords("Series", "StudyInstanceUID = '" + rowStudy["StudyInstanceUID"].ToString() + "'"); 
         seriesCount += dvSeries.Count; 
         foreach (DataRowView drvSeries in dvSeries) 
         { 
            DataRow rowSeries = drvSeries.Row; 
            DataView drvImages; 
 
            drvImages = server.MainForm.DicomData.FindRecords("Images", "SeriesInstanceUID = '" + rowSeries["SeriesInstanceUID"].ToString() + "'"); 
            imageCount += drvImages.Count; 
         } 
      } 
      Utils.SetKeyElement(rspDs, DicomTag.NumberOfPatientRelatedSeries, seriesCount); 
      Utils.SetKeyElement(rspDs, DicomTag.NumberOfPatientRelatedInstances, imageCount); 
 
      try 
      { 
         client.SendCFindResponse(_PresentationID, _MessageID, _Class, DicomCommandStatusType.Pending, rspDs); 
         server.MainForm.Log("C-FIND-RESPONSE: Response sent to " + _AETitle + " (pending)"); 
      } 
      catch 
      { 
      } 
 
      rspDs.Dispose(); 
   } 
   client.SendCFindResponse(_PresentationID, _MessageID, _Class, DicomCommandStatusType.Success, null); 
   server.MainForm.Log("C-FIND-RESPONSE: Response sent to " + _AETitle + " (final)"); 
} 

Add the Study Level Find Code

Add the following code to implement the find action for the study query level and send the matching datasets in response to the client.

The response dataset contains the following key elements:

Note

The value for the Study Date inside the DICOM dataset in the request is formatted as yyyymmdd (e.g.: 19930822 would represent August 22, 1993). To create the string query filter for searching the database, the code parses the binary value and produces one of the following results, depending on the request by the client:

  1. A string of the form "<date1> - <date2>" that matches all occurrences of dates which fall between <date1> and <date2> inclusive.
  2. A string of the form "- <date1>" that matches all occurrences of dates prior to and including <date1>.
  3. A string of the form "<date1> -" that matches all occurrences of <date1> and subsequent dates.
C#
private void DoStudyFind() 
{ 
   DataView dv; 
   string filter = ""; 
   string patientID = "", accessionNum, studyID, patientName, referringPhysicianName; 
   StringCollection studyInstance; 
   DicomDataSet rspDs; 
   byte[] studyDate; 
 
   if (Utils.IsTagPresent(ds, DicomTag.PatientID)) 
      patientID = Utils.GetStringValue(ds, DicomTag.PatientID); 
 
   accessionNum = Utils.GetStringValue(ds, DicomTag.AccessionNumber); 
   studyID = Utils.GetStringValue(ds, DicomTag.StudyID); 
   patientName = Utils.GetStringValue(ds, DicomTag.PatientName); 
   referringPhysicianName = Utils.GetStringValue(ds, DicomTag.ReferringPhysicianName); 
 
   // Build study filter 
   if (patientID.Length > 0) 
      filter += "PatientID = '" + patientID + "'"; 
   if (patientName.Length > 0) 
   { 
      if (filter.Length > 0) 
         filter += " AND "; 
      filter += "PatientName = '" + patientName + "'"; 
   } 
   studyInstance = Utils.GetStringValues(ds, DicomTag.StudyInstanceUID); 
   foreach (string instance in studyInstance) 
   { 
      if (filter.Length > 0) 
         filter += " AND "; 
      filter += "StudyInstanceUID = '" + instance + "'"; 
   } 
   studyDate = Utils.GetBinaryValues(ds, DicomTag.StudyDate); 
   // Parse date 
   if (studyDate != null && studyDate.Length > 0) 
   { 
      string date; 
      string del = @"\"; 
      string[] dateArray; 
 
      date = System.Text.Encoding.ASCII.GetString(studyDate); 
 
      while (date.IndexOf("\0") != -1) 
         date = date.Remove(date.IndexOf("\0"), 1); 
 
      dateArray = date.Split(del.ToCharArray()); 
      for (int i = 0; i < dateArray.Length; i++) 
         dateArray[i] = dateArray[i].Replace(" ", ""); 
 
      if (filter.Length > 0) 
         filter += " AND "; 
      if (dateArray[0].IndexOf("-") != -1) 
      { 
         string reqDate; 
 
         if (dateArray[0].Substring(0, 1) == "-") 
         {	// If it starts with a '-' then it's an upper range 
            reqDate = dateArray[0].Substring(1); 
 
            filter += "( StudyDate <= #" + ConvertToQueryDate(reqDate) + "#)"; 
         } 
         else if (dateArray[0].Substring(dateArray[0].Length - 1, 1) == "-") 
         {	// If it ends with a '-' then it's the lower range 
            reqDate = dateArray[0].Substring(0, dateArray[0].Length - 1); 
 
            filter += "( StudyDate >= #" + ConvertToQueryDate(reqDate) + "#)"; 
         } 
         else 
         {	// Both dates provided 
            string[] cmpDates = dateArray[0].Split('-'); 
 
            filter += "( StudyDate >= #" + ConvertToQueryDate(cmpDates[0]) + "# AND "; 
            filter += "StudyDate <= #" + ConvertToQueryDate(cmpDates[1]) + "#)"; 
         } 
      } 
      else 
         filter += "( StudyDate = #" + ConvertToQueryDate(dateArray[0]) + "#)"; 
   } 
   if (accessionNum.Length > 0) 
   { 
      if (filter.Length > 0) 
         filter += " AND "; 
      filter += "AccessionNumber LIKE '" + accessionNum + "'"; 
   } 
   if (studyID.Length > 0) 
   { 
      if (filter.Length > 0) 
         filter += " AND "; 
      filter += "StudyID LIKE '" + studyID + "'"; 
   } 
 
   if (referringPhysicianName.Length > 0) 
   { 
      if (filter.Length > 0) 
         filter += " AND "; 
      filter += "ReferringDrName LIKE '" + referringPhysicianName + "'"; 
   } 
   if (filter.Length == 0) 
      filter = "STUDYID LIKE '*'"; 
 
   // Search database for matching records 
   server.MainForm.Log("DB QUERY: " + filter); 
   dv = server.MainForm.DicomData.FindRecords("Studies", filter); 
 
   // Populate the response dataset with data from found records 
   foreach (DataRowView drv in dv) 
   { 
      DataRow row = drv.Row; 
 
      rspDs = InitResponseDS(QueryLevel.Study); 
 
      if (_Class == DicomUidType.PatientRootQueryFind) 
         Utils.SetKeyElement(rspDs, DicomTag.PatientID, patientID); 
      Utils.SetKeyElement(rspDs, DicomTag.StudyInstanceUID, row["StudyInstanceUID"]); 
      if (row["StudyDate"] != null) 
         Utils.SetKeyElement(rspDs, DicomTag.StudyDate, row["StudyDate"]); 
      if (row["StudyTime"] != null) 
         Utils.SetKeyElement(rspDs, DicomTag.StudyTime, row["StudyTime"]); 
      if (row["AccessionNumber"] != null) 
         Utils.SetKeyElement(rspDs, DicomTag.AccessionNumber, row["AccessionNumber"]); 
      if (row["StudyID"] != null) 
         Utils.SetKeyElement(rspDs, DicomTag.StudyID, row["StudyID"]); 
      if (row["PatientID"] != null) 
         Utils.SetKeyElement(rspDs, DicomTag.PatientID, row["PatientID"]); 
      if (row["PatientName"] != null) 
         Utils.SetKeyElement(rspDs, DicomTag.PatientName, row["PatientName"]); 
      if (row["ReferringDrName"] != null) 
         Utils.SetKeyElement(rspDs, DicomTag.ReferringPhysicianName, row["ReferringDrName"]); 
 
      DataView dvSeries; 
      int seriesCount = 0; 
      int imageCount = 0; 
 
      dvSeries = server.MainForm.DicomData.FindRecords("Series", "StudyInstanceUID = '" + row["StudyInstanceUID"].ToString() + "'"); 
      seriesCount += dvSeries.Count; 
      foreach (DataRowView drvSeries in dvSeries) 
      { 
         DataRow rowSeries = drvSeries.Row; 
         DataView drvImages; 
 
         drvImages = server.MainForm.DicomData.FindRecords("Images", "SeriesInstanceUID = '" + rowSeries["SeriesInstanceUID"].ToString() + "'"); 
         imageCount += drvImages.Count; 
      } 
 
      Utils.SetKeyElement(rspDs, DicomTag.NumberOfStudyRelatedSeries, seriesCount); 
      Utils.SetKeyElement(rspDs, DicomTag.NumberOfStudyRelatedInstances, imageCount); 
 
      try 
      { 
         client.SendCFindResponse(_PresentationID, _MessageID, _Class, DicomCommandStatusType.Pending, rspDs); 
         server.MainForm.Log("C-FIND-RESPONSE: Response sent to " + _AETitle + " (pending)"); 
      } 
      catch 
      { 
      } 
 
      rspDs.Dispose(); 
   } 
   client.SendCFindResponse(_PresentationID, _MessageID, _Class, DicomCommandStatusType.Success, null); 
   server.MainForm.Log("C-FIND-RESPONSE: Response sent to " + _AETitle + " (final)"); 
} 

Add a new ConvertToQueryDate method and add the code below to parse the code for the date stored in the database to a suitable filter to be used for retrieving the specified study.

C#
private string ConvertToQueryDate(string reqDate) 
{ 
   return reqDate.Substring(4, 2) + "/" + reqDate.Substring(6, 2) + "/" + reqDate.Substring(0, 4); 
} 

Add the Series Level Find Code

Add the following code to implement the find action for the series query level and send the matching datasets in response to the client.

The response dataset contains the following key elements:

C#
private void DoSeriesFind() 
{ 
   DataView dv; 
   string filter; 
   string modality, seriesNumber, patientID, studyInstance; 
   StringCollection seriesInstance; 
   DicomDataSet rspDs; 
 
   patientID = Utils.GetStringValue(ds, DicomTag.PatientID); 
   modality = Utils.GetStringValue(ds, DicomTag.Modality); 
   seriesNumber = Utils.GetStringValue(ds, DicomTag.SeriesNumber); 
   studyInstance = Utils.GetStringValue(ds, DicomTag.StudyInstanceUID); 
 
   // Build series filter 
   filter = "StudyInstanceUID = '" + studyInstance + "'"; 
   if (_Class == DicomUidType.PatientRootQueryFind && patientID.Length > 0) 
      filter += " AND PatientID = '" + patientID + "'"; 
   seriesInstance = Utils.GetStringValues(ds, DicomTag.SeriesInstanceUID); 
   foreach (string instance in seriesInstance) 
      filter += " AND SeriesInstanceUID='" + instance + "'"; 
   if (modality.Length > 0) 
      filter += " AND Modality LIKE '" + modality + "'"; 
   if (seriesNumber.Length > 0) 
      filter += " AND SeriesNumber = " + seriesNumber; 
 
   // Search database for matching records 
   server.MainForm.Log("DB QUERY: " + filter); 
   dv = server.MainForm.DicomData.FindRecords("Series", filter); 
 
   // Populate the response dataset with data from found records 
   foreach (DataRowView drv in dv) 
   { 
      DataRow row = drv.Row; 
 
      rspDs = InitResponseDS(QueryLevel.Series); 
 
      if (_Class == DicomUidType.PatientRootQueryFind) 
         Utils.SetKeyElement(rspDs, DicomTag.PatientID, patientID); 
      Utils.SetKeyElement(rspDs, DicomTag.StudyInstanceUID, studyInstance); 
      Utils.SetKeyElement(rspDs, DicomTag.SeriesInstanceUID, row["SeriesInstanceUID"]); 
      if (row["Modality"] != null) 
         Utils.SetKeyElement(rspDs, DicomTag.Modality, row["Modality"]); 
      if (row["SeriesNumber"] != null) 
         Utils.SetKeyElement(rspDs, DicomTag.SeriesNumber, row["SeriesNumber"]); 
      if (row["SeriesDate"] != null) 
         Utils.SetKeyElement(rspDs, DicomTag.SeriesDate, row["SeriesDate"]); 
 
      DataView dvImages; 
 
      dvImages = server.MainForm.DicomData.FindRecords("Images", "SeriesInstanceUID = '" + row["SeriesInstanceUID"].ToString() + "'"); 
      if (dvImages != null) 
         Utils.SetKeyElement(rspDs, DicomTag.NumberOfSeriesRelatedInstances, dvImages.Count); 
 
      try 
      { 
         client.SendCFindResponse(_PresentationID, _MessageID, _Class, DicomCommandStatusType.Pending, rspDs); 
         server.MainForm.Log("C-FIND-RESPONSE: Response sent to " + _AETitle + " (pending)"); 
      } 
      catch 
      { 
      } 
 
      rspDs.Dispose(); 
   } 
   client.SendCFindResponse(_PresentationID, _MessageID, _Class, DicomCommandStatusType.Success, null); 
   server.MainForm.Log("C-FIND-RESPONSE: Response sent to " + _AETitle + " (final)"); 
} 

Add the Image Level Find Code

Use the code below to implement the find action for the images query level and send the matching datasets in response to the client.

The response dataset contains the following key elements:

C#
private void DoFindImage() 
{ 
   DataView dv; 
   string filter; 
   DicomDataSet rspDS; 
   string studyInstance, patientID, instanceNumber; 
   string seriesInstance; 
   StringCollection sopInstanceUID; 
 
   studyInstance = Utils.GetStringValue(ds, DicomTag.StudyInstanceUID); 
   seriesInstance = Utils.GetStringValue(ds, DicomTag.SeriesInstanceUID); 
   patientID = Utils.GetStringValue(ds, DicomTag.PatientID); 
   instanceNumber = Utils.GetStringValue(ds, DicomTag.InstanceNumber); 
 
   filter = "StudyInstanceUID = '" + studyInstance + "'"; 
   filter += " AND SeriesInstanceUID = '" + seriesInstance + "'"; 
 
   if (_Class == DicomUidType.PatientRootQueryFind && patientID.Length > 0) 
      filter += " AND PatientID = '" + patientID + "'"; 
 
   sopInstanceUID = Utils.GetStringValues(ds, DicomTag.SOPInstanceUID); 
   foreach (string instance in sopInstanceUID) 
      filter += " AND SOPInstanceUID ='" + instance + "'"; 
 
   if (instanceNumber.Length > 0) 
      filter += " AND InstanceNumber = " + instanceNumber.ToString(); 
 
   server.MainForm.Log("DB QUERY: " + filter); 
   dv = server.MainForm.DicomData.FindRecords("Images", filter); 
   foreach (DataRowView drv in dv) 
   { 
      DataRow row = drv.Row; 
 
      rspDS = InitResponseDS(QueryLevel.Image); 
 
      if (_Class == DicomUidType.PatientRootQueryFind) 
         Utils.SetKeyElement(rspDS, DicomTag.PatientID, patientID); 
 
      Utils.SetKeyElement(rspDS, DicomTag.SOPInstanceUID, row["SOPInstanceUID"]); 
 
      if (row["InstanceNumber"] != null) 
         Utils.SetKeyElement(rspDS, DicomTag.InstanceNumber, row["InstanceNumber"]); 
 
      rspDS.Dispose(); 
   } 
   client.SendCFindResponse(_PresentationID, _MessageID, _Class, DicomCommandStatusType.Success, null); 
   server.MainForm.Log("C-FIND-RESPONSE: Response sent to " + _AETitle + " (final)"); 
} 

Add the Code to Initialize the Response DICOM Dataset

Use the code below to add the InitResponseDS() method which creates the DICOM dataset object that will be sent as a response for each matching record in the find action.

Once created, the method inserts key elements into the dataset according to the query level.

C#
private DicomDataSet InitResponseDS(QueryLevel level) 
{ 
   DicomDataSet rspDs = new DicomDataSet(); 
 
   rspDs.Initialize(DicomClassType.Undefined, DicomDataSetInitializeType.ExplicitVRLittleEndian); 
   switch (level) 
   { 
      case QueryLevel.Patient: 
         Utils.SetTag(rspDs, DicomTag.QueryRetrieveLevel, "PATIENT"); 
 
         // Required Keys 
         Utils.InsertKeyElement(rspDs, ds, DicomTag.PatientName); 
 
         // Optional Keys 
         Utils.InsertKeyElement(rspDs, ds, DicomTag.PatientBirthDate); 
         Utils.InsertKeyElement(rspDs, ds, DicomTag.PatientBirthTime); 
         Utils.InsertKeyElement(rspDs, ds, DicomTag.PatientSex); 
         Utils.InsertKeyElement(rspDs, ds, DicomTag.EthnicGroup); 
         Utils.InsertKeyElement(rspDs, ds, DicomTag.PatientComments); 
         Utils.InsertKeyElement(rspDs, ds, DicomTag.NumberOfPatientRelatedStudies); 
         Utils.InsertKeyElement(rspDs, ds, DicomTag.NumberOfPatientRelatedSeries); 
         Utils.InsertKeyElement(rspDs, ds, DicomTag.NumberOfPatientRelatedInstances); 
         break; 
      case QueryLevel.Study: 
         Utils.SetTag(rspDs, DicomTag.QueryRetrieveLevel, "STUDY"); 
 
         // Required Keys 
         Utils.InsertKeyElement(rspDs, ds, DicomTag.StudyDate); 
         Utils.InsertKeyElement(rspDs, ds, DicomTag.StudyTime); 
         Utils.InsertKeyElement(rspDs, ds, DicomTag.AccessionNumber); 
         Utils.InsertKeyElement(rspDs, ds, DicomTag.StudyID); 
         Utils.InsertKeyElement(rspDs, ds, DicomTag.PatientName); 
         Utils.InsertKeyElement(rspDs, ds, DicomTag.PatientID); 
 
         // Optional Keys 
         Utils.InsertKeyElement(rspDs, ds, DicomTag.StudyDescription); 
         Utils.InsertKeyElement(rspDs, ds, DicomTag.ReferringPhysicianName); 
         Utils.InsertKeyElement(rspDs, ds, DicomTag.NumberOfStudyRelatedSeries); 
         Utils.InsertKeyElement(rspDs, ds, DicomTag.NumberOfStudyRelatedInstances); 
         break; 
      case QueryLevel.Series: 
         Utils.SetTag(rspDs, DicomTag.QueryRetrieveLevel, "SERIES"); 
 
         // Required Keys 
         Utils.InsertKeyElement(rspDs, ds, DicomTag.Modality); 
         Utils.InsertKeyElement(rspDs, ds, DicomTag.SeriesNumber); 
         Utils.InsertKeyElement(rspDs, ds, DicomTag.SeriesDate); 
 
         // Optional Keys 
         Utils.InsertKeyElement(rspDs, ds, DicomTag.NumberOfSeriesRelatedInstances); 
         break; 
      case QueryLevel.Image: 
         Utils.SetTag(rspDs, DicomTag.QueryRetrieveLevel, "IMAGE"); 
 
         // Required Keys 
         Utils.InsertKeyElement(rspDs, ds, DicomTag.InstanceNumber); 
         break; 
   } 
 
   if (_Class == DicomUidType.PatientRootQueryFind && level != QueryLevel.Study) 
      rspDs.InsertElement(null, false, DicomTag.PatientID, DicomVRType.UN, false, 0); 
   if (level == QueryLevel.Study || level == QueryLevel.Series || level == QueryLevel.Image) 
      rspDs.InsertElement(null, false, DicomTag.StudyInstanceUID, DicomVRType.UN, false, 0); 
   if (level == QueryLevel.Series || level == QueryLevel.Image) 
      rspDs.InsertElement(null, false, DicomTag.SeriesInstanceUID, DicomVRType.UN, false, 0); 
   if (level == QueryLevel.Image) 
      rspDs.InsertElement(null, false, DicomTag.SOPInstanceUID, DicomVRType.UN, false, 0); 
 
   return rspDs; 
} 

Add the Root Query Abstract Syntaxes to the Inclusion List

Before running the project, open the Utilities/Server.cs file and add the syntaxes to support find actions.

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 
 
   // Find Abstract Syntax 
   _UidInclusionList.Add(DicomUidType.PatientRootQueryFind); 
   _UidInclusionList.Add(DicomUidType.StudyRootQueryFind); 
} 

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-FIND request to the server. Upon successful validation, the request is processed allowing the specific datasets to be retrieved and sent as a response to the client.

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

Use the Options button to configure the connection, then click the Search button to find all DICOM datasets stored in the PACS Server.

Note

See the Handle Store Requests in a PACS Server tutorial for how to add DICOM dataset files to the PACS server database in order to search for them.

Dicom Client Search
PACS Server C-FIND

Wrap-up

This tutorial showed how to add a database to a PACS Server and implement the handling of C-FIND requests by searching the database and sending a dataset in response that corresponds to the query level of the request.

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.