This tutorial shows how to load and display a stack of 2D DICOM image slices as a 3D object in the LEADTOOLS MedicalViewer
WinForms control.
Overview | |
---|---|
Summary | This tutorial covers how to load and display a stack of 2D DICOM image slices as a 3D object in a WinForms C# Application. |
Completion Time | 30 minutes |
Visual Studio Project | Download tutorial project (12 KB) |
Platform | Windows WinForms C# Application |
IDE | Visual Studio 2022 |
Development License | Download LEADTOOLS |
Get familiar with the basic steps of creating a project and displaying a DICOM image 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 Load and Display DICOM Image Slices as a 3D Object - WinForms C# tutorial.
The Medical3DControl
renders medical 3D objects and integrates with the MedicalViewer
class as it is inherited from the MedicalViewerBaseCell
base object, meaning that it shares similar functionality with the MedicalViewerMultiCell
class used to display 2D medical images normally.
To render the 3D volume correctly with the correct measurements and scaling, the following information is needed:
In addition, for a given set of 2D image slices, the cross-sectional planes must be parallel and in the correct order.
For illustration, this tutorial uses the image1.dcm
sample from: <INSTALL_DIR>\LEADTOOLS23\Resources\Images\DICOM
This is a Magnetic Resonance (MR) sample which contains the images in the correct order as well as the above information stored in the dataset tags.
Start with a copy of the project created in the Load and Display a DICOM Image in the Medical Viewer tutorial. If the project is not available, 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.Medical.Viewer.WinForms
Leadtools.Jpeg2000
If using local DLL references, the following DLLs are needed.
The DLLs are located at <INSTALL_DIR>\LEADTOOLS23\Bin\net
:
Leadtools.dll
Leadtools.Codecs.dll
Leadtools.Core.dll
Leadtools.Dicom.dll
Leadtools.MedicalViewer.dll
Leadtools.Medical3D.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:
Now that the LEADTOOLS references have been added to the project and the license has been set, coding can begin.
Right-click on Form1.cs
in the Solution Explorer and select View Code to display the code behind the form.
Add Leadtools.Medical3D
to the using
block at the top of the file.
using System;
using System.IO;
using System.Windows.Forms;
using Leadtools;
using Leadtools.Dicom;
using Leadtools.Medical3D;
using Leadtools.MedicalViewer;
Add the Medical3DControl
object to the global variables.
private MedicalViewer _medicalViewer;
private MedicalViewerMultiCell _cell;
private Medical3DControl _control3D;
Modify the loadDICOMToolStripMenuItem_Click
event handler to add a call to the Initialize3DControl
function after _medicalViewer.Cells.Add(_cell)
.
// Add initialized cell
_medicalViewer.Cells.Add(_cell);
// Initialize 3D Control
if (_cell.Image.PageCount > 1)
Initialize3DControl(_cell.Image.Width, _cell.Image.Height, _cell.Image.PageCount);
Ensure the code below is added to the Initialize3DControl
function.
private void Initialize3DControl(int width, int height, int totalFrames)
{
// Create 3D Control
bool useSoftwareRendering = !Medical3DEngine.CanUse3DTexturing(width, height, totalFrames);
_control3D = new Medical3DControl(useSoftwareRendering);
_control3D.ObjectsContainer.RenderingType = Medical3DVolumeRenderingType.Auto;
_control3D.ObjectsContainer.Objects.Add(new Medical3DObject());
// Initialize 3D frames
bool created = _control3D.ObjectsContainer.Objects[0].MemoryEfficientInit(totalFrames);
if (!created)
{
// Remove the object
_control3D.ObjectsContainer.Objects.RemoveAt(0);
_control3D.Dispose();
_control3D = null;
return;
}
// Add 3D control actions
_control3D.AddAction(MedicalViewerActionType.WindowLevel);
_control3D.AddAction(MedicalViewerActionType.Rotate3DObject);
_control3D.AddAction(MedicalViewerActionType.Scale3DObject);
_control3D.SetAction(MedicalViewerActionType.Rotate3DObject, MedicalViewerMouseButtons.Right, MedicalViewerActionFlags.Active);
_control3D.SetAction(MedicalViewerActionType.WindowLevel, MedicalViewerMouseButtons.Left, MedicalViewerActionFlags.Active);
_control3D.SetAction(MedicalViewerActionType.Scale3DObject, MedicalViewerMouseButtons.Middle, MedicalViewerActionFlags.Active);
}
After initializing the 3D control, modify the code in the loadDICOMToolStripMenuItem_Click
event handler to get the image position information for each frame in the dataset and use it to load the frames correctly into the 3D control.
// Initialize 3D Control
if (_cell.Image.PageCount > 1)
Initialize3DControl(_cell.Image.Width, _cell.Image.Height, _cell.Image.PageCount);
if (_control3D != null)
{
// Fill cell frames with image position information from the DICOM file
SetCellFramesImagePosition(dataSet);
// Add image pages to the 3D control frames
for (int index = 0; index < _cell.Image.PageCount; index++)
{
_cell.Image.Page = index + 1;
_control3D.ObjectsContainer.Objects[0].MemoryEfficientSetFrame(_cell.Image.Clone(), index, _cell.GetImagePosition(index), true);
}
}
Add a new method named SetCellFramesImagePosition(DicomDataSet dataSet)
. This method will be called inside the loadDICOMToolStripMenuItem_Click
event handler, as shown above. Add the code below to load the image position information from the DICOM dataset and set it in the property for each frame in the 2D cell.
private void SetCellFramesImagePosition(DicomDataSet dataSet)
{
int index = 0;
DicomElement element = dataSet.FindFirstElement(null, DicomTag.PerFrameFunctionalGroupsSequence, true);
if (element != null)
{
element = dataSet.GetChildElement(element, true);
DicomElement firstItem = dataSet.FindFirstElement(element, DicomTag.Item, true);
while (firstItem != null)
{
element = dataSet.GetChildElement(firstItem, true);
element = dataSet.FindFirstElement(element, DicomTag.PlanePositionSequence, true);
element = dataSet.GetChildElement(element, true);
element = dataSet.FindFirstElement(element, DicomTag.Item, true);
element = dataSet.GetChildElement(element, true);
element = dataSet.FindFirstElement(element, DicomTag.ImagePositionPatient, true);
if (element != null)
{
double[] doubleArray = dataSet.GetDoubleValue(element, 0, 3);
_cell.SetImagePosition(index, Point3D.FromDoubleArray(doubleArray), false);
index++;
}
firstItem = dataSet.GetNextElement(firstItem, true, true);
}
}
}
Once all the frames are loaded into the 3D control, add the code below to the loadDICOMToolStripMenuItem_Click
event handler to finish setting the frames and tags.
if (_control3D != null)
{
// Fill cell frames with image position information from the DICOM file
SetCellFramesImagePosition(dataSet);
// Add image pages to the 3D control frames
for (int index = 0; index < _cell.Image.PageCount; index++)
{
_cell.Image.Page = index + 1;
_control3D.ObjectsContainer.Objects[0].MemoryEfficientSetFrame(_cell.Image.Clone(), index, _cell.GetImagePosition(index), true);
}
// Get Image Orientation from DICOM
SetCellImageOrientation(dataSet);
// Get Pixel Spacing from DICOM
SetCellPixelSpacing(dataSet);
// Finish
_control3D.ObjectsContainer.Objects[0].MemoryEfficientEnd(_cell.ImageOrientation, _cell.PixelSpacing);
// Set tags
Set3DTags();
}
Add two new methods named SetCellImageOrientation(DicomDataSet dataSet)
and SetCellPixelSpacing(DicomDataSet dataSet)
. Both of these methods will be called inside the loadDICOMToolStripMenuItem_Click
event handler, as shown above. Add the code below to the SetCellImageOrientation
function to get the image orientation from the dataset.
private void SetCellImageOrientation(DicomDataSet dataSet)
{
DicomElement element = dataSet.FindFirstElement(null, DicomTag.PerFrameFunctionalGroupsSequence, true);
if (element != null)
{
element = dataSet.GetChildElement(element, true);
element = dataSet.FindFirstElement(element, DicomTag.Item, true);
element = dataSet.GetChildElement(element, true);
element = dataSet.FindFirstElement(element, DicomTag.PlaneOrientationSequence, true);
element = dataSet.GetChildElement(element, true);
element = dataSet.FindFirstElement(element, DicomTag.Item, true);
element = dataSet.GetChildElement(element, true);
element = dataSet.FindFirstElement(element, DicomTag.ImageOrientationPatient, true);
double[] doubleArray = dataSet.GetDoubleValue(element, 0, 6);
_cell.ImageOrientation = new float[] { (float)doubleArray[0], (float)doubleArray[1], (float)doubleArray[2], (float)doubleArray[3], (float)doubleArray[4], (float)doubleArray[5] };
}
}
Add the code below to the SetCellPixelSpacing
function to get the pixel spacing information from the dataset.
private void SetCellPixelSpacing(DicomDataSet dataSet)
{
DicomElement element = dataSet.FindFirstElement(null, DicomTag.SharedFunctionalGroupsSequence, true);
if (element != null)
{
element = dataSet.FindFirstElement(element, DicomTag.Item, false);
element = dataSet.FindFirstElement(element, DicomTag.PixelMeasuresSequence, false);
element = dataSet.FindFirstElement(element, DicomTag.Item, false);
element = dataSet.FindFirstElement(element, DicomTag.PixelSpacing, false);
double[] doubleArray = dataSet.GetDoubleValue(element, 0, 2);
_cell.PixelSpacing = new Point2D(doubleArray[0], doubleArray[1]);
}
}
Add a new function named Set3DTags() to the Program class. This function will be called at the bottom of the loadDICOMToolStripMenuItem_Click
event handler, as shown in the previous step.
Add the code below to set the relevant 3D control tags using the tags from the 2D cell.
private void Set3DTags()
{
MedicalViewerTagInformation information;
// Load user data tags from the multi cell
information = _cell.GetTag(2, MedicalViewerTagAlignment.TopRight);
if (information != null) _control3D.SetTag(1, MedicalViewerTagAlignment.TopRight, information.Type, information.Text);
information = _cell.GetTag(3, MedicalViewerTagAlignment.TopRight);
if (information != null) _control3D.SetTag(2, MedicalViewerTagAlignment.TopRight, information.Type, information.Text);
information = _cell.GetTag(4, MedicalViewerTagAlignment.TopRight);
if (information != null) _control3D.SetTag(3, MedicalViewerTagAlignment.TopRight, information.Type, information.Text);
information = _cell.GetTag(5, MedicalViewerTagAlignment.TopRight);
if (information != null) _control3D.SetTag(4, MedicalViewerTagAlignment.TopRight, information.Type, information.Text);
information = _cell.GetTag(6, MedicalViewerTagAlignment.TopRight);
if (information != null) _control3D.SetTag(5, MedicalViewerTagAlignment.TopRight, information.Type, information.Text);
information = _cell.GetTag(7, MedicalViewerTagAlignment.TopRight);
if (information != null) _control3D.SetTag(5, MedicalViewerTagAlignment.TopRight, information.Type, information.Text);
// Other tags
_control3D.SetTag(4, MedicalViewerTagAlignment.TopLeft, MedicalViewerTagType.Frame);
_control3D.SetTag(6, MedicalViewerTagAlignment.TopLeft, MedicalViewerTagType.Scale);
_control3D.SetTag(2, MedicalViewerTagAlignment.BottomLeft, MedicalViewerTagType.WindowLevelData);
_control3D.SetTag(1, MedicalViewerTagAlignment.BottomLeft, MedicalViewerTagType.FieldOfView);
_control3D.SetTag(0, MedicalViewerTagAlignment.BottomLeft, MedicalViewerTagType.RulerUnit);
}
In the Solution Explorer, double-click Form1.cs
to display it in the Designer. Add a &Cell Type menu to menuStrip1
with a &VRT and a &2D Cell menu items. Right-click on each item and uncheck the Enabled option for both. Use _menuVolumeVRT
and _menu2DCell
as the Name property for each menu item, respectively.
For the Cell Type menu object, click the Events icon in the Properties Window. Then, double-click the DropDownOpening event to create an event handler if one does not already exist.
Add the following code inside the cellTypeToolStripMenuItem_DropDownOpening
event handler.
private void cellTypeToolStripMenuItem_DropDownOpening(object sender, EventArgs e)
{
bool Allow3D = false;
bool Allow2D = false;
if (_cell != null)
Allow2D = true;
if (_control3D != null)
Allow3D = true;
_menuVolumeVRT.Enabled = Allow3D;
_menu2DCell.Enabled = Allow2D;
}
Double-click the VRT menu item to edit its event handler. Add the following code to load the 3D control into the viewer, if available.
private void _menuVolumeVRT_Click(object sender, EventArgs e)
{
foreach (ToolStripMenuItem toolStripMenuItem in cellTypeToolStripMenuItem.DropDownItems)
toolStripMenuItem.Checked = false;
_menuVolumeVRT.Checked = true;
if (_medicalViewer.Cells[0] != _control3D)
{
_medicalViewer.Cells.Clear();
_medicalViewer.Cells.Add(_control3D);
}
}
Next, double-click the 2D Cell menu item to edit its event handler. Add the following code to load the 2D cell into the viewer.
private void _menu2DCell_Click(object sender, EventArgs e)
{
foreach (ToolStripMenuItem toolStripMenuItem in cellTypeToolStripMenuItem.DropDownItems)
toolStripMenuItem.Checked = false;
_menu2DCell.Checked = true;
if (_medicalViewer.Cells[0] != _cell)
{
_medicalViewer.Cells.Clear();
_medicalViewer.Cells.Add(_cell);
}
}
Finally, add the code below to clear the check-mark from the selected cell type menu item when loading a new image.
private void loadDICOMToolStripMenuItem_Click(object sender, EventArgs e)
{
try
{
// Clear existing cells
if (_medicalViewer.Cells.Count > 0)
_medicalViewer.Cells.Clear();
// Clear check-marks from cell type menu
foreach (ToolStripMenuItem toolStripMenuItem in cellTypeToolStripMenuItem.DropDownItems)
toolStripMenuItem.Checked = false;
Run the project by pressing F5, or by selecting Debug -> Start Debugging.
If the steps were followed correctly, the application runs and allows loading and displaying a selected set of 2D DICOM image slices in the MedicalViewer
control in a 3D space. This can be seen using the image1.dcm
sample from: <INSTALL_DIR>\LEADTOOLS23\Resources\Images\DICOM
Load the sample file, and then select Cell Type -> VRT.
This tutorial showed how to add the necessary references to render a 3D DICOM image in the MedicalViewer
WinForms control.