Skip to content

Commit 50a6f72

Browse files
noerogchenyumic
authored and
chenyumic
committed
CHC API alpha DICOMweb samples and tests (#1814)
* CHC API alpha DICOMweb samples and tests * Have to add "from email import generator." * Remove "from email import generator"
1 parent 1d868fb commit 50a6f72

File tree

2 files changed

+521
-0
lines changed

2 files changed

+521
-0
lines changed
Lines changed: 322 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,322 @@
1+
# Copyright 2018 Google LLC All Rights Reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import argparse
16+
import json
17+
import os
18+
19+
import email
20+
from email import encoders
21+
from email.mime import application
22+
from email.mime import multipart
23+
24+
from google.auth.transport import requests
25+
from googleapiclient.errors import HttpError
26+
from google.oauth2 import service_account
27+
28+
_BASE_URL = 'https://healthcare.googleapis.com/v1alpha'
29+
30+
31+
def get_session(service_account_json):
32+
"""Returns an authorized Requests Session class using the service account
33+
credentials JSON. This class is used to perform requests to the
34+
Cloud Healthcare API endpoint."""
35+
36+
# Pass in the credentials and project ID. If none supplied, get them
37+
# from the environment.
38+
credentials = service_account.Credentials.from_service_account_file(
39+
service_account_json)
40+
scoped_credentials = credentials.with_scopes(
41+
['https://www.googleapis.com/auth/cloud-platform'])
42+
43+
# Create a requests Session object with the credentials.
44+
session = requests.AuthorizedSession(scoped_credentials)
45+
46+
return session
47+
48+
# [START healthcare_dicomweb_store_instance]
49+
50+
51+
def dicomweb_store_instance(
52+
service_account_json,
53+
base_url,
54+
project_id,
55+
cloud_region,
56+
dataset_id,
57+
dicom_store_id,
58+
dcm_file):
59+
"""Handles the POST requests specified in the DICOMweb standard."""
60+
url = '{}/projects/{}/locations/{}'.format(base_url,
61+
project_id, cloud_region)
62+
63+
dicomweb_path = '{}/datasets/{}/dicomStores/{}/dicomWeb/studies'.format(
64+
url, dataset_id, dicom_store_id)
65+
66+
# Make an authenticated API request
67+
session = get_session(service_account_json)
68+
69+
with open(dcm_file) as dcm:
70+
dcm_content = dcm.read()
71+
72+
# All requests to store an instance are multipart messages, as designated
73+
# by the multipart/related portion of the Content-Type. This means that
74+
# the request is made up of multiple sets of data that are combined after
75+
# the request completes. Each of these sets of data must be separated using
76+
# a boundary, as designated by the boundary portion of the Content-Type.
77+
multipart_body = multipart.MIMEMultipart(
78+
subtype='related', boundary=email.generator._make_boundary())
79+
part = application.MIMEApplication(
80+
dcm_content, 'dicom', _encoder=encoders.encode_noop)
81+
multipart_body.attach(part)
82+
boundary = multipart_body.get_boundary()
83+
84+
content_type = (
85+
'multipart/related; type="application/dicom"; ' +
86+
'boundary="%s"') % boundary
87+
headers = {'Content-Type': content_type}
88+
89+
try:
90+
response = session.post(
91+
dicomweb_path,
92+
data=multipart_body.as_string(),
93+
headers=headers)
94+
response.raise_for_status()
95+
print('Stored DICOM instance:')
96+
print(response.text)
97+
return response
98+
except HttpError as err:
99+
print(err)
100+
return ""
101+
# [END healthcare_dicomweb_store_instance]
102+
103+
104+
# [START healthcare_dicomweb_search_instance]
105+
def dicomweb_search_instance(
106+
service_account_json,
107+
base_url,
108+
project_id,
109+
cloud_region,
110+
dataset_id,
111+
dicom_store_id):
112+
"""Handles the GET requests specified in DICOMweb standard."""
113+
url = '{}/projects/{}/locations/{}'.format(base_url,
114+
project_id, cloud_region)
115+
116+
dicomweb_path = '{}/datasets/{}/dicomStores/{}/dicomWeb/instances'.format(
117+
url, dataset_id, dicom_store_id)
118+
119+
# Make an authenticated API request
120+
session = get_session(service_account_json)
121+
122+
headers = {
123+
'Content-Type': 'application/dicom+json; charset=utf-8'
124+
}
125+
126+
response = session.get(dicomweb_path, headers=headers)
127+
response.raise_for_status()
128+
129+
instances = response.json()
130+
131+
print('Instances:')
132+
print(json.dumps(instances, indent=2))
133+
134+
return instances
135+
# [END healthcare_dicomweb_search_instance]
136+
137+
138+
# [START healthcare_dicomweb_retrieve_study]
139+
def dicomweb_retrieve_study(
140+
service_account_json,
141+
base_url,
142+
project_id,
143+
cloud_region,
144+
dataset_id,
145+
dicom_store_id,
146+
study_uid):
147+
"""Handles the GET requests specified in the DICOMweb standard."""
148+
url = '{}/projects/{}/locations/{}'.format(base_url,
149+
project_id, cloud_region)
150+
151+
dicomweb_path = '{}/datasets/{}/dicomStores/{}/dicomWeb/studies/{}'.format(
152+
url, dataset_id, dicom_store_id, study_uid)
153+
154+
# Make an authenticated API request
155+
session = get_session(service_account_json)
156+
157+
headers = {
158+
'Content-Type': 'application/dicom+json; charset=utf-8'
159+
}
160+
161+
response = session.get(dicomweb_path, headers=headers)
162+
response.raise_for_status()
163+
164+
print('Retrieved study with UID: {}'.format(study_uid))
165+
166+
return response
167+
# [END healthcare_dicomweb_retrieve_study]
168+
169+
170+
# [START healthcare_dicomweb_delete_study]
171+
def dicomweb_delete_study(
172+
service_account_json,
173+
base_url,
174+
project_id,
175+
cloud_region,
176+
dataset_id,
177+
dicom_store_id,
178+
study_uid):
179+
"""Handles DELETE requests equivalent to the GET requests specified in
180+
the WADO-RS standard.
181+
"""
182+
url = '{}/projects/{}/locations/{}'.format(base_url,
183+
project_id, cloud_region)
184+
185+
dicomweb_path = '{}/datasets/{}/dicomStores/{}/dicomWeb/studies/{}'.format(
186+
url, dataset_id, dicom_store_id, study_uid)
187+
188+
# Make an authenticated API request
189+
session = get_session(service_account_json)
190+
191+
headers = {
192+
'Content-Type': 'application/dicom+json; charset=utf-8'
193+
}
194+
195+
response = session.delete(dicomweb_path, headers=headers)
196+
response.raise_for_status()
197+
198+
print('Deleted study.')
199+
200+
return response
201+
# [END healthcare_dicomweb_delete_study]
202+
203+
204+
def parse_command_line_args():
205+
"""Parses command line arguments."""
206+
207+
parser = argparse.ArgumentParser(
208+
description=__doc__,
209+
formatter_class=argparse.RawDescriptionHelpFormatter)
210+
211+
parser.add_argument(
212+
'--service_account_json',
213+
default=os.environ.get("GOOGLE_APPLICATION_CREDENTIALS"),
214+
help='Path to service account JSON file.')
215+
216+
parser.add_argument(
217+
'--base_url',
218+
default=_BASE_URL,
219+
help='Healthcare API URL')
220+
221+
parser.add_argument(
222+
'--project_id',
223+
default=(os.environ.get("GOOGLE_CLOUD_PROJECT")),
224+
help='GCP project name')
225+
226+
parser.add_argument(
227+
'--cloud_region',
228+
default='us-central1',
229+
help='GCP region')
230+
231+
parser.add_argument(
232+
'--dataset_id',
233+
default=None,
234+
help='Name of dataset')
235+
236+
parser.add_argument(
237+
'--dicom_store_id',
238+
default=None,
239+
help='Name of DICOM store')
240+
241+
parser.add_argument(
242+
'--dcm_file',
243+
default=None,
244+
help='File name for DCM file to store.')
245+
246+
parser.add_argument(
247+
'--study_uid',
248+
default=None,
249+
help='Unique identifier for a study.')
250+
251+
command = parser.add_subparsers(dest='command')
252+
253+
command.add_parser(
254+
'dicomweb-store-instance',
255+
help=dicomweb_store_instance.__doc__)
256+
command.add_parser(
257+
'dicomweb-search-instance',
258+
help=dicomweb_search_instance.__doc__)
259+
command.add_parser(
260+
'dicomweb-retrieve-study',
261+
help=dicomweb_retrieve_study.__doc__)
262+
command.add_parser(
263+
'dicomweb-delete-study',
264+
help=dicomweb_delete_study.__doc__)
265+
266+
return parser.parse_args()
267+
268+
269+
def run_command(args):
270+
"""Calls the program using the specified command."""
271+
if args.project_id is None:
272+
print('You must specify a project ID or set the '
273+
'"GOOGLE_CLOUD_PROJECT" environment variable.')
274+
return
275+
276+
elif args.command == 'dicomweb-store-instance':
277+
dicomweb_store_instance(
278+
args.service_account_json,
279+
args.base_url,
280+
args.project_id,
281+
args.cloud_region,
282+
args.dataset_id,
283+
args.dicom_store_id,
284+
args.dcm_file)
285+
286+
elif args.command == 'dicomweb-search-instance':
287+
dicomweb_search_instance(
288+
args.service_account_json,
289+
args.base_url,
290+
args.project_id,
291+
args.cloud_region,
292+
args.dataset_id,
293+
args.dicom_store_id)
294+
295+
elif args.command == 'dicomweb-retrieve-study':
296+
dicomweb_retrieve_study(
297+
args.service_account_json,
298+
args.base_url,
299+
args.project_id,
300+
args.cloud_region,
301+
args.dataset_id,
302+
args.dicom_store_id,
303+
args.study_uid)
304+
305+
elif args.command == 'dicomweb-delete-study':
306+
dicomweb_delete_study(
307+
args.service_account_json,
308+
args.base_url,
309+
args.project_id,
310+
args.cloud_region,
311+
args.dataset_id,
312+
args.dicom_store_id,
313+
args.study_uid)
314+
315+
316+
def main():
317+
args = parse_command_line_args()
318+
run_command(args)
319+
320+
321+
if __name__ == '__main__':
322+
main()

0 commit comments

Comments
 (0)