Partager via


Utilisez les API standard DICOMweb avec Python

Cet article explique comment utiliser le service DICOMweb à l’aide de Python et d’exemples de fichiers .dcm DICOM®.

Utilisez ces exemples de fichiers :

  • blue-circle.dcm
  • dicom-metadata.csv
  • green-square.dcm
  • red-triangle.dcm

Les noms de fichier, studyUID, seriesUID et instanceUID des exemples de fichiers DICOM sont les suivants :

Fichier StudyUID SeriesUID InstanceUID
green-square.dcm 1.2.826.0.1.3680043.8.498.13230779778012324449356534479549187420 1.2.826.0.1.3680043.8.498.45787841905473114233124723359129632652 1.2.826.0.1.3680043.8.498.12714725698140337137334606354172323212
red-triangle.dcm 1.2.826.0.1.3680043.8.498.13230779778012324449356534479549187420 1.2.826.0.1.3680043.8.498.45787841905473114233124723359129632652 1.2.826.0.1.3680043.8.498.47359123102728459884412887463296905395
blue-circle.dcm 1.2.826.0.1.3680043.8.498.13230779778012324449356534479549187420 1.2.826.0.1.3680043.8.498.77033797676425927098669402985243398207 1.2.826.0.1.3680043.8.498.13273713909719068980354078852867170114

Remarque

Chacun de ces fichiers représente une instance unique et fait partie de la même étude. De plus, green-square et red-triangle font partie de la même série, alors que blue-circle fait partie d’une série distincte.

Prérequis

Pour utiliser les API DICOMweb standard, vous devez avoir une instance du service DICOM déployée. Pour plus d’informations, consultez Déployer le service DICOM à l’aide du portail Azure.

Une fois que vous avez déployé une instance du service DICOM, récupérez l’URL de votre service d’application :

  1. Connectez-vous au portail Azure.
  2. Recherchez les Ressources récentes et sélectionnez votre instance de service DICOM.
  3. Copiez l’URL du service de votre service DICOM.
  4. Si vous n’avez pas de jeton, consultez Obtenir le jeton d’accès pour le service DICOM à l’aide d’Azure CLI.

Pour ce code, vous accédez à un service Azure en préversion publique. Il est important que vous ne chargiez pas d’informations de santé privées (PHI).

Utiliser le service DICOM

DICOMweb standard utilise fortement les requêtes HTTP multipart/related combinées avec des en-têtes d’acceptation spécifiques à DICOM. Les développeurs familiarisés avec d’autres API REST trouvent souvent l’utilisation de DICOMweb standard difficile. Toutefois, une fois qu’il est opérationnel, il est facile à utiliser. Il y a juste un délai d’adaptation pour commencer.

Importer les bibliothèques Python

Tout d’abord, importez les bibliothèques Python nécessaires.

Nous implémentons cet exemple à l’aide de la bibliothèque synchrone requests. Pour la prise en charge asynchrone, envisagez d’utiliser httpx ou une autre bibliothèque asynchrone. En outre, nous importons deux fonctions de prise en charge de urllib3 pour prendre en charge l’utilisation des requêtes de multipart/related.

En outre, nous importons DefaultAzureCredential pour nous connecter à Azure et obtenir un jeton.

import requests
import pydicom
from pathlib import Path
from urllib3.filepost import encode_multipart_formdata, choose_boundary
from azure.identity import DefaultAzureCredential

Configurer des variables définies par l’utilisateur

Remplacez toutes les valeurs de variable encapsulées dans { } par vos propres valeurs. En outre, vérifiez que toutes les variables construites sont correctes. Par exemple, base_url est construit à l’aide de l’URL du service, puis ajouté avec la version de l’API REST utilisée. L’URL du service de votre service DICOM est la suivante : https://<workspacename-dicomservicename>.dicom.azurehealthcareapis.com. Vous pouvez utiliser le portail Azure pour accéder au service DICOM et obtenir votre URL de service. Vous pouvez également consulter la Documentation sur le contrôle de version des API pour le service DICOM pour plus d’informations sur le contrôle de version. Si vous utilisez une URL personnalisée, vous devez remplacer cette valeur par votre propre URL.

dicom_service_name = "{server-name}"
path_to_dicoms_dir = "{path to the folder that includes green-square.dcm and other dcm files}"

base_url = f"{Service URL}/v{version}"

study_uid = "1.2.826.0.1.3680043.8.498.13230779778012324449356534479549187420"; #StudyInstanceUID for all 3 examples
series_uid = "1.2.826.0.1.3680043.8.498.45787841905473114233124723359129632652"; #SeriesInstanceUID for green-square and red-triangle
instance_uid = "1.2.826.0.1.3680043.8.498.47359123102728459884412887463296905395"; #SOPInstanceUID for red-triangle

S’authentifier auprès d’Azure et obtenir un jeton

DefaultAzureCredential permet d’utiliser différentes façons d’obtenir des jetons pour se connecter au service. Dans cet exemple, utilisez AzureCliCredential pour obtenir un jeton pour vous connecter au service. Il existe d’autres fournisseurs d’informations d’identification tels que ManagedIdentityCredential et EnvironmentCredential qui sont également possibles à utiliser. Pour utiliser AzureCliCredential, vous devez vous connecter à Azure à partir de l’interface CLI avant d’exécuter ce code. Pour plus d’informations, consultez Obtenir le jeton d’accès pour le service DICOM à l’aide d’Azure CLI. Vous pouvez également copier et coller le jeton récupéré lors de la connexion à partir de l’interface CLI.

Remarque

DefaultAzureCredential retourne plusieurs objets d’informations d’identification différents. Nous référençons le AzureCliCredential comme étant le 5ème élément de la collection retournée. Cela peut ne pas être cohérent. Si c’est le cas, supprimez les marques de commentaire de la ligne print(credential.credential). Cela liste tous les éléments. Trouvez l’index correct, en vous rappelant que Python utilise l’indexation basée sur zéro.

Remarque

Si vous n’êtes pas connecté à Azure à l’aide de l’interface CLI, cette opération échouera. Vous devez être connecté à Azure à partir de l’interface CLI pour que cela fonctionne.

from azure.identity import DefaultAzureCredential
credential = DefaultAzureCredential()

#print(credential.credentials) # this can be used to find the index of the AzureCliCredential
token = credential.credentials[4].get_token('https://dicom.healthcareapis.azure.com')
bearer_token = f'Bearer {token.token}'

Créez des méthodes de prise en charge pour prendre en charge multipart\related

Les bibliothèques Requests (et la plupart des bibliothèques Python) ne fonctionnent pas avec multipart\related d’une manière qui prenne en charge DICOMweb. En raison de ces bibliothèques, nous devons ajouter quelques méthodes pour prendre en charge l’utilisation des fichiers DICOM.

encode_multipart_related prend un ensemble de champs (dans le cas DICOM, ces bibliothèques sont généralement des fichiers dam de la partie 10) et une limite facultative définie par l’utilisateur. Il retourne à la fois le corps complet, ainsi que le content_type, qu’il peut utiliser.

def encode_multipart_related(fields, boundary=None):
    if boundary is None:
        boundary = choose_boundary()

    body, _ = encode_multipart_formdata(fields, boundary)
    content_type = str('multipart/related; boundary=%s' % boundary)

    return body, content_type

Créer une session requests

Crée une session requests, appelée client qui est utilisée pour communiquer avec le service DICOM.

client = requests.session()

Vérifier que l’authentification est configurée correctement

Appelez le point de terminaison de l’API changefeed, qui retourne une valeur 200 si l’authentification réussit.

headers = {"Authorization":bearer_token}
url= f'{base_url}/changefeed'

response = client.get(url,headers=headers)
if (response.status_code != 200):
    print('Error! Likely not authenticated!')

Charger des instances DICOM (STOW)

Les exemples suivants mettent en évidence la persistance des fichiers DICOM.

Stocker des instances à l’aide de multipart/related

Cet exemple montre comment charger un seul fichier DICOM et utilise un peu de Python pour précharger le fichier DICOM (sous forme d’octets) en mémoire. Lorsqu’un tableau de fichiers est transmis au paramètre de champs encode_multipart_related, plusieurs fichiers peuvent être chargés dans une seule PUBLICATION. Cette méthode est parfois utilisée pour charger plusieurs instances d’une série ou d’une étude complète.

Détails :

  • Path : ../studies

  • Méthode : POST

  • Headers :

    • Accept : application/dicom+json
    • Content-Type : multipart/related; type="application/dicom"
    • Authorization : « Bearer $token »
  • Corps :

    • Content-Type : application/dicom pour chaque fichier chargé, séparé par une valeur de limite

Certains langages de programmation et outils se comportent différemment. Par exemple, certains nécessitent que vous définissiez vos propres limites. Pour ces langues et outils, vous devrez peut-être utiliser un en-tête Content-Type légèrement modifié. Ces langages et outils peuvent être utilisés avec succès.

  • Content-Type : multipart/related; type="application/dicom"; boundary=ABCD1234
  • Content-Type : multipart/related; boundary=ABCD1234
  • Content-Type : multipart/related
#upload blue-circle.dcm
filepath = Path(path_to_dicoms_dir).joinpath('blue-circle.dcm')

# Read through file and load bytes into memory 
with open(filepath,'rb') as reader:
    rawfile = reader.read()
files = {'file': ('dicomfile', rawfile, 'application/dicom')}

#encode as multipart_related
body, content_type = encode_multipart_related(fields = files)

headers = {'Accept':'application/dicom+json', "Content-Type":content_type, "Authorization":bearer_token}

url = f'{base_url}/studies'
response = client.post(url, body, headers=headers, verify=False)

Stocker des instances pour une étude spécifique

Cet exemple montre comment charger plusieurs fichiers DICOM dans l’étude spécifiée. Il utilise un peu de Python pour précharger le fichier DICOM (sous forme d’octets) en mémoire.

Lorsqu’un tableau de fichiers est transmis au paramètre de champs encode_multipart_related, plusieurs fichiers peuvent être chargés dans une seule PUBLICATION. Cette méthode est parfois utilisée pour charger une série ou une étude complète.

Détails :

  • Path : ../studies/{study}
  • Méthode : POST
  • Headers :
    • Accept : application/dicom+json
    • Content-Type : multipart/related; type="application/dicom"
    • Authorization : « Bearer $token »
  • Corps :
    • Content-Type : application/dicom pour chaque fichier chargé, séparé par une valeur de limite

filepath_red = Path(path_to_dicoms_dir).joinpath('red-triangle.dcm')
filepath_green = Path(path_to_dicoms_dir).joinpath('green-square.dcm')

# Open up and read through file and load bytes into memory 
with open(filepath_red,'rb') as reader:
    rawfile_red = reader.read()
with open(filepath_green,'rb') as reader:
    rawfile_green = reader.read()  
       
files = {'file_red': ('dicomfile', rawfile_red, 'application/dicom'),
         'file_green': ('dicomfile', rawfile_green, 'application/dicom')}

#encode as multipart_related
body, content_type = encode_multipart_related(fields = files)

headers = {'Accept':'application/dicom+json', "Content-Type":content_type, "Authorization":bearer_token}

url = f'{base_url}/studies'
response = client.post(url, body, headers=headers, verify=False)

Stocker une instance unique (nonstandard)

L’exemple de code suivant montre comment charger un seul fichier DICOM. Il s’agit d’un point de terminaison d’API non standard qui simplifie le chargement d’un fichier unique en tant qu’octets binaires envoyés dans le corps d’une requête

Détails :

  • Path : ../studies
  • Méthode : POST
  • Headers :
    • Accept : application/dicom+json
    • Content-Type : application/dicom
    • Authorization : « Bearer $token »
  • Corps :
    • Contient un seul fichier DICOM sous forme d’octets binaires.
#upload blue-circle.dcm
filepath = Path(path_to_dicoms_dir).joinpath('blue-circle.dcm')

# Open up and read through file and load bytes into memory 
with open(filepath,'rb') as reader:
    body = reader.read()

headers = {'Accept':'application/dicom+json', 'Content-Type':'application/dicom', "Authorization":bearer_token}

url = f'{base_url}/studies'
response = client.post(url, body, headers=headers, verify=False)
response  # response should be a 409 Conflict if the file was already uploaded in the above request

Récupérer des instances DICOM (WADO)

Les exemples suivants mettent en évidence la récupération d’instances DICOM.

Récupérer toutes les instances dans une étude

Cet exemple récupère toutes les instances dans une seule étude.

Détails :

  • Path : ../studies/{study}
  • Méthode : GET
  • Headers :
    • Accept : multipart/related; type="application/dicom"; transfer-syntax=*
    • Authorization : « Bearer $token »

Les trois fichiers dcm que nous avons chargés précédemment font partie de la même étude. La réponse doit donc retourner les trois instances. Vérifiez que la réponse a le code d’état OK, et que les trois instances sont retournées.

url = f'{base_url}/studies/{study_uid}'
headers = {'Accept':'multipart/related; type="application/dicom"; transfer-syntax=*', "Authorization":bearer_token}

response = client.get(url, headers=headers) #, verify=False)

Utiliser les instances récupérées

Les instances sont récupérées sous forme d’octets binaires. Vous pouvez parcourir les éléments retournés et convertir les octets en un fichier que pydicom peut lire.

import requests_toolbelt as tb
from io import BytesIO

mpd = tb.MultipartDecoder.from_response(response)
for part in mpd.parts:
    # Note that the headers are returned as binary!
    print(part.headers[b'content-type'])
    
    # You can convert the binary body (of each part) into a pydicom DataSet
    #   And get direct access to the various underlying fields
    dcm = pydicom.dcmread(BytesIO(part.content))
    print(dcm.PatientName)
    print(dcm.SOPInstanceUID)

Récupérer les métadonnées de toutes les instances dans une étude

Cette requête récupère les métadonnées de toutes les instances au sein d’une seule étude.

Détails :

  • Path : ../studies/{study}/metadata
  • Méthode : GET
  • Headers :
    • Accept : application/dicom+json
    • Authorization : « Bearer $token »

Les trois fichiers .dcm que nous avons chargés font partie de la même étude. La réponse doit donc retourner les métadonnées des trois instances. Vérifiez que la réponse a un code d’état OK et que toutes les métadonnées sont retournées.

url = f'{base_url}/studies/{study_uid}/metadata'
headers = {'Accept':'application/dicom+json', "Authorization":bearer_token}

response = client.get(url, headers=headers) #, verify=False)

Récupérer toutes les instances dans une série

Cette requête récupère toutes les instances d’une seule série.

Détails :

  • Path : ../studies/{study}/series/{series}
  • Méthode : GET
  • Headers :
    • Accept : multipart/related; type="application/dicom"; transfer-syntax=*
    • Authorization : « Bearer $token »

Cette série a deux instances (green-square et red-triangle). La réponse doit donc retourner les deux instances. Vérifiez que la réponse a le code d’état OK, et que les deux instances sont retournées.

url = f'{base_url}/studies/{study_uid}/series/{series_uid}'
headers = {'Accept':'multipart/related; type="application/dicom"; transfer-syntax=*', "Authorization":bearer_token}

response = client.get(url, headers=headers) #, verify=False)

Récupérer les métadonnées de toutes les instances de la série

Cette requête récupère les métadonnées de toutes les instances d’une même série.

Détails :

  • Path : ../studies/{study}/series/{series}/metadata
  • Méthode : GET
  • Headers :
    • Accept : application/dicom+json
    • Authorization : « Bearer $token »

Cette série a deux instances (green-square et red-triangle). La réponse doit donc retourner pour les deux instances. Vérifiez que la réponse a un code d’état OK et que les deux métadonnées d’instances sont retournées.

url = f'{base_url}/studies/{study_uid}/series/{series_uid}/metadata'
headers = {'Accept':'application/dicom+json', "Authorization":bearer_token}

response = client.get(url, headers=headers) #, verify=False)

Récupérer une instance unique dans une série d’études

Cette requête récupère une instance unique.

Détails :

  • Path : ../studies/{study}/series{series}/instances/{instance}
  • Méthode : GET
  • Headers :
    • Accept : application/dicom; transfer-syntax=*
    • Authorization : « Bearer $token »

Cet exemple de code doit retourner uniquement l’instance de red-triangle. Vérifiez que la réponse a le code d’état OK, et que l’instance est retournée.

url = f'{base_url}/studies/{study_uid}/series/{series_uid}/instances/{instance_uid}'
headers = {'Accept':'application/dicom; transfer-syntax=*', "Authorization":bearer_token}

response = client.get(url, headers=headers) #, verify=False)

Récupérer les métadonnées d’une seule instance dans une série d’études

Cette requête récupère les métadonnées d’une seule instance au sein d’une seule étude et d’une seule série.

Détails :

  • Path : ../studies/{study}/series/{series}/instances/{instance}/metadata
  • Méthode : GET
  • Headers :
    • Accept : application/dicom+json
    • Authorization : « Bearer $token »

Cet exemple de code doit retourner uniquement les métadonnées de l’instance de red-triangle. Vérifiez que la réponse a le code d’état OK, et que les métadonnées sont retournées.

url = f'{base_url}/studies/{study_uid}/series/{series_uid}/instances/{instance_uid}/metadata'
headers = {'Accept':'application/dicom+json', "Authorization":bearer_token}

response = client.get(url, headers=headers) #, verify=False)

Récupérer une ou plusieurs images à partir d’une seule instance

Cette requête récupère une ou plusieurs images à partir d’une seule instance.

Détails :

  • Path : ../studies/{study}/series{series}/instances/{instance}/frames/1,2,3
  • Méthode : GET
  • Headers :
    • Authorization : « Bearer $token »
    • Accept: multipart/related; type="application/octet-stream"; transfer-syntax=1.2.840.10008.1.2.1 (Par défaut) ou
    • Accept: multipart/related; type="application/octet-stream"; transfer-syntax=* ou
    • Accept: multipart/related; type="application/octet-stream";

Cet exemple de code doit retourner la seule image de red-triangle. Vérifiez que la réponse a le code d’état OK, et que l’image est retournée.

url = f'{base_url}/studies/{study_uid}/series/{series_uid}/instances/{instance_uid}/frames/1'
headers = {'Accept':'multipart/related; type="application/octet-stream"; transfer-syntax=*', "Authorization":bearer_token}

response = client.get(url, headers=headers) #, verify=False)

Interroger DICOM (QIDO)

Dans les exemples suivants, nous recherchons des éléments à l’aide de leurs identificateurs uniques. Vous pouvez également rechercher d’autres attributs, tels que PatientName.

Consultez le document Déclaration de conformité de DICOM pour connaître les attributs DICOM pris en charge.

Rechercher des études

Cette requête recherche une ou plusieurs études en fonction d’attributs DICOM.

Détails :

  • Path : ../studies?StudyInstanceUID={study}
  • Méthode : GET
  • Headers :
    • Accept : application/dicom+json
    • Authorization : « Bearer $token »

Vérifiez que la réponse inclut une étude et que le code de réponse est OK.

url = f'{base_url}/studies'
headers = {'Accept':'application/dicom+json', "Authorization":bearer_token}
params = {'StudyInstanceUID':study_uid}

response = client.get(url, headers=headers, params=params) #, verify=False)

Rechercher des séries

Cette requête recherche une ou plusieurs séries par attributs DICOM.

Détails :

  • Path : ../series?SeriesInstanceUID={series}
  • Méthode : GET
  • Headers :
    • Accept : application/dicom+json
    • Authorization : « Bearer $token »

Vérifiez que la réponse inclut une série et que le code de réponse est OK.

url = f'{base_url}/series'
headers = {'Accept':'application/dicom+json', "Authorization":bearer_token}
params = {'SeriesInstanceUID':series_uid}

response = client.get(url, headers=headers, params=params) #, verify=False)

Rechercher des séries dans une étude

Cette requête recherche une ou plusieurs séries au sein d’une seule étude en fonction d’attributs DICOM.

Détails :

  • Path : ../studies/{study}/series?SeriesInstanceUID={series}
  • Méthode : GET
  • Headers :
    • Accept : application/dicom+json
    • Authorization : « Bearer $token »

Vérifiez que la réponse inclut une série et que le code de réponse est OK.

url = f'{base_url}/studies/{study_uid}/series'
headers = {'Accept':'application/dicom+json', "Authorization":bearer_token}
params = {'SeriesInstanceUID':series_uid}

response = client.get(url, headers=headers, params=params) #, verify=False)

Rechercher des instances

Cette requête recherche une ou plusieurs instances en fonction d’attributs DICOM.

Détails :

  • Path : ../instances?SOPInstanceUID={instance}
  • Méthode : GET
  • Headers :
    • Accept : application/dicom+json
    • Authorization : « Bearer $token »

Vérifiez que la réponse inclut une instance et que le code de réponse est OK.

url = f'{base_url}/instances'
headers = {'Accept':'application/dicom+json', "Authorization":bearer_token}
params = {'SOPInstanceUID':instance_uid}

response = client.get(url, headers=headers, params=params) #, verify=False)

Rechercher des instances dans une étude

Cette requête recherche une ou plusieurs instances au sein d’une seule étude en fonction d’attributs DICOM.

Détails :

  • Path : ../studies/{study}/instances?SOPInstanceUID={instance}
  • Méthode : GET
  • Headers :
    • Accept : application/dicom+json
    • Authorization : « Bearer $token »

Vérifiez que la réponse inclut une instance et que le code de réponse est OK.

url = f'{base_url}/studies/{study_uid}/instances'
headers = {'Accept':'application/dicom+json', "Authorization":bearer_token}
params = {'SOPInstanceUID':instance_uid}

response = client.get(url, headers=headers, params=params) #, verify=False)

Rechercher des instances au sein d’une étude et d’une série

Cette requête recherche une ou plusieurs instances au sein d’une seule étude et d’une seule série en fonction d’attributs DICOM.

Détails :

  • Path : ../studies/{study}/series/{series}/instances?SOPInstanceUID={instance}
  • Méthode : GET
  • Headers :
    • Accept : application/dicom+json
    • Authorization : « Bearer $token »

Vérifiez que la réponse inclut une instance et que le code de réponse est OK.

url = f'{base_url}/studies/{study_uid}/series/{series_uid}/instances'
headers = {'Accept':'application/dicom+json'}
params = {'SOPInstanceUID':instance_uid}

response = client.get(url, headers=headers, params=params) #, verify=False)

Supprimer DICOM

Remarque

La suppression ne fait pas partie du standard DICOM, mais elle a été ajoutée pour des raisons pratiques.

Un code de réponse 204 est retourné lorsque la suppression réussit. Un code de réponse 404 est retourné si le ou les éléments n’ont jamais existé ou sont déjà supprimés.

Supprimer une instance spécifique au sein d’une étude et d’une série

Cette requête supprime une seule instance dans une seule étude et une seule série.

Détails :

  • Path : ../studies/{study}/series/{series}/instances/{instance}
  • Méthode : DELETE
  • Headers :
    • Authorization : Bearer $token

Cette requête supprime l’instance de red-triangle du serveur. Si l’opération réussit, le code d’état de la réponse ne contient aucun contenu.

headers = {"Authorization":bearer_token}
url = f'{base_url}/studies/{study_uid}/series/{series_uid}/instances/{instance_uid}'
response = client.delete(url, headers=headers) 

Supprimer une série spécifique dans une étude

Cette requête supprime une seule série (et toutes les instances enfants) dans une seule étude.

Détails :

  • Path : ../studies/{study}/series/{series}
  • Méthode : DELETE
  • Headers :
    • Authorization : Bearer $token

Cet exemple de code supprime l’instance de green-square (il s’agit du seul élément restant de la série) du serveur. Si l’opération réussit, le code d’état de la réponse ne supprime pas le contenu.

headers = {"Authorization":bearer_token}
url = f'{base_url}/studies/{study_uid}/series/{series_uid}'
response = client.delete(url, headers=headers) 

Supprimer une étude spécifique

Cette demande supprime une seule étude (et toutes les séries et instances enfants).

Détails :

  • Path : ../studies/{study}
  • Méthode : DELETE
  • Headers :
    • Authorization : Bearer $token
headers = {"Authorization":bearer_token}
url = f'{base_url}/studies/{study_uid}'
response = client.delete(url, headers=headers) 

Remarque

DICOM® est une marque déposée de la National Electrical Manufacturers Association pour ses publications de standards relatifs aux communications numériques des informations médicales.