Använda DICOMweb Standard-API:er med Python
Den här artikeln visar hur du arbetar med DICOMweb-tjänsten med Python och exempel på .dcm DICOM-filer®.
Använd följande exempelfiler:
- blue-circle.dcm
- dicom-metadata.csv
- green-square.dcm
- red-triangle.dcm
Filnamn, studyUID, seriesUID och instanceUID för DICOM-exempelfilerna är:
Fil | 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 |
Kommentar
Var och en av dessa filer representerar en enda instans och ingår i samma studie. Dessutom är den gröna kvadraten och den röda triangeln en del av samma serie, medan den blå cirkeln är i en separat serie.
Förutsättningar
Om du vill använda DICOMweb Standard API:er måste du ha en instans av DICOM-tjänsten distribuerad. Mer information finns i Distribuera DICOM-tjänsten med hjälp av Azure Portal.
När du har distribuerat en instans av DICOM-tjänsten hämtar du URL:en för apptjänsten:
- Logga in på Azure-portalen.
- Sök efter senaste resurser och välj din DICOM-tjänstinstans.
- Kopiera tjänst-URL:en för dicom-tjänsten.
- Om du inte har någon token kan du läsa Hämta åtkomsttoken för DICOM-tjänsten med Hjälp av Azure CLI.
För den här koden får du åtkomst till en Azure-tjänst för offentlig förhandsversion. Det är viktigt att du inte laddar upp någon privat hälsoinformation (PHI).
Arbeta med DICOM-tjänsten
DICOMweb Standard använder multipart/related
HTTP-begäranden i kombination med DICOM-specifika accepthuvuden. Utvecklare som är bekanta med andra REST-baserade API:er tycker ofta att det är besvärligt att arbeta med DICOMweb-standarden. Men när den är igång är den lätt att använda. Det krävs bara lite förtrogenhet för att komma igång.
Importera Python-biblioteken
Importera först de nödvändiga Python-biblioteken.
Vi implementerar det här exemplet med hjälp av det synkrona requests
biblioteket. Överväg att använda httpx
eller ett annat asynkront bibliotek för asynkront stöd. Dessutom importerar vi två stödfunktioner från urllib3
för att stödja arbete med multipart/related
begäranden.
Dessutom importerar DefaultAzureCredential
vi för att logga in på Azure och hämta en token.
import requests
import pydicom
from pathlib import Path
from urllib3.filepost import encode_multipart_formdata, choose_boundary
from azure.identity import DefaultAzureCredential
Konfigurera användardefinierade variabler
Ersätt alla variabelvärden som omsluts i { } med dina egna värden. Kontrollera dessutom att alla konstruerade variabler är korrekta. Till exempel base_url
skapas med hjälp av tjänstens URL och läggs sedan till med den version av REST-API:et som används. Tjänstens URL för dicom-tjänsten är: https://<workspacename-dicomservicename>.dicom.azurehealthcareapis.com
. Du kan använda Azure Portal för att navigera till DICOM-tjänsten och hämta din tjänst-URL. Du kan också gå till dokumentationen om API-versionshantering för DICOM-tjänsten för mer information om versionshantering. Om du använder en anpassad URL måste du åsidosätta det värdet med din egen.
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
Autentisera till Azure och hämta en token
DefaultAzureCredential
gör att vi kan använda olika sätt att hämta token för att logga in på tjänsten. I det här exemplet använder du AzureCliCredential
för att hämta en token för att logga in på tjänsten. Det finns andra leverantörer av autentiseringsuppgifter som ManagedIdentityCredential
och EnvironmentCredential
som du kan använda. Om du vill använda AzureCliCredential måste du logga in på Azure från CLI innan du kör den här koden. Mer information finns i Hämta åtkomsttoken för DICOM-tjänsten med hjälp av Azure CLI. Du kan också kopiera och klistra in den token som hämtades när du loggade in från CLI.
Kommentar
DefaultAzureCredential
returnerar flera olika autentiseringsobjekt. Vi refererar till AzureCliCredential
som det femte objektet i den returnerade samlingen. Detta kanske inte alltid är fallet. Om inte avkommentarer du print(credential.credential)
raden. Då visas alla objekt. Hitta rätt index och kom ihåg att Python använder nollbaserad indexering.
Kommentar
Om du inte har loggat in på Azure med hjälp av CLI misslyckas detta. Du måste vara inloggad i Azure från CLI för att det ska fungera.
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}'
Skapa stödmetoder som stöd multipart\related
Biblioteken Requests
(och de flesta Python-bibliotek) fungerar inte med multipart\related
på ett sätt som stöder DICOMweb. På grund av dessa bibliotek måste vi lägga till några metoder som stöder arbete med DICOM-filer.
encode_multipart_related
tar en uppsättning fält (i DICOM-fallet är dessa bibliotek vanligtvis del 10-dammfiler) och en valfri användardefinierad gräns. Den returnerar både hela kroppen, tillsammans med content_type, som kan användas.
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
Skapa en requests
session
Skapar en requests
session med namnet client
som används för att kommunicera med DICOM-tjänsten.
client = requests.session()
Kontrollera att autentiseringen är korrekt konfigurerad
Anropa api-slutpunkten för changefeed, som returnerar 200 om autentiseringen lyckas.
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!')
Ladda upp DICOM-instanser (STOW)
I följande exempel markeras bevarade DICOM-filer.
Lagra instanser med hjälp av multipart/related
Det här exemplet visar hur du laddar upp en enda DICOM-fil och använder Python för att förinstallera DICOM-filen i minnet som byte. När en matris med filer skickas till fältparametern encode_multipart_related
kan flera filer laddas upp i en enda POST. Det används ibland för att ladda upp flera instanser i en komplett serie eller studie.
Detaljer:
Stig:.. /studier
Metod: POST
Sidhuvuden:
- Acceptera: application/dicom+json
- Innehållstyp: flera delar/relaterade; type="application/dicom"
- Auktorisering: $token"
Brödtext:
- Innehållstyp: program/dicom för varje fil som laddas upp, avgränsad med ett gränsvärde
Vissa programmeringsspråk och verktyg fungerar annorlunda. Vissa kräver till exempel att du definierar din egen gräns. För dessa språk och verktyg kan du behöva använda en något ändrad rubrik för innehållstyp. Dessa språk och verktyg kan användas.
- Innehållstyp: flera delar/relaterade; type="application/dicom"; boundary=ABCD1234
- Innehållstyp: flera delar/relaterade; boundary=ABCD1234
- Innehållstyp: flera delar/relaterade
#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)
Lagra instanser för en specifik studie
Det här exemplet visar hur du laddar upp flera DICOM-filer till den angivna studien. Den använder Python för att förinläsa DICOM-filen i minnet som byte.
När en matris med filer skickas till fältparametern encode_multipart_related
kan flera filer laddas upp i en enda POST. Det används ibland för att ladda upp en komplett serie eller studie.
Detaljer:
- Stig:.. /studies/{study}
- Metod: POST
- Headers:
- Acceptera: application/dicom+json
- Innehållstyp: flera delar/relaterade; type="application/dicom"
- Auktorisering: $token"
- Kropp:
- Innehållstyp: program/dicom för varje fil som laddas upp, avgränsad med ett gränsvärde
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)
Lagra en enskild instans (icke-standard)
I följande kodexempel visas hur du laddar upp en enda DICOM-fil. Det är en icke-standard-API-slutpunkt som förenklar uppladdning av en enda fil som binära byte som skickas i brödtexten i en begäran
Detaljer:
- Stig:.. /studier
- Metod: POST
- Headers:
- Acceptera: application/dicom+json
- Innehållstyp: program/dicom
- Auktorisering: $token"
- Kropp:
- Innehåller en enda DICOM-fil som binära byte.
#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
Hämta DICOM-instanser (WADO)
I följande exempel markeras hämtning av DICOM-instanser.
Hämta alla instanser i en studie
Det här exemplet hämtar alla instanser i en enda studie.
Detaljer:
- Stig:.. /studies/{study}
- Metod: GET
- Headers:
- Acceptera: flera delar/relaterade; type="application/dicom"; transfer-syntax=*
- Auktorisering: $token"
Alla tre dcm-filerna som laddades upp tidigare ingår i samma studie, så svaret bör returnera alla tre instanserna. Kontrollera att svaret har statuskoden OK och att alla tre instanserna returneras.
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)
Använda de hämtade instanserna
Instanserna hämtas som binära byte. Du kan loopa igenom de returnerade objekten och konvertera byte till en fil som pydicom
kan läsas på följande sätt.
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)
Hämta metadata för alla instanser i studien
Den här begäran hämtar metadata för alla instanser i en enda studie.
Detaljer:
- Stig:.. /studies/{study}/metadata
- Metod: GET
- Headers:
- Acceptera: application/dicom+json
- Auktorisering: $token"
Alla tre .dcm
filerna som vi laddade upp tidigare ingår i samma studie, så svaret bör returnera metadata för alla tre instanserna. Kontrollera att svaret har statuskoden OK och att alla metadata returneras.
url = f'{base_url}/studies/{study_uid}/metadata'
headers = {'Accept':'application/dicom+json', "Authorization":bearer_token}
response = client.get(url, headers=headers) #, verify=False)
Hämta alla instanser i en serie
Den här begäran hämtar alla instanser i en enda serie.
Detaljer:
- Stig:.. /studies/{study}/series/{series}
- Metod: GET
- Headers:
- Acceptera: flera delar/relaterade; type="application/dicom"; transfer-syntax=*
- Auktorisering: $token"
Den här serien har två instanser (grön-kvadrat och röd-triangel), så svaret bör returnera båda instanserna. Kontrollera att svaret har statuskoden OK och att båda instanserna returneras.
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)
Hämta metadata för alla instanser i serien
Den här begäran hämtar metadata för alla instanser i en enda serie.
Detaljer:
- Stig:.. /studies/{study}/series/{series}/metadata
- Metod: GET
- Headers:
- Acceptera: application/dicom+json
- Auktorisering: $token"
Den här serien har två instanser (grön-kvadrat och röd-triangel), så svaret bör returneras för båda instanserna. Kontrollera att svaret har statuskoden OK och att metadata för båda instanserna returneras.
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)
Hämta en enskild instans i en serie av en studie
Den här begäran hämtar en enda instans.
Detaljer:
- Stig:.. /studies/{study}/series{series}/instances/{instance}
- Metod: GET
- Headers:
- Acceptera: program/dicom; transfer-syntax=*
- Auktorisering: $token"
Det här kodexemplet bör endast returnera instansens röda triangel. Kontrollera att svaret har statuskoden OK och att instansen returneras.
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)
Hämta metadata för en enskild instans i en serie av en studie
Den här begäran hämtar metadata för en enskild instans i en enda studie och serie.
Detaljer:
- Stig:.. /studies/{study}/series/{series}/instances/{instance}/metadata
- Metod: GET
- Headers:
- Acceptera: application/dicom+json
- Auktorisering: $token"
Det här kodexemplet bör endast returnera metadata för den röda instanstriangeln. Kontrollera att svaret har statuskoden OK och att metadata returneras.
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)
Hämta en eller flera bildrutor från en enda instans
Den här begäran hämtar en eller flera bildrutor från en enda instans.
Detaljer:
- Stig:.. /studies/{study}/series{series}/instances/{instance}/frames/1,2,3
- Metod: GET
- Headers:
- Auktorisering: $token"
Accept: multipart/related; type="application/octet-stream"; transfer-syntax=1.2.840.10008.1.2.1
(Standard) ellerAccept: multipart/related; type="application/octet-stream"; transfer-syntax=*
ellerAccept: multipart/related; type="application/octet-stream";
Det här kodexemplet bör returnera den enda ramen från den röda triangeln. Kontrollera att svaret har statuskoden OK och att ramen returneras.
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)
Fråga DICOM (QIDO)
I följande exempel söker vi efter objekt med hjälp av deras unika identifierare. Du kan också söka efter andra attribut, till exempel PatientName.
Se DICOM Conformance-instruktionen för DICOM-attribut som stöds.
Sök efter studier
Den här begäran söker efter en eller flera studier med DICOM-attribut.
Detaljer:
- Stig:.. /studier? StudyInstanceUID={study}
- Metod: GET
- Headers:
- Acceptera: application/dicom+json
- Auktorisering: $token"
Kontrollera att svaret innehåller en studie och att svarskoden är 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)
Sök efter serier
Den här begäran söker efter en eller flera serier efter DICOM-attribut.
Detaljer:
- Stig:.. /serie? SeriesInstanceUID={series}
- Metod: GET
- Headers:
- Acceptera: application/dicom+json
- Auktorisering: $token"
Kontrollera att svaret innehåller en serie och att svarskoden är 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)
Söka efter serier i en studie
Den här begäran söker efter en eller flera serier i en enda studie med DICOM-attribut.
Detaljer:
- Stig:.. /studies/{study}/series? SeriesInstanceUID={series}
- Metod: GET
- Headers:
- Acceptera: application/dicom+json
- Auktorisering: $token"
Kontrollera att svaret innehåller en serie och att svarskoden är 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)
Sök efter instanser
Den här begäran söker efter en eller flera instanser efter DICOM-attribut.
Detaljer:
- Stig:.. /Instanser? SOPInstanceUID={instance}
- Metod: GET
- Headers:
- Acceptera: application/dicom+json
- Auktorisering: $token"
Kontrollera att svaret innehåller en instans och att svarskoden är 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)
Sök efter instanser i en studie
Den här begäran söker efter en eller flera instanser i en enda studie med DICOM-attribut.
Detaljer:
- Stig:.. /studies/{study}/instances? SOPInstanceUID={instance}
- Metod: GET
- Headers:
- Acceptera: application/dicom+json
- Auktorisering: $token"
Kontrollera att svaret innehåller en instans och att svarskoden är 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)
Sök efter instanser i en studie och serie
Den här begäran söker efter en eller flera instanser i en enda studie och en enda serie med DICOM-attribut.
Detaljer:
- Stig:.. /studies/{study}/series/{series}/instances? SOPInstanceUID={instance}
- Metod: GET
- Headers:
- Acceptera: application/dicom+json
- Auktorisering: $token"
Kontrollera att svaret innehåller en instans och att svarskoden är 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)
Ta bort DICOM
Kommentar
Borttagning är inte en del av DICOM-standarden, men den har lagts till för enkelhetens skull.
En 204-svarskod returneras när borttagningen lyckas. En 404-svarskod returneras om objekten aldrig fanns eller redan har tagits bort.
Ta bort en specifik instans i en studie och serie
Den här begäran tar bort en enskild instans i en enda studie och en enda serie.
Detaljer:
- Stig:.. /studies/{study}/series/{series}/instances/{instance}
- Metod: TA BORT
- Headers:
- Auktorisering: $token
Den här begäran tar bort den röda triangelinstansen från servern. Om det lyckas innehåller svarsstatuskoden inget innehåll.
headers = {"Authorization":bearer_token}
url = f'{base_url}/studies/{study_uid}/series/{series_uid}/instances/{instance_uid}'
response = client.delete(url, headers=headers)
Ta bort en specifik serie i en studie
Den här begäran tar bort en enskild serie (och alla underordnade instanser) i en enda studie.
Detaljer:
- Stig:.. /studies/{study}/series/{series}
- Metod: TA BORT
- Headers:
- Auktorisering: $token
Det här kodexemplet tar bort den grön-kvadratiska instansen från servern (det är det enda elementet kvar i serien). Om det lyckas tar svarsstatuskoden inte bort innehåll.
headers = {"Authorization":bearer_token}
url = f'{base_url}/studies/{study_uid}/series/{series_uid}'
response = client.delete(url, headers=headers)
Ta bort en specifik studie
Den här begäran tar bort en enskild studie (och alla underordnade serier och instanser).
Detaljer:
- Stig:.. /studies/{study}
- Metod: TA BORT
- Headers:
- Auktorisering: $token
headers = {"Authorization":bearer_token}
url = f'{base_url}/studies/{study_uid}'
response = client.delete(url, headers=headers)
Kommentar
DICOM® är ett registrerat varumärke som tillhör National Electrical Manufacturers Association för dess standarder publikationer som rör digital kommunikation av medicinsk information.