This tutorial shows how to configure a simple PACS client application that can establish a connection to a PACS Server, query for stored DICOM studies and series, then retrieve a dataset and display it in a medical viewer control.
Overview | |
---|---|
Summary | This tutorial covers how to create a simple PACS client in a WinForms C# Application. |
Completion Time | 30 minutes |
Visual Studio Project | Download tutorial project (16 KB) |
Platform | Windows WinForms C# Application |
IDE | Visual Studio 2019 |
Development License | Download LEADTOOLS |
Get familiar with the basic steps of creating a project by reviewing the Add References and Set a License and Load and Display a DICOM Image in the Medical Viewer tutorials, Before working on the Create a Simple PACS Client With a Medical Viewer - WinForms C# tutorial.
Start with a copy of the project created in the Load and Display a DICOM Image in the Medical Viewer tutorial. If you don't have that project, follow the steps in that tutorial to create it.
Ensure the project has the necessary LEADTOOLS references mentioned in the original tutorial.
If NuGet references are used, this tutorial requires the following additional NuGet package:
Leadtools.Dicom.Scu
If local DLL references are used, the following additional DLLs are needed. The DLLs are located at <INSTALL_DIR>\LEADTOOLS23\Bin\Dotnet4\x64
:
Leadtools.Dicom.Common.dll
Leadtools.Dicom.Scu.dll
For a complete list of which Codec DLLs are required for specific formats, refer to File Format Support.
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.
Now that the LEADTOOLS references have been added to the project and the license has been set, coding can begin.
Open Form1.cs
in the Designer, then add a PACS menu with a Retrieve Image menu item. Leave the new item's name as retrieveImageToolStripMenuItem
.
Double-click the *Retrieve Image menu item to edit its event handler. Add the following code in it:
private void retrieveImageToolStripMenuItem_Click(object sender, EventArgs e)
{
PACSQueryDialog dialog = new PACSQueryDialog();
DialogResult dr = dialog.ShowDialog(this);
if (dr == DialogResult.OK)
{
try
{
_medicalViewer.Cells.Clear();
foreach (DicomDataSet dataSet in dialog.retrievedDatasets)
{
_cell = null;
_dataSet = dataSet;
InitializeMultiCell();
if (_cell != null)
{
SetTags();
_medicalViewer.Cells.Add(_cell);
}
}
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
}
Right-click on the project and add a new form named PACSQueryDialog. Open PACSQueryDialog.cs
in the designer and add the following controls:
A button control with the name buttonDisplaySeries and text Display Series
Right-click on PACSQueryDialog.cs
in the Solution Explorer and select View Code to display the code behind the form.
Add the following code to the using block:
// Using block at the top
using System;
using System.Collections.Generic;
using System.Net;
using System.Windows.Forms;
using Leadtools.Dicom;
using Leadtools.Dicom.Scu;
using Leadtools.Dicom.Scu.Common;
Add the code below to the dialog's global variables:
// Add these global variables
public List<DicomDataSet> retrievedDatasets = new List<DicomDataSet>();
private QueryRetrieveScu clientSCU = new QueryRetrieveScu();
private DicomScp sourceSCP = new DicomScp();
private FindQuery query = new FindQuery();
// Client Connection Info
private string clientAE = @"";
private string clientIP = @"";
private string clientPort = @"";
// Server Connection Info
private string pacsServerAE = @"";
private string pacsServerIP = @"";
private string pacsServerPort = @"";
The public collection, retrievedDatasets
, will hold the retrieved DICOM datasets that will then be accessible for display in the medical viewer control.
Valid PACS Client and Server information need to be filled for the application to function successfully.
In the Solution Explorer, Go to the PACSQueryDialog.cs
form's properties and double-click the Load event to create a PACSQueryDialog_Load
event handler for the form. This will bring up the code behind the form.
Add the following code to initialize the DicomNet
, sourceSCP
, and clientSCU
classes then use clientSCU.Find()
to retrieve all DICOM studies found in the Server and add them to the ListView control.
private void PACSQueryDialog_Load(object sender, EventArgs e)
{
DicomNet.Startup();
// Initialize the SCU
try
{
clientSCU.AETitle = clientAE;
clientSCU.HostAddress = IPAddress.Parse(clientIP);
clientSCU.HostPort = Convert.ToInt32(clientPort);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Exception initializing the QueryRetrieveScu Class", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
try
{
clientSCU.MatchStudy += new MatchStudyDelegate(clientSCU_MatchStudy);
clientSCU.MatchSeries += new MatchSeriesDelegate(clientSCU_MatchSeries);
clientSCU.AfterCFind += new AfterCFindDelegate(clientSCU_AfterCFind);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Exception adding event handlers to the QueryRetrieveScu Class", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
// Initialize the SCP
try
{
sourceSCP.AETitle = pacsServerAE;
sourceSCP.PeerAddress = IPAddress.Parse(pacsServerIP);
sourceSCP.Port = Convert.ToInt32(pacsServerPort);
sourceSCP.Timeout = 60;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Exception setting up SCP", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
// Find Studies and add them to ListView
try
{
listViewStudies.BeginUpdate();
query.QueryLevel = QueryLevel.Study;
clientSCU.Find(sourceSCP, query);
listViewStudies.AutoResizeColumns(ColumnHeaderAutoResizeStyle.HeaderSize);
listViewStudies.EndUpdate();
listViewStudies.Items[0].Selected = true;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Exception from QueryRetrieveScu.Move()", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
// Add the information from each study to the ListView control
void clientSCU_MatchStudy(object sender, MatchEventArgs<Study> e)
{
ListViewItem item;
item = listViewStudies.Items.Add(e.Info.Patient.Name.ToString());
item.SubItems.Add(e.Info.Patient.Id.ToString());
item.SubItems.Add(e.Info.AccessionNumber.ToString());
item.SubItems.Add(e.Info.Date.ToString());
item.SubItems.Add(e.Info.ReferringPhysiciansName.ToString());
item.SubItems.Add(e.Info.Description.ToString());
item.SubItems.Add(e.Info.InstanceUID.ToString());
}
In the Solution Explorer, open the designer for the PACSQueryDialog.cs
form. Go to the listViewStudies
ListView control properties and double-click the SelectedIndexChanged event to create a listViewStudies_SelectedIndexChanged
event handler for the form. This will bring up the code behind the form.
Add the following code so that the series for the selected study are retrieved and added to the series ListView control.
private void listViewStudies_SelectedIndexChanged(object sender, EventArgs e)
{
if (listViewStudies.SelectedItems.Count == 0)
return;
listViewSeries.BeginUpdate();
listViewSeries.Items.Clear();
query.QueryLevel = QueryLevel.Series;
query.StudyInstanceUID = listViewStudies.SelectedItems[0].SubItems[6].Text;
clientSCU.Find(sourceSCP, query);
listViewSeries.AutoResizeColumns(ColumnHeaderAutoResizeStyle.HeaderSize);
listViewSeries.EndUpdate();
listViewSeries.Items[0].Selected = true;
}
void clientSCU_MatchSeries(object sender, MatchEventArgs<Series> e)
{
ListViewItem item;
item = listViewSeries.Items.Add(e.Info.Date.ToString());
item.SubItems.Add(e.Info.Number.ToString());
item.SubItems.Add(e.Info.Description.ToString());
item.SubItems.Add(e.Info.Modality.ToString());
item.SubItems.Add(e.Info.NumberOfRelatedInstances.ToString());
item.SubItems.Add(e.Info.InstanceUID.ToString());
}
void clientSCU_AfterCFind(object sender, AfterCFindEventArgs e)
{
if (e.Status != DicomCommandStatusType.Success)
{
// If there is a problem, print the additional status elements
string statusAllString = e.StatusAll.ToString();
Console.WriteLine(statusAllString);
}
}
Double-click on the Display Series Button, to create a buttonDisplaySeries_Click
event handler and to bring up the code behind the form.
Add the code below to move the dataset for the selected series from the server to the client and make it available for the medical viewer control.
private void buttonDisplaySeries_Click(object sender, EventArgs e)
{
try
{
clientSCU.Moved += new MovedDelegate(retrieveStudy_Moved);
clientSCU.AfterCMove += new AfterCMoveDelegate(retrieveStudy_AfterCMove);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Exception adding SCU C-MOVE event handlers", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
try
{
string studyUID, seriesUID;
if (listViewStudies.SelectedItems.Count == 0 && listViewSeries.SelectedItems.Count == 0)
return;
studyUID = listViewStudies.SelectedItems[0].SubItems[6].Text;
seriesUID = listViewSeries.SelectedItems[0].SubItems[5].Text;
if (studyUID != string.Empty && seriesUID != string.Empty)
clientSCU.Move(sourceSCP, string.Empty, studyUID, seriesUID);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Exception from QueryRetrieveScu.Move()", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
// Done
this.DialogResult = DialogResult.OK;
this.Close();
}
private void retrieveStudy_Moved(object sender, MovedEventArgs movedEventArgs)
{
retrievedDatasets.Add(movedEventArgs.Dataset);
}
private void retrieveStudy_AfterCMove(object sender, AfterCMoveEventArgs e)
{
if (e.Status != DicomCommandStatusType.Success)
{
// If there is a problem, print the additional status elements
string statusAllString = e.StatusAll.ToString();
Console.WriteLine(statusAllString);
}
}
In the Solution Explorer, Go to the PACSQueryDialog.cs
form's properties and double-click the FormClosing event to create a PACSQueryDialog_FormClosing
event handler for the form. This will bring up the code behind the form.
Call DicomNet.Shutdown()
in the event handler:
private void PACSQueryDialog_FormClosing(object sender, FormClosingEventArgs e)
{
try
{
DicomNet.Shutdown();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Exception from DicomNet.Shutdown()", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
}
Run the project by pressing F5, or by selecting Debug -> Start Debugging.
If the steps were followed correctly, the application runs and allows the user to find and retrieve DICOM studies and series from a PACS server. The user can then retrieve the DICOM dataset associated to a selected series and display it on the medical viewer control.
Note
For the application to function properly, the PACS Server should be configured to allow connections from the Client AE, IP, and port defined in this tutorial.
This LEADTOOLS Dicom Server demo (found here: <INSTALL_DIR>\LEADTOOLS23\Shortcuts\PACS\.NET Framework Class Libraries\PACS (Low Level)\Server
) can be used with this client. The client connection can be configured from the Users tab in this demo after clicking Add User.
This tutorial showed how to add the necessary references to create a PACS client application that can connect, find, and move DICOM datasets for display in a MedicalViewer
WinForms control. In addition, it showed how to use the QueryRetrieveScu
, DicomScp
, and FindQuery
objects.