Load and Display DICOM Image Slices as a 3D Object - WinForms C# .NET 6

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

Required Knowledge

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 Object

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.

Create the Project and Add the LEADTOOLS References

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:

If using local DLL references, the following DLLs are needed.

The DLLs are located at <INSTALL_DIR>\LEADTOOLS23\Bin\net:

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 function is called. For details, including tutorials for different platforms, refer to Setting a Runtime License.

There are two types of runtime licenses:

Initialize the Medical3DControl

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.

C#
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.

C#
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).

C#
// 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.

C#
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); 
} 

Set the Image Frames in the 3D Control

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.

C#
// 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.

C#
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); 
      } 
   } 
} 

Set the 3D Frames in the Control

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.

C#
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.

C#
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.

C#
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 the Set3DTags Function Code

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.

C#
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); 
} 

Add Form Controls to Switch from 2D to 3D

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.

C#
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.

C#
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.

C#
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.

C#
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

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.

Displayed Output

Wrap-up

This tutorial showed how to add the necessary references to render a 3D DICOM image in the MedicalViewer WinForms control.

See Also

Help Version 23.0.2024.5.22
Products | Support | Contact Us | Intellectual Property Notices
© 1991-2024 LEAD Technologies, Inc. All Rights Reserved.

Products | Support | Contact Us | Intellectual Property Notices
© 1991-2023 LEAD Technologies, Inc. All Rights Reserved.