mercredi 7 novembre 2018

TP 2 - Construction d'objet DICOM Image (Natif)

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 fleurPhoto 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.

Exemples:
Capture d'écranImage 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:
Nous allons également utiliser les parties PS3.3, PS3.5, PS3.6 et PS3.19 du standard DICOM.
  • 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)); }