This page permanently redirects to gemini://blog.flozz.fr/2025/01/06/decouverte-de-dicom-le-format-dimagerie-medicale-partie-1-la-structure/.
2025-01-06
Pour les besoins d'un projet au boulot, j'ai récemment dû me pencher sur le format DICOM. Mon rôle était de comprendre comment se structure un fichier DICOM afin de le vulgariser aux collègues qui bossent sur le projet pour qu'ils puissent générer des fichiers DICOM valides, lisibles par les logiciels de visualisation d'imagerie médicale existants.
Le DICOM, pour Digital Imaging and Communication in Medicine est un standard créé en 1985 conjointement par l'ACR (American College of Radiology) et la NEMA (National Electric Manufacturers Association) afin de standardiser les formats d'imagerie médicale et plus généralement de n'importe quelle donnée médicale.
Le format DICOM est aujourd'hui utilisé pour les radiographies, les scanners, les IRM, les échographies, etc. Si vous avez passé l'un de ces examens et qu'on vous a remis un petit CD-ROM à la sortie, félicitation, vous êtes l'heureux propriétaire de fichiers DICOM ! 😉️
Une chose à savoir avec les fichiers DICOM, c'est qu'ils peuvent contenir une ou plusieurs images dans le même fichier, et que certains examens médicaux comme les scanners et les IRM produisent une série de fichiers indépendants qui, mis bout à bout, permettent de reconstituer une image du corps en trois dimensions.
Dans ce premier article je vais essayer de vous expliquer dans les grandes lignes comment se compose un fichier DICOM, les principales structures et types de données à connaître pour être capable de le manipuler. J'essayerai également d'introduire les termes utiles et de vous pointer les parties intéressantes de la spécification (ce qui ne sera pas du luxe quand on sait qu'elle fait plusieurs milliers de pages ! 😅️).
Vous êtes toujours là ? Alors c'est parti !
📝️ Note:
Cet article fait partie d'une série de deux articles sur DICOM, le format d'imagerie médicale :
=> Découverte de DICOM, le format d'imagerie médicale - PARTIE 1 : la structure | Découverte de DICOM, le format d'imagerie médicale - PARTIE 2 : les données
Bien que le format en lui-même ne soit pas si compliqué, il existe des tas de variations qui rendent le tout plus complexe que nécessaire. Étant donné que cet article est une introduction au format DICOM, je ne vais pas pouvoir tout aborder et je fais donc le choix de ne parler que de l'une des possibilités lorsque plusieurs alternatives sont possibles.
Il y a plusieurs manières d'encoder les structures de données dans la norme DICOM. La norme parle de « Transfer Syntax » pour désigner ces encodages. On dénombre trois syntaxes de transfert principales :
La première est celle désignée par la norme comme encodage par défaut pour le Data Set (le corps du fichier), la seconde est celle à utiliser obligatoirement pour les structures de la partie Meta Information (l'entête du fichier), mais elle peut aussi être utilisée dans le Data Set, et la dernière a été retirée de la norme en 2016, donc à moins de vouloir ouvrir des vieux fichiers utilisant cette syntaxe, il n'est pas nécessaire de s'en préoccuper.
À tout cela il faut encore rajouter toute une série de syntaxes de transfert à utiliser spécifiquement lorsque l'image contenue dans le fichier est encodée ou compressée. La norme les désigne sous le nom de « Transfer Syntaxes For Encapsulation of Encoded Pixel Data » et elles sont définies dans la section 4 de l'annexe A. Globalement elles reprennent la syntaxe « DICOM Explicit VR Little Endian » (PS3.5 A.2) mais avec des spécificités au niveau de la structure de donnée « Pixel Data » et de son contenu. Je les mentionne uniquement pour que vous sachiez que ça existe, mais on n'en parlera pas davantage ici.
Dans cet article, je vais faire le choix de n'aborder que la syntaxe de transfert nommée « DICOM Explicit VR Little Endian » (PS3.5 A.2), pour trois bonnes raisons :
Mais si vous souhaitez développer une bibliothèque capable de lire n'importe quel DICOM, il vous faudra bien sûr implémenter la syntaxe de transfert « DICOM Implicit VR Little Endian » (PS3.5 A.1) pour être conforme... 😅️
Références utiles :
=> Informations générales sur les syntaxes de transfert des fichiers DICOM | Spécification de la syntaxe de transfert « DICOM Implicit VR Little Endian » (PS3.5 A.1) que vous devriez aussi supporter si vous voulez être conformes à la norme et pouvoir lire un maximum de fichiers | Spécification de la syntaxe de transfert « DICOM Explicit VR Little Endian » (PS3.5 A.2) que l'on va utiliser par la suite
Maintenant qu'on a défini quelle syntaxe de transfert on allait utiliser, on peut passer à la suite.
Globalement un fichier DICOM se structure en deux grandes parties :
=> Schéma de la structure générale d'un fichier DICOM
La partie Meta Information est composée de la manière suivante :
=> Schéma de la partie « Meta Information » d'un fichier DICOM
⚠️ Warning
ATTENTION : Quelle que soit la syntaxe de transfert utilisée, les structures « Data Element » de l'entête d'un fichier DICOM sont toujours encodées en utilisant la « Explicit VR Little Endian Transfer Syntax » (PS3.5 A.2) ; celle que l'on va utiliser partout dans notre cas.
Références utiles :
=> Spécification de la partie « DICOM File Meta Information » (PS3.10 7.1)
La partie Data Set contient toutes les données du fichier DICOM. Elle est composée uniquement de structures « Data Element » collées à la suite les unes des autres.
=> Schéma de la partie « Data Set » d'un fichier DICOM
Références utiles :
=> Spécification de la partie « Data Set » d'un fichier DICOM (PS3.5 7)
Comme on a pu le voir, un fichier DICOM est essentiellement composé de structures de données nommées « Data Element ». Le problème c'est qu'il en existe de deux formes différentes avec la syntaxe de transfert que j'ai choisi (Explicit VR Little Endian (PS3.5 A.2)) :
Ne vous inquiétez pas pour l'instant de ce à quoi correspondent ces types, on y revient un peu plus tard ! 😉️
📝️ Note:
NOTE : Dans la norme DICOM, ces structures de données sont présentées dans l'ordre inverse du mien (d'où la dénomination de 7.1-2 pour la première et 7.1-1 pour la seconde), mais je trouvais plus logique et plus pratique d'en parler dans cet ordre.
La forme la plus courante, définie dans le tableau 7.1-2 de la spec, est composée de la manière suivante :
Exemple concret avec un Data Element contenant le nom d'un patient :
+----------------+---------------+-------+-------+----------------------------------------+--------+ | *(7.1-2)* | Tag | VR | VL | Value | (pad?) | +================+===============+=======+=======+========================================+========+ | Interprétation | (0010,0010) | PN | 14 | Amanda^Ripley | +----------------+-------+-------+-------+-------+----------------------------------------+--------+ | Donnée binaire | 10 00 | 10 00 | 50 4E | 0E 00 | 41 6D 61 6E 64 61 5E 52 69 70 6C 65 79 | 20 | +----------------+-------+-------+-------+-------+----------------------------------------+--------+
Ici on a donc l'élément de donnée (0010,0010) qui correspond au nom du patient. Cet élément est de type PN (Person Name) qui est une chaîne de caractère spécialisée dans le stockage du nom des gens. La valeur fait 14 octets de long et contient le nom de la patiente : Amanda Ripley. Le prénom et le nom ont été séparés par un petit chapeau, conformément à la norme. Et comme la longueur du nom était impaire, un petit padding a été ajouté à la fin.
Les plus attentifs d'entre vous auront sans doute remarqué que j'ai paddé ma valeur avec 0x20 et non avec 0x00 ce qui est un peu inhabituel dans un format binaire... Pourquoi cette excentricité ? Et bah je vais faire durer un peu le suspense, on en reparle un tout petit peu plus loin... 😛️
Références utiles :
=> Spécification de la structure Data Element avec VR explicite 7.1-2
La seconde forme est presque identique à la première, à deux exceptions près :
Et comme pour la structure de donnée 7.1-2, il faut que la longueur de la valeur soit paire, sinon il faut « padder ». 🙃️
Exemple concret avec un Data Element contenant une petite image monochrome de 2×2 px représentant un damier :
+----------------+---------------+-------+-------+-------------+-------------+--------+ | *(7.1-1)* | Tag | VR | pad. | VL | Value | (pad?) | +================+===============+=======+=======+=============+=============+========+ | Interprétation | (7FE0,0010) | OB | | 4 | ▞ | +----------------+-------+-------+-------+-------+-------------+-------------+--------+ | Donnée binaire | E0 7F | 10 00 | 4F 42 | 00 00 | 04 00 00 00 | FF 00 00 FF | | +----------------+-------+-------+-------+-------+-------------+-------------+--------+
Ici on retrouve donc l'élément (7FE0,0010) (Pixel Data) de type OB (Other Byte), soit un tableau d'octets. La valeur fait 4 octets de long et contient les 4 pixels qui composent notre image. Dans le cas présent la longueur de la valeur étant paire, on a pas besoin d'ajouter de padding.
Références utiles :
=> Spécification de la structure Data Element avec VR explicite 7.1-1
Il est important de noter que la longueur totale de la structure de donnée est toujours paire. Lorsque la valeur a une longueur impaire, on lui ajoute un padding pour compenser.
La manière de faire le padding dépend du type de donnée (VR) et est définie type par type dans la norme DICOM. Par exemple pour le nom d'un patient (VR = PN) on utilise des espaces (0x20), pour un tableau d'octet (VR = OB) on utilise des zéros (0x00).
À noter également que pour certains types de données (surtout ceux basés sur des chaînes de caractères), la norme autorise à « padder » avec plus de 1 octet, et de manière indifférenciée avant ou après la valeur (en gros les espaces en début et en fin de chaîne ne sont pas significatifs et doivent être ignorés).
Enfin, certaines implémentations sont buggées et il n'est pas rare de se retrouver avec des chaînes de caractères « paddées » avec des zéros (0x00) en lieu et place des espaces (0x20)... Vous voilà prévenus ! 😅️
Chaque élément de donnée (qui est donc encodée dans une structure de donnée « Data Element ») se voit associer un tag qui sert à l'identifier.
Comme on l'a vu un peu plus tôt, les tags sont composés de deux éléments :
Dans la spécification DICOM, vous retrouverez généralement les tags écrits sous la forme d'un tuple de deux nombres hexadécimaux : (GGGG,EEEE), où les G représentent le numéro de groupe et les E le numéro d'élément.
Par exemple le tag (0010,0010) correspond au nom du patient. Pour les plus curieux, sachez qu'il est défini dans la partie PS3.3 annexe C.2.2 de la spécification, intitulée « Patient Identification Module ». Notez au passage que cette section ne définit pas le type de la donnée (VR), il faut pour cela se référer au registre des éléments de données qui se trouve carrément dans une autre partie de la spec (la PS3.6 chapitre 6)... 😅️
=> partie PS3.3 annexe C.2.2 de la spécification | registre des éléments de données
⚠️ Warning
ATTENTION : Dans la norme, les numéros de groupe et d'éléments sont exprimés avec des entiers 16 bits non signé ordonné en big endian, le tout écrit en notation hexadécimale. Or, dans le fichier DICOM ces nombres seront écrits en little endian, il faut donc en tenir compte si vous essayez d'analyser votre fichier avec un éditeur hexadécimal.
Par exemple, le tag (7FE0,0010), qui correspond à un élément « Pixel Data », se retrouvera concrètement écrit comme ceci dans le fichier DICOM : E0 7F 10 00.
Autres informations importantes à savoir sur nos éléments de données :
Voilà, je crois qu'on a fait le tour ! 😮💨️
Références utiles :
=> Spécification des éléments de données (PS3.5 7.1) | Registre des éléments de données (PS3.6 6)
Il existe 34 types de données différents dans la spécification DICOM. Ces types sont désignés par le terme « VR » (Value Representation) dans la norme.
Bref les VR sont nombreuses et je vous laisse vous référer à la doc pour en avoir une liste exhaustive avec toutes les informations utiles :
=> Spécification des types de données (VR) (PS3.5 6.2)
Voilà à quoi ressemble notre fichier DICOM au complet lorsque l'on recolle tous ce qu'on a vu précédemment ensemble :
Pour résumer, un fichier DICOM est composé d'un entête (Meta Information) et d'un corps (Data Set) qui contiennent essentiellement des structures de données (Data Element) dont la forme exacte peut varier en fonction de la syntaxe de transfert (Transfer Syntax) utilisée et du type de donnée (VR) qu'elle contient.
C'est pas si compliqué finalement non ? 😋️
Je vais à présent vous parler de quelques documents et outils dont je me suis servi et qui me semblent essentiels si vous développez un logiciel capable de lire ou d'écrire des fichiers DICOM.
Bon évidemment le tout premier élément que je dois mentionner c'est la norme DICOM elle-même. C'est là que se trouvent toutes les informations nécessaires ; vous avez dû vous en rendre compte avec toutes les références que j'y ai faites dans cet article. 😄️
Elle se compose de 22 parties nommées « PS3.N » où N est le numéro de la partie. Chaque partie est composée de chapitres et d'annexes qui sont eux-mêmes découpés en sections. Je vous mets ci-dessous un lien vers la section PS3.1 6.1 qui liste les différentes parties de la norme avec leur nom ; ça me semble un bon point de départ pour explorer :
=> https://dicom.nema.org/medical/dicom/2024d/output/chtml/part01/chapter_6.html#sect_6.1
Dans cet article on a surtout utilisé la partie 5 (PS3.5) car c'est celle qui définit le format des structures de données, mais dans le prochain article on visitera pas mal les parties 3, 6 et 10.
Pour finir sur la norme, il faut savoir qu'elle est mise à jour plusieurs fois par ans (jusqu'à 5× par ans même) et qu'il faut donc bien faire attention de pas se référer à une version complètement obsolète (on a vite fait de se retrouver sur une révision de 2013 au détour d'une recherche sur les zinterwebz 😉️). Pour cet article je me suis basé sur la version 2024d et tous les liens pointent donc vers cette version. Si vous voulez être sûr de lire la révision la plus récente de la norme, vous pouvez remplacer la version dans l'URL par « current ».
Vous trouverez la liste de toutes les révisions par là :
=> https://dicom.nema.org/medical/dicom/
Après la norme DICOM, l'outil qui m'a le plus aidé à comprendre les fichiers DICOM est l'éditeur hexadécimal ImHex. C'est simplement le meilleur que j'ai trouvé pour ce job. Il fournit un puissant éditeur de pattern qui lui permet de mettre en évidence et de décrire chaque partie du fichier. Dans le cas des DICOM c'est supporté de base donc quand vous en ouvrez un, il vous propose directement de l'analyser.
Voici une petite capture d'écran pour que vous puissiez voir à quoi ça ressemble :
=> Capture d'écran de ImHex avec un fichier DICOM en cours de visualisation
En haut à gauche on peut voir les données binaires du fichier DICOM représentées en hexadécimal et en ASCII, le tout bariolé de couleur. Chaque zone de couleur correspond à une partie ou à un champ de l'une des structures de données du DICOM.
La partie en bas à gauche permet de se promener dans le DICOM un peu de la même façon que l'inspecteur HTML intégré aux navigateurs Web. En cliquant sur un élément il est directement mis en évidence dans l'éditeur hexa, et les colonnes « Type » et « Value » nous informent sur le type des données et sur leur interprétation. C'est juste indispensable pour analyser un fichier ! 👌️
En haut au milieu on a un inspecteur de donnée qui permet de nous aider à interpréter ce que l'on pointe. Par exemple si je clique sur un octet dans l'éditeur hexa, il va pouvoir me dire ce que ça donne si on considère cette valeur en tant que nombre 8 / 16 / 24 / 32 bits, signé ou non signé, en big ou en little endian, etc.
Enfin sur la partie supérieure droite, on a l'éditeur de pattern... Pas de panique, vous n'avez pas trop à vous en préoccuper pour le moment : il s'agit du code qui a été automatiquement chargé pour analyser le DICOM. Vous pouvez le modifier pour vos propres besoins, notamment si vous voulez analyser des types de fichiers qui ne sont pas (encore ?) supportés par ImHex, mais ce n'est pas nécessaire pour le DICOM. Je ferais certainement un article à ce sujet car je trouve ça super intéressant ! 😁️
Si vous voulez tester cet outil, sachez qu'il est disponible pour Linux, macOS et Windows, et qu'il est bien sûr open source. Je vous mets les liens ci-dessous :
=> Site officiel de ImHex | Page GitHub de ImHex | Page FlatHub de ImHex pour l'installer sous Linux
Amide est un petit viewer de fichier DICOM en GTK 2. Il est surtout conçu pour afficher les séries d'images issues de scanners et d'IRM. C'est assez pratique de l'avoir pour afficher nos DICOM et pour pouvoir s'assurer qu'ils fonctionnent bien.
=> Capture d'écran d'Amide récupérée sur le site officiel du projet
Amide est disponible pour Linux, macOS et Windows. Il est présent dans les dépôts Debian et Ubuntu, vous pouvez donc l'installer sur ces systèmes avec la commande :
sudo apt install amide
Pour les autres vous pouvez vous référer au site officiel :
=> Site officiel du viewer de DICOM Amide
Je vous rajoute en vrac quelques autres outils que j'ai aussi utilisés :
=> DICOM Standard Browser | Gimp | pydicom | dicomParser
Et un petit dernier que je n'ai pas utilisé personnellement mais qui est une telle référence que je ne pouvais pas ne pas le citer :
=> DCMTK
Quand on travaille sur le format DICOM, l'une des choses un peu compliquée c'est de mettre la main sur un jeu de donnée sur lequel s'exercer. J'ai eu la chance de ne pas avoir ce souci car notre client nous a partagé quelques séries de scanners anonymisés, mais je ne peux malheureusement pas vous les partager.
J'ai cependant pu mettre la main sur des jeux de données en accès libre aux adresses suivantes :
=> https://www.aliza-dicom-viewer.com/download/datasets | https://medimodel.com/sample-dicom-files/ | https://www.rubomedical.com/dicom_files/
📝️ Note:
NOTE : pour le premier lien, j'ai constaté que les fichiers de certains jeux de données ne contenaient que la partie Data Set (l'entête est manquant), ce qui empêche la plupart des outils de les ouvrir...
Si vous tombez sur des fichiers qui ne semblent pas fonctionner vous pouvez vérifier avec ImHex si l'entête est présent, et dans le cas contraire passer à une autre série de fichiers. Si vous êtes motivés, vous pouvez aussi compléter les fichiers en écrivant vous-même les entêtes manquants... 😅️
Comme vous avez pu le voir, le format DICOM c'est pas si compliqué mais c'est quand même un sujet assez vaste. Dans le présent article j'ai essayé de vous montrer la structure globale d'un fichier DICOM de manière plutôt théorique ; dans le prochain article on entrera davantage dans la pratique. On verra notamment comment confectionner à la main notre propre fichier DICOM, artisanal, bio et tout et tout... 😛️
Je vous donne donc rendez-vous dans 2 semaines environ pour la suite. À bientôt ! 😉️
L'image de couverture est dérivée d'une image médicale de Nevit Dilmen et est diffusée sous licence CC-BY-SA.
=> image médicale de Nevit Dilmen
=> 🏠 Accueil This content has been proxied by September (3851b).Proxy Information
text/gemini