DICOM Basics using Java - Character Set Handling

Introduction

This is part of my series of articles on the DICOM standard. In this tutorial, we'll explore DICOM character set handling, which is essential for international healthcare systems that need to store patient names, institution names, and other text data in various languages and scripts.

Proper character set handling ensures that names like "Müller", "山田太郎", or "محمد" are correctly stored and displayed across different DICOM systems.

Prerequisites

Before you begin, ensure you have the following:

  • Java JDK installed (Java 8 or later)
  • PixelMed Java DICOM Toolkit
  • You can find all the code demonstrated in this tutorial on GitHub here

“The limits of my language mean the limits of my world.” ~ Ludwig Wittgenstein

The Theory Behind Character Encoding

Character encoding is one of the most subtle yet critical aspects of healthcare interoperability. When a patient named "José García" or "田中太郎" has their name corrupted to garbage characters, the consequences range from inconvenience to dangerous misidentification.

The Unicode Revolution

DICOM predates Unicode's widespread adoption, which explains its complex character set handling. Understanding this history helps navigate the current state:

  • ASCII Era (1960s-1980s): 7-bit encoding, 128 characters, English-only
  • ISO 8859 Era (1980s-1990s): 8-bit encodings, one per language family (Latin-1, Cyrillic, Arabic, etc.)
  • CJK Double-Byte (1990s): Complex multi-byte schemes for Chinese, Japanese, Korean
  • Unicode Era (2000s-present): Single universal encoding for all scripts

DICOM's Specific Character Set mechanism was designed to handle the ISO 8859 and CJK encodings. UTF-8 (ISO_IR 192) was added later and is now the recommended choice.

The Code Extension Problem

For legacy Japanese, Korean, and Chinese support, DICOM uses ISO 2022 code extension - a mechanism where escape sequences switch between different character sets within a single string. This creates complexity:

  • Multiple character sets can appear in a single attribute value
  • Escape sequences must be correctly parsed and handled
  • Different components of a person name may use different character sets

UTF-8 eliminates this complexity by providing a single encoding that handles all scripts without escape sequences.

The Person Name Component Groups

DICOM Person Names support three component groups separated by "=" characters:

  1. Alphabetic: Latin alphabet representation (for display in Western systems)
  2. Ideographic: Native script representation (Kanji, Hanzi, Hangul)
  3. Phonetic: Pronunciation guide (Hiragana, Katakana, Bopomofo)

This three-part structure allows a Japanese patient name to be stored as:

Yamada^Taro=山田^太郎=やまだ^たろう

Systems can display whichever representation is appropriate for their locale and user preferences.

Why UTF-8 is Now Recommended

UTF-8 (ISO_IR 192) should be used for all new implementations because:

  • Universal Coverage: Every Unicode character is representable
  • Backward Compatible: ASCII text is valid UTF-8
  • Self-Synchronizing: Easy to detect character boundaries in byte stream
  • Web Standard: Modern APIs and databases expect UTF-8
  • No Escape Sequences: Simpler parsing than ISO 2022

Understanding Character Sets in DICOM

The key attribute for character encoding is Specific Character Set (0008,0005). If not specified, ASCII (ISO 646) is assumed, which only supports basic Latin characters.

Character SetCodeDescription
(empty)DefaultASCII only (A-Z, a-z, 0-9)
ISO_IR 100Latin-1Western European (French, German, Spanish)
ISO_IR 192UTF-8Unicode - all languages (recommended)
ISO 2022 IR 87JapaneseJIS X 0208 (Kanji)
ISO 2022 IR 149KoreanKS X 1001

Demo: Character Set Handling

package com.saravanansubramanian.dicom.pixelmedtutorial;

import com.pixelmed.dicom.*;

public class CharacterSetHandlingDemo {

    public static void main(String[] args) {

        try {

            System.out.println("=== DICOM Character Set Handling Demo ===\n");

            // Demo 1: Default ASCII character set
            System.out.println("--- Demo 1: Default (ASCII) ---");
            demonstrateDefaultCharacterSet();

            // Demo 2: ISO 8859-1 (Latin-1) for Western European
            System.out.println("\n--- Demo 2: ISO 8859-1 (Latin-1) ---");
            demonstrateLatin1CharacterSet();

            // Demo 3: UTF-8 (Unicode)
            System.out.println("\n--- Demo 3: UTF-8 (Unicode) ---");
            demonstrateUtf8CharacterSet();

        } catch (Exception e) {
            e.printStackTrace(System.err);
        }
    }
}

Using Default ASCII Character Set

When no Specific Character Set is specified, only basic Latin characters are allowed:

private static void demonstrateDefaultCharacterSet() throws Exception {

    AttributeList list = new AttributeList();

    // No Specific Character Set - defaults to ASCII
    // Only basic Latin characters allowed: A-Z, a-z, 0-9, basic punctuation

    Attribute patientName = new PersonNameAttribute(TagFromName.PatientName);
    patientName.addValue("Doe^John");
    list.put(patientName);

    Attribute institution = new LongStringAttribute(TagFromName.InstitutionName);
    institution.addValue("General Hospital");
    list.put(institution);

    System.out.println("Patient Name: Doe^John");
    System.out.println("Specific Character Set: (not specified - ASCII default)");
    System.out.println("Supported characters: A-Z, a-z, 0-9, basic punctuation");
}

Using Latin-1 for Western European Languages

ISO 8859-1 (Latin-1) supports Western European languages with accented characters:

private static void demonstrateLatin1CharacterSet() throws Exception {

    AttributeList list = new AttributeList();

    // Set ISO 8859-1 (Latin-1) character set
    Attribute charSet = new CodeStringAttribute(TagFromName.SpecificCharacterSet);
    charSet.addValue("ISO_IR 100"); // ISO 8859-1
    list.put(charSet);

    // Create SpecificCharacterSet for proper encoding
    SpecificCharacterSet scs = new SpecificCharacterSet(new String[]{"ISO_IR 100"});

    // Now we can use accented characters
    Attribute patientName = new PersonNameAttribute(TagFromName.PatientName);
    patientName.addValue("Müller^François");
    list.put(patientName);

    Attribute institution = new LongStringAttribute(TagFromName.InstitutionName);
    institution.addValue("Hôpital Général de Zürich");
    list.put(institution);

    System.out.println("Patient Name: Müller^François");
    System.out.println("Specific Character Set: ISO_IR 100 (ISO 8859-1, Latin-1)");
    System.out.println("Supports: Western European languages");
}

Using UTF-8 for Universal Support

UTF-8 (ISO_IR 192) supports all Unicode characters and is recommended for new implementations:

private static void demonstrateUtf8CharacterSet() throws Exception {

    AttributeList list = new AttributeList();

    // Set UTF-8 character set
    Attribute charSet = new CodeStringAttribute(TagFromName.SpecificCharacterSet);
    charSet.addValue("ISO_IR 192"); // UTF-8
    list.put(charSet);

    SpecificCharacterSet scs = new SpecificCharacterSet(new String[]{"ISO_IR 192"});

    // UTF-8 supports all Unicode characters
    System.out.println("Examples of names in different scripts:");
    System.out.println("  Chinese: 王小明 (Wang Xiaoming)");
    System.out.println("  Japanese: 山田太郎 (Yamada Taro)");
    System.out.println("  Korean: 김철수 (Kim Cheolsu)");
    System.out.println("  Arabic: محمد أحمد (Muhammad Ahmad)");
    System.out.println("  Russian: Иван Петров (Ivan Petrov)");
    System.out.println("  Greek: Νίκος Παπαδόπουλος (Nikos Papadopoulos)");
    System.out.println();
    System.out.println("Specific Character Set: ISO_IR 192 (UTF-8)");
    System.out.println("RECOMMENDED for new implementations");
}

Character Set Reference Table

CodeStandardLanguages/Scripts
ISO_IR 100ISO 8859-1Western European
ISO_IR 101ISO 8859-2Central European
ISO_IR 144ISO 8859-5Cyrillic
ISO_IR 127ISO 8859-6Arabic
ISO_IR 126ISO 8859-7Greek
ISO_IR 138ISO 8859-8Hebrew
ISO_IR 166TIS 620Thai
ISO_IR 192UTF-8All Unicode
GB18030GB18030Chinese

Person Name Components for Asian Languages

For languages like Japanese, Korean, and Chinese, person names can have multiple representations:

Format: Alphabetic=Ideographic=Phonetic
Example: Yamada^Taro=山田^太郎=やまだ^たろう
ComponentPurpose
AlphabeticLatin alphabet representation
IdeographicNative script (Kanji, Hanzi, Hangul)
PhoneticPronunciation guide (Hiragana, Bopomofo)

Best Practices

  • Use UTF-8 (ISO_IR 192) for new implementations - supports all languages
  • Always specify the Specific Character Set attribute explicitly
  • Test with international data during development
  • Handle encoding errors gracefully when reading legacy data
  • Be aware of legacy system limitations - older systems may not support UTF-8

Conclusion

Proper character set handling is crucial for DICOM systems operating in international healthcare environments. UTF-8 is the recommended choice for new implementations as it supports all Unicode characters, making your system truly global-ready.

When working with legacy systems, understanding the various ISO character sets ensures you can correctly interpret and display patient information regardless of the original encoding. In the next tutorial in this series, I will cover DICOM private tags for vendor-specific data. See you then!