This tutorial shows how to encapsulate and extract documents using the Encapsulated Document Information Object Definition (IOD) of a DICOM file in a C# WinForms application.
Overview | |
---|---|
Summary | This tutorial covers how to encapsulate and extract documents using a DICOM dataset in a WinForms C# Application. |
Completion Time | 30 minutes |
Visual Studio Project | Download tutorial project (9 KB) |
Platform | .NET 6 WinForms C# Application Application |
IDE | Visual Studio 2022 |
Development License | Download LEADTOOLS |
Get familiar with the basic steps of creating a project by reviewing the Add References and Set a License tutorial, before working on the Encapsulate DICOM Documents - WinForms C# tutorial.
The specifications for the Encapsulated Document information objects are listed in Part 3 Section A.45 of the DICOM standard.
Two types of documents can be encapsulated in a DICOM dataset:
More details on the Encapsulated Document Module can be found in the Working With DICOM Encapsulated Documents topic.
In Visual Studio, create a new C# Windows WinForms project, and add the below necessary LEADTOOLS references.
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.Scu
If using local DLL references, the following DLLs are needed.
The DLLs are located at <INSTALL_DIR>\LEADTOOLS23\Bin\net
:
Leadtools.dll
Leadtools.Dicom.dll
Leadtools.Document.dll
Leadtools.Core.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 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.
With the project created, the references added, and the license set, coding can begin.
In Solution Explorer, double-click Form1.cs
to display it in the Designer. From the Toolbox, add the following controls:
typeComboBox
, with the following values in the Items collection: burnedInAnnComboBox
, with the following values in the Items collection: verificationFlagComboBox
, with the following values in the Items collection: instanceNumberTextBox
contentDateTextBox
contentTimeTextBox
acquisitionDateTextBox
acquisitionTimeTextBox
documentTitleTextBox
HL7InstanceIdentifierTextBox
codingSchemeDesignatorTextBox
codeValueTextBox
codeMeaningTextBox
mimeTypesTextBox
, with the Multiline property set to TrueencapsulateButton
with the text Encapsulate DocumentextractButton
with the text Extract DocumentclearButton
with the text ClearLabel controls to accompany the TextBox controls and show the proper input format for the date and time inputs.
Set the Enabled property for all TextBox, ComboBox, and Button controls to false so that the application enables them only when a DICOM DataSet is loaded.
Add the using
statements below to the top.
using Leadtools;
using Leadtools.Dicom;
Add the following global variables to the Form1
class.
private DicomDataSet _dicomDS;
private MemoryStream _docStream;
In Solution Explorer, double-click Form1.cs
to display it in the Designer. Click the Events icon in the Properties Window. Then, double-click the Load event to create an event handler if one does not already exist. This will bring up the code behind the form.
Add the following code inside the Form1_Load
event handler to startup the DicomEngine
object.
private void Form1_Load(object sender, EventArgs e)
{
DicomEngine.Startup();
}
Add a MenuStrip from the toolbox and add a DICOM menu to the form with a Load menu item. Set the text to &Load and leave the new item's name as loadToolStripMenuItem
.
Double-click the Load menu item to edit its event handler. Add the following code in it:
private void loadToolStripMenuItem_Click(object sender, System.EventArgs e)
{
// Load DataSet
_dicomDS = new DicomDataSet();
OpenFileDialog openDialog = new OpenFileDialog();
openDialog.InitialDirectory = @"C:\LEADTOOLS23\Resources\Images\DICOM";
openDialog.Filter = "DICOM dataset (.dcm)|*.dcm";
if (openDialog.ShowDialog(this) == DialogResult.OK)
{
_dicomDS.Load(openDialog.FileName, DicomDataSetLoadFlags.None);
DicomElement encapsulatedDocElement = _dicomDS.FindFirstElement(null, DicomTag.EncapsulatedDocument, true);
// Enable Controls
foreach (Control child in Controls)
if (child.GetType() == typeof(TextBox) || child.GetType() == typeof(Button) || child.GetType() == typeof(ComboBox))
child.Enabled = true;
if (encapsulatedDocElement != null)
{
DisableEditing();
GetEncapsulatedDocInfo(encapsulatedDocElement);
// Enable extraction of encapsulated document
encapsulateButton.Enabled = false;
extractButton.Enabled = true;
}
else // No encapsulated document
{
EnableEditing();
// Enable encapsulation of encapsulated document
encapsulateButton.Enabled = true;
extractButton.Enabled = false;
}
}
}
This shows an open file dialog to load a DICOM DataSet from disk. If the selected DataSet contains an encapsulated document, it displays the document information in the form, extracts the encapsulated document to a memory stream, and disables editing of the controls. The Extract Document button is enabled, as well.
If the loaded DataSet does not contain an encapsulated document, the application enables the controls to allow the input of information and the Encapsulate Document button is enabled instead.
The code for enabling and disabling edit of the control is as follows:
private void EnableEditing()
{
foreach (Control child in Controls)
{
if (child.GetType() == typeof(TextBox))
{
(child as TextBox).Clear();
(child as TextBox).ReadOnly = false;
}
if (child.GetType() == typeof(ComboBox))
{
(child as ComboBox).DropDownStyle = ComboBoxStyle.DropDownList;
(child as ComboBox).KeyDown -= Form1_KeyDown;
(child as ComboBox).SelectedIndex = -1;
}
}
}
private void DisableEditing()
{
foreach (Control child in Controls)
{
if (child.GetType() == typeof(TextBox))
(child as TextBox).ReadOnly = true;
if (child.GetType() == typeof(ComboBox))
{
(child as ComboBox).DropDownStyle = ComboBoxStyle.Simple;
(child as ComboBox).KeyDown += Form1_KeyDown;
}
}
}
private void Form1_KeyDown(object sender, KeyEventArgs e)
{
e.SuppressKeyPress = true;
}
The code below extracts the encapsulated document into a memory stream and displays the information about the document in the form controls.
private void GetEncapsulatedDocInfo(DicomElement encapsulatedDocElement)
{
DicomEncapsulatedDocument encapsulatedDocument = new DicomEncapsulatedDocument();
DicomCodeSequenceItem conceptNameCodeSequence = new DicomCodeSequenceItem();
_docStream = new MemoryStream();
// Get the encapsulated document
_dicomDS.GetEncapsulatedDocument(encapsulatedDocElement, false, _docStream, encapsulatedDocument, conceptNameCodeSequence);
// Document Type
switch (encapsulatedDocument.Type)
{
case DicomEncapsulatedDocumentType.Pdf:
typeComboBox.SelectedIndex = 0; // PDF
break;
case DicomEncapsulatedDocumentType.Cda:
typeComboBox.SelectedIndex = 1; // CDA
break;
default:
typeComboBox.Text = "Unknown"; // Unknown
break;
}
// Instance Number
instanceNumberTextBox.Text = encapsulatedDocument.InstanceNumber.ToString();
// Content Date
contentDateTextBox.Text =
encapsulatedDocument.ContentDate.Month.ToString("D2") +
'-' + encapsulatedDocument.ContentDate.Day.ToString("D2") +
'-' + encapsulatedDocument.ContentDate.Year.ToString("D4");
// Content Time
int contentDateTimeMicroseconds = encapsulatedDocument.ContentTime.Fractions / 1000;
contentTimeTextBox.Text =
encapsulatedDocument.ContentTime.Hours.ToString("D2") +
':' + encapsulatedDocument.ContentTime.Minutes.ToString("D2") +
':' + encapsulatedDocument.ContentTime.Seconds.ToString("D2") +
'.' + contentDateTimeMicroseconds.ToString("D3");
// Acquisition Date
acquisitionDateTextBox.Text =
encapsulatedDocument.AcquisitionDateTime.Month.ToString("D2") +
'-' + encapsulatedDocument.AcquisitionDateTime.Day.ToString("D2") +
'-' + encapsulatedDocument.AcquisitionDateTime.Year.ToString("D4");
// Acquisition Time
int acquisitionDateTimeMicroseconds = encapsulatedDocument.AcquisitionDateTime.Fractions / 1000;
acquisitionTimeTextBox.Text =
encapsulatedDocument.AcquisitionDateTime.Hours.ToString("D2") +
':' + encapsulatedDocument.AcquisitionDateTime.Minutes.ToString("D2") +
':' + encapsulatedDocument.AcquisitionDateTime.Seconds.ToString("D2") +
'.' + acquisitionDateTimeMicroseconds.ToString("D3");
// Burned Annotations
if (encapsulatedDocument.BurnedInAnnotation == "NO")
burnedInAnnComboBox.SelectedIndex = 0;
else
burnedInAnnComboBox.SelectedIndex = 1;
// Document Title
documentTitleTextBox.Text = encapsulatedDocument.DocumentTitle;
// Verification Flag
if (encapsulatedDocument.VerificationFlag == "UNVERIFIED")
verificationFlagComboBox.SelectedIndex = 0;
else
verificationFlagComboBox.SelectedIndex = 1;
// HL7 Flag Identifier
HL7InstanceIdentifierTextBox.Text = encapsulatedDocument.HL7InstanceIdentifier;
// MIME Types
mimeTypesTextBox.Lines = encapsulatedDocument.GetListOfMimeTypes();
// Concept Name Code Sequence
codingSchemeDesignatorTextBox.Text = conceptNameCodeSequence.CodingSchemeDesignator;
codeValueTextBox.Text = conceptNameCodeSequence.CodeValue;
codeMeaningTextBox.Text = conceptNameCodeSequence.CodeMeaning;
}
The code below prompts a user to save a copy of the encapsulated document to disk.
private void extractButton_Click(object sender, EventArgs e)
{
SaveFileDialog saveDialog = new SaveFileDialog();
saveDialog.InitialDirectory = @"C:\LEADTOOLS23\Resources\Images\";
saveDialog.FileName = documentTitleTextBox.Text;
if (typeComboBox.Text == "PDF")
saveDialog.Filter = "Portable Document Format (.pdf)|*.pdf";
else if (typeComboBox.Text == "CDA")
saveDialog.Filter = "Clinical Document Architecture format (.xml)|*.xml";
if (saveDialog.ShowDialog(this) == DialogResult.OK)
{
try
{
using (FileStream fs = new FileStream(saveDialog.FileName, FileMode.Create))
_docStream.WriteTo(fs);
MessageBox.Show("Encapsulated document saved");
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}
The code below encapsulates a document from disk using the document information inputted into the forms.
private void encapsulateButton_Click(object sender, EventArgs e)
{
OpenFileDialog openDocumentDialog = new OpenFileDialog();
openDocumentDialog.InitialDirectory = @"C:\LEADTOOLS23\Resources\Images";
if (typeComboBox.SelectedIndex == 0) // PDF
openDocumentDialog.Filter = "Portable Document Format (.pdf)|*.pdf";
else if (typeComboBox.SelectedIndex == 1) // CDA
openDocumentDialog.Filter = "Clinical Document Architecture format (.xml)|*.xml";
else
{
MessageBox.Show("Select Document Type first.");
return;
}
if (openDocumentDialog.ShowDialog(this) == DialogResult.OK)
{
DicomEncapsulatedDocument encapsulatedDocument = new DicomEncapsulatedDocument();
// Document Type
switch (typeComboBox.SelectedIndex)
{
case 0:
encapsulatedDocument.Type = DicomEncapsulatedDocumentType.Pdf;
break;
case 1:
encapsulatedDocument.Type = DicomEncapsulatedDocumentType.Cda;
break;
default:
encapsulatedDocument.Type = DicomEncapsulatedDocumentType.Unknown;
break;
}
// Instance Number
try
{
encapsulatedDocument.InstanceNumber = int.Parse(instanceNumberTextBox.Text);
}
catch (Exception)
{
MessageBox.Show("Unable to parse Instance Number. Enter a valid value");
return;
}
// Content Date and Time
try
{
string contentDateTimeString = contentDateTextBox.Text + ' ' + contentTimeTextBox.Text;
DateTime contentDateTime = DateTime.ParseExact(contentDateTimeString, "MM-dd-yyyy HH:mm:ss.fff", new System.Globalization.CultureInfo("en-US"));
encapsulatedDocument.ContentDate = new DicomDateValue(contentDateTime);
encapsulatedDocument.ContentTime = new DicomTimeValue(contentDateTime);
}
catch (FormatException)
{
MessageBox.Show("Unable to parse Content Date/Time. Be sure to follow the displayed format");
return;
}
// Acquisition Date and Time
try
{
string acquisitionDateTimeString = acquisitionDateTextBox.Text + ' ' + acquisitionTimeTextBox.Text;
DateTime acquisitionDateTime = DateTime.ParseExact(acquisitionDateTimeString, "MM-dd-yyyy HH:mm:ss.fff", new System.Globalization.CultureInfo("en-US"));
encapsulatedDocument.AcquisitionDateTime = new DicomDateTimeValue(acquisitionDateTime);
}
catch (FormatException)
{
MessageBox.Show("Unable to parse Acquisition Date/Time. Be sure to follow the displayed format");
return;
}
// Burned Annotations
if (burnedInAnnComboBox.SelectedIndex != -1)
encapsulatedDocument.BurnedInAnnotation = burnedInAnnComboBox.Text;
else
{
MessageBox.Show("Unable to parse Burned-In Annotation. Select a value from the drop-down list");
return;
}
// Document Title
encapsulatedDocument.DocumentTitle = documentTitleTextBox.Text;
// Verification Flag
encapsulatedDocument.VerificationFlag = verificationFlagComboBox.Text;
// HL7 Instance Identifier
if (typeComboBox.Text == "CDA" && string.IsNullOrEmpty(HL7InstanceIdentifierTextBox.Text))
{
MessageBox.Show("Unable to parse HL7 Instance Identifier. Enter a valid value");
return;
}
else
encapsulatedDocument.HL7InstanceIdentifier = HL7InstanceIdentifierTextBox.Text;
// Mime types of the document
string[] mimeTypes = mimeTypesTextBox.Lines;
encapsulatedDocument.SetListOfMimeTypes(mimeTypes);
// Concept Name Code Sequence
DicomCodeSequenceItem conceptNameCodeSequence = new DicomCodeSequenceItem();
conceptNameCodeSequence.CodingSchemeDesignator = codingSchemeDesignatorTextBox.Text;
conceptNameCodeSequence.CodeValue = codeValueTextBox.Text;
conceptNameCodeSequence.CodeMeaning = codeMeaningTextBox.Text;
// Encapsulate loaded document in loaded DICOM dataset
DicomElement element = _dicomDS.FindFirstElement(null, DicomTag.EncapsulatedDocument, true);
_dicomDS.SetEncapsulatedDocument(element, false, openDocumentDialog.FileName, encapsulatedDocument, conceptNameCodeSequence);
MessageBox.Show("The document " + openDocumentDialog.FileName + " has been encapsulated in the loaded dataset. Press OK to save the dataset");
SaveFileDialog saveDicomDialog = new SaveFileDialog();
saveDicomDialog.InitialDirectory = @"C:\LEADTOOLS23\Resources\Images\DICOM";
saveDicomDialog.Filter = "DICOM dataset (.dcm)|*.dcm";
if (saveDicomDialog.ShowDialog(this) == DialogResult.OK)
_dicomDS.Save(saveDicomDialog.FileName, DicomDataSetSaveFlags.None);
MessageBox.Show("DataSet save complete");
}
}
The following code resets the controls and the loaded DICOM DataSet.
private void clearButton_Click(object sender, EventArgs e)
{
// Clear loaded DataSet
if (_dicomDS != null)
_dicomDS = null;
// Reset form controls
foreach (Control child in Controls)
{
if (child.GetType() == typeof(TextBox))
{
(child as TextBox).Clear();
(child as TextBox).ReadOnly = false;
}
if (child.GetType() == typeof(ComboBox))
{
(child as ComboBox).DropDownStyle = ComboBoxStyle.DropDownList;
(child as ComboBox).KeyDown -= Form1_KeyDown;
(child as ComboBox).SelectedIndex = -1;
}
if (child.GetType() == typeof(TextBox) || child.GetType() == typeof(Button) || child.GetType() == typeof(ComboBox))
child.Enabled = false;
}
}
In Solution Explorer, double-click Form1.cs
to display it in the Designer. Click the Events icon in the Properties Window. Then, double-click the FormClosing event.
Use the code below to shutdown the DICOM Engine and clear the memory stream.
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
DicomEngine.Shutdown();
}
Run the project by pressing F5, or by selecting Debug -> Start Debugging.
If the steps are followed correctly, the application runs and allows the user to load a DICOM DataSet.
If the DataSet contains an encapsulated document, the application displays the information in the form and allows the document to be saved to disk. If the DataSet does not contain and encapsulated document, the user can encapsulate a document after providing the necessary information in the form.
This tutorial showed how to use the DicomEncapsulatedDocument
and the DicomDataSet
classes to extract and write Private Data elements in DICOM DataSets.