搭配 Python 使用 DICOMweb 標準 API
本文說明如何透過 Python 和 .dcm DICOM® 檔案範例來使用 DICOMweb 服務。
使用這些檔案範例:
- blue-circle.dcm
- dicom-metadata.csv
- green-square.dcm
- red-triangle.dcm
DICOM 檔案範例的檔名、studyUID、seriesUID 和 instanceUID 為:
檔案 | 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 |
注意
其中每一個檔案都代表單一執行個體,而且是相同研究的一部分。 此外,綠色方形和紅色三角形是相同系列的一部分,而藍色圓圈則位於個別系列中。
必要條件
若要使用 DICOMweb 標準 API,您必須部署 DICOM 服務的執行個體。 如需詳細資訊,請參閱使用 Azure 入口網站部署 DICOM 服務。
部署 DICOM 服務的執行個體之後,擷取應用程式服務的 URL:
- 登入 Azure 入口網站。
- 搜尋最近使用的資源,然後選取您的 DICOM 服務執行個體。
- 複製 DICOM 服務的服務 URL。
- 如果您沒有權杖,請參閱使用 Azure CLI 取得 DICOM 服務的存取權杖。
此程式碼可讓您存取公開預覽版 Azure 服務。 切勿上傳任何私人健康資訊 (PHI)。
使用 DICOM 服務
DICOMweb 標準會大量使用結合 DICOM 專屬接受標頭的 multipart/related
HTTP 要求。 熟悉其他 REST 型 API 的開發人員通常會在使用 DICOMweb 標準時覺得不習慣。 不過,一旦開始並執行之後,就能輕易上手。 只有剛開始時需要熟悉一下。
匯入 Python 程式庫
首先,匯入必要的 Python 程式庫。
我們會使用同步的 requests
程式庫來實作此範例。 針對非同步支援,請考慮使用 httpx
或其他非同步程式庫。 此外,我們會從 urllib3
匯入兩個支援函式,以支援處理 multipart/related
要求。
還會匯入 DefaultAzureCredential
以登入 Azure 並取得權杖。
import requests
import pydicom
from pathlib import Path
from urllib3.filepost import encode_multipart_formdata, choose_boundary
from azure.identity import DefaultAzureCredential
設定使用者定義變數
將所有包含在 { } 中的變數值取代為您自己的值。 此外,請驗證任何組成的變數是否正確。 例如,base_url
是由服務 URL 組成,然後附加所使用的 REST API 版本。 DICOM 服務的服務 URL 為:https://<workspacename-dicomservicename>.dicom.azurehealthcareapis.com
。 您可以使用 Azure 入口網站瀏覽至 DICOM 服務,並取得您的服務 URL。 您也可以瀏覽適用於 DICOM 服務的 API 版本設定文件 (英文),以取得版本設定的詳細資訊。 如果您使用自訂 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
向 Azure 驗證並取得權杖
DefaultAzureCredential
可讓我們使用各種方法取得用來登入服務的權杖。 此範例中使用 AzureCliCredential
來取得登入服務的權杖。 還有其他認證提供者,例如您可以使用 ManagedIdentityCredential
和 EnvironmentCredential
。 若要使用 AzureCliCredential,您需要先從 CLI 登入 Azure,再執行此程式碼。 如需詳細資訊,請參閱使用 Azure CLI 取得 DICOM 服務的存取權杖。 或者,複製並貼上從 CLI 登入時擷取的權杖。
注意
DefaultAzureCredential
會傳回數個不同的認證物件。 我們會將 AzureCliCredential
當作傳回集合中的第 5 個項目來參考。 情況可能不一定如此。 如果沒有,請取消註解 print(credential.credential)
這一行。 這將會列出所有項目。 尋找正確的索引,並重新叫用 Python 使用以零起始的索引。
注意
如果您尚未使用 CLI 登入 Azure,此動作將會失敗。 您必須從 CLI 登入 Azure,才能使其運作。
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}'
建立支援的方法來支援 multipart\related
Requests
程式庫 (以及大部分的 Python 程式庫) 無法以支援 DICOMweb 的方式使用 multipart\related
。 因為這些程式庫,我們必須新增一些方法來支援使用 DICOM 檔案。
encode_multipart_related
會採用一組欄位 (在 DICOM 案例中,這些程式庫通常是第 10 部分 dam 檔案) 和選擇性的使用者定義界限。 這會傳回完整本文,以及可以使用的 content_type。
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
建立 requests
工作階段
建立稱為 client
的 requests
工作階段,用來與 DICOM 服務通訊。
client = requests.session()
確認已正確設定驗證
呼叫變更摘要 API 端點,如果驗證成功,則會傳回 200。
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!')
上傳 DICOM 執行個體 (STOW)
下列範例會強調保存 DICOM 檔案。
使用 multipart/related
儲存執行個體
此範例示範如何上傳單一 DICOM 檔案,並使用 Python 將 DICOM 檔案預先載入記憶體中作為位元組。 當檔案陣列傳遞至 encode_multipart_related
的欄位參數時,即可在單一 POST 中上傳多個檔案。 這有時會用來在完整的系列或研究內上傳數個執行個體。
詳細資料:
路徑:../studies
方法:POST
標頭:
- Accept: application/dicom+json
- Content-Type: multipart/related; type="application/dicom"
- Authorization: Bearer $token"
本文:
- Content-Type:每個上傳檔案的 application/dicom,以界限值分隔
某些程式設計語言和工具的行為不同。 例如,有些會要求您定義自己的界限。 針對這些語言和工具,您可能需要使用稍微修改的 Content-Type 標頭。 您可以順利地使用這些語言和工具。
- 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)
儲存特定研究的執行個體
此範例示範如何將多個 DICOM 檔案上傳至指定的研究。 它會使用 Python 將 DICOM 檔案預先載入記憶體作為位元組。
當檔案陣列傳遞至 encode_multipart_related
的欄位參數時,即可在單一 POST 中上傳多個檔案。 這有時會用來上傳完整的系列或研究。
詳細資料:
- 路徑:../studies/{study}
- 方法:POST
- Headers:
- Accept: application/dicom+json
- Content-Type: multipart/related; type="application/dicom"
- Authorization: Bearer $token"
- 本文:
- Content-Type:每個上傳檔案的 application/dicom,以界限值分隔
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)
儲存單一執行個體 (非標準)
下列程式碼範例示範如何上傳單一 DICOM 檔案。 這是一個非標準 API 端點,可將單一檔案上傳簡化為要求本文中傳送的二進位位元組
詳細資料:
- 路徑:../studies
- 方法:POST
- Headers:
- Accept: application/dicom+json
- Content-Type: application/dicom
- Authorization: Bearer $token"
- 本文:
- 包含二進位位元組形式的單一 DICOM 檔案。
#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
擷取 DICOM 執行個體 (WADO)
下列範例會強調擷取 DICOM 執行個體。
擷取研究中的所有執行個體
此範例會擷取單一研究內的所有執行個體。
詳細資料:
- 路徑:../studies/{study}
- 方法:GET
- Headers:
- Accept: multipart/related; type="application/dicom"; transfer-syntax=*
- Authorization: Bearer $token"
先前上傳的三個 dcm 檔案都是相同研究的一部分,因此回應應該會傳回這三個執行個體。 驗證回應的狀態碼為 OK,且傳回這三個執行個體。
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)
使用擷取的執行個體
執行個體會擷取為二進位位元組。 您可以重複查看傳回的項目,並將位元組轉換成如下 pydicom
可讀取的檔案。
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)
擷取研究中所有執行個體的中繼資料
此要求會擷取單一研究內所有執行個體的中繼資料。
詳細資料:
- 路徑:../studies/{study}/metadata
- 方法:GET
- Headers:
- Accept: application/dicom+json
- Authorization: Bearer $token"
我們先前上傳的三個 .dcm
檔案都是相同研究的一部分,因此回應應該會傳回這三個執行個體的中繼資料。 驗證回應的狀態碼為 OK,且傳回所有中繼資料。
url = f'{base_url}/studies/{study_uid}/metadata'
headers = {'Accept':'application/dicom+json', "Authorization":bearer_token}
response = client.get(url, headers=headers) #, verify=False)
擷取系列中的所有執行個體
此要求會擷取單一系列內的所有執行個體。
詳細資料:
- 路徑:../studies/{study}/series/{series}
- 方法:GET
- Headers:
- Accept: multipart/related; type="application/dicom"; transfer-syntax=*
- Authorization: Bearer $token"
此系列有兩個執行個體 (綠色方形和紅色三角形),因此回應應該會傳回這兩個執行個體。 驗證回應的狀態碼為 OK,且傳回這兩個執行個體。
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)
擷取系列中所有執行個體的中繼資料
此要求會擷取單一系列內所有執行個體的中繼資料。
詳細資料:
- 路徑:../studies/{study}/series/{series}/metadata
- 方法:GET
- Headers:
- Accept: application/dicom+json
- Authorization: Bearer $token"
此系列有兩個執行個體 (綠色方形和紅色三角形),因此應該針對這兩個執行個體傳回回應。 驗證回應的狀態碼為 OK,且傳回兩個執行個體的中繼資料。
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)
擷取一系列研究內的單一執行個體
此要求會擷取單一執行個體。
詳細資料:
- 路徑:../studies/{study}/series{series}/instances/{instance}
- 方法:GET
- Headers:
- Accept: application/dicom; transfer-syntax=*
- Authorization: Bearer $token"
此程式碼範例應該只會傳回紅色三角形執行個體。 驗證回應的狀態碼為 OK,且傳回執行個體。
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)
擷取一系列研究內單一執行個體的中繼資料
此要求會擷取單一研究和系列中單一執行個體的中繼資料。
詳細資料:
- 路徑:../studies/{study}/series/{series}/instances/{instance}/metadata
- 方法:GET
- Headers:
- Accept: application/dicom+json
- Authorization: Bearer $token"
此程式碼範例應該只會傳回紅色三角形執行個體的中繼資料。 驗證回應的狀態碼為 OK,且傳回中繼資料。
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)
從單一執行個體擷取一個或多個畫面格
此要求會從單一執行個體擷取一個或多個畫面格。
詳細資料:
- 路徑:../studies/{study}/series{series}/instances/{instance}/frames/1,2,3
- 方法:GET
- Headers:
- Authorization: Bearer $token"
Accept: multipart/related; type="application/octet-stream"; transfer-syntax=1.2.840.10008.1.2.1
(預設值) 或Accept: multipart/related; type="application/octet-stream"; transfer-syntax=*
或Accept: multipart/related; type="application/octet-stream";
此程式碼範例應該會從紅色三角形傳回唯一的畫面格。 驗證回應的狀態碼為 OK,且傳回畫面格。
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)
查詢 DICOM (QIDO)
在下列範例中,我們會使用唯一識別碼來搜尋項目。 您也可以搜尋其他屬性,例如 PatientName。
有關支援的 DICOM 屬性,請參閱 DICOM 一致性聲明。
搜尋研究
此要求會依據 DICOM 屬性搜尋一個或多個研究。
詳細資料:
- 路徑:../studies?StudyInstanceUID={study}
- 方法:GET
- Headers:
- Accept: application/dicom+json
- Authorization: Bearer $token"
驗證回應是否包含一個研究,且回應碼為 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)
搜尋系列
此要求會依據 DICOM 屬性搜尋一個或多個系列。
詳細資料:
- 路徑:../series?SeriesInstanceUID={series}
- 方法:GET
- Headers:
- Accept: application/dicom+json
- Authorization: Bearer $token"
驗證回應是否包含一個系列,且回應碼為 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)
搜尋研究中的系列
此要求會依據 DICOM 屬性搜尋單一研究中的一個或多個系列。
詳細資料:
- 路徑:../studies/{study}/series?SeriesInstanceUID={series}
- 方法:GET
- Headers:
- Accept: application/dicom+json
- Authorization: Bearer $token"
驗證回應是否包含一個系列,且回應碼為 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)
搜尋執行個體
此要求會依據 DICOM 屬性搜尋一個或多個執行個體。
詳細資料:
- 路徑:../instances?SOPInstanceUID={instance}
- 方法:GET
- Headers:
- Accept: application/dicom+json
- Authorization: Bearer $token"
驗證回應是否包含一個執行個體,且回應碼為 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)
搜尋研究內的執行個體
此要求會依據 DICOM 屬性搜尋單一研究中的一個或多個執行個體。
詳細資料:
- 路徑:../studies/{study}/instances?SOPInstanceUID={instance}
- 方法:GET
- Headers:
- Accept: application/dicom+json
- Authorization: Bearer $token"
驗證回應是否包含一個執行個體,且回應碼為 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)
搜尋研究與系列內的執行個體
此要求會依據 DICOM 屬性搜尋單一研究和單一系列中的一個或多個執行個體。
詳細資料:
- 路徑:../studies/{study}/series/{series}/instances?SOPInstanceUID={instance}
- 方法:GET
- Headers:
- Accept: application/dicom+json
- Authorization: Bearer $token"
驗證回應是否包含一個執行個體,且回應碼為 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)
刪除 DICOM
注意
刪除不是 DICOM 標準的一部分,但為了方便起見,已將其加入。
刪除成功時會傳回 204 回應碼。 如果項目不存在或已刪除,則會傳回 404 回應碼。
刪除研究與系列內的特定執行個體
此要求會刪除單一研究和單一系列內的單一執行個體。
詳細資料:
- 路徑:../studies/{study}/series/{series}/instances/{instance}
- 方法 - 刪除
- Headers:
- Authorization: Bearer $token
此要求會從伺服器中刪除紅色三角形執行個體。 如果成功,回應狀態碼不會包含任何內容。
headers = {"Authorization":bearer_token}
url = f'{base_url}/studies/{study_uid}/series/{series_uid}/instances/{instance_uid}'
response = client.delete(url, headers=headers)
刪除研究中的特定系列
此要求會刪除單一研究內的單一系列 (以及所有子執行個體)。
詳細資料:
- 路徑:../studies/{study}/series/{series}
- 方法 - 刪除
- Headers:
- Authorization: Bearer $token
此程式碼範例會從伺服器中刪除綠色方形執行個體 (這是系列中唯一留下的元素)。 如果成功,回應狀態碼不會刪除內容。
headers = {"Authorization":bearer_token}
url = f'{base_url}/studies/{study_uid}/series/{series_uid}'
response = client.delete(url, headers=headers)
刪除特定研究
此要求會刪除單一研究 (以及所有子系列和執行個體)。
詳細資料:
- 路徑:../studies/{study}
- 方法 - 刪除
- Headers:
- Authorization: Bearer $token
headers = {"Authorization":bearer_token}
url = f'{base_url}/studies/{study_uid}'
response = client.delete(url, headers=headers)
注意
DICOM® 是美國電氣製造商協會對於其與醫療資訊數位通訊相關的標準出版物的註冊商標。