MediPy – Plate-forme de développement logiciel pour la neuro-imagerie Julien Lamy <[email protected]> Laboratoire d'imagerie et de neurosciences cognitives, équipe Imagerie in vivo Université de Strasbourg Introduction ● Historique ● ● ● Développement d'algorithmes de traitement d'images médicales depuis une quinzaine d'années, collaboration avec l'équipe TIM du LSIIT Naissance de la plate-forme Medimax Medimax ● ● ● C/C++, Gtk+ v.1, contient les travaux de nombreux chercheurs et doctorants Difficilement portable (Linux seulement) Plat de spaghetti : dépendances croisées, code dupliqué, pas d'utilisation de bibliothèques externes, … Introduction ● MediPy ● ● Refonte totale de Medimax (Python/C++) ● Python : facilité de la syntaxe pour les nouveaux arrivants ● C++ : performances Utilisation de bibliothèques tierces reconnues (ITK, VTK, NumPy, …) ● Plate-forme portable ● Modularité, indépendance traitement-visualisation ● Distribution du logiciel sous licence libre (CeCILL-B) Plan de la présentation ● ● MediPy – l'application ● Visualisation ● Traitement MediPy – la plate-forme de développement ● Structures de données ● Création d'un plugin ● Compilation ● Application dérivées ● Redistribution MediPy – l'application Visualisation & traitement MediPy – l'application Entrées & sorties ● ● Images ● Formats classiques 2D : JPEG, PNG, BMP, … ● Formats spécifiques imagerie médicale : DICOM, NIfTI, NRRD, … ● Formats spécifiques constructeurs : SMIS, Bruker, … Surfaces ● VRML (surface uniquement) ● VTK Entrées & sorties – DICOM ● Lecture d'un DICOMDIR ou d'une liste de fichiers ● Présentation hiérarchique (patient, étude, série) ● Utilisation de GDCM Visualisation ● ● Images ● 2D ou 3D (visualisation de coupes dans le volume 3D) ● « Image » au sens large : visualisation de spectres HR-MAS Surfaces ● Fonctionalités basiques ● Plan de coupe ● Pseudo-texture Visualisation – Images ● Multiplanaire ou une seule coupe Visualisation – Images ● Spectre HR-MAS (iso-contours) Visualisation – 2D ● Plusieurs calques avec table de couleurs Visualisation – 2D ● Annotations Visualisation 2D ● Plusieurs images synchronisées Visualisation – Objets 3D ● Plusieurs objets colorisés Visualisation – Objets 3D ● Pseudo-texture en fonction d'une profondeur dans une image ● Plans de coupe Traitement ● ● Fournis par des plugins (e.g. débruitage, morphologie) Accessibles ● ● Depuis l'interface graphique (pour la mise au point d'une chaîne de traitements) Depuis des scripts Python (pour les traitement par lots) Traitement – Interface Graphique ● Plugins répertoriés dans l'arborescence en haut à gauche ● Interface de la fonction en bas à gauche Traitement – Script ● Script Python avec appel direct des fonctions de MediPy ● Exemple de traitement d'un lot d'images : import import import import os sys medipy.io medipy.morphology root = sys.argv[1] for dir in os.listdir(root) : image = medipy.io.load( os.path.join(root, dir, "image.nii")) result = medipy.morphology.grayscale_dilate( image, "ball", 3) medipy.io.save(image, "result.nii") MediPy – la plate-forme de développement Structures de données, plugins, applications, redistribution Briques logicielles ● ● Langages : Python (2.5, 2.6), C++ Traitement : ITK/WrapITK, NumPy, SciPy, VTK, Cython, OpenMP, FSL (en cours) ● Visualisation : VTK, wxPython ● Déploiement : SCons, py2exe, WiX Structures de données ● ● Deux principaux types d'objet ● Image ● Objet 3D Image : découplage traitements/visualisation ● ● ● Traitement : « image » = une image Visualisation : « image » = piles d'images (calques) + mode de visualisation + annotations + outils + … Objet 3D : plus simple, traitement = visualisation Image (traitement) ● Image ● ● ● ● ● données n-dimensionnelles (numpy.ndarray) transformation linéaire espace pixel ↔ espace réel Préel = direction*taille_pixels*Ppixel+origine ● direction : matrice n×n ● taille_pixels, origine : en mm, vecteur de taille n dimensionnalité des données : e.g. un tableau de dimension 3 est il une image 3D de scalaires ou une image 2D de vecteurs ? méta-données : identifiant du patient, paramètres de séquence IRM, … C++ : utilisation des classes itk::Image et itk::VectorImage Image (traitement) ● Compatible avec NumPy/SciPy : result.data = numpy.add(image1, image2) ● Pont de/vers ITK, sans recopie des données, transfert optionnel de la propriété des données : from medipy.itk import (medipy_image_to_itk_image, itk_image_to_medipy_image) itk_input = medipy_image_to_itk_image(input, False) filter = itk.GrayscaleErodeImageFilter[...].New(...) itk_output = filter()[0] output = itk_image_to_medipy_image(itk_output, None, True) Image (visualisation) ● ● Visualisation d'une image : coupes, calques, couleurs, annotations, outils, … Trois classes principales ● Calque : Layer, dont dérivent ImageLayer et ContourLayer ● ● ● Données, table de couleur, opacité, coordonnées pixels ou physiques Coupe 2D : Slice ● Ensemble de calques (position synchronisée) ● Matrice de projection, curseur, outils ● vtkRenderer Image : widget wxPython ● Ensemble de coupes (position, zoom, calques, etc. synchronisés) ● vtkRenderWindowInteractor Objet 3D ● Basé sur un vtkDataSet ● Lien optionnel avec une image ● Texturage de plans de coupe ● Pseudo-texture Création d'un plugin ● ● Un plugin peut ajouter ● Fonctions de traitement ● Outils ● Autres (duck-punching) Plugin ↔ paquet Python Création d'une fonction ● Deux aspects ● ● Traitement ● Bibliothèques utilisables ● Ponts entre les représentations Interface graphique ● Définition de l'interface graphique reliée à une fonction ● Représentation graphique des paramètres ● Contraintes sur les valeurs Création d'une fonction – Traitements ● ● ● Bibliothèques utilisables ● ITK : C++, Python (WrapITK) ● NumPy, SciPy : Python, C++ ● Pont en cours vers FSL (C++, open source mais non libre) Ponts ● medipy.base.Image ↔ itk.Image ↔ itk::Image ● medipy.base.Image ↔ numpy.ndarray ↔ itk::Image Possibilité de tout mélanger dans une fonction, les ponts opèrent tous dans les deux sens Création d'une fonction – GUI ● Lien fort entre la fonction et son interface graphique ● ● Utilisation de la docstring Syntaxe simple ● XML def erode(input, shape, radius) : """ Gray-scale erosion of an image. <gui> <item name="input" type="Image" label="Input"/> <item name="shape" type="Enum" label="Shape" initializer="('ball', 'box','cross')"/> <item name="radius" type="Int" label="Radius" initializer="1"/> <item name="output" type="Image" label="Output" initializer="output=True" role="return"/> </gui> """ Création d'une fonction – GUI ● Éléments XML <gui><item name="input" type="Image" label="Input"/></gui> ● ● ● gui : contient la description de l'interface graphique item : décrit un des paramètres (ou une des valeurs de retour) de la fonction. Attributs de l'élément item : ● name : nom du paramètre de la fonction ● type : type du contrôle utilisé pour représenter ce paramètre ● label : nom affiché ● role : input, output ou return, sémantique du paramètre (optionnel) ● initializer : expression pour initialiser le contrôle (optionnel) ● tooltip : optionnel Création d'une fonction – GUI ● Types de contrôles et paramètres ● Bool : valeur ● Enum : valeur, liste de choix ● ● ● ● Int, Float, FloatInterval : valeur, intervalle String : valeur File, Directory : valeur, filtre de noms, lecture ou écriture Image, Object3D : valeur, liste de choix, création d'un nouvel objet Création d'une fonction – GUI ● Dépendance entre contrôles ● ● Syntaxe : ${nom_du_parametre} Exemple : limiter un entier à la dynamique d'une image <gui> <item name="input" type="Image" label="Input"/> <item name="value" type="Int" label="Value" initializer="range=(${input}.data.min(), ${input}.data.max())"/> <item name="output" type="Image" label="Output" role="output" initializer="output=True" /> </gui> Création d'un outil ● ● Outil (e.g. position, contraste, zoom) ● Sur la visu 2D ou 3D ● Clavier (appui sur une touche) ou souris (clic, glissement) ● Classe dérivant de KeyboardTool ou MouseTool Fonctions communes clavier et souris : ● select : sélection de l'outil (e.g. affiche un dialogue) ● deselect : déselection de l'outil Création d'un outil ● ● Fonctions outils clavier : ● press ● release Fonctions outils souris : ● start_interaction ● dispatch_interaction ● stop_interaction Création d'un plugin ● ● Paquet Python classique Présence d'un module api.py décrivant les fonctions exportées : from from from from ● ● binary import erode as binary_erode binary import dilate as binary_dilate grayscale import erode as grayscale_erode grayscale import dilate as grayscale_dilate Intégration automatique des fonctions déclarées dans le fichier api.py dans le menu Chemin de recherche des plugins : variable d'environnement MEDIPY_PLUGINS_PATH Compilation ● Utilisation de SCons ● Utilisation de variant_dir par défaut ● Un SConstruct par répertoire ● Builders spécifiques : ● CythonModule("my_module", ["foo.pyx", "bar.pyx"]) ● VTKPythonModule("my_module", ["vtkFoo", "vtkBar"]) ● WrapITKPythonModule Compilation – WrapITK ● Builder plus complexe, dû aux templates C++ ● Très fortement inspiré du processus CMake ● Instanciations de chaque classe : >>> get_instantiations("itk::Image", ["float", "short"], [2, 3]) [('Image', 'float', 2), ('Image', 'float', 3), ('Image', 'short', 2), ('Image', 'short', 3)] ● Appel du builder env.WrapITKPythonModule( "my_module", [["itk::Foo", foo_instantiations], ["itk::Bar", bar_instantiations]]) Applications dérivées ● ● Facilité de réaliser des applications dérivées ● Inclusion d'un nombre restreint de fonctions ● GUI spécifique Exemples ● Lecture randomisée d'images, annotation de lésions de SEP ● Planificateur de TMS robotisée Applications dérivées Applications dérivées Applications dérivées ● Classes simplifiant la réalisation d'applications dérivées ● ● Recherche des resources dans l'application courante ou dans MediPy Simplification de la gestion des fichiers XRC, création automatique des membres correspondant aux widgets class MainFrame(medipy.gui.base.Frame): class UI(medipy.gui.base.UI): pass def __init__(self, parent=None, *args, **kwargs): self.ui = MainFrame.UI() xrc_file = medipy.base.find_resource("my_file.xrc") medipy.gui.base.Frame.__init__( self, xrc_file, ui=self.ui, self.ui.controls, parent, *args, **kwargs) Applications dérivées class MainFrame(medipy.gui.base.Frame): class UI(medipy.gui.base.UI): def __init__(self): self.t1_image = None self.flair_image = None self.slice = None self.lesions = None self.next_image = None self.controls = ["t1_image", "flair_image", "slice", "lesions", "next_image"] medipy.gui.base.UI.__init__(self) def __init__(self, parent=None, *args, **kwargs): pass Redistribution ● Pour l'instant, seulement sous Windows ● Deux étapes ● ● Création d'une version redistribuable (choix des plugins, dépendances, intégration de l'interpréteur Python) Création d'un installeur Redistribution ● Création d'une version redistribuable ● Utilisation de py2exe ● Prise en compte des composants et des plugins à inclure import os.path import shutil from medipy.deployement import setup includes = ["itk", "medipy.itk", "medipy.gui.control", "medipy.segmentation"] setup.setup("TMSPlanification","tms_planification.py", includes) Redistribution ● Création d'un installeur ● Utilisation de WiX ● Enrobage Python autour de heat, candle et light from medipy.deployement import build_msi build_msi("MyKillerMediPyApplication") Conclusion ● ● ● ● MediPy : logiciel de traitement d'images médicales et plate-forme de développement ● Traitement d'images ● Visualisation ● Création d'application spécialisées Licence CeCILL-B (BSD-like) Disponible sur Google Code : http://code.google.com/p/medipy/ Fonctionne sous Linux et Windows Perspectives ● ● ● Portage sur MacOS Amélioration de la visu 2D pour les données nonscalaires (IRMf, DTI) Connexion aux sources de données (PACS, XNAT, Shanoir, …) Merci de votre attention !