Menggunakan API Standar DICOMweb dengan Python
Artikel ini memperlihatkan cara bekerja dengan layanan DICOMweb menggunakan Python dan sampel .dcm file DICOMĀ®.
Gunakan file sampel ini:
- blue-circle.dcm
- dicom-metadata.csv
- green-square.dcm
- red-triangle.dcm
Nama file, studyUID, seriesUID, dan instanceUID dari sampel file DICOM adalah:
File | 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 |
Catatan
Masing-masing file ini mewakili satu instans dan merupakan bagian dari studi yang sama. Selain itu, segitiga hijau-persegi dan merah adalah bagian dari seri yang sama, sementara lingkaran biru berada dalam seri terpisah.
Prasyarat
Untuk menggunakan DICOMweb Standard API, Anda harus memiliki instans layanan DICOM yang disebarkan. Untuk informasi selengkapnya, lihat Menyebarkan layanan DICOM menggunakan portal Azure.
Setelah Anda menyebarkan instans layanan DICOM, ambil URL untuk layanan Aplikasi Anda:
- Masuk ke portal Azure.
- Cari Sumber daya Terbaru dan pilih instans layanan DICOM Anda.
- Salin URL Layanan layanan DICOM Anda.
- Jika Anda tidak memiliki token, lihat Mendapatkan token akses untuk layanan DICOM menggunakan Azure CLI.
Untuk kode ini, Anda mengakses layanan Pratinjau Publik Azure. Penting bahwa Anda tidak mengunggah informasi kesehatan privat (PHI).
Bekerja dengan layanan DICOM
DICOMweb Standard membuat penggunaan permintaan HTTP yang multipart/related
berat dikombinasikan dengan header terima khusus DICOM. Pengembang yang terbiasa dengan API berbasis REST lainnya sering menemukan bekerja dengan standar DICOMweb canggung. Namun, setelah aktif dan berjalan, mudah digunakan. Hanya perlu sedikit keakraban untuk memulai.
Mengimpor pustaka Python
Pertama, impor pustaka Python yang diperlukan.
Kami menerapkan contoh ini dengan menggunakan pustaka sinkron requests
. Untuk dukungan asinkron, pertimbangkan untuk menggunakan httpx
atau pustaka asinkron lainnya. Selain itu, kami mengimpor dua fungsi pendukung dari urllib3
untuk mendukung bekerja dengan multipart/related
permintaan.
Selain itu, kami mengimpor DefaultAzureCredential
untuk masuk ke Azure dan mendapatkan token.
import requests
import pydicom
from pathlib import Path
from urllib3.filepost import encode_multipart_formdata, choose_boundary
from azure.identity import DefaultAzureCredential
Mengonfigurasi variabel yang ditentukan pengguna
Ganti semua nilai variabel yang dibungkus dalam { } dengan nilai Anda sendiri. Selain itu, validasi bahwa variabel yang dibangun sudah benar. Misalnya, base_url
dibangun menggunakan URL Layanan lalu ditambahkan dengan versi REST API yang digunakan. URL Layanan layanan DICOM Anda adalah: https://<workspacename-dicomservicename>.dicom.azurehealthcareapis.com
. Anda dapat menggunakan portal Azure untuk menavigasi ke layanan DICOM dan mendapatkan URL Layanan Anda. Anda juga dapat mengunjungi Dokumentasi versi API untuk layanan DICOM untuk informasi selengkapnya tentang penerapan versi. Jika Anda menggunakan URL kustom, Anda perlu mengambil alih nilai tersebut dengan nilai Anda sendiri.
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
Mengautentikasi ke Azure dan mendapatkan token
DefaultAzureCredential
memungkinkan kami menggunakan berbagai cara untuk mendapatkan token untuk masuk ke layanan. Dalam contoh ini, gunakan AzureCliCredential
untuk mendapatkan token untuk masuk ke layanan. Ada penyedia kredensial lain seperti ManagedIdentityCredential
dan EnvironmentCredential
yang dapat Anda gunakan. Untuk menggunakan AzureCliCredential, Anda perlu masuk ke Azure dari CLI sebelum menjalankan kode ini. Untuk informasi selengkapnya, lihat Mendapatkan token akses untuk layanan DICOM menggunakan Azure CLI. Atau, salin dan tempel token yang diambil saat masuk dari CLI.
Catatan
DefaultAzureCredential
mengembalikan beberapa objek Kredensial yang berbeda. Kami mereferensikan AzureCliCredential
sebagai item ke-5 dalam koleksi yang dikembalikan. Ini mungkin tidak selalu terjadi. Jika tidak, batalkan print(credential.credential)
komentar baris. Ini akan mencantumkan semua item. Temukan indeks yang benar, ingat bahwa Python menggunakan pengindeksan berbasis nol.
Catatan
Jika Anda belum masuk ke Azure menggunakan CLI, ini akan gagal. Anda harus masuk ke Azure dari CLI agar ini berfungsi.
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}'
Membuat metode pendukung untuk didukung multipart\related
Requests
Pustaka (dan sebagian besar pustaka Python) tidak berfungsi dengan multipart\related
cara yang mendukung DICOMweb. Karena pustaka ini, kita harus menambahkan beberapa metode untuk mendukung bekerja dengan file DICOM.
encode_multipart_related
mengambil sekumpulan bidang (dalam kasus DICOM, pustaka ini umumnya adalah file bendungan Bagian 10) dan batas opsional yang ditentukan pengguna. Ini mengembalikan kedua tubuh penuh, bersama dengan content_type, yang dapat digunakan.
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
Membuat requests
sesi
requests
Membuat sesi, yang disebut client
yang digunakan untuk berkomunikasi dengan layanan DICOM.
client = requests.session()
Pastikan autentikasi dikonfigurasi dengan benar
Panggil titik akhir API changefeed, yang mengembalikan 200 jika autentikasi berhasil.
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!')
Mengunggah instans DICOM (STOW)
Contoh berikut menyoroti file DICOM yang bertahan.
Menyimpan instans menggunakan multipart/related
Contoh ini menunjukkan cara mengunggah satu file DICOM, dan menggunakan Python untuk memuat file DICOM ke dalam memori sebagai byte. Ketika array file diteruskan ke parameter encode_multipart_related
bidang , beberapa file dapat diunggah dalam satu POST. Terkadang digunakan untuk mengunggah beberapa instans di dalam seri atau studi lengkap.
Detail:
Jalan:.. /Studi
Metode: POST
Header:
- Terima: application/dicom+json
- Jenis Konten: multipihak/terkait; type="application/dicom"
- Otorisasi: Pembawa $token"
Badan:
- Jenis Konten: aplikasi/dicom untuk setiap file yang diunggah, dipisahkan oleh nilai batas
Beberapa bahasa dan alat pemrograman berulah secara berbeda. Misalnya, beberapa mengharuskan Anda menentukan batas Anda sendiri. Untuk bahasa dan alat tersebut, Anda mungkin perlu menggunakan header Tipe Konten yang sedikit dimodifikasi. Bahasa dan alat ini dapat berhasil digunakan.
- Jenis Konten: multipihak/terkait; type="application/dicom"; boundary=ABCD1234
- Jenis Konten: multipihak/terkait; boundary=ABCD1234
- Tipe Konten: multipihak/terkait
#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)
Menyimpan instans untuk studi tertentu
Contoh ini menunjukkan cara mengunggah beberapa file DICOM ke dalam studi yang ditentukan. Ini menggunakan Python untuk memuat file DICOM ke dalam memori sebagai byte.
Ketika array file diteruskan ke parameter encode_multipart_related
bidang , beberapa file dapat diunggah dalam satu POST. Terkadang digunakan untuk mengunggah seri atau studi lengkap.
Detail:
- Jalan:.. /studies/{study}
- Metode: POST
- Header:
- Terima: application/dicom+json
- Jenis Konten: multipihak/terkait; type="application/dicom"
- Otorisasi: Pembawa $token"
- Badan:
- Jenis Konten: aplikasi/dicom untuk setiap file yang diunggah, dipisahkan oleh nilai batas
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)
Menyimpan instans tunggal (nonstandar)
Contoh kode berikut menunjukkan cara mengunggah satu file DICOM. Ini adalah titik akhir API non-standar yang menyederhanakan pengunggahan satu file sebagai byte biner yang dikirim dalam isi permintaan
Detail:
- Jalan:.. /Studi
- Metode: POST
- Header:
- Terima: application/dicom+json
- Jenis Konten: aplikasi/dicom
- Otorisasi: Pembawa $token"
- Badan:
- Berisi satu file DICOM sebagai byte biner.
#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
Mengambil instans DICOM (WADO)
Contoh berikut menyoroti pengambilan instans DICOM.
Mengambil semua instans dalam studi
Contoh ini mengambil semua instans dalam satu studi.
Detail:
- Jalan:.. /studies/{study}
- Metode: GET
- Header:
- Terima: multipihak/terkait; type="application/dicom"; transfer-sintaks=*
- Otorisasi: Pembawa $token"
Ketiga file dcm yang diunggah sebelumnya adalah bagian dari studi yang sama, sehingga respons harus mengembalikan ketiga instans. Validasi bahwa respons memiliki kode status OK dan ketiga instans dikembalikan.
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)
Menggunakan instans yang diambil
Instans diambil sebagai byte biner. Anda dapat mengulangi item yang dikembalikan dan mengonversi byte menjadi file yang pydicom
dapat dibaca sebagai berikut.
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)
Mengambil metadata semua instans dalam studi
Permintaan ini mengambil metadata untuk semua instans dalam satu studi.
Detail:
- Jalan:.. /studies/{study}/metadata
- Metode: GET
- Header:
- Terima: application/dicom+json
- Otorisasi: Pembawa $token"
Ketiga .dcm
file yang kami unggah sebelumnya adalah bagian dari studi yang sama sehingga respons harus mengembalikan metadata untuk ketiga instans. Validasi bahwa respons memiliki kode status OK dan semua metadata dikembalikan.
url = f'{base_url}/studies/{study_uid}/metadata'
headers = {'Accept':'application/dicom+json', "Authorization":bearer_token}
response = client.get(url, headers=headers) #, verify=False)
Mengambil semua instans dalam seri
Permintaan ini mengambil semua instans dalam satu seri.
Detail:
- Jalan:.. /studies/{study}/series/{series}
- Metode: GET
- Header:
- Terima: multipihak/terkait; type="application/dicom"; transfer-sintaks=*
- Otorisasi: Pembawa $token"
Seri ini memiliki dua instans (segitiga hijau-persegi dan merah), sehingga respons harus mengembalikan kedua instans. Validasi bahwa respons memiliki kode status OK dan kedua instans dikembalikan.
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)
Mengambil metadata semua instans dalam seri
Permintaan ini mengambil metadata untuk semua instans dalam satu seri.
Detail:
- Jalan:.. /studies/{study}/series/{series}/metadata
- Metode: GET
- Header:
- Terima: application/dicom+json
- Otorisasi: Pembawa $token"
Seri ini memiliki dua instans (persegi hijau dan segitiga merah), sehingga respons harus kembali untuk kedua instans. Validasi bahwa respons memiliki kode status OK dan metadata kedua instans dikembalikan.
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)
Mengambil satu instans dalam serangkaian studi
Permintaan ini mengambil satu instans.
Detail:
- Jalan:.. /studies/{study}/series{series}/instances/{instance}
- Metode: GET
- Header:
- Terima: aplikasi/dicom; transfer-sintaks=*
- Otorisasi: Pembawa $token"
Contoh kode ini hanya boleh mengembalikan instans segitiga merah. Validasi bahwa respons memiliki kode status OK dan instans dikembalikan.
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)
Mengambil metadata dari satu instans dalam serangkaian studi
Permintaan ini mengambil metadata untuk satu instans dalam satu studi dan seri.
Detail:
- Jalan:.. /studies/{study}/series/{series}/instances/{instance}/metadata
- Metode: GET
- Header:
- Terima: application/dicom+json
- Otorisasi: Pembawa $token"
Contoh kode ini hanya boleh mengembalikan metadata untuk instans segitiga merah. Validasi bahwa respons memiliki kode status OK dan metadata dikembalikan.
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)
Mengambil satu atau beberapa bingkai dari satu instans
Permintaan ini mengambil satu atau beberapa bingkai dari satu instans.
Detail:
- Jalan:.. /studies/{study}/series{series}/instances/{instance}/frames/1,2,3
- Metode: GET
- Header:
- Otorisasi: Pembawa $token"
Accept: multipart/related; type="application/octet-stream"; transfer-syntax=1.2.840.10008.1.2.1
(Default) atauAccept: multipart/related; type="application/octet-stream"; transfer-syntax=*
atauAccept: multipart/related; type="application/octet-stream";
Contoh kode ini harus mengembalikan satu-satunya bingkai dari segitiga merah. Validasi bahwa respons memiliki kode status OK dan bingkai dikembalikan.
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)
KUERI DICOM (QIDO)
Dalam contoh berikut, kami mencari item menggunakan pengidentifikasi uniknya. Anda juga dapat mencari atribut lain, seperti PatientName.
Lihat Pernyataan Kesuaian DICOM untuk atribut DICOM yang didukung.
Mencari studi
Permintaan ini mencari satu atau beberapa studi berdasarkan atribut DICOM.
Detail:
- Jalan:.. /Studi? StudyInstanceUID={study}
- Metode: GET
- Header:
- Terima: application/dicom+json
- Otorisasi: Pembawa $token"
Validasi bahwa respons mencakup satu studi dan bahwa kode respons 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)
Cari seri
Permintaan ini mencari satu atau beberapa seri menurut atribut DICOM.
Detail:
- Jalan:.. /seri? SeriesInstanceUID={series}
- Metode: GET
- Header:
- Terima: application/dicom+json
- Otorisasi: Pembawa $token"
Validasi bahwa respons mencakup satu seri dan bahwa kode respons 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)
Mencari seri dalam studi
Permintaan ini mencari satu atau beberapa seri dalam satu studi oleh atribut DICOM.
Detail:
- Jalan:.. /studies/{study}/series? SeriesInstanceUID={series}
- Metode: GET
- Header:
- Terima: application/dicom+json
- Otorisasi: Pembawa $token"
Validasi bahwa respons mencakup satu seri dan bahwa kode respons 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)
Mencari instans
Permintaan ini mencari satu atau beberapa instans berdasarkan atribut DICOM.
Detail:
- Jalan:.. /Contoh? SOPInstanceUID={instance}
- Metode: GET
- Header:
- Terima: application/dicom+json
- Otorisasi: Pembawa $token"
Validasi bahwa respons mencakup satu instans dan bahwa kode respons 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)
Mencari instans dalam studi
Permintaan ini mencari satu atau beberapa instans dalam satu studi oleh atribut DICOM.
Detail:
- Jalan:.. /studies/{study}/instances? SOPInstanceUID={instance}
- Metode: GET
- Header:
- Terima: application/dicom+json
- Otorisasi: Pembawa $token"
Validasi bahwa respons mencakup satu instans dan bahwa kode respons 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)
Mencari instans dalam studi dan seri
Permintaan ini mencari satu atau beberapa instans dalam satu studi dan satu seri oleh atribut DICOM.
Detail:
- Jalan:.. /studies/{study}/series/{series}/instances? SOPInstanceUID={instance}
- Metode: GET
- Header:
- Terima: application/dicom+json
- Otorisasi: Pembawa $token"
Validasi bahwa respons mencakup satu instans dan bahwa kode respons 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)
Hapus DICOM
Catatan
Penghapusan bukan bagian dari standar DICOM, tetapi telah ditambahkan untuk kenyamanan.
Kode respons 204 dikembalikan ketika penghapusan berhasil. Kode respons 404 dikembalikan jika item tidak pernah ada atau sudah dihapus.
Menghapus instans tertentu dalam studi dan seri
Permintaan ini menghapus satu instans dalam satu studi dan satu seri.
Detail:
- Jalan:.. /studies/{study}/series/{series}/instances/{instance}
- Metode: DELETE
- Header:
- Otorisasi: Pembawa $token
Permintaan ini menghapus instans segitiga merah dari server. Jika berhasil, kode status respons tidak berisi konten.
headers = {"Authorization":bearer_token}
url = f'{base_url}/studies/{study_uid}/series/{series_uid}/instances/{instance_uid}'
response = client.delete(url, headers=headers)
Menghapus seri tertentu dalam studi
Permintaan ini menghapus satu seri (dan semua instans anak) dalam satu studi.
Detail:
- Jalan:.. /studies/{study}/series/{series}
- Metode: DELETE
- Header:
- Otorisasi: Pembawa $token
Contoh kode ini menghapus instans green-square dari server (ini adalah satu-satunya elemen yang tersisa dalam seri). Jika berhasil, kode status respons tidak menghapus konten.
headers = {"Authorization":bearer_token}
url = f'{base_url}/studies/{study_uid}/series/{series_uid}'
response = client.delete(url, headers=headers)
Menghapus studi tertentu
Permintaan ini menghapus satu studi (dan semua seri dan instans anak).
Detail:
- Jalan:.. /studies/{study}
- Metode: DELETE
- Header:
- Otorisasi: Pembawa $token
headers = {"Authorization":bearer_token}
url = f'{base_url}/studies/{study_uid}'
response = client.delete(url, headers=headers)
Catatan
DICOMĀ® adalah merek dagang terdaftar dari Asosiasi Produsen Listrik Nasional untuk publikasi Standar yang berkaitan dengan komunikasi digital informasi medis.