DICOM Basics using .NET and C# - Extracting Image Data
Introduction
This is part of my series of articles on the DICOM standard. In this tutorial, we'll learn how to extract pixel data from DICOM files and export them to standard image formats like JPEG, PNG, and BMP using .NET and the fo-dicom library.
Extracting image data from DICOM files is a common requirement in medical imaging applications. Whether you're building a web-based viewer that needs to display images in standard formats, creating thumbnails for a patient worklist, or exporting images for reports, understanding how to properly extract and render DICOM images is essential.
Prerequisites
Before you begin, ensure you have the following:
- A .NET development environment (Visual Studio or Visual Studio Code)
- The Fellow Oak DICOM library (fo-dicom) installed via NuGet
- Sample DICOM files for testing (available from DICOM Library)
- You can find all the code demonstrated in this tutorial on GitHub here
“The real voyage of discovery consists not in seeking new landscapes, but in having new eyes.” ~ Marcel Proust
Key Image Attributes
Before extracting image data, it's important to understand the key DICOM attributes that define how pixel data is stored:
| Tag | Name | Description |
|---|---|---|
| (0028,0010) | Rows | Image height in pixels |
| (0028,0011) | Columns | Image width in pixels |
| (0028,0100) | Bits Allocated | 8, 16, or 32 bits per pixel |
| (0028,0101) | Bits Stored | Actual bits used for pixel values |
| (0028,0004) | Photometric Interpretation | MONOCHROME1/2, RGB |
| (0028,1050) | Window Center | Brightness control (Level) |
| (0028,1051) | Window Width | Contrast control |
| (7FE0,0010) | Pixel Data | Raw pixel bytes |
Step 1 of 3: Reading DICOM File and Image Parameters
First, let's open a DICOM file and display its image parameters to understand what we're working with:
using System;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using FellowOakDicom;
using FellowOakDicom.Imaging;
namespace ExtractingImageData
{
public class Program
{
private static readonly string PathToDicomTestFile =
Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Test Files", "0002.dcm");
private static readonly string OutputPath =
Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Output");
public static void Main(string[] args)
{
try
{
LogToDebugConsole("=== Extracting DICOM Image Data Tutorial ===");
LogToDebugConsole($"Reading DICOM file: {PathToDicomTestFile}");
// Ensure output directory exists
if (!Directory.Exists(OutputPath))
{
Directory.CreateDirectory(OutputPath);
}
// Open the DICOM file
var file = DicomFile.Open(PathToDicomTestFile);
var dataset = file.Dataset;
// Display Image Parameters
LogToDebugConsole("--- Image Parameters ---");
var rows = dataset.GetSingleValueOrDefault(DicomTag.Rows, (ushort)0);
var columns = dataset.GetSingleValueOrDefault(DicomTag.Columns, (ushort)0);
var bitsAllocated = dataset.GetSingleValueOrDefault(DicomTag.BitsAllocated, (ushort)0);
var bitsStored = dataset.GetSingleValueOrDefault(DicomTag.BitsStored, (ushort)0);
var samplesPerPixel = dataset.GetSingleValueOrDefault(DicomTag.SamplesPerPixel, (ushort)0);
var photometricInterpretation = dataset.GetSingleValueOrDefault(DicomTag.PhotometricInterpretation, "");
var windowCenter = dataset.GetSingleValueOrDefault(DicomTag.WindowCenter, 0.0);
var windowWidth = dataset.GetSingleValueOrDefault(DicomTag.WindowWidth, 0.0);
LogToDebugConsole($" Dimensions: {columns} x {rows} pixels");
LogToDebugConsole($" Bits Allocated: {bitsAllocated}");
LogToDebugConsole($" Bits Stored: {bitsStored}");
LogToDebugConsole($" Samples Per Pixel: {samplesPerPixel} ({(samplesPerPixel == 1 ? "Grayscale" : "Color")})");
LogToDebugConsole($" Photometric: {photometricInterpretation}");
LogToDebugConsole($" Window Center: {windowCenter}");
LogToDebugConsole($" Window Width: {windowWidth}");
}
catch (Exception e)
{
LogToDebugConsole($"Error: {e.Message}");
}
}
private static void LogToDebugConsole(string message)
{
Debug.WriteLine(message);
}
}
}
Step 2 of 3: Rendering DICOM Images
The fo-dicom library provides the DicomImage class which handles the complexity of rendering DICOM pixel data, including applying window/level settings and handling different photometric interpretations:
// Create a DicomImage for rendering
var dicomImage = new DicomImage(PathToDicomTestFile);
// Get the number of frames (for multi-frame images like cine)
var frameCount = dicomImage.NumberOfFrames;
LogToDebugConsole($" Number of Frames: {frameCount}");
// Render and process frames
for (int frame = 0; frame < frameCount; frame++)
{
// Render the image to a bitmap
var renderedImage = dicomImage.RenderImage(frame);
var bitmap = renderedImage.As<Bitmap>();
// Process the bitmap (shown in next step)
// ...
// Clean up bitmap when done
bitmap.Dispose();
}
Step 3 of 3: Exporting to Standard Image Formats
Once we have the rendered bitmap, we can save it to various standard image formats:
LogToDebugConsole("--- Exporting Image ---");
// Create a DicomImage for rendering
var dicomImage = new DicomImage(PathToDicomTestFile);
// Get the number of frames
var frameCount = dicomImage.NumberOfFrames;
LogToDebugConsole($" Number of Frames: {frameCount}");
// Render each frame (or just the first for single-frame images)
for (int frame = 0; frame < frameCount; frame++)
{
// Render the image to a bitmap
var renderedImage = dicomImage.RenderImage(frame);
var bitmap = renderedImage.As<Bitmap>();
// Generate output filenames
var frameLabel = frameCount > 1 ? $"_frame_{frame + 1:D3}" : "";
var jpegPath = Path.Combine(OutputPath, $"exported_image{frameLabel}.jpg");
var pngPath = Path.Combine(OutputPath, $"exported_image{frameLabel}.png");
var bmpPath = Path.Combine(OutputPath, $"exported_image{frameLabel}.bmp");
// Save in multiple formats
bitmap.Save(jpegPath, ImageFormat.Jpeg);
bitmap.Save(pngPath, ImageFormat.Png);
bitmap.Save(bmpPath, ImageFormat.Bmp);
if (frameCount > 1)
{
LogToDebugConsole($" Exported frame {frame + 1}/{frameCount}");
}
else
{
LogToDebugConsole($" JPEG: {jpegPath}");
LogToDebugConsole($" PNG: {pngPath}");
LogToDebugConsole($" BMP: {bmpPath}");
}
// Clean up bitmap
bitmap.Dispose();
}
LogToDebugConsole("Image export completed successfully!");
The output will show the extracted image parameters and confirm the export:
=== Extracting DICOM Image Data Tutorial ===
Reading DICOM file: C:\...\Test Files\0002.dcm
--- Image Parameters ---
Dimensions: 512 x 512 pixels
Bits Allocated: 8
Bits Stored: 8
Samples Per Pixel: 1 (Grayscale)
Photometric: MONOCHROME2
Window Center: 128
Window Width: 256
--- Exporting Image ---
Number of Frames: 96
Exported frame 1/96
Exported frame 2/96
...
Exported frame 96/96
Image export completed successfully!
Understanding Photometric Interpretation
The Photometric Interpretation attribute tells us how to interpret the pixel values:
| Value | Description |
|---|---|
| MONOCHROME1 | Minimum pixel value is white |
| MONOCHROME2 | Maximum pixel value is white (most common) |
| RGB | Color image with red, green, blue samples |
| YBR_FULL | YCbCr color space |
| PALETTE COLOR | Indexed color with lookup table |
Handling Multi-Frame Images
Multi-frame DICOM images (cine loops) contain multiple images in a single file. The NumberOfFrames property indicates how many frames are present. When exporting, you can either:
- Export each frame as a separate image file
- Create an animated GIF or video from the frames
- Extract only specific frames of interest
Conclusion
In this tutorial, we've learned how to extract pixel data from DICOM files and export them to standard image formats using .NET and the fo-dicom library. We covered reading image parameters, rendering DICOM images with proper window/level settings, and saving to JPEG, PNG, and BMP formats.
This capability is essential for building medical imaging applications that need to display images in web browsers, create reports with embedded images, or integrate with non-DICOM systems.
Please check out the next tutorial in this series where we cover viewing DICOM images with window/level adjustments.