6
6
7
7
import requests
8
8
from requests .exceptions import JSONDecodeError as JSONDecodeError
9
- import io , zipfile , os
9
+ import io
10
+ import zipfile
10
11
11
12
12
13
class NBIAClient :
13
14
"""
14
15
TODO:: Add docstring
15
- FIXME:: logger prints duplicate logs if you instantiate the class more than once
16
+ FIXME:: logger prints duplicate logs if you instantiate the class more
17
+ than once
16
18
"""
17
-
18
- def __init__ (self ,
19
- username : str = "nbia_guest" ,
19
+
20
+ def __init__ (self ,
21
+ username : str = "nbia_guest" ,
20
22
password : str = "" ,
21
23
log_level : str = "INFO"
22
24
) -> None :
23
-
25
+
24
26
# Setup logger
25
27
self .logger = setup_logger (
26
- name = "NBIAClient" ,
27
- console_logging = True ,
28
- log_level = log_level ,
29
- log_file = None )
30
-
28
+ name = "NBIAClient" , console_logging = True ,
29
+ log_level = log_level , log_file = None )
30
+
31
31
# Setup OAuth2 client
32
- self .logger .debug ("Setting up OAuth2 client... with username %s" , username )
32
+ self .logger .debug (
33
+ "Setting up OAuth2 client... with username %s" , username )
34
+
33
35
self ._oauth2_client = OAuth2 (username = username , password = password )
34
36
self .api_headers = self ._oauth2_client .getToken ()
35
-
37
+
36
38
def query_api (self , endpoint : NBIA_ENDPOINTS , params : dict = {}) -> dict :
37
- base_url = "https://services.cancerimagingarchive.net/nbia-api/services/"
38
- query_url = base_url + endpoint .value
39
-
39
+
40
+ query_url = NBIA_ENDPOINTS . BASE_URL . value + endpoint .value
41
+
40
42
self .logger .debug ("Querying API endpoint: %s" , query_url )
41
- # self.logger.debug("API headers: %s", (self._createDebugURL(endpoint, params)))
42
-
43
43
try :
44
44
response = requests .get (
45
- url = query_url ,
45
+ url = query_url ,
46
46
headers = self .api_headers ,
47
47
params = params
48
48
)
49
- # Check if response is likely to be JSON
50
49
if response .headers .get ('Content-Type' ) == 'application/json' :
51
50
response_data = response .json ()
52
51
else :
53
52
# If response is binary data, return raw response
54
53
response_data = response .content
55
54
except JSONDecodeError as j :
56
- if (response .text == "" ):
55
+ if (response .text == "" ):
57
56
self .logger .error ("Response text is empty." )
58
57
return response
59
58
self .logger .error ("Error parsing response as JSON: %s" , j )
60
59
self .logger .debug ("Response: %s" , response .text )
61
60
except Exception as e :
62
61
self .logger .error ("Error querying API: %s" , e )
63
62
raise e
64
-
63
+
65
64
return response_data
66
-
67
- def _createDebugURL (self , endpoint , params ):
68
- auth = "'Authorization:" + self .api_headers ["Authorization" ] + "' -k "
69
- base_url = "'https://services.cancerimagingarchive.net/nbia-api/services/"
70
- query_url = auth + base_url + endpoint .value
71
- debug_url = query_url + "?"
72
- for key , value in params .items ():
73
- debug_url += f"{ key } ={ value } &"
74
- debug_url = debug_url [:- 1 ] + "'"
75
- return debug_url
76
-
65
+
77
66
def getCollections (self ) -> list :
78
67
response = self .query_api (NBIA_ENDPOINTS .GET_COLLECTIONS )
79
68
collections = []
@@ -86,41 +75,44 @@ def getCollectionPatientCount(self) -> list:
86
75
patientCount = []
87
76
for collection in response :
88
77
patientCount .append ({
89
- "Collection" : collection ["criteria" ],
78
+ "Collection" : collection ["criteria" ],
90
79
"PatientCount" : collection ["count" ]})
91
-
80
+
92
81
return patientCount
93
-
94
- def getBodyPartCounts (self , collection : str = "" , modality : str = "" ) -> list :
82
+
83
+ def getBodyPartCounts (
84
+ self , collection : str = "" , modality : str = "" ) -> list :
85
+
95
86
PARAMS = {}
96
87
if collection :
97
88
PARAMS ["Collection" ] = collection
98
89
if modality :
99
90
PARAMS ["Modality" ] = modality
100
- response = self .query_api (
101
- endpoint = NBIA_ENDPOINTS .GET_BODY_PART_PATIENT_COUNT ,
102
- params = PARAMS )
103
-
91
+ response = self .query_api (
92
+ endpoint = NBIA_ENDPOINTS .GET_BODY_PART_PATIENT_COUNT ,
93
+ params = PARAMS )
94
+
104
95
bodyparts = []
105
96
for bodypart in response :
106
97
bodyparts .append ({
107
- "BodyPartExamined" : bodypart ["criteria" ],
98
+ "BodyPartExamined" : bodypart ["criteria" ],
108
99
"Count" : bodypart ["count" ]})
109
100
return bodyparts
110
101
111
102
def getPatients (self , collection : str , modality : str ) -> list :
112
103
assert collection is not None
113
104
assert modality is not None
114
-
115
- PARAMS = {"Collection" : collection ,"Modality" : modality }
105
+
106
+ PARAMS = {"Collection" : collection , "Modality" : modality }
116
107
response = self .query_api (
117
- endpoint = NBIA_ENDPOINTS .GET_PATIENT_BY_COLLECTION_AND_MODALITY ,
118
- params = PARAMS )
108
+ endpoint = NBIA_ENDPOINTS .GET_PATIENT_BY_COLLECTION_AND_MODALITY ,
109
+ params = PARAMS )
110
+
119
111
patientList = [_ ["PatientId" ] for _ in response ]
120
112
return patientList
121
113
122
114
def getSeries (self ,
123
- Collection : str = "" ,
115
+ Collection : str = "" ,
124
116
PatientID : str = "" ,
125
117
StudyInstanceUID : str = "" ,
126
118
Modality : str = "" ,
@@ -129,75 +121,63 @@ def getSeries(self,
129
121
ManufacturerModelName : str = "" ,
130
122
Manufacturer : str = "" ,
131
123
) -> list :
132
-
124
+
133
125
params = dict ()
134
-
126
+
135
127
for key , value in locals ().items ():
136
128
if (value != "" ) and (key != "self" ):
137
129
params [key ] = value
138
-
139
-
130
+
131
+
140
132
response = self .query_api (
141
133
endpoint = NBIA_ENDPOINTS .GET_SERIES ,
142
134
params = params )
143
-
135
+
144
136
return response
145
-
146
-
137
+
138
+
147
139
def downloadSeries (
148
- self , SeriesInstanceUID : str , downloadDir : str ,
140
+ self , SeriesInstanceUID : str , downloadDir : str ,
149
141
filePattern : str = '%PatientName/%StudyDescription-%StudyDate/%SeriesNumber-%SeriesDescription-%SeriesInstanceUID/%InstanceNumber.dcm' ,
150
142
overwrite : bool = False
151
143
) -> bool :
152
-
144
+
153
145
# create temporary directory
154
146
from tempfile import TemporaryDirectory
155
-
147
+
156
148
params = dict ()
157
149
params ["SeriesInstanceUID" ] = SeriesInstanceUID
158
-
150
+
159
151
response = self .query_api (
160
- endpoint = NBIA_ENDPOINTS .DOWNLOAD_SERIES ,
161
- params = params )
162
-
152
+ endpoint = NBIA_ENDPOINTS .DOWNLOAD_SERIES ,
153
+ params = params )
154
+
163
155
if not isinstance (response , bytes ):
164
156
# Handle the case where the expected binary data is not received
165
157
# Log error or raise an exception
166
158
return False
167
-
159
+
168
160
file = zipfile .ZipFile (io .BytesIO (response ))
169
-
161
+
170
162
with TemporaryDirectory () as tempDir :
171
163
file .extractall (path = tempDir )
172
- assert validateMD5 (seriesDir = tempDir ) == True
164
+ if not validateMD5 (seriesDir = tempDir ) and not overwrite :
165
+ self .logger .error ("MD5 validation failed. Exiting..." )
166
+ return False
173
167
174
168
# Create an instance of DICOMSorter with the desired target pattern
175
169
sorter = DICOMSorter (
176
- sourceDir = tempDir ,
170
+ sourceDir = tempDir ,
177
171
destinationDir = downloadDir ,
178
172
targetPattern = filePattern ,
179
173
truncateUID = True ,
180
174
sanitizeFilename = True
181
175
)
182
-
176
+
183
177
sorter .sortDICOMFiles (option = "move" , overwrite = overwrite )
184
178
185
-
186
-
187
- # if isinstance(response, bytes):
188
- # file = zipfile.ZipFile(io.BytesIO(response))
189
- # seriesDir = os.path.join(downloadDir, SeriesInstanceUID)
190
- # file.extractall(path=seriesDir)
191
-
192
- # validateMD5(seriesDir=seriesDir)
193
- # else:
194
- # # Handle the case where the expected binary data is not received
195
- # # Log error or raise an exception
196
- # pass
197
-
198
179
return True
199
-
200
-
201
-
202
-
203
-
180
+
181
+
182
+
183
+
0 commit comments