Accessing the DICOM service from the TRE

pixl
tre
Author

Tom Young

Published

March 4, 2023

1 Accessing the DICOM service from the TRE

Note: DICOM data accessed through this service has been anonymised to some degree and will not mirror data from the original source.

  • PII has been removed
  • Dates have been moved
import requests
import pydicom
from pathlib import Path
from urllib3.filepost import encode_multipart_formdata, choose_boundary
from azure.identity import DefaultAzureCredential

1.0.1 Set api URL and version

service_url="https://hdspixldflowehrprod-dicom-pixld-flowehr-prod.dicom.azurehealthcareapis.com"
version="v1"
base_url = f"{service_url}/{version}"
print(service_url)

1.0.2 Authenticate to Azure

Enter the provided code in a browser outside of the TRE VM

!az login --use-device-code

Ensure the correct subscription is set as the ‘default’ subscription. Please select the subscription name you would like to use for futher authentication against the DICOM service from the list of subscriptions returned by the previous cell.

Replace your-subscription-name with the actual subscription name in the below cell and run the cell.

!az account set --subscription "your-subscription-name"

1.0.3 Generate bearer token via DefaultAzureCredential

from azure.identity import DefaultAzureCredential, AzureCliCredential
credential = DefaultAzureCredential()
token = credential.credentials[3].get_token('https://dicom.healthcareapis.azure.com')
bearer_token = f'Bearer {token.token}'

1.0.4 Optional - Alternative token generation with AzureCliCredential

Generates an equivalent token to the above cell, may be used if problems with DefaultAzureCredential are encountered.

credential = AzureCliCredential()
bearer_token = f"Bearer {credential.get_token('https://dicom.healthcareapis.azure.com').token}"

1.1 Create a requests session

client = requests.session()

1.2 Verify authentication has performed correctly

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!')
print(response.status_code)

1.3 Querying the DICOM Service

1.3.1 Search for studies

url = f"{base_url}/studies"
headers = {'Accept':'application/dicom+json', "Authorization":bearer_token}
response_query = client.get(url, headers=headers)
print(f"{response_query.status_code=}, {response_query.content=}")

Extract study IDs from response content - returned as bytes

StudyInstanceUID corresponds to 0020000D - see the DICOM documentation for details

import json
r = json.loads(response_query.content.decode())
study_uids = [study["0020000D"]["Value"][0] for study in r]

1.3.2 Search by study UID

study_uid = study_uids[0] # as an example, use the previous query
url = f"{base_url}/studies"
headers = {'Accept':'application/dicom+json', "Authorization":bearer_token}
params = {'StudyInstanceUID':study_uid}
response_query = client.get(url, headers=headers, params=params)
print(f"{response_query.status_code=}, {response_query.content=}")

Return series UIDs within a single study

url = f'{base_url}/studies/{study_uids[0]}/series'
headers = {'Accept':'application/dicom+json', "Authorization":bearer_token}
response = client.get(url, headers=headers)
print(f"{response.status_code=}, {response.content=}")

Extract series IDs from response content - returned as bytes

SeriesInstanceUID corresponds to 0020000E - see the DICOM documentation for details

r = json.loads(response.content.decode())
series_uids = [study["0020000E"]["Value"][0] for study in r]

Search within study by series UID

series_uid = series_uids[0]
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)
print(f"{response.status_code=}, {response.content=}")

Search all studies by series UID

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)
print(f"{response.status_code=}, {response.content=}")

1.4 Retrieve all instances within a study

1.4.1 For a single study UID

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)

Instances are retrieved as bytes - to return useful output, we’ll loop through returned items and convert to files that can be read by pydicom

import requests_toolbelt as tb
from io import BytesIO

mpd = tb.MultipartDecoder.from_response(response)

retrieved_dcm_files = []
for part in mpd.parts:
    # headers returned as binary
    print(part.headers[b'content-type'])
    
    dcm = pydicom.dcmread(BytesIO(part.content))
    print(dcm.PatientName)
    print(dcm.SOPInstanceUID)
    retrieved_dcm_files.append(dcm)
print(retrieved_dcm_files[0].file_meta)

1.4.2 For multiple study UIDs

response_array = []
for study_uid in study_uids:
    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)
    response_array.append(response)

Parse returned items and output a list of lists, with a list of instances per study

import requests_toolbelt as tb
from io import BytesIO

retrieved_dcm_files_multistudy = []
for r in response_array:
    mpd = tb.MultipartDecoder.from_response(r)

    retrieved_study_dcm_files = []
    for part in mpd.parts:
        dcm = pydicom.dcmread(BytesIO(part.content))
        retrieved_study_dcm_files.append(dcm)
    retrieved_dcm_files_multistudy.append(retrieved_study_dcm_files)
print(retrieved_dcm_files_multistudy[0][0].file_meta)

1.5 View an image with matplotlib

Instance images can be viewed by plotting the pixel array with matplotlib (or a similar library)

import matplotlib.pyplot as plt
plt.imshow(retrieved_dcm_files[0].pixel_array, cmap=plt.cm.bone)

1.6 Retrieve a single instance within a study

Extract instance IDs from response content - returned as bytes

SOPInstanceUID corresponds to 00080018 - see the DICOM documentation for details

study_uid, series_uid = study_uids[0], series_uids[0]
url = f'{base_url}/studies/{study_uid}/series/{series_uid}/instances'
headers = {'Accept': 'application/dicom+json', "Authorization":bearer_token}
response = client.get(url, headers=headers)

r = json.loads(response.content.decode())
instance_uids = [series["00080018"]["Value"][0] for series in r]
instance_uid = instance_uids[0]
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)

Again, the single instance is returned as bytes, which we can pass to pydicom with

dicom_file = pydicom.dcmread(BytesIO(response.content))
print(dicom_file.PatientName)
print(dicom_file.SOPInstanceUID)
print(dicom_file.file_meta)
plt.imshow(dicom_file.pixel_array, cmap=plt.cm.bone)