DICOM Basics using .NET and C# - Multi-modality Examples

Introduction

This is part of my series of articles on the DICOM standard. In this tutorial, we'll explore modality-specific DICOM attributes for different imaging modalities including CT, MR, US, XA, DX, MG, and NM/PT.

Each imaging modality has its own Information Object Definition (IOD) with specific modules and attributes that capture the unique characteristics of that modality. Understanding these modality-specific attributes is essential for properly handling DICOM data from different sources.

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
  • Basic understanding of DICOM concepts from previous tutorials
  • You can find all the code demonstrated in this tutorial on GitHub here

“The physician should not treat the disease but the patient who is suffering from it.” ~ Maimonides

The Theory Behind Multi-Modality Attributes

Every imaging modality generates different physics-based data. CT measures X-ray attenuation (Hounsfield units), MR measures signal from hydrogen protons (arbitrary units dependent on sequence), PET measures radiotracer uptake (activity concentration). These fundamental differences require modality-specific attributes to fully describe what the image represents and how it was acquired.

DICOM's Information Object Definition (IOD) architecture handles this elegantly. Each modality has an IOD built from modules. Some modules are shared (Patient, Study, Equipment), while others are modality-specific (CT Image Module, MR Image Module). This composition model means common code handles common data, while modality-specific processing only engages where needed.

The Rescale Intercept/Slope paradigm illustrates why modality-specific understanding matters. For CT, HU = Slope × StoredValue + Intercept converts stored pixels to Hounsfield units with physical meaning. For MR, the same mechanism might apply vendor-specific scaling. For PET, conversion to SUV requires additional factors (patient weight, injected dose, decay time). Each modality has different semantics for the same mathematical operation.

The Enhanced Multi-frame IODs represent DICOM's evolution. Classic CT stores one file per slice with redundant metadata. Enhanced CT stores all slices in one file, with Per-Frame Functional Groups describing what varies between frames (position, timing) and Shared Functional Groups for what's constant (reconstruction kernel). This reduces redundancy and enables more sophisticated multi-dimensional data (4D CT, multi-echo MR).

Understanding modality differences is crucial for clinical decision support. Automated processing must know that CT Window Width/Level for bone (2000/300) differs from soft tissue (400/40), that MR T1-weighted and T2-weighted have opposite signal intensity for fluids, and that PET SUV calculations require decay correction. The modality-specific attributes provide the context needed for correct interpretation.

CT (Computed Tomography) Attributes

CT images are defined by the CT Image Storage SOP Class (1.2.840.10008.5.1.4.1.1.2):

using System;
using System.Diagnostics;
using FellowOakDicom;

namespace DicomMultiModalityExamples
{
    public class Program
    {
        public static void Main(string[] args)
        {
            LogToDebugConsole("=== CT (Computed Tomography) ===");

            LogToDebugConsole("SOP Class: CT Image Storage (1.2.840.10008.5.1.4.1.1.2)");

            LogToDebugConsole("CT Image Module:");
            LogToDebugConsole("  (0018,0060) KVP - X-ray tube voltage (e.g., 120 kV)");
            LogToDebugConsole("  (0018,1151) X-Ray Tube Current - mA (e.g., 250)");
            LogToDebugConsole("  (0018,1150) Exposure Time - ms (e.g., 500)");
            LogToDebugConsole("  (0018,1152) Exposure - mAs (e.g., 125)");
            LogToDebugConsole("  (0018,9345) CTDIvol - dose index in mGy");
            LogToDebugConsole("  (0018,0050) Slice Thickness - mm (e.g., 1.25)");
            LogToDebugConsole("  (0018,0088) Spacing Between Slices - mm");

            LogToDebugConsole("CT Reconstruction:");
            LogToDebugConsole("  (0018,1100) Reconstruction Diameter - mm (FOV)");
            LogToDebugConsole("  (0018,1210) Convolution Kernel - e.g., STANDARD, BONE");
            LogToDebugConsole("  (0018,5100) Patient Position - HFS, HFP, FFS, FFP");

            LogToDebugConsole("Hounsfield Units:");
            LogToDebugConsole("  (0028,1052) Rescale Intercept - typically -1024");
            LogToDebugConsole("  (0028,1053) Rescale Slope - typically 1");
            LogToDebugConsole("  HU = Rescale Slope * Pixel Value + Rescale Intercept");
        }

        private static void LogToDebugConsole(string message)
        {
            Debug.WriteLine(message);
        }
    }
}
CT AttributeTagExample Value
KVP(0018,0060)120
Tube Current(0018,1151)250 mA
Slice Thickness(0018,0050)1.25 mm
Convolution Kernel(0018,1210)STANDARD
CTDIvol(0018,9345)15.2 mGy

MR (Magnetic Resonance) Attributes

MR images use the MR Image Storage SOP Class (1.2.840.10008.5.1.4.1.1.4):

private static void DemonstrateMRAttributes()
{
    LogToDebugConsole("=== MR (Magnetic Resonance) ===");
    LogToDebugConsole("SOP Class: MR Image Storage (1.2.840.10008.5.1.4.1.1.4)");

    LogToDebugConsole("MR Image Module:");
    LogToDebugConsole("  (0018,0020) Scanning Sequence - SE, IR, GR, EP, RM");
    LogToDebugConsole("  (0018,0021) Sequence Variant - SK, MTC, SS, TRSS, SP");
    LogToDebugConsole("  (0018,0022) Scan Options - PER, RG, CG, PPG, FC");
    LogToDebugConsole("  (0018,0023) MR Acquisition Type - 2D, 3D");
    LogToDebugConsole("  (0018,0080) Repetition Time (TR) - ms");
    LogToDebugConsole("  (0018,0081) Echo Time (TE) - ms");
    LogToDebugConsole("  (0018,0082) Inversion Time (TI) - ms");
    LogToDebugConsole("  (0018,0083) Number of Averages (NEX/NSA)");
    LogToDebugConsole("  (0018,0087) Magnetic Field Strength - Tesla");
    LogToDebugConsole("  (0018,1314) Flip Angle - degrees");

    LogToDebugConsole("Common Sequence Types:");
    LogToDebugConsole("  T1-weighted: Short TR (~500ms), Short TE (~10-20ms)");
    LogToDebugConsole("  T2-weighted: Long TR (~2000-4000ms), Long TE (~80-120ms)");
    LogToDebugConsole("  FLAIR: Long TR, Long TE, TI ~2500ms");
    LogToDebugConsole("  DWI: Echo-planar, b-value in (0018,9087)");
}
MR AttributeTagT1-WeightedT2-Weighted
Repetition Time (TR)(0018,0080)~500 ms~2000-4000 ms
Echo Time (TE)(0018,0081)~10-20 ms~80-120 ms
Flip Angle(0018,1314)~90°~90°

US (Ultrasound) Attributes

Ultrasound images use the US Image Storage SOP Class (1.2.840.10008.5.1.4.1.1.6.1):

private static void DemonstrateUSAttributes()
{
    LogToDebugConsole("=== US (Ultrasound) ===");
    LogToDebugConsole("SOP Class: US Image Storage (1.2.840.10008.5.1.4.1.1.6.1)");
    LogToDebugConsole("         US Multi-frame (1.2.840.10008.5.1.4.1.1.3.1)");

    LogToDebugConsole("US Image Module:");
    LogToDebugConsole("  (0018,6011) Sequence of Ultrasound Regions");
    LogToDebugConsole("  (0018,602C) Physical Delta X - mm per pixel");
    LogToDebugConsole("  (0018,602E) Physical Delta Y - mm per pixel");
    LogToDebugConsole("  (0008,2142) Start Trim - frame number");
    LogToDebugConsole("  (0008,2143) Stop Trim - frame number");
    LogToDebugConsole("  (0008,2144) Recommended Display Frame Rate");
    LogToDebugConsole("  (0018,6030) Transducer Type - SECTOR, LINEAR, CURVED");

    LogToDebugConsole("Multi-frame:");
    LogToDebugConsole("  (0028,0008) Number of Frames - high for cine loops");
    LogToDebugConsole("  (0018,1063) Frame Time - ms between frames");
}

XA (X-Ray Angiography) Attributes

XA images use the X-Ray Angiographic Image Storage SOP Class:

private static void DemonstrateXAAttributes()
{
    LogToDebugConsole("=== XA (X-Ray Angiography) ===");
    LogToDebugConsole("SOP Class: X-Ray Angiographic Image (1.2.840.10008.5.1.4.1.1.12.1)");

    LogToDebugConsole("XA Image Module:");
    LogToDebugConsole("  (0018,1147) Field of View Shape - RECTANGLE, ROUND");
    LogToDebugConsole("  (0018,1149) Field of View Dimension(s) - mm");
    LogToDebugConsole("  (0018,1500) Positioner Motion - STATIC, DYNAMIC");
    LogToDebugConsole("  (0018,1510) Positioner Primary Angle - LAO/RAO");
    LogToDebugConsole("  (0018,1511) Positioner Secondary Angle - CRAN/CAUD");
    LogToDebugConsole("  (0018,1114) Magnification Factor");
    LogToDebugConsole("  (0018,1164) Imager Pixel Spacing - mm/pixel");

    LogToDebugConsole("Multi-frame (cine):");
    LogToDebugConsole("  (0028,0008) Number of Frames");
    LogToDebugConsole("  (0008,2144) Recommended Display Frame Rate - fps");
}

DX/CR (Digital Radiography) Attributes

Digital radiography includes both DX and CR modalities:

private static void DemonstrateDXAttributes()
{
    LogToDebugConsole("=== CR/DX (Radiography) ===");
    LogToDebugConsole("SOP Classes:");
    LogToDebugConsole("  Digital X-Ray (DX): 1.2.840.10008.5.1.4.1.1.1.1");
    LogToDebugConsole("  Computed Radiography (CR): 1.2.840.10008.5.1.4.1.1.1");

    LogToDebugConsole("DX Anatomy Imaged Module:");
    LogToDebugConsole("  (0018,5101) View Position - AP, PA, LL, RL");
    LogToDebugConsole("  (0008,2218) Anatomic Region Sequence");
    LogToDebugConsole("  (0020,0060) Laterality - R, L");

    LogToDebugConsole("DX Positioning Module:");
    LogToDebugConsole("  (0018,1110) Distance Source to Detector - mm");
    LogToDebugConsole("  (0018,1111) Distance Source to Patient - mm");
    LogToDebugConsole("  (0018,1166) Grid - IN, NONE");

    LogToDebugConsole("Exposure:");
    LogToDebugConsole("  (0018,0060) KVP");
    LogToDebugConsole("  (0018,1152) Exposure - mAs");
}

MG (Mammography) Attributes

Digital Mammography uses the Digital Mammography X-Ray Image Storage SOP Class:

private static void DemonstrateMGAttributes()
{
    LogToDebugConsole("=== MG (Mammography) ===");
    LogToDebugConsole("SOP Class: Digital Mammography (1.2.840.10008.5.1.4.1.1.1.2)");

    LogToDebugConsole("Mammography Image Module:");
    LogToDebugConsole("  (0018,0060) KVP - typically 26-32 kV");
    LogToDebugConsole("  (0018,1114) Magnification Factor");
    LogToDebugConsole("  (0018,1166) Grid - IN or NONE");
    LogToDebugConsole("  (0018,7004) Detector Type - DIRECT, SCINTILLATOR");
    LogToDebugConsole("  (0018,7050) Filter Material - Mo, Rh, Al");
    LogToDebugConsole("  (0018,11A0) Body Part Thickness - compressed mm");
    LogToDebugConsole("  (0018,11A2) Compression Force - N (Newtons)");

    LogToDebugConsole("View Information:");
    LogToDebugConsole("  (0020,0060) Laterality - R, L");
    LogToDebugConsole("  (0018,5101) View Position - CC, MLO, ML, LM");
}
MG ViewAbbreviationDescription
CraniocaudalCCTop-down view
Mediolateral ObliqueMLOAngled side view
MediolateralMLTrue lateral (from middle)
LateromedialLMTrue lateral (from side)

NM/PT (Nuclear Medicine/PET) Attributes

Nuclear Medicine and PET imaging have unique radiopharmaceutical attributes:

private static void DemonstrateNMAttributes()
{
    LogToDebugConsole("=== NM/PT (Nuclear Medicine/PET) ===");
    LogToDebugConsole("SOP Classes:");
    LogToDebugConsole("  NM Image: 1.2.840.10008.5.1.4.1.1.20");
    LogToDebugConsole("  PET Image: 1.2.840.10008.5.1.4.1.1.128");

    LogToDebugConsole("NM Image Module:");
    LogToDebugConsole("  (0054,0016) Radiopharmaceutical Information Sequence");
    LogToDebugConsole("  (0018,0031) Radiopharmaceutical - e.g., Tc-99m, FDG");
    LogToDebugConsole("  (0018,1071) Radiopharmaceutical Volume - ml");
    LogToDebugConsole("  (0018,1074) Radionuclide Total Dose - MBq");
    LogToDebugConsole("  (0018,1072) Radiopharmaceutical Start DateTime");

    LogToDebugConsole("PET-specific:");
    LogToDebugConsole("  (0054,1001) Units - BQML, CNTS");
    LogToDebugConsole("  (0054,1102) Decay Correction - START, ADMIN, NONE");
    LogToDebugConsole("  (0010,1030) Patient Weight - needed for SUV");
    LogToDebugConsole("  SUV = Activity / (Injected Dose / Patient Weight)");
}

Reading Modality-Specific Attributes

Here's how to read modality-specific attributes from a DICOM file:

public static void ReadModalityAttributes(string dicomPath)
{
    var file = DicomFile.Open(dicomPath);
    var dataset = file.Dataset;

    var modality = dataset.GetSingleValueOrDefault(DicomTag.Modality, "");
    LogToDebugConsole($"Modality: {modality}");

    switch (modality)
    {
        case "CT":
            LogToDebugConsole($"  KVP: {dataset.GetSingleValueOrDefault(DicomTag.KVP, "")}");
            LogToDebugConsole($"  Slice Thickness: {dataset.GetSingleValueOrDefault(DicomTag.SliceThickness, "")} mm");
            LogToDebugConsole($"  Convolution Kernel: {dataset.GetSingleValueOrDefault(DicomTag.ConvolutionKernel, "")}");
            break;

        case "MR":
            LogToDebugConsole($"  TR: {dataset.GetSingleValueOrDefault(DicomTag.RepetitionTime, "")} ms");
            LogToDebugConsole($"  TE: {dataset.GetSingleValueOrDefault(DicomTag.EchoTime, "")} ms");
            LogToDebugConsole($"  Field Strength: {dataset.GetSingleValueOrDefault(DicomTag.MagneticFieldStrength, "")} T");
            break;

        case "US":
            LogToDebugConsole($"  Number of Frames: {dataset.GetSingleValueOrDefault(DicomTag.NumberOfFrames, "")}");
            break;

        case "MG":
            LogToDebugConsole($"  Laterality: {dataset.GetSingleValueOrDefault(DicomTag.Laterality, "")}");
            LogToDebugConsole($"  View Position: {dataset.GetSingleValueOrDefault(DicomTag.ViewPosition, "")}");
            LogToDebugConsole($"  Compression Force: {dataset.GetSingleValueOrDefault(DicomTag.CompressionForce, "")} N");
            break;
    }
}

Modality Summary

ModalitySOP Class UIDKey Attributes
CT1.2.840.10008.5.1.4.1.1.2KVP, Slice Thickness, CTDIvol
MR1.2.840.10008.5.1.4.1.1.4TR, TE, Field Strength
US1.2.840.10008.5.1.4.1.1.6.1Number of Frames, Transducer Type
XA1.2.840.10008.5.1.4.1.1.12.1Positioner Angles, Frame Rate
DX1.2.840.10008.5.1.4.1.1.1.1View Position, Laterality
MG1.2.840.10008.5.1.4.1.1.1.2Laterality, View, Compression
NM1.2.840.10008.5.1.4.1.1.20Radiopharmaceutical, Dose
PT1.2.840.10008.5.1.4.1.1.128SUV, Decay Correction

Conclusion

Understanding modality-specific DICOM attributes is essential for building applications that correctly handle images from different imaging devices. Each modality has unique characteristics that must be properly interpreted for clinical use.

When developing DICOM applications, always check the modality tag first and then read the appropriate modality-specific attributes. This ensures your application can handle the diverse range of imaging data found in healthcare environments.

Please check out the next tutorial in this series where we cover DICOM Radiation Therapy (RT) objects.