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 seule instance 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 :
- Connectez-vous au portail Azure.
- Recherchez les Ressources récentes et sélectionnez votre instance de service DICOM.
- Copiez l’URL du service de votre service DICOM.
- 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. Vous pouvez utiliser d’autres fournisseurs d’informations d’identification tels que ManagedIdentityCredential
et EnvironmentCredential
. 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. Ce n’est peut-être pas toujours le cas. Si ce n’est pas 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 et le type de contenu (content_type) qui peuvent être utilisés.
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 Python pour précharger le fichier DICOM en mémoire sous forme d’octets. Lorsqu’un tableau de fichiers est passé au paramètre de champs encode_multipart_related
, plusieurs fichiers peuvent être chargés dans une seule opération POST. 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 Python pour précharger le fichier DICOM en mémoire sous forme d’octets.
Lorsqu’un tableau de fichiers est passé au paramètre de champs encode_multipart_related
, plusieurs fichiers peuvent être chargés dans une seule opération POST. 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 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 fichier lisible par pydicom
, comme ceci.
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. Validez que la réponse a le code d’état OK et que les métadonnées des deux 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) ouAccept: multipart/related; type="application/octet-stream"; transfer-syntax=*
ouAccept: 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 la 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 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 comprend 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 du serveur (il s’agit du seul élément restant de la série). 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.