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 (5 KB) |
Platform | .NET 6 WinForms C# Application |
IDE | Visual Studio 2022 |
Development License | Download LEADTOOLS |
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.
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:
Leadtools.Dicom.Pacs.Scp
If using local DLL references, the following DLLs are needed.
The DLLs are located at <INSTALL_DIR>\LEADTOOLS23\Bin\net
:
Leadtools.dll
Leadtools.Core.dll
Leadtools.Dicom.dll
For a complete list of which DLL files are required for your application, refer to Files to be Included With Your Application.
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.
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.
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;
}
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:
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();
}
Open the DicomCommon/Utils.cs
file and add the following using
statements.
// 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.
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;
}
Open the Utilities\DicomAction.cs
file and add the following to the using
block.
using System;
using System.Collections.Specialized;
using System.Data;
Add the FindRequest
to the enumeration directly after the namespace declaration.
public enum ProcessType
{
EchoRequest,
StoreRequest,
FindRequest
}
Add a QueryLevel
enumeration to specify the query level the find action occurs on.
public enum QueryLevel
{
Patient,
Study,
Series,
Image
}
Modify the DoAction
method to parse the FindRequest
process.
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.
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.
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;
}
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:
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 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:
- A string of the form "<date1> - <date2>" that matches all occurrences of dates which fall between <date1> and <date2> inclusive.
- A string of the form "- <date1>" that matches all occurrences of dates prior to and including <date1>.
- A string of the form "<date1> -" that matches all occurrences of <date1> and subsequent dates.
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.
private string ConvertToQueryDate(string reqDate)
{
return reqDate.Substring(4, 2) + "/" + reqDate.Substring(6, 2) + "/" + reqDate.Substring(0, 4);
}
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:
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)");
}
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:
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)");
}
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.
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;
}
Before running the project, open the Utilities/Server.cs
file and add the syntaxes to support find actions.
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 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>\LEADTOOLS23\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.
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.