Il y a quelques jours, j'ai passé une IRM. Ils m'ont glissé dans un grand tube et pendant 15 minutes, la machine autour de moi a bourdonné, bourdonné et cliqué. A la fin de l'examen, j'ai reçu un CD contenant les données. Que fait un bon développeur dans une telle situation ? Bien sûr, dès leur retour chez eux, ils commencent à examiner les données et à réfléchir à la manière dont ils pourraient les extraire à l'aide de Python .
Le CD contenait un tas de DLL, un EXE et quelques fichiers supplémentaires pour une visionneuse Windows, qui sont évidemment inutiles sous Linux. Le point crucial se trouvait dans un dossier nommé DICOM. Il contenait une série de fichiers sans extension répartis dans huit dossiers. ChatGPT m'a aidé à comprendre que DICOM est un format standard généralement utilisé pour stocker les images IRM, CT et radiographiques. Il s'agit essentiellement d'un simple format d'emballage contenant des images en niveaux de gris ainsi que des métadonnées.
Le code suivant permet d'exporter facilement les fichiers au format PNG :
import os import pydicom from PIL import Image import sys def save_image(pixel_array, output_path): if pixel_array.dtype != 'uint8': pixel_array = ((pixel_array - pixel_array.min()) / (pixel_array.max() - pixel_array.min()) * 255).astype('uint8') img = Image.fromarray(pixel_array) img.save(output_path) print(f"Saved image to {output_path}") def process_dicom_directory(dicomdir_path, save_directory): if not os.path.exists(save_directory): os.makedirs(save_directory) dicomdir = pydicom.dcmread(dicomdir_path) for patient_record in dicomdir.patient_records: if 'PatientName' in patient_record: print(f"Patient: {patient_record.PatientName}") for study_record in patient_record.children: if 'StudyDate' in study_record: print(f"Study: {study_record.StudyDate}") for series_record in study_record.children: for image_record in series_record.children: if 'ReferencedFileID' in image_record: file_id = list(map(str, image_record.ReferencedFileID)) file_id_path = os.path.join(*file_id) print(f"Image: {file_id_path}") image_path = os.path.join(os.path.dirname(dicomdir_path), file_id_path) modified_filename = '_'.join(file_id) + '.png' image_save_path = os.path.join(save_directory, modified_filename) try: img_data = pydicom.dcmread(image_path) print("DICOM image loaded successfully.") save_image(img_data.pixel_array, image_save_path) except FileNotFoundError as e: print(f"Failed to read DICOM file at {image_path}: {e}") except Exception as e: print(f"An error occurred: {e}") if __name__ == "__main__": if len(sys.argv) < 3: print("Usage: python dicomsave.py <path_to_DICOMDIR> <path_to_save_images>") sys.exit(1) dicomdir_path = sys.argv[1] save_directory = sys.argv[2] process_dicom_directory(dicomdir_path, save_directory)
Le code attend deux paramètres. Le premier est le chemin d'accès au fichier DICOMDIR et le second est un répertoire dans lequel les images seront enregistrées au format PNG.
Pour lire les fichiers DICOM, nous utilisons la bibliothèque Pydicom . La structure est chargée à l'aide de la fonction pydicom.dcmread , à partir de laquelle les métadonnées (telles que le nom du patient) et les études contenant les images peuvent être extraites. Les données d'image peuvent également être lues avec dcmread . Les données brutes sont accessibles via le champ pixelarray , que la fonction save_image convertit en PNG. Ce n'est pas trop compliqué.
Le résultat est des images en niveaux de gris de quelques carrés (généralement 512 x 512), qui peuvent être facilement utilisées ultérieurement.
Pourquoi ai-je pensé qu’il était important d’écrire cette nouvelle sur mon aventure au format DICOM ? Lorsqu'un programmeur entend parler du traitement de données médicales, il peut penser que c'est quelque chose de sérieux, quelque chose que seules les universités et les instituts de recherche peuvent gérer (du moins, c'est ce que je pensais). Comme vous pouvez le voir, nous parlons d'images simples en niveaux de gris, très faciles à traiter et idéales pour des choses comme le traitement des réseaux neuronaux.
Considérez les possibilités que cela ouvre. Pour quelque chose comme un simple détecteur de tumeur, tout ce dont vous avez besoin est un réseau convolutif relativement simple et un nombre suffisant d’échantillons. En réalité, la tâche n’est pas très différente de la reconnaissance de chiens ou de chats sur des images.
Pour illustrer à quel point cela est vrai, permettez-moi de donner un exemple. Après une brève recherche sur Kaggle, j'ai trouvé un cahier dans lequel les tumeurs cérébrales sont classées sur la base de données IRM (images en niveaux de gris). L'ensemble de données classe les images en quatre groupes : trois types de tumeurs cérébrales et une quatrième catégorie avec des images de cerveaux sains. L'architecture du réseau est la suivante :
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓ ┃ Layer (type) ┃ Output Shape ┃ Param # ┃ ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩ │ conv2d (Conv2D) │ (None, 164, 164, 64) │ 1,664 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ max_pooling2d (MaxPooling2D) │ (None, 54, 54, 64) │ 0 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ conv2d_1 (Conv2D) │ (None, 50, 50, 64) │ 102,464 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ max_pooling2d_1 (MaxPooling2D) │ (None, 16, 16, 64) │ 0 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ conv2d_2 (Conv2D) │ (None, 13, 13, 128) │ 131,200 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ max_pooling2d_2 (MaxPooling2D) │ (None, 6, 6, 128) │ 0 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ conv2d_3 (Conv2D) │ (None, 3, 3, 128) │ 262,272 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ max_pooling2d_3 (MaxPooling2D) │ (None, 1, 1, 128) │ 0 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ flatten (Flatten) │ (None, 128) │ 0 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ dense (Dense) │ (None, 512) │ 66,048 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ dense_1 (Dense) │ (None, 4) │ 2,052 │ └─────────────────────────────────┴────────────────────────┴───────────────┘
Comme vous pouvez le voir, les premières couches sont constituées d'un sandwich de convolution et de pooling maximum, qui extrait les modèles de tumeurs, suivi de deux couches denses qui effectuent la classification. Il s'agit exactement de la même architecture que celle utilisée dans l'exemple de code CNN de TensorFlow qui utilise l'ensemble de données CIFAR-10. Je m'en souviens bien, car c'était l'un des premiers réseaux de neurones que j'ai rencontré. L'ensemble de données CIFAR-10 contient 60 000 images classées en dix classes, dont deux classes pour les chiens et les chats, comme je l'ai mentionné plus tôt. L'architecture du réseau ressemble à ceci :
Model: “sequential” _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= conv2d (Conv2D) (None, 30, 30, 32) 896 _________________________________________________________________ max_pooling2d (MaxPooling2D) (None, 15, 15, 32) 0 _________________________________________________________________ conv2d_1 (Conv2D) (None, 13, 13, 64) 18496 _________________________________________________________________ max_pooling2d_1 (MaxPooling2 (None, 6, 6, 64) 0 _________________________________________________________________ conv2d_2 (Conv2D) (None, 4, 4, 64) 36928 _________________________________________________________________ flatten (Flatten) (None, 1024) 0 _________________________________________________________________ dense (Dense) (None, 64) 65600 _________________________________________________________________ dense_1 (Dense) (None, 10) 650 ================================================================= Total params: 122,570 Trainable params: 122,570 Non-trainable params: 0 _________________________________________________________________
Il est clair que nous avons affaire à la même architecture, mais avec des paramètres légèrement différents, donc reconnaître des tumeurs cérébrales sur des images IRM n'est en effet pas beaucoup plus difficile que d'identifier des chats ou des chiens sur des images. De plus, le carnet Kaggle mentionné est capable de classer les tumeurs avec une efficacité de 99 % !
Je veux juste souligner à quel point il peut être simple d'extraire et de traiter des données médicales dans certains cas et comment des problèmes qui semblent complexes, comme la détection de tumeurs cérébrales, peuvent souvent être résolus de manière assez efficace avec des solutions simples et traditionnelles.
Par conséquent, j’encourage tout le monde à ne pas craindre les données médicales. Comme vous pouvez le constater, nous avons affaire à des formats relativement simples, et en utilisant des technologies relativement simples et bien établies (réseaux convolutifs, transformateurs de vision, etc.), un impact significatif peut être obtenu ici. Si vous rejoignez un projet de santé open source pendant votre temps libre, simplement comme passe-temps et que vous parvenez à apporter ne serait-ce qu'une légère amélioration au réseau neuronal utilisé, vous pourriez potentiellement sauver des vies !