DICOM File Encoding Deep Dive

Section 19 of 27
70% complete

Understanding DICOM binary encoding is essential for low-level file manipulation and debugging.

Binary File Structure

DICOM File Binary Layout
========================

Offset 0-127:    File Preamble (128 bytes)
                 Usually zeros, can contain application data

Offset 128-131:  DICOM Prefix "DICM"
                 ASCII bytes: 0x44, 0x49, 0x43, 0x4D

Offset 132+:     File Meta Information (Group 0002)
                 Always Explicit VR Little Endian

Offset N+:       Dataset
                 Encoded per Transfer Syntax in (0002,0010)
DICOM Part 10 File Layoutsaravanansubramanian.compreamble, DICM magic, File Meta Information, then the dataset per Transfer SyntaxOFFSETSECTION0x000000 00 00 00 … (128 zero bytes)File Preamble · usually zeros, may hold application dataPreamble0x008044 49 43 4D (“DICM”)Magic0x0084FILE META INFORMATION · group 0002 · always Explicit VR Little Endian02 00 00 00 UL 04 00 00 00 [group length]Group Length02 00 01 00 OB 00 00 00 02 00 00 01Meta Info Version02 00 02 00 UI [length] [SOP Class UID]Media Storage SOP Class02 00 10 00 UI [length] [Transfer Syntax UID]Transfer Syntax UIDselects encoding for the dataset that followsMeta (0002)N+DATASET · encoded per the Transfer Syntax above08 00 05 00 CS [length] ISO_IR 100Specific Character Set08 00 16 00 UI [length] [SOP Class UID]SOP Class UID10 00 10 00 PN [length] Doe^JohnPatient NameFE 7F 00 10 OW [undefined length] [pixel data]Pixel Data (7FE0,0010)tag = group + element (each 2 bytes, little-endian)Explicit VR short form: group | element | VR | length (2) | valueExplicit VR long form: group | element | VR | reserved (2) | length (4) | valueImplicit VR: group | element | length (4) | value (VR looked up)Dataset

Explicit VR vs Implicit VR Encoding

Explicit VR Little Endian

Explicit VR Data Element (Short Form)
=====================================

For VRs: AE, AS, AT, CS, DA, DS, DT, FL, FD, IS, LO, LT, PN, SH, SL, SS, ST, TM, UI, UL, US

Bytes 0-1:   Group Number (little endian)
Bytes 2-3:   Element Number (little endian)
Bytes 4-5:   Value Representation (2 ASCII chars)
Bytes 6-7:   Value Length (2 bytes, little endian)
Bytes 8+:    Value

Example: Patient Name "Doe^John"
Explicit VR Data Element (Short Form)saravanansubramanian.comPatient Name (0010,0010) PN “Doe^John” · 8 bytes of valueB0-1B2-3B4-5B6-7B8-1510 00Group 001010 00Element 0010P NVR (2 ASCII)08 00Length 8 (LE, 2B)44 6F 65 5E 4A 6F 68 6EValue “Doe^John”used for short-form VRs: AE, AS, AT, CS, DA, DS, DT, FL, FD, IS, LO, LT, PN, SH, SL, SS, ST, TM, UI, UL, US

Explicit VR Data Element (Long Form)

For VRs: OB, OD, OF, OL, OW, SQ, UC, UN, UR, UT

  • Bytes 0-1: Group Number
  • Bytes 2-3: Element Number
  • Bytes 4-5: Value Representation
  • Bytes 6-7: Reserved (0x0000)
  • Bytes 8-11: Value Length (4 bytes)
  • Bytes 12+: Value

Example: Pixel Data

Explicit VR Data Element (Long Form)saravanansubramanian.comPixel Data (7FE0,0010) OW · length 1,048,576 (1 MB)B0-1B2-3B4-5B6-7B8-11B12+E0 7FGroup 7FE010 00Element 0010O WVR (2 ASCII)00 00Reserved00 00 10 00Length 1,048,576 (LE, 4B)[ pixel bytes ]Value 1 MB payloadused for long-form VRs: OB, OD, OF, OL, OW, SQ, UC, UN, UR, UT · 2 reserved bytes + 4-byte length

Implicit VR Little Endian

Implicit VR Data Element
========================

Bytes 0-1:   Group Number (little endian)
Bytes 2-3:   Element Number (little endian)
Bytes 4-7:   Value Length (4 bytes)
Bytes 8+:    Value

Example: Patient Name "Doe^John"

Note: VR is NOT included — must be looked up in dictionary.
Implicit VR Data Elementsaravanansubramanian.comPatient Name (0010,0010) “Doe^John” · VR looked up in the dictionaryB0-1B2-3B4-7B8-1510 00Group 001010 00Element 001008 00 00 00Length 8 (LE, 4B)44 6F 65 5E 4A 6F 68 6EValue “Doe^John”no VR bytes on the wire — parser looks up VR by (group, element) in a data dictionary

Encoding Comparison

public class EncodingExample
{
    public void DemonstrateEncoding()
    {
        // Data to encode: Patient Name = "Doe^John"
        string tagGroup = "0010";
        string tagElement = "0010";
        string vr = "PN";
        string value = "Doe^John";

        Console.WriteLine("Explicit VR Little Endian:");
        byte[] explicitVR = EncodeExplicitVR(tagGroup, tagElement, vr, value);
        Console.WriteLine(BitConverter.ToString(explicitVR));
        // Output: 10-00-10-00-50-4E-08-00-44-6F-65-5E-4A-6F-68-6E

        Console.WriteLine("\nImplicit VR Little Endian:");
        byte[] implicitVR = EncodeImplicitVR(tagGroup, tagElement, value);
        Console.WriteLine(BitConverter.ToString(implicitVR));
        // Output: 10-00-10-00-08-00-00-00-44-6F-65-5E-4A-6F-68-6E
    }

    private byte[] EncodeExplicitVR(string group, string element, string vr, string value)
    {
        using (var ms = new MemoryStream())
        using (var writer = new BinaryWriter(ms))
        {
            // Group (Little Endian)
            writer.Write(Convert.ToUInt16(group, 16));
            // Element (Little Endian)
            writer.Write(Convert.ToUInt16(element, 16));
            // VR (2 ASCII chars)
            writer.Write(Encoding.ASCII.GetBytes(vr));
            // Length (2 bytes for short-form VRs)
            writer.Write((ushort)value.Length);
            // Value
            writer.Write(Encoding.ASCII.GetBytes(value));

            return ms.ToArray();
        }
    }

    private byte[] EncodeImplicitVR(string group, string element, string value)
    {
        using (var ms = new MemoryStream())
        using (var writer = new BinaryWriter(ms))
        {
            // Group
            writer.Write(Convert.ToUInt16(group, 16));
            // Element
            writer.Write(Convert.ToUInt16(element, 16));
            // Length (4 bytes in Implicit VR)
            writer.Write((uint)value.Length);
            // Value
            writer.Write(Encoding.ASCII.GetBytes(value));

            return ms.ToArray();
        }
    }
}

Sequence Encoding

Sequences contain nested items, each with their own data elements.

Sequence Encoding (Defined Length)saravanansubramanian.comReferenced Image Sequence (0008,1140) SQ · length 0x80 · each item length is knownSEQUENCE HEADER08 00 40 11Tag (0008,1140)SQVR00 00Reserved00 00 00 80Length 128ITEM #1FE FF 00 E0Item (FFFE,E000)00 00 00 38Length 5608 00 50 11 UI 1A 1.2.840.10008…Data Element 1 · Referenced SOP Class UID08 00 55 11 UI 40 1.2.840.113619…Data Element 2 · Referenced SOP Instance UIDITEM #2 (repeat pattern)FE FF 00 E0Item (FFFE,E000)00 00 00 38Length 56… more data elements … Sequence Encoding (Undefined Length)saravanansubramanian.comlength 0xFFFFFFFF · parser scans forward until delimiter tags close each item and the sequenceSEQUENCE HEADER08 00 40 11Tag (0008,1140)SQVR00 00ReservedFF FF FF FFUndefined lengthITEM (undefined length)FE FF 00 E0Item (FFFE,E000)FF FF FF FFUndefined lengthData Elements … (parsed until the item delimiter appears)FE FF 0D E0Item Delimitation Item (FFFE,E00D) · length 0SEQUENCE CLOSEFE FF DD E0Sequence Delim. (FFFE,E0DD)00 00 00 00Length 0closes the sequence and returns control to the parent parser

Sequence Delimiter Tags

TagNamePurpose
(FFFE,E000)ItemStart of sequence item
(FFFE,E00D)Item Delimitation ItemEnd of item (undefined length)
(FFFE,E0DD)Sequence Delimitation ItemEnd of sequence (undefined length)

Pixel Data Encoding

Uncompressed (Native) Pixel Data

Native Pixel Data
=================

Tag: (7FE0,0010)
VR: OW or OB
Length: rows x columns x bytesPerPixel x samplesPerPixel

Example: 512x512, 16-bit, Monochrome
Length = 512 * 512 * 2 * 1 = 524,288 bytes

Binary layout:
Uncompressed Pixel Data Elementsaravanansubramanian.comPixel Data (7FE0,0010) OW · 512 x 512 x 16-bit monochrome · 524,288 bytesB0-1B2-3B4-5B6-7B8-11B12+E0 7FGroup 7FE010 00Element 0010O WVR00 00Reserved00 08 00 00Length 524,288[ 524,288 raw bytes ]Pixel Data payloadlength = rows x columns x bytesPerPixel x samplesPerPixel · no fragmentation, no delimiters

Encapsulated (Compressed) Pixel Data

Encapsulated Pixel Data
=======================

Used for: JPEG, JPEG 2000, JPEG-LS, RLE compression

Tag: (7FE0,0010)
VR: OB
Length: FFFFFFFF (undefined)

Structure:
Encapsulated (Compressed) Pixel Datasaravanansubramanian.comused for JPEG, JPEG 2000, JPEG-LS, RLE · each compressed frame is one or more itemsPixel Data Tag(7FE0,0010) OB · length FFFFFFFF (undefined)FF FF FF FFBasic Offset TableItem (FFFE,E000) · length 0 (empty) OR N * 4 (offsets to each frame)Fragment 1Item (FFFE,E000) · length = size of compressed frame 1 · value = compressed bytesFragment 2Item (FFFE,E000) · length = size of compressed frame 2 · value = compressed bytes… more fragments …Sequence Delimitation(FFFE,E0DD) · length 0 · closes the encapsulated pixel data

Low-Level Parser Example

public class LowLevelDicomParser
{
    public void ParseDicomFile(string filePath)
    {
        using (FileStream fs = new FileStream(filePath, FileMode.Open))
        using (BinaryReader reader = new BinaryReader(fs))
        {
            // Skip preamble (128 bytes)
            reader.ReadBytes(128);

            // Read DICM prefix
            byte[] prefix = reader.ReadBytes(4);
            if (Encoding.ASCII.GetString(prefix) != "DICM")
            {
                Console.WriteLine("Not a valid DICOM file");
                return;
            }

            // Read File Meta Information (always Explicit VR LE)
            Console.WriteLine("=== File Meta Information ===");
            ParseElements(reader, explicitVR: true, stopGroup: 0x0008);

            // Determine transfer syntax
            string transferSyntax = GetTransferSyntax();
            bool isExplicitVR = transferSyntax != "1.2.840.10008.1.2";

            Console.WriteLine("\n=== Dataset ===");
            ParseElements(reader, isExplicitVR);
        }
    }

    private void ParseElements(BinaryReader reader, bool explicitVR, ushort? stopGroup = null)
    {
        while (reader.BaseStream.Position < reader.BaseStream.Length)
        {
            ushort group = reader.ReadUInt16();
            ushort element = reader.ReadUInt16();

            if (stopGroup.HasValue && group >= stopGroup.Value)
            {
                reader.BaseStream.Position -= 4;
                break;
            }

            string vr = "";
            uint length;

            if (explicitVR)
            {
                vr = Encoding.ASCII.GetString(reader.ReadBytes(2));

                // Check for long-form VRs
                bool isLongForm = vr == "OB" || vr == "OD" || vr == "OF" ||
                                  vr == "OL" || vr == "OW" || vr == "SQ" ||
                                  vr == "UC" || vr == "UN" || vr == "UR" || vr == "UT";

                if (isLongForm)
                {
                    reader.ReadUInt16(); // Reserved
                    length = reader.ReadUInt32();
                }
                else
                {
                    length = reader.ReadUInt16();
                }
            }
            else
            {
                length = reader.ReadUInt32();
                vr = LookupVR(group, element);
            }

            // Handle undefined length (sequences, encapsulated data)
            if (length == 0xFFFFFFFF)
            {
                Console.WriteLine($"({group:X4},{element:X4}) {vr} = (undefined length)");
                // Would need to parse until delimiter
                break;
            }

            // Read value
            byte[] value = reader.ReadBytes((int)length);

            // Display
            string valueStr = FormatValue(vr, value);
            Console.WriteLine($"({group:X4},{element:X4}) {vr} [{length}] = {valueStr}");

            // Stop at pixel data
            if (group == 0x7FE0 && element == 0x0010)
            {
                Console.WriteLine("... (pixel data)");
                break;
            }
        }
    }

    private string LookupVR(ushort group, ushort element)
    {
        // Simplified - would use full DICOM dictionary
        string tag = $"{group:X4}{element:X4}";
        return tag switch
        {
            "00100010" => "PN",
            "00100020" => "LO",
            "00080020" => "DA",
            "00080060" => "CS",
            _ => "UN"
        };
    }

    private string FormatValue(string vr, byte[] value)
    {
        if (value.Length == 0) return "(empty)";

        return vr switch
        {
            "UI" or "LO" or "SH" or "PN" or "CS" or "DA" or "TM" or "AE" =>
                Encoding.ASCII.GetString(value).Trim('\0', ' '),
            "US" => value.Length >= 2 ? BitConverter.ToUInt16(value, 0).ToString() : "(invalid)",
            "UL" => value.Length >= 4 ? BitConverter.ToUInt32(value, 0).ToString() : "(invalid)",
            "SQ" => "(sequence)",
            "OB" or "OW" => $"(binary, {value.Length} bytes)",
            _ => $"(binary, {value.Length} bytes)"
        };
    }

    private string GetTransferSyntax()
    {
        // Would be stored from parsing (0002,0010)
        return "1.2.840.10008.1.2.1"; // Explicit VR LE default
    }
}

Understanding DICOM binary encoding enables debugging, custom parsing, and low-level file manipulation.

Quiz: DICOM File Encoding Deep Dive

Question 1 of 4

How does Explicit VR differ from Implicit VR encoding?