Le but de cet exercice est de construire un petit programme qui prend en entrée une image au format BMP, JPEG, TIFF ou PNG, et génère en sortie un objet DICOM de type « Visible Light Photographic Image » ou « Secondary Capture Image ».
Un objet de type « Visible Light Photographic Image » (cf. PS3.3 - Section A.32.4) permet de stocker des images photographiques.
Exemples:
![]() |
![]() |
Macro photo d'une fleur | Photo dermatologique d'un grain de beauté |
Un objet de type « Secondary Capture Image » (cf. PS3.3 - Section A.8) permet de stocker des images qui ne sont pas photographiques.
Par exemple des images issues d'une capture d'écran, ou encore des images calculées par une solution automatisée de post-traitement.
Par exemple des images issues d'une capture d'écran, ou encore des images calculées par une solution automatisée de post-traitement.
Exemples:
![]() |
![]() |
Capture d'écran | Image calculée par une solution automatisée de post-traitement |
Pour comprendre tout le vocabulaire utilisé dans ce TP, il est préférable d'avoir lu les articles suivants:
- Les Données dans DICOM ;
- L'encodage d'objet DICOM - Format Classique (Partie 1) ;
- L'encodage d'objet DICOM - Format Classique (Partie 2) ;
- L'encodage d'objet DICOM - Format XML ;
- Les images en DICOM - Partie 1.
- PS3.3 : pour les spécifications des objets « Visible Light Photographic Image » et « Secondary Capture Image » ;
- PS3.5 : pour les types de données (Value Representation) ;
- PS3.6 : Pour le dictionnaire des UID (notamment pour la syntaxe de transfert et les SOP class UID) ;
- PS3.19 : Pour les spécifications XML d'un objet DICOM.
Dans le cadre de cet exercice seuls les éléments obligatoires seront retenus. L'objectif étant de construire un objet DICOM minimal, mais valide.
1. Détail du TP
Le but de cet exercice est de créer un programme ayant les spécificités suivantes:
- Prendre en paramètre d'entrée un fichier image au format JPEG, BMP, TIFF ou PNG ;
- Prendre en paramètre d'entrée le type de fichier DICOM voulu en résultat (« Visible Light Photographic » ou « Secondary Capture »). Par défaut, si le paramètre n'est pas spécifié, l'objet généré sera un « Secondary Capture » ;
- Prendre en paramètre d'entrée un fichier XML DICOM ayant les méta-données DICOM désirées pour l'objet DICOM résultat (exemple: les informations sur le patient, etc.). Par défaut, si le paramètre n'est pas spécifié, le programme devra proposer des valeurs par défauts. ;
- Délivrer en sortie un fichier DICOM contenant l'image passée en paramètre d'entrée ;
- L'image dans le fichier DICOM doit être au format DICOM Natif (et non pas encapsulé).
2. Outils utilisés
Pour cet exercice, les outils suivants sont utilisés:
- Java 8 ;
- Eclipse Oxygen JEE ;
- Maven ;
- L'utilitaire open source dcm4che. Il propose une API pour manipuler les données DICOM (format classique et XML) et créer des fichiers (il propose bien plus, mais dans ce TP, seule cette partie nous intéresse) ;
- L'utilitaire OpenCV. Cet outil est une bibliothèque graphique libre, initialement développée par Intel, spécialisée dans le traitement d'images en temps réel. Cet utilitaire est assez populaire en ce moment et a récemment été intégré dans dcm4che ;
- Le gestionnaire de log applicatif Logback. L'utilitaire dcm4che utilise l'API d'abstraction de frameworks de log slf4j et log4j comme implémentation par défaut. Cette implémentation sera remplacée dans ce TP par Logback.
3. Spécification des objets DICOM
Voyons tout d'abord les spécifications des deux objets à construire.
3.1. Les modules DICOM
Dans un objet DICOM, certains modules (des regroupements d'attributs DICOM) peuvent être obligatoires, alors que d'autres sont optionnels. DICOM utilise la convention suivante pour indiquer quels sont les modules obligatoires et optionnels (voir DICOM PS 3.3 Annexe A1.3).
M
|
Module obligatoire (M pour Mandatory)
|
C
|
Module obligatoire sous condition (C pour Conditional)
|
U
|
Module
optionel (U pour User Option)
|
3.2. Les types de données
Dans un module, certains Attributs (Data Element) sont requis et d'autres optionnels. Le tableau ci-dessous récapitule les types de données tels que décrit dans le standard DICOM PS 3.5 (Annexe 7.4).
Type 1
|
Élément obligatoire
La valeur doit être connue et valide avec une taille >
0. L'absence d'un élément de type 1 ou de sa valeur dans l'objet constitue une
violation du protocole DICOM.
|
Type 1C
|
Élément obligatoire sous condition
Les éléments ayant ce type doivent être inclus sous certaines
conditions spécifiées. Les éléments de type 1C ont les mêmes obligations, exigences et conditions que le type 1.
|
Type 2
|
Élément obligatoire avec valeur vide autorisée
La valeur peut être inconnue ou sa taille peut être 0.
L'absence d'un élément de type 2 (mais pas de sa valeur) dans l'objet constitue une violation
du protocole DICOM.
|
Type 2C
|
Élément obligatoire avec valeur vide autorisée sous
condition
Les éléments ayant ce type doivent être inclus sous
certaines conditions spécifiées. Les éléments de type 2C ont les mêmes
obligations, exigences et conditions que le type 2.
|
Type 3
|
Élément optionnel
L'absence d'un élément de type 3 ou de sa valeur dans l'objet ne
constitue pas une violation du protocole DICOM.
|
3.1. Spécification de l'objet « Visible Light Photographic Image »
Les spécifications de cet objet sont décrites dans la partie PS3.3 du standard, annexe A.32.4. D'après ces spécifications, les éléments obligatoires sont les suivants:
Nom Attribut
|
Tag
|
VR
|
Description
|
Module Patient
|
|||
Patient's Name
|
(0010, 0010)
|
PN
|
Nom du patient.
Chaîne de caractères utilisant une convention
suivante: Nom^prénom
Exemple: DUPOND^JEAN
|
Patient ID
|
(0010, 0020)
|
LO
|
Identifiant du patient
Chaîne de caractères représentant la valeur de
l'identifiant du patient.
Exemple : 001
|
Patient’s Birth Date
|
(0010, 0030)
|
DA
|
Date de naissance du patient
Chaîne de caractères au format YYYYMMDD ou:
· YYYY est l'année
de naissance;
· MM le mois de naissance;
· DD le jour de naissance.
|
Patient’s Sex
|
(0010, 0040)
|
CS
|
Sexe du patient, peut être vide
Valeurs possibles:
· M = Masculin
· F = Féminin
· O = Other
|
Module General Study
|
|||
Study Instance UID
|
(0020, 000D)
|
UI
|
Identifiant unique de l'examen
Chaîne de caractères contenant un UID.
Un UID est une série de chiffres séparés par le
caractère "." Longueur maximum 64 caractères.
|
Study Date
|
(0008, 0020)
|
DA
|
Date de début de l'examen
Chaîne de caractères au format YYYYMMDD ou:
· YYYY est l'année
de naissance;
· MM le mois de naissance;
· DD le jour de naissance.
Peut être vide
|
Study Time
|
(0008, 0030)
|
TM
|
Heure de début de l'examen
Chaîne de caractères au format HHMMSS où
· HH représente les heures
("00" - "23");
· MM les minutes
("00" - "59");
· SS les
secondes ("00" - "59").
Peut être vide
|
Referring Physician's Name
|
(0008,0090)
|
PN
|
Nom de la personne (physique ou morale)
responsable de la photographie. Auteur de la photo.
Sous la forme: <nom>^<prénom>
Peut être vide
|
Study ID
|
(0020, 0010)
|
SH
|
Identifiant d'examen généré par un utilisateur
ou un équipement.
Peut être vide
|
Accession Number
|
(0008, 0050)
|
SH
|
Numéro généré par le RIS qui identifie la
demande d'examen radiologique. Ce champ est utilisé si un seul Accession
Number est nécessaire.
Si plusieurs Accession Number sont
nécessaires, utiliser l'élément Request Attributes Sequence (0040, 0275).
Peut être vide
|
Module General Series
|
|||
Modality
|
(0008, 0060)
|
CS
|
Type de modalité, valeurs possibles: :
Valeur "XC" = External-camera Photography
|
Series Instance UID
|
(0020, 000E)
|
UI
|
Identifiant unique de la série
Chaîne de caractères contenant un UID.
Un UID est une série de chiffres séparés par le caractère "." Longueur maximum 64 caractères.
|
Serie Number
|
(0020, 0011)
|
IS
|
Un numéro qui identifie la série. Peut-être vide
Exemple : 1
|
Module General Equipment
|
|||
Manufacturer
|
(0008,0070)
|
LO
|
Fabriquant ou éditeur qui a produit la photographie (ou l'objet DICOM).
Peut être vide. |
Module General Image
|
|||
Instance Number
|
(0020, 0013)
|
IS
|
Un numéro qui identifie l'objet.
Valeur possible: 1
|
Module Image Pixel & VL Image
|
|||
Image Type
|
(0008, 0008)
|
CS
|
Permet d'identifier les caractéristiques de l'image
Dans le cadre de ce TP, les valeurs ORIGINAL/DERIVED seront utilisées. |
Samples per Pixel
|
(0028, 0002)
|
US
|
Nombre de canaux de l'image. Les valeurs permises par le standard sont:
· "1" pour les images monochromes · "3" pour les images couleurs. |
Photometric Interpretation
|
(0028, 0004)
|
CS
|
Indique comment les pixels de l'image doivent être interprétés.
Dans le cadre de ce TP, les valeurs MONOCHROME2 et RGB seront utilisées. |
Rows
|
(0028,0010)
|
US
|
Hauteur de l'image
|
Columns
|
(0028,0011)
|
US
|
Largeur de l'image
|
Bits Allocated
|
(0028,0100)
|
US
|
Nombre de bits alloués pour chacun des pixels et pour chaque canal. Chaque canal de l'image se voit allouer le même nombre de bits. Si une image a 3 composantes (RGB par exemple) donc 3 canaux, et que chaque composante utilise 8 bits, la valeur de ce tag sera 8 et non pas 24.
Dans le cas d'une image VL, la valeur doit être 8. |
Bits Stored
|
(0028, 0101)
|
US
|
Nombre de bits réellement utilisés parmi ceux alloués (voir Bits Allocated).
Dans le cas d'une image VL, la valeur doit être 8. |
High Bit
|
(0028, 0102)
|
US
|
Indique le début de l'emplacement de la suite de bits utilisés dans l'espace alloué.
Dans le cas d'une image VL, la valeur doit être 7. |
Pixel Representation
|
(0028, 0103)
|
US
|
Indique si les pixels sont signés ou non.
Valeurs possibles: · 0000h : non signé · 0001h : signé Dans le cas d'une image VL, la valeur doit être 0000h. |
Planar Configuration
|
(0028, 0006)
|
US
|
Indique si les pixels sont encodés de manière entrelacée ou continue. Ce champ est requis uniquement si Sample Per Pixel est > 1.
Valeurs possibles: · 0000h : entrelacé · 0001h: continu |
Lossy Image Compression
|
(0028, 2110)
|
CS
|
Spécifie si l'image a subit une compression avec perte.
Valeurs possibles: · 00 : l'image n'a pas été sujette à une compression avec perte. · 01 : l'image a été sujette à une compression avec perte. Peut être vide. |
Pixel Data
|
(7FE0, 0010)
|
OB/OW
|
Flux de données des pixels de l'image.
|
Module Acquisition Context
|
|||
Acquisiton Context Sequence
|
(0040, 0555)
|
SQ
|
Séquence décrivant les conditions d'acquisition de l'image.
Peut être vide. |
Module SOP Common
|
|||
SOP Class UID
|
(0008, 0016)
|
UI
|
Identifiant sous forme d'un OID permettant
d'identifier l'objet DICOM.
La valeur doit être: 1.2.840.10008.5.1.4.1.1.104.1
|
SOP Instance UID
|
(0008, 0018)
|
UI
|
Identifiant unique de l'objet DICOM.
Chaîne de caractères contenant un UID.
Un UID est une série de chiffres séparés par le caractère ".".
Longueur maximum 64 caractères. |
Specific Character Set
|
(0008, 0005)
|
CS
|
Cet attribut doit avoir la valeur "ISO_IR
100".
Note: ISO_IR 100 représente le système de
codage ISO 8859-1 (Latin 1)
|
3.2. Spécification de l'objet « Secondary Capture Image »
Les spécifications de cet objet sont décrites dans la partie PS3.3 du standard, annexe A.8. D'après ces spécifications, les éléments obligatoires sont les suivants:
Nom Attribut
|
Tag
|
VR
|
Description
|
Module Patient
|
|||
Patient's Name
|
(0010, 0010)
|
PN
|
Nom du patient.
Chaîne de caractères utilisant la convention suivante: Nom^prénom
Exemple: DUPOND^JEAN
|
Patient ID
|
(0010, 0020)
|
LO
|
Identifiant du patient
Chaîne de caractères représentant la valeur de l'identifiant du patient.
Exemple : 001
|
Patient’s Birth Date
|
(0010, 0030)
|
DA
|
Date de naissance du patient
Chaîne de caractères au format YYYYMMDD où:
· YYYY est l'année
de naissance;
· MM le mois de naissance;
· DD le jour de naissance.
|
Patient’s Sex
|
(0010, 0040)
|
CS
|
Sexe du patient, peut être vide
Valeurs possibles:
· M = Masculin
· F = Féminin
· O = Other
|
Module General Study
|
|||
Study Instance UID
|
(0020, 000D)
|
UI
|
Identifiant unique de l'examen
Chaîne de caractères contenant un UID.
Un UID est une série de chiffres séparés par le caractère ".".
Longueur maximum 64 caractères. |
Study Date
|
(0008, 0020)
|
DA
|
Date de début de l'examen
Chaîne de caractères au format YYYYMMDD où:
· YYYY est l'année
de naissance;
· MM le mois de naissance;
· DD le jour de naissance.
Peut être vide
|
Study Time
|
(0008, 0030)
|
TM
|
Heure de début de l'examen
Chaîne de caractères au format HHMMSS où
· HH représente les heures
("00" - "23");
· MM les minutes
("00" - "59");
· SS les
secondes ("00" - "59").
Peut être vide
|
Referring Physician's Name
|
(0008,0090)
|
PN
|
Nom de la personne (physique ou morale) responsable de l'image. Auteur du Document.
Sous la forme: <nom>^<prénom>
Peut être vide
|
Study ID
|
(0020, 0010)
|
SH
|
Identifiant d'examen généré par un utilisateur
ou un équipement.
Peut être vide
|
Accession Number
|
(0008, 0050)
|
SH
|
Numéro généré par le RIS qui identifie la
demande d'examen radiologique. Ce champ est utilisé si un seul Accession
Number est nécessaire.
Si plusieurs Accession Number sont
nécessaires, utiliser l'élément Request Attributes Sequence (0040, 0275).
Peut être vide
|
Module General Series
|
|||
Modality
|
(0008, 0060)
|
CS
|
Type de modalité, valeurs possibles: :
Valeur "SC" = Secondary Capture
Valeur "OT" = Other |
Series Instance UID
|
(0020, 000E)
|
UI
|
Identifiant unique de la série
Chaîne de caractères contenant un UID.
Un UID est une série de chiffres séparés par le caractère ".".
Longueur maximum 64 caractères. |
Serie Number
|
(0020, 0011)
|
IS
|
Un numéro qui identifie la série. Peut-être vide
Exemple : 1
|
Module SC Equipment
|
|||
Conversion Type
|
(0008,0064)
|
CS
|
Défini le type de conversion de l’image ou du
document
Valeurs possibles :
· DV : Digitized Video; · DI : Digital Interface; · DF : Digitized Film; · WSD : Workstation; · SD : Scanned Document; · SI : Scanned Image; · DRW : Drawing; · SYN : Synthetic Image.
Exemple:
SD
|
Module General Image
|
|||
Instance Number
|
(0020, 0013)
|
IS
|
Un numéro qui identifie le document
Valeur possible: 1
|
Module Image Pixel
|
|||
Samples per Pixel
|
(0028, 0002)
|
US
|
Nombre de canaux de l'image. Les valeurs permises par le standard sont:
· "1" pour les images monochromes · "3" pour les images couleurs. |
Photometric Interpretation
|
(0028, 0004)
|
CS
|
Indique comment les pixels de l'image doivent être interprétés.
Les valeurs permises sont: · MONOCHROME1 · MONOCHROME2 · PALETTE COLOR · RGB · YBR_FULL · YBR_FULL_422 · YBR_PARTIAL_420 · YBR_RCT Dans le cadre de ce TP, les valeurs MONOCHROME2 et RGB seront utilisées. |
Rows
|
(0028,0010)
|
US
|
Hauteur de l'image
|
Columns
|
(0028,0011)
|
US
|
Largeur de l'image
|
Bits Allocated
|
(0028,0100)
|
US
|
Nombre de bits alloués pour chacun des pixels et pour chaque canal. Chaque canal de l'image se voit allouer le même nombre de bits. Si une image a 3 composantes (RGB par exemple) donc 3 canaux, et que chaque composante utilise 8 bits, la valeur de ce tag sera 8 et non pas 24.
|
Bits Stored
|
(0028, 0101)
|
US
|
Nombre de bits réellement utilisés parmi ceux alloués (voir Bits Allocated)
|
High Bit
|
(0028, 0102)
|
US
|
Indique le début de l'emplacement de la suite de bits utilisés dans l'espace alloué.
|
Pixel Representation
|
(0028, 0103)
|
US
|
Indique si les pixels sont signés ou non.
Valeurs possibles: · 0000h : non signé · 0001h : signé |
Planar Configuration
|
(0028, 0006)
|
US
|
Indique si les pixels sont encodés de manière entrelacée ou continue. Ce champ est requis uniquement si Sample Per Pixel est > 1.
Valeurs possibles: · 0000h : entrelacé · 0001h: continu |
Pixel Data
|
(7FE0, 0010)
|
OB/OW
|
Flux de données des pixels de l'image.
|
Module SOP Common
|
|||
SOP Class UID
|
(0008, 0016)
|
UI
|
Identifiant sous forme d'un OID permettant
d'identifier l'objet DICOM.
La valeur doit être: 1.2.840.10008.5.1.4.1.1.104.1
|
SOP Instance UID
|
(0008, 0018)
|
UI
|
Identifiant unique de l'objet DICOM.
Chaîne de caractères contenant un UID.
Un UID est une série de chiffres séparés par le caractère ".".
Longueur maximum 64 caractères. |
Specific Character Set
|
(0008, 0005)
|
CS
|
Cet attribut doit avoir la valeur "ISO_IR
100".
Note: ISO_IR 100 représente le système de
codage ISO 8859-1 (Latin 1)
|
4. Code exemple
Le projet maven de ce TP est disponible ---> ici <---.
Une partie du code est présentée ci-dessous:
Pour réaliser ce programme, nous allons avoir besoin d'une classe de commodité permettant de faire des traitements sur les images. Appelons cette classe « ImageUtils ».
Cette classe va comporter 4 fonctions :
La première permet de tester si un fichier est une image:
public static boolean isImage(final Path path) {
try { // Ce n'est pas la méthode la plus élégante pour tester si un fichier est une // image mais pour ce TP c'est suffisant. final String mimetype = Files.probeContentType(path); final String type = mimetype.substring(0, mimetype.lastIndexOf("/")); return "image".equalsIgnoreCase(type); } catch (IOException e) { LOG.error(String.format("Erreur lors de l'évaluation du fichier %s", path)); } return false; }
La seconde méthode permet de charger une image en mémoire, on utilise OpenCV pour faire cela. Une fois chargée, on convertit l'image en BufferedImage grâce à la méthode mat2Img.
public static BufferedImage loadImageFile(final Path path) { // Permet de charger une image et de la convertir en BufferedImage // L'option Imgcodecs.CV_LOAD_IMAGE_UNCHANGED permet de charger l'image telle quelle. final Mat matrix = Imgcodecs.imread(path.toFile().getAbsolutePath(), Imgcodecs.CV_LOAD_IMAGE_UNCHANGED); return mat2Img(matrix); }
La méthode suivante permet de convertir un objet java Image OpenCV en BufferedImage.
private static BufferedImage mat2Img(final Mat mat) {
int type; if (mat.channels() == 1) { // Monochrome type = BufferedImage.TYPE_BYTE_GRAY; } else { // Couleur type = BufferedImage.TYPE_3BYTE_BGR; } final BufferedImage image = new BufferedImage(mat.width(), mat.height(), type); final WritableRaster raster = image.getRaster(); final DataBufferByte dataBuffer = (DataBufferByte) raster.getDataBuffer(); byte[] data = dataBuffer.getData(); mat.get(0, 0, data); return image; }
La dernière méthode permet de convertir un flux d'octet d'une image couleur de l'espace de couleur BGR à l'espace de couleur RGB.
public static byte[] convertToRGB(final int type, final int height, final int width, final byte[] data) { // format : CV_<bit-depth>{U|S|F}C(<nombre_de_canal>) ou U indique // "unsigned integer", S indique "signed integer", et F indique "float". switch (type) { case BufferedImage.TYPE_3BYTE_BGR: final Mat matSrc = new Mat(height, width, CvType.CV_8UC3); matSrc.put(0, 0, data); final Mat matDst = new Mat(height, width, CvType.CV_8UC3); Imgproc.cvtColor(matSrc, matDst, Imgproc.COLOR_BGR2RGB); byte[] rgb_data = new byte[matDst.rows() * matDst.cols() * (int) (matDst.elemSize())]; matDst.get(0, 0, rgb_data); return rgb_data; default: return data; } }
La fonction « convert » représente la routine principale de ce programme et permet d'effectuer la conversion. Pour les autres méthodes, voir le code exemple complet téléchargeable au début de cette section.
private void convert(final Path inputFile, final Path outputFile)
throws IOException, ParserConfigurationException, SAXException {
// Récupère la taille du fichier en entré.
final long fileLength = Files.size(inputFile);
// Vérifie que la taille du fichier n'exède pas la limite fixée.
if (fileLength > MAX_FILE_SIZE) {
throw new IllegalArgumentException(
MessageFormat.format(rb.getString("file-too-large"), inputFile.getFileName()));
}
// Vérifie si le fichier en entrée représente bien une image.
// La méthode de test n'est pas la meilleure mais elle fait le job pour
// ce TP.
if (!ImageUtils.isImage(inputFile)) {
throw new IllegalArgumentException(
MessageFormat.format(rb.getString("not-image-file"), inputFile.getFileName()));
}
// Charge l'image en mémoire
final BufferedImage buffImg = ImageUtils.loadImageFile(inputFile);
// Créer les méta données DICOM (métadonnées = tous les attributs DICOM
// sauf ceux concernant l'image)
final Attributes dataset = createAndPopulateMetadata();
// Détermine les attributs DICOM concernant l'image.
populateImageAttributes(buffImg, dataset);
// Ecriture de l'objet DICOM sur le disque
writeDcmFile(dataset, outputFile);
// Affiche un message indiquant que la conversion est terminée
System.out.println(MessageFormat.format(rb.getString("converted"), inputFile, outputFile));
}