Blog ENI : Toute la veille numérique !
Accès illimité 24h/24 à tous nos livres & vidéos ! 
Découvrez la Bibliothèque Numérique ENI. Cliquez ici
💥 Du 22 au 24 novembre : Accès 100% GRATUIT
à la Bibliothèque Numérique ENI. Je m'inscris !

Accéder aux terminologies médicales et à UMLS

Introduction

Dans ce chapitre, nous verrons comment importer en Python les principales terminologies médicales à partir d’UMLS, en utilisant PyMedTermino, un module permettant d’intégrer ces notions à Owlready. Nous verrons également comment relier ces terminologies entre elles à l’aide des concepts unifiés (CUI) d’UMLS.

UMLS

UMLS (Unified Medical Language System) est une collection de plus de 400 terminologies du domaine biomédical. UMLS intègre également des passerelles entre les différentes terminologies. UMLS est produit par la National Library of Medicine (NLM) aux États-Unis, et peut être téléchargé gratuitement après inscription en ligne à l’adresse suivante : https://www.nlm.nih.gov/research/umls/licensedcontent/umlsknowledgesources.html

Attention cependant, certaines terminologies incluses dans UMLS ne sont pas librement utilisables en France. C’est notamment le cas de la SNOMED CT, qui est soumise à une licence payée soit par les états, soit par des institutions ou entreprises. Actuellement, et contrairement au Canada ou à la Belgique, la France n’a pas pris de licence, par conséquent l’utilisation de la SNOMED CT en France est restreinte aux entreprises ou institutions ayant achetées une licence.

À l’heure où j’écris ces lignes, la version la plus récente est la 2018AB (Full Release (umls-2018AB-full.zip)). Pour utiliser UMLS avec Owlready et PyMedTermino, vous devez télécharger ce fichier (environ 4,5 Go). En revanche, vous n’avez pas besoin de le décompresser (PyMedTermino s’en chargera).

Importer des terminologies à partir d’UMLS

PyMedTermino est un module Python permettant d’accéder aux terminologies médicales. La version 2 de PyMedTermino est directement incluse dans Owlready, vous n’avez donc pas besoin de l’installer.

Le module owlready2.pymedtermino2 permet d’importer tout ou partie d’UMLS dans un quadstore Owlready, via la fonction globale import_umls() :

import_umls("/chemin/vers/umls-2018AB-full.zip", 
       terminologies = [...], 
       langs = [...]) 

Le premier paramètre de la fonction est le chemin vers le fichier ZIP contenant UMLS, que nous avons téléchargé précédemment. Dans l’exemple ci-dessus, il s’agit d’un chemin local, mais il peut s’agir d’un chemin complet, par exemple /home/jiba/telechargements/umls2018AB-full.zip ou C:\\Downloads\\umls-2018AB-full.zip, selon l’endroit où vous avez enregistré ce fichier.

Le second paramètre est la liste des terminologies à importer. S’il est manquant, elles seront toutes importées. La page Internet suivante liste les terminologies disponibles dans UMLS (420 terminologies actuellement) et les codes associés : https://www.nlm.nih.gov/research/umls/sourcereleasedocs/index.html

Le troisième paramètre indique les langues à importer, par exemple...

Importer la CIM10 en français

UMLS ne contient pas la CIM10 en version française, bien qu’une traduction officielle existe. Le module owlready2.pymedtermino2.icd10_french permet d’importer la traduction de l’ATIH.

>>> from owlready2.pymedtermino2.icd10_french import *  
>>> import_icd10_french()  
>>> default_world.save() 

Le module télécharge automatiquement les données à partir du site web de l’ATIH. Il est aussi possible de passer en paramètre le chemin vers le fichier ZIP, que l’on peut télécharger à partir de l’adresse suivante : https://www.atih.sante.fr/plateformes-de-transmission-et-logiciels/logiciels-espace-detelechargement/id_lot/456

Charger les terminologies après importation

Bien évidemment, les prochaines fois que nous voudrons utiliser les terminologies importées, nous n’aurons plus besoin d’appeler les fonctions d’importation. Nous n’aurons désormais besoin que des trois lignes suivantes :

>>> from owlready2 import *  
>>> default_world.set_backend(filename = "pymedtermino.sqlite3") 
>>> PYM = get_ontology("http://PYM/").load() 

Ces trois lignes rechargent le quadstore (avec la méthode set_backend()) et l’ontologie PyMedTermino (PYM). N’oubliez pas l’appel à load(), celui-ci est nécessaire pour charger les méthodes Python associées à l’ontologie.

Utiliser la CIM10 (ICD10)

PyMedTermino permet d’accéder à toutes les terminologies avec la même interface. Nous étudierons ici les terminologies CIM10 et SNOMED CT, mais les fonctionnalités sont similaires pour toutes les autres.

La Classification Internationale des Maladies, 10ème édition (CIM10, en anglais International Classification of Diseases, 10 th revision ou ICD10) est une classification de maladies qui est très utilisée. Elle est utilisée en particulier en France pour le codage PMSI (Programme de Médicalisation des Systèmes d’Information hospitaliers) dans les hôpitaux et sert à la T2A (tarification à l’activité). La CIM10 comprend environ 12 000 concepts (environ le double dans la version française, qui est plus détaillée). Elle est organisée selon un arbre composé de 21 concepts racines correspondant aux grands chapitres de maladies : cancer, maladies infectieuses, maladies cardiovasculaires, maladies pulmonaires...

Nous pouvons obtenir la terminologie CIM10 anglaise (dont le code est « ICD10 ») de la manière suivante :

>>> ICD10 = PYM["ICD10"]  
 
>>> ICD10  
PYM["ICD10"] # ICD10  
 
>>> ICD10.name  
'ICD10' 

PyMedTermino affiche les concepts de la manière suivante : « terminologie["code"] # libellé du concept » (pour les concepts ayant plusieurs libellés, un seul est affiché, choisi parmi les libellés préférés).

Nous pouvons obtenir la CIM10 en français (dont le code est « CIM10 ») de la même manière :

>>> CIM10 = PYM["CIM10"]  
 
>>> CIM10  
PYM["CIM10"] # CIM10 

Les objets terminologies et concepts de terminologies sont des classes Owlready. Nous pouvons donc utiliser les méthodes des classes, par exemple la méthode subclasses() pour obtenir les classes-filles de CIM10, c’est-à-dire les 21 grands chapitres de maladies :

>>> list(CIM10.subclasses())  
[ CIM10["A00-B99"] # Certaines maladies infectieuses et parasitaires 
, CIM10["C00-D48"] # Tumeurs  
...] 

Cependant, PyMedTermino propose des attributs...

Utiliser la SNOMED CT

La SNOMED CT (Systematized Nomenclature of Medicine - Clinical Terms) est une terminologie médicale plus riche et plus complète que la CIM10. Attention, comme mentionné précédemment, la SNOMED CT n’est pas utilisable librement en France, et il n’existe pas de traduction française complète.

De la même manière que pour la CIM10, nous pouvons accéder à la terminologie SNOMED CT et à ses concepts, ainsi qu’aux libellés, aux concepts parents, enfants, ancêtres et descendants.

>>> SNOMEDCT_US = PYM["SNOMEDCT_US"]  
 
>>> SNOMEDCT_US["45913009"]  
SNOMEDCT_US["45913009"] # Laryngitis  
 
>>> SNOMEDCT_US["45913009"].parents  
[ SNOMEDCT_US["129134004"] # Inflammatory disorder of  
                            upper respiratory tract  
, SNOMEDCT_US["363169009"] # Inflammation of specific body organs 
, SNOMEDCT_US["60600009"] # Disorder of the larynx  
]  
 
>>> SNOMEDCT_US["45913009"].children  
[ SNOMEDCT_US["1282001"] # Perichondritis of larynx  
, SNOMEDCT_US["14969004"] # Catarrhal laryngitis  
, SNOMEDCT_US["17904003"] # Hypertrophic laryngitis  
...] 

La SNOMED CT définit des libellés (label) mais aussi des synonymes (synonyms) :

>>> SNOMEDCT_US["45913009"].label  
['Laryngitis']  
 
>>> SNOMEDCT_US["45913009"].synonyms  
['Laryngitis (disorder)'] 

Contrairement à la CIM10, la SNOMED CT autorise un concept à...

Utiliser les concepts unifiés (CUI) d’UMLS

UMLS définit des concepts unifiés (CUI, Concept Unique Identifier) permettant de naviguer entre terminologies. Ces CUI peuvent être importés avec PyMedTermino, en utilisant le code spécial de terminologie « CUI ». Attention, lorsque seules certaines terminologies sont importées, PyMedTermino n’importe que les CUI correspondantes. Si vous voulez avoir accès à l’ensemble des CUI, il faudra importer la totalité d’UMLS.

>>> CUI = PYM["CUI"] 

La propriété unifieds permet d’obtenir le ou les concepts unifiés associés à un concept de n’importe quelle terminologie (ici, nous avons pris la CIM10) :

>>> CIM10["I10"]  
CIM10["I10"] # Hypertension essentielle (primitive)  
 
>>> CIM10["I10"].unifieds  
[CUI["C0085580"] # Essential hypertension  
] 

Les concepts unifiés disposent tous d’un label, et de synonymes (issues des terminologies importées, et donc dépendant du choix de celles-ci) :

>>> CUI["C0085580"].synonyms  
['Essential (primary) hypertension',  
'Idiopathic hypertension',  
'Primary hypertension',  
'Systemic primary arterial hypertension',  
'Essential hypertension...

Transcoder entre terminologies

L’opérateur >> permet de convertir une terminologie vers une autre, en utilisant les liens existants dans UMLS (ou les identifiants pour les conversions entre CIM10 française et anglaise). Cette opération est souvent appelée transcodage, mise en correspondance ou mapping. Par exemple, nous pouvons transcoder le concept « E11 » de la CIM10 française vers la version anglaise, de la manière suivante :

>>> CIM10["E11"]  
CIM10["E11"] # Diabète sucré de type 2 
>>> CIM10["E11"] >> ICD10  
Concepts([  
ICD10["E11"] # Non-insulin-dependent diabetes mellitus  
]) 

Lorsqu’il n’existe pas de concept aussi précis dans la terminologie d’arrivée, un concept (ou plusieurs) plus général est retourné. Dans l’exemple suivant, le concept « I21.000 » existe dans la CIM10 française de l’ATIH, mais pas dans la CIM10 originelle. C’est pourquoi le concept plus général « I21.0 » est renvoyé :

>>> CIM10["I21.000"]  
CIM10["I21.000"] # Infarctus (transmural aigu) du myocarde  
                  (de la paroi antérieure), prise en charge  
         ...

Manipuler des ensembles de concepts

La classe PYM.Concepts permet de créer un ensemble de concepts. Cette classe hérite de la classe set de Python (voir chapitre Le langage Python : adoptez un serpent !, section Les ensembles (set)), et possède donc les mêmes méthodes pour calculer l’intersection, l’union, la soustraction..., de deux ensembles. Elle ajoute des méthodes spécifiques aux terminologies, par exemple, la méthode lowest_common_ancestors() permet de calculer le ou les plus proches ancêtres communs à plusieurs concepts :

>>> PYM.Concepts([CIM10["E11.1"], CIM10["E12.0"]]).lowest_common_ancestors() 
Concepts([  
   CIM10["E10-E14"] # Diabète sucré  
]) 

Cette méthode est pratique pour généraliser plusieurs concepts et les regrouper en un seul, de plus haut niveau.

La méthode find() permet de chercher le premier concept d’un ensemble qui est un descendant d’un concept donné (lui-même compris). Par exemple, nous pouvons rechercher dans un ensemble de 4 concepts la présence d’un concept cardiaque :

>>> cs = PYM.Concepts([ SNOMEDCT_US["49260003"], SNOMEDCT_US["371438008"], 
                       SNOMEDCT_US["373137001"], SNOMEDCT_US["300562000"] ]) 
>>> cs  
Concepts([  
SNOMEDCT_US["300562000"] # Genitourinary tract problem  
, SNOMEDCT_US["373137001"]...

Importer toutes les terminologies d’UMLS

Lorsque le paramètre terminologies est absent, la fonction import_umls() importe toutes les terminologies présentes dans UMLS. Nous pouvons donc importer tout UMLS ainsi (attention, cela demande au moins 20 Go d’espace disque, 16 Go de mémoire vive, et près d’une heure) :

>>> from owlready2 import *  
>>> from owlready2.pymedtermino2 import *  
>>> from owlready2.pymedtermino2.umls import *  
>>> default_world.set_backend(filename = "pymedtermino.sqlite3", 
...                            sqlite_tmp_dir = "/home/jiba/tmp") 
>>> import_umls("umls-2018AB-full.zip")  
>>> PYM = get_ontology("http://PYM/").load() 

Notez que, lors de l’appel à la méthode set_backend(), nous avons ajouté le paramètre optionnel sqlite_tmp_dir, qui indique un chemin vers un répertoire où stocker les fichiers temporaires volumineux (voir chapitre Gestion du texte : annotations, langue, recherche - section Charger DBpedia).

Ensuite, pour rechercher des concepts dans toutes les terminologies à la fois, la méthode PYM.search() peut être utilisée :

>>> PYM.search("hypertension*")  
[ CIM10["O10-O16"]  ...

Exemple : relier l’ontologie des bactéries à UMLS

Nous pouvons à présent reprendre l’ontologie des bactéries et la relier à l’UMLS. Pour cela, nous créerons des relations entre les concepts de cette ontologie et les concepts unifiés (CUI) d’UMLS. Comme il s’agit de classes, nous utiliserons les propriétés de classe d’Owlready (voir chapitre Constructeurs et restrictions, propriétés de classes, section Restrictions comme propriétés de classe).

Les lignes de code suivantes permettent de relier les trois classes de bactéries (Pseudomonas, Streptocoque et Staphylocoque) aux CUI correspondants (que nous avons recherchés avec search()). Ces relations sont placées dans une nouvelle ontologie, nommée bacterie_umls.owl.

>>> onto = get_ontology("bacterie.owl").load()  
>>> onto_bacterie_umls = get_ontology("http://lesfleursdunormal.fr/ \ 
                                       static/_downloads/bacterie_umls.owl") 
 
>>> CUI = PYM["CUI"]  
 
>>> with onto_bacterie_umls:  
...     onto.Pseudomonas  .mapped_to = [ CUI["C0033808"] ]  
...     onto.Streptocoque .mapped_to = [ CUI["C0038402"]...

Exemple : un navigateur multiterminologique

La consultation des terminologies médicales est tout à fait possible dans un terminal Python avec PyMedTermino, cependant cela s’avère vite laborieux. Nous allons donc construire un navigateur minimaliste multiterminologique permettant à la fois de rechercher des concepts par mots-clefs et de naviguer parmi eux. Il utilisera le module Python Flask pour générer un site web dynamique (voir chapitre Accéder aux ontologies en Python, section Exemple : créer un site web dynamique à partir d’une ontologie) et intégrera toutes les terminologies importées dans PyMedTermino.

Le programme suivant décrit sa mise en œuvre :

# Fichier navigateur_termino.py 
from owlready2 import *  
default_world.set_backend(filename = "pymedtermino.sqlite3") 
PYM = get_ontology("http://PYM/").load()  
 
from flask import Flask, url_for, request  
app = Flask(__name__)  
 
def repr_concept(concept):  
   return """[<a href="%s">%s:%s</a>] %s""" % (  
       url_for("page_concept", iri = concept.iri),  
       concept.terminology.name,  
       concept.name,  
       concept.label.first() )  
 
def repr_relations(entity, border = False):  
   if border: html = """<table style="border: 1px solid #aaa;">"""  
   else:      html = """<table>"""  
   for Prop in entity.get_class_properties():  ...