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:
- Alphabetic: Latin alphabet representation (for display in Western systems)
- Ideographic: Native script representation (Kanji, Hanzi, Hangul)
- 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 Set | Code | Description |
|---|---|---|
| (empty) | Default | ASCII only (A-Z, a-z, 0-9) |
| ISO_IR 100 | Latin-1 | Western European (French, German, Spanish) |
| ISO_IR 192 | UTF-8 | Unicode - all languages (recommended) |
| ISO 2022 IR 87 | Japanese | JIS X 0208 (Kanji) |
| ISO 2022 IR 149 | Korean | KS 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
| Code | Standard | Languages/Scripts |
|---|---|---|
| ISO_IR 100 | ISO 8859-1 | Western European |
| ISO_IR 101 | ISO 8859-2 | Central European |
| ISO_IR 144 | ISO 8859-5 | Cyrillic |
| ISO_IR 127 | ISO 8859-6 | Arabic |
| ISO_IR 126 | ISO 8859-7 | Greek |
| ISO_IR 138 | ISO 8859-8 | Hebrew |
| ISO_IR 166 | TIS 620 | Thai |
| ISO_IR 192 | UTF-8 | All Unicode |
| GB18030 | GB18030 | Chinese |
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=山田^太郎=やまだ^たろう
| Component | Purpose |
|---|---|
| Alphabetic | Latin alphabet representation |
| Ideographic | Native script (Kanji, Hanzi, Hangul) |
| Phonetic | Pronunciation 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!