| Home | Trees | Indices | Help |
|
|---|
|
|
1 # -*- coding: utf-8 -*-
2 #============================================================
3 __doc__ = """GNUmed DICOM handling middleware"""
4
5 __license__ = "GPL v2 or later"
6 __author__ = "K.Hilbert <Karsten.Hilbert@gmx.net>"
7
8
9 # stdlib
10 import io
11 import os
12 import sys
13 import re as regex
14 import logging
15 import http.client # exception names used by httplib2
16 import socket
17 import httplib2
18 import json
19 import zipfile
20 import shutil
21 import time
22 import datetime as pydt
23 from urllib.parse import urlencode
24 import distutils.version as version
25
26
27 # GNUmed modules
28 if __name__ == '__main__':
29 sys.path.insert(0, '../../')
30 from Gnumed.pycommon import gmTools
31 from Gnumed.pycommon import gmShellAPI
32 from Gnumed.pycommon import gmMimeLib
33 #from Gnumed.pycommon import gmHooks
34 #from Gnumed.pycommon import gmDispatcher
35
36 _log = logging.getLogger('gm.dicom')
37
38 _map_gender_gm2dcm = {
39 'm': 'M',
40 'f': 'F',
41 'tm': 'M',
42 'tf': 'F',
43 'h': 'O'
44 }
45
46 #============================================================
48 # REST API access to Orthanc DICOM servers
49
50 # def __init__(self):
51 # self.__server_identification = None
52 # self.__user = None
53 # self.__password = None
54 # self.__conn = None
55 # self.__server_url = None
56
57 #--------------------------------------------------------
58 - def connect(self, host, port, user, password, expected_minimal_version=None, expected_name=None, expected_aet=None):
59 try:
60 int(port)
61 except Exception:
62 _log.error('invalid port [%s]', port)
63 return False
64 if (host is None) or (host.strip() == ''):
65 host = 'localhost'
66 try:
67 self.__server_url = str('http://%s:%s' % (host, port))
68 except Exception:
69 _log.exception('cannot create server url from: host [%s] and port [%s]', host, port)
70 return False
71 self.__user = user
72 self.__password = password
73 _log.info('connecting as [%s] to Orthanc server at [%s]', self.__user, self.__server_url)
74 cache_dir = os.path.join(gmTools.gmPaths().user_tmp_dir, '.orthanc2gm-cache')
75 gmTools.mkdir(cache_dir, 0o700)
76 _log.debug('using cache directory: %s', cache_dir)
77 self.__conn = httplib2.Http(cache = cache_dir)
78 self.__conn.add_credentials(self.__user, self.__password)
79 _log.debug('connected to server: %s', self.server_identification)
80 self.connect_error = ''
81 if self.server_identification is False:
82 self.connect_error += 'retrieving server identification failed'
83 return False
84 if expected_minimal_version is not None:
85 if version.LooseVersion(self.server_identification['Version']) < version.LooseVersion(expected_min_version):
86 _log.error('server too old, needed [%s]', expected_min_version)
87 self.connect_error += 'server too old, needed version [%s]' % expected_min_version
88 return False
89 if expected_name is not None:
90 if self.server_identification['Name'] != expected_name:
91 _log.error('wrong server name, expected [%s]', expected_name)
92 self.connect_error += 'wrong server name, expected [%s]' % expected_name
93 return False
94 if expected_aet is not None:
95 if self.server_identification['DicomAet'] != expected_name:
96 _log.error('wrong server AET, expected [%s]', expected_aet)
97 self.connect_error += 'wrong server AET, expected [%s]' % expected_aet
98 return False
99 return True
100
101 #--------------------------------------------------------
103 try:
104 return self.__server_identification
105 except AttributeError:
106 pass
107 system_data = self.__run_GET(url = '%s/system' % self.__server_url)
108 if system_data is False:
109 _log.error('unable to get server identification')
110 return False
111 _log.debug('server: %s', system_data)
112 self.__server_identification = system_data
113
114 self.__initial_orthanc_encoding = self.__run_GET(url = '%s/tools/default-encoding' % self.__server_url)
115 _log.debug('initial Orthanc encoding: %s', self.__initial_orthanc_encoding)
116
117 # check time skew
118 tolerance = 60 # seconds
119 client_now_as_utc = pydt.datetime.utcnow()
120 start = time.time()
121 orthanc_now_str = self.__run_GET(url = '%s/tools/now' % self.__server_url) # 20180208T165832
122 end = time.time()
123 query_duration = end - start
124 orthanc_now_unknown_tz = pydt.datetime.strptime(orthanc_now_str, '%Y%m%dT%H%M%S')
125 _log.debug('GNUmed "now" (UTC): %s', client_now_as_utc)
126 _log.debug('Orthanc "now" (UTC): %s', orthanc_now_unknown_tz)
127 _log.debug('wire roundtrip (seconds): %s', query_duration)
128 _log.debug('maximum skew tolerance (seconds): %s', tolerance)
129 if query_duration > tolerance:
130 _log.info('useless to check GNUmed/Orthanc time skew, wire roundtrip (%s) > tolerance (%s)', query_duration, tolerance)
131 else:
132 if orthanc_now_unknown_tz > client_now_as_utc:
133 real_skew = orthanc_now_unknown_tz - client_now_as_utc
134 else:
135 real_skew = client_now_as_utc - orthanc_now_unknown_tz
136 _log.info('GNUmed/Orthanc time skew: %s', real_skew)
137 if real_skew > pydt.timedelta(seconds = tolerance):
138 _log.error('GNUmed/Orthanc time skew > tolerance (may be due to timezone differences on Orthanc < v1.3.2)')
139
140 return self.__server_identification
141
142 server_identification = property(_get_server_identification, lambda x:x)
143
144 #--------------------------------------------------------
146 # fixed type :: user level instance name :: DICOM AET
147 return 'Orthanc::%(Name)s::%(DicomAet)s' % self.__server_identification
148
149 as_external_id_issuer = property(_get_as_external_id_issuer, lambda x:x)
150
151 #--------------------------------------------------------
153 if self.__user is None:
154 return self.__server_url
155 return self.__server_url.replace('http://', 'http://%s@' % self.__user)
156
157 url_browse_patients = property(_get_url_browse_patients, lambda x:x)
158
159 #--------------------------------------------------------
161 # http://localhost:8042/#patient?uuid=0da01e38-cf792452-65c1e6af-b77faf5a-b637a05b
162 return '%s/#patient?uuid=%s' % (self.url_browse_patients, patient_id)
163
164 #--------------------------------------------------------
166 # http://localhost:8042/#study?uuid=0da01e38-cf792452-65c1e6af-b77faf5a-b637a05b
167 return '%s/#study?uuid=%s' % (self.url_browse_patients, study_id)
168
169 #--------------------------------------------------------
170 # download API
171 #--------------------------------------------------------
173 _log.info('searching for Orthanc patients matching %s', person)
174
175 # look for patient by external ID first
176 pacs_ids = person.get_external_ids(id_type = 'PACS', issuer = self.as_external_id_issuer)
177 if len(pacs_ids) > 1:
178 _log.error('GNUmed patient has more than one ID for this PACS: %s', pacs_ids)
179 _log.error('the PACS ID is expected to be unique per PACS')
180 return []
181
182 pacs_ids2use = []
183
184 if len(pacs_ids) == 1:
185 pacs_ids2use.append(pacs_ids[0]['value'])
186 pacs_ids2use.extend(person.suggest_external_ids(target = 'PACS'))
187
188 for pacs_id in pacs_ids2use:
189 _log.debug('using PACS ID [%s]', pacs_id)
190 pats = self.get_patients_by_external_id(external_id = pacs_id)
191 if len(pats) > 1:
192 _log.warning('more than one Orthanc patient matches PACS ID: %s', pacs_id)
193 if len(pats) > 0:
194 return pats
195
196 _log.debug('no matching patient found in PACS')
197 # return find type ? especially useful for non-matches on ID
198
199 # search by name
200
201 # # then look for name parts
202 # name = person.get_active_name()
203 return []
204
205 #--------------------------------------------------------
207 matching_patients = []
208 _log.info('searching for patients with external ID >>>%s<<<', external_id)
209
210 # elegant server-side approach:
211 search_data = {
212 'Level': 'Patient',
213 'CaseSensitive': False,
214 'Expand': True,
215 'Query': {'PatientID': external_id.strip('*')}
216 }
217 _log.info('server-side C-FIND SCU over REST search, mogrified search data: %s', search_data)
218 matches = self.__run_POST(url = '%s/tools/find' % self.__server_url, data = search_data)
219
220 # paranoia
221 for match in matches:
222 self.protect_patient(orthanc_id = match['ID'])
223 return matches
224
225 # # recursive brute force approach:
226 # for pat_id in self.__run_GET(url = '%s/patients' % self.__server_url):
227 # orthanc_pat = self.__run_GET(url = '%s/patients/%s' % (self.__server_url, pat_id))
228 # orthanc_external_id = orthanc_pat['MainDicomTags']['PatientID']
229 # if orthanc_external_id != external_id:
230 # continue
231 # _log.debug(u'match: %s (name=[%s], orthanc_id=[%s])', orthanc_external_id, orthanc_pat['MainDicomTags']['PatientName'], orthanc_pat['ID'])
232 # matching_patients.append(orthanc_pat)
233 # if len(matching_patients) == 0:
234 # _log.debug(u'no matches')
235 # return matching_patients
236
237 #--------------------------------------------------------
239 _log.info('name parts %s, gender [%s], dob [%s], fuzzy: %s', name_parts, gender, dob, fuzzy)
240 if len(name_parts) > 1:
241 return self.get_patients_by_name_parts(name_parts = name_parts, gender = gender, dob = dob, fuzzy = fuzzy)
242 if not fuzzy:
243 search_term = name_parts[0].strip('*')
244 else:
245 search_term = name_parts[0]
246 if not search_term.endswith('*'):
247 search_term += '*'
248 search_data = {
249 'Level': 'Patient',
250 'CaseSensitive': False,
251 'Expand': True,
252 'Query': {'PatientName': search_term}
253 }
254 if gender is not None:
255 gender = _map_gender_gm2dcm[gender.lower()]
256 if gender is not None:
257 search_data['Query']['PatientSex'] = gender
258 if dob is not None:
259 search_data['Query']['PatientBirthDate'] = dob.strftime('%Y%m%d')
260 _log.info('server-side C-FIND SCU over REST search, mogrified search data: %s', search_data)
261 matches = self.__run_POST(url = '%s/tools/find' % self.__server_url, data = search_data)
262 return matches
263
264 #--------------------------------------------------------
266 # fuzzy: allow partial/substring matches (but not across name part boundaries ',' or '^')
267 matching_patients = []
268 clean_parts = []
269 for part in name_parts:
270 if part.strip() == '':
271 continue
272 clean_parts.append(part.lower().strip())
273 _log.info('client-side patient search, scrubbed search terms: %s', clean_parts)
274 pat_ids = self.__run_GET(url = '%s/patients' % self.__server_url)
275 if pat_ids is False:
276 _log.error('cannot retrieve patients')
277 return []
278 for pat_id in pat_ids:
279 orthanc_pat = self.__run_GET(url = '%s/patients/%s' % (self.__server_url, pat_id))
280 if orthanc_pat is False:
281 _log.error('cannot retrieve patient')
282 continue
283 orthanc_name = orthanc_pat['MainDicomTags']['PatientName'].lower().strip()
284 if not fuzzy:
285 orthanc_name = orthanc_name.replace(' ', ',').replace('^', ',').split(',')
286 parts_in_orthanc_name = 0
287 for part in clean_parts:
288 if part in orthanc_name:
289 parts_in_orthanc_name += 1
290 if parts_in_orthanc_name == len(clean_parts):
291 _log.debug('name match: "%s" contains all of %s', orthanc_name, clean_parts)
292 if gender is not None:
293 gender = _map_gender_gm2dcm[gender.lower()]
294 if gender is not None:
295 if orthanc_pat['MainDicomTags']['PatientSex'].lower() != gender:
296 _log.debug('gender mismatch: dicom=[%s] gnumed=[%s], skipping', orthanc_pat['MainDicomTags']['PatientSex'], gender)
297 continue
298 if dob is not None:
299 if orthanc_pat['MainDicomTags']['PatientBirthDate'] != dob.strftime('%Y%m%d'):
300 _log.debug('dob mismatch: dicom=[%s] gnumed=[%s], skipping', orthanc_pat['MainDicomTags']['PatientBirthDate'], dob)
301 continue
302 matching_patients.append(orthanc_pat)
303 else:
304 _log.debug('name mismatch: "%s" does not contain all of %s', orthanc_name, clean_parts)
305 return matching_patients
306
307 #--------------------------------------------------------
308 - def get_studies_list_by_patient_name(self, name_parts=None, gender=None, dob=None, fuzzy=False):
309 return self.get_studies_list_by_orthanc_patient_list (
310 orthanc_patients = self.get_patients_by_name(name_parts = name_parts, gender = gender, dob = dob, fuzzy = fuzzy)
311 )
312
313 #--------------------------------------------------------
315 return self.get_studies_list_by_orthanc_patient_list (
316 orthanc_patients = self.get_patients_by_external_id(external_id = external_id)
317 )
318
319 #--------------------------------------------------------
321 if filename is None:
322 filename = gmTools.get_unique_filename(prefix = r'DCM-', suffix = r'.zip')
323 _log.info('exporting study [%s] into [%s]', study_id, filename)
324 f = io.open(filename, 'wb')
325 f.write(self.__run_GET(url = '%s/studies/%s/archive' % (self.__server_url, str(study_id)), allow_cached = True))
326 f.close()
327 return filename
328
329 #--------------------------------------------------------
331 if filename is None:
332 filename = gmTools.get_unique_filename(prefix = r'DCM-', suffix = r'.zip')
333 _log.info('exporting study [%s] into [%s]', study_id, filename)
334 f = io.open(filename, 'wb')
335 f.write(self.__run_GET(url = '%s/studies/%s/media' % (self.__server_url, str(study_id)), allow_cached = True))
336 f.close()
337 return filename
338
339 #--------------------------------------------------------
341 if filename is None:
342 filename = gmTools.get_unique_filename(prefix = r'DCM-', suffix = r'.zip')
343 if study_ids is None:
344 _log.info('exporting all studies of patient [%s] into [%s]', patient_id, filename)
345 f = io.open(filename, 'wb')
346 f.write(self.__run_GET(url = '%s/patients/%s/archive' % (self.__server_url, str(patient_id)), allow_cached = True))
347 f.close()
348 return filename
349
350 #--------------------------------------------------------
351 - def _manual_get_studies_with_dicomdir(self, study_ids=None, patient_id=None, target_dir=None, filename=None, create_zip=False):
352
353 if filename is None:
354 filename = gmTools.get_unique_filename(prefix = r'DCM-', suffix = r'.zip', tmp_dir = target_dir)
355
356 # all studies
357 if study_ids is None:
358 _log.info('exporting all studies of patient [%s] into [%s]', patient_id, filename)
359 f = io.open(filename, 'wb')
360 url = '%s/patients/%s/media' % (self.__server_url, str(patient_id))
361 _log.debug(url)
362 f.write(self.__run_GET(url = url, allow_cached = True))
363 f.close()
364 if create_zip:
365 return filename
366 if target_dir is None:
367 target_dir = gmTools.mk_sandbox_dir(prefix = 'dcm-')
368 if not gmTools.unzip_archive(filename, target_dir = target_dir, remove_archive = True):
369 return False
370 return target_dir
371
372 # a selection of studies
373 dicomdir_cmd = 'gm-create_dicomdir' # args: 1) name of DICOMDIR to create 2) base directory where to start recursing for DICOM files
374 found, external_cmd = gmShellAPI.detect_external_binary(dicomdir_cmd)
375 if not found:
376 _log.error('[%s] not found', dicomdir_cmd)
377 return False
378
379 if create_zip:
380 sandbox_dir = gmTools.mk_sandbox_dir(prefix = 'dcm-')
381 _log.info('exporting studies [%s] into [%s] (sandbox [%s])', study_ids, filename, sandbox_dir)
382 else:
383 sandbox_dir = target_dir
384 _log.info('exporting studies [%s] into [%s]', study_ids, sandbox_dir)
385 _log.debug('sandbox dir: %s', sandbox_dir)
386 idx = 0
387 for study_id in study_ids:
388 study_zip_name = gmTools.get_unique_filename(prefix = 'dcm-', suffix = '.zip')
389 # getting with DICOMDIR returns DICOMDIR compatible subdirs and filenames
390 study_zip_name = self.get_study_as_zip_with_dicomdir(study_id = study_id, filename = study_zip_name)
391 # non-beautiful per-study dir name required by subsequent DICOMDIR generation
392 idx += 1
393 study_unzip_dir = os.path.join(sandbox_dir, 'STUDY%s' % idx)
394 _log.debug('study [%s] -> %s -> %s', study_id, study_zip_name, study_unzip_dir)
395 # need to extract into per-study subdir because get-with-dicomdir
396 # returns identical-across-studies subdirs / filenames
397 if not gmTools.unzip_archive(study_zip_name, target_dir = study_unzip_dir, remove_archive = True):
398 return False
399
400 # create DICOMDIR across all studies,
401 # we simply ignore the already existing per-study DICOMDIR files
402 target_dicomdir_name = os.path.join(sandbox_dir, 'DICOMDIR')
403 gmTools.remove_file(target_dicomdir_name, log_error = False) # better safe than sorry
404 _log.debug('generating [%s]', target_dicomdir_name)
405 cmd = '%(cmd)s %(DICOMDIR)s %(startdir)s' % {
406 'cmd': external_cmd,
407 'DICOMDIR': target_dicomdir_name,
408 'startdir': sandbox_dir
409 }
410 success = gmShellAPI.run_command_in_shell (
411 command = cmd,
412 blocking = True
413 )
414 if not success:
415 _log.error('problem running [gm-create_dicomdir]')
416 return False
417 # paranoia
418 try:
419 io.open(target_dicomdir_name)
420 except Exception:
421 _log.error('[%s] not generated, aborting', target_dicomdir_name)
422 return False
423
424 # return path to extracted studies
425 if not create_zip:
426 return sandbox_dir
427
428 # else return ZIP of all studies
429 studies_zip = shutil.make_archive (
430 gmTools.fname_stem_with_path(filename),
431 'zip',
432 root_dir = gmTools.parent_dir(sandbox_dir),
433 base_dir = gmTools.dirname_stem(sandbox_dir),
434 logger = _log
435 )
436 _log.debug('archived all studies with one DICOMDIR into: %s', studies_zip)
437 # studies can be _large_ so attempt to get rid of intermediate files
438 gmTools.rmdir(sandbox_dir)
439 return studies_zip
440
441 #--------------------------------------------------------
442 - def get_studies_with_dicomdir(self, study_ids=None, patient_id=None, target_dir=None, filename=None, create_zip=False):
443
444 if filename is None:
445 filename = gmTools.get_unique_filename(prefix = r'DCM-', suffix = r'.zip', tmp_dir = target_dir)
446
447 # all studies
448 if study_ids is None:
449 if patient_id is None:
450 raise ValueError('<patient_id> must be defined if <study_ids> is None')
451 _log.info('exporting all studies of patient [%s] into [%s]', patient_id, filename)
452 f = io.open(filename, 'wb')
453 url = '%s/patients/%s/media' % (self.__server_url, str(patient_id))
454 _log.debug(url)
455 f.write(self.__run_GET(url = url, allow_cached = True))
456 f.close()
457 if create_zip:
458 return filename
459 if target_dir is None:
460 target_dir = gmTools.mk_sandbox_dir(prefix = 'dcm-')
461 if not gmTools.unzip_archive(filename, target_dir = target_dir, remove_archive = True):
462 return False
463 return target_dir
464
465 # selection of studies
466 _log.info('exporting %s studies into [%s]', len(study_ids), filename)
467 _log.debug('studies: %s', study_ids)
468 f = io.open(filename, 'wb')
469 # You have to make a POST request against URI "/tools/create-media", with a
470 # JSON body that contains the array of the resources of interest (as Orthanc
471 # identifiers). Here is a sample command-line:
472 # curl -X POST http://localhost:8042/tools/create-media -d '["8c4663df-c3e66066-9e20a8fc-dd14d1e5-251d3d84","2cd4848d-02f0005f-812ffef6-a210bbcf-3f01a00a","6eeded74-75005003-c3ae9738-d4a06a4f-6beedeb8","8a622020-c058291c-7693b63f-bc67aa2e-0a02e69c"]' -v > /tmp/a.zip
473 # (this will not create duplicates but will also not check for single-patient-ness)
474 url = '%s/tools/create-media-extended' % self.__server_url
475 _log.debug(url)
476 try:
477 downloaded = self.__run_POST(url = url, data = study_ids, output_file = f)
478 if not downloaded:
479 _log.error('this Orthanc version probably does not support "create-media-extended"')
480 except TypeError:
481 f.close()
482 _log.exception('cannot retrieve multiple studies as one archive with DICOMDIR, probably not supported by this Orthanc version')
483 return False
484 # retry with old URL
485 if not downloaded:
486 url = '%s/tools/create-media' % self.__server_url
487 _log.debug('retrying: %s', url)
488 try:
489 downloaded = self.__run_POST(url = url, data = study_ids, output_file = f)
490 if not downloaded:
491 return False
492 except TypeError:
493 _log.exception('cannot retrieve multiple studies as one archive with DICOMDIR, probably not supported by this Orthanc version')
494 return False
495 finally:
496 f.close()
497 if create_zip:
498 return filename
499 if target_dir is None:
500 target_dir = gmTools.mk_sandbox_dir(prefix = 'dcm-')
501 _log.debug('exporting studies into [%s]', target_dir)
502 if not gmTools.unzip_archive(filename, target_dir = target_dir, remove_archive = True):
503 return False
504 return target_dir
505
506 #--------------------------------------------------------
514
515 #--------------------------------------------------------
517 if filename is None:
518 filename = gmTools.get_unique_filename(suffix = '.png')
519
520 _log.debug('exporting preview for instance [%s] into [%s]', instance_id, filename)
521 download_url = '%s/instances/%s/preview' % (self.__server_url, instance_id)
522 f = io.open(filename, 'wb')
523 f.write(self.__run_GET(url = download_url, allow_cached = True))
524 f.close()
525 return filename
526
527 #--------------------------------------------------------
529 if filename is None:
530 filename = gmTools.get_unique_filename(suffix = '.dcm')
531
532 _log.debug('exporting instance [%s] into [%s]', instance_id, filename)
533 download_url = '%s/instances/%s/attachments/dicom/data' % (self.__server_url, instance_id)
534 f = io.open(filename, 'wb')
535 f.write(self.__run_GET(url = download_url, allow_cached = True))
536 f.close()
537 return filename
538
539 #--------------------------------------------------------
540 # server-side API
541 #--------------------------------------------------------
543 url = '%s/patients/%s/protected' % (self.__server_url, str(orthanc_id))
544 if self.__run_GET(url) == 1:
545 _log.debug('patient already protected: %s', orthanc_id)
546 return True
547 _log.warning('patient [%s] not protected against recycling, enabling protection now', orthanc_id)
548 self.__run_PUT(url = url, data = '1')
549 if self.__run_GET(url) == 1:
550 return True
551 _log.error('cannot protect patient [%s] against recycling', orthanc_id)
552 return False
553
554 #--------------------------------------------------------
556 url = '%s/patients/%s/protected' % (self.__server_url, str(orthanc_id))
557 if self.__run_GET(url) == 0:
558 return True
559 _log.info('patient [%s] protected against recycling, disabling protection now', orthanc_id)
560 self.__run_PUT(url = url, data = '0')
561 if self.__run_GET(url) == 0:
562 return True
563 _log.error('cannot unprotect patient [%s] against recycling', orthanc_id)
564 return False
565
566 #--------------------------------------------------------
568 url = '%s/patients/%s/protected' % (self.__server_url, str(orthanc_id))
569 return (self.__run_GET(url) == 1)
570
571 #--------------------------------------------------------
573 _log.info('verifying DICOM data of patient [%s]', orthanc_id)
574 bad_data = []
575 instances_url = '%s/patients/%s/instances' % (self.__server_url, orthanc_id)
576 instances = self.__run_GET(instances_url)
577 for instance in instances:
578 instance_id = instance['ID']
579 attachments_url = '%s/instances/%s/attachments' % (self.__server_url, instance_id)
580 attachments = self.__run_GET(attachments_url, allow_cached = True)
581 for attachment in attachments:
582 verify_url = '%s/%s/verify-md5' % (attachments_url, attachment)
583 # False, success = "{}"
584 #2018-02-08 19:11:27 ERROR gm.dicom [-1211701504 MainThread] (gmDICOM.py::__run_POST() #986): cannot POST: http://localhost:8042/instances/5a8206f4-24619e76-6650d9cd-792cdf25-039e96e6/attachments/dicom-as-json/verify-md5
585 #2018-02-08 19:11:27 ERROR gm.dicom [-1211701504 MainThread] (gmDICOM.py::__run_POST() #987): response: {'status': '400', 'content-length': '0'}
586 if self.__run_POST(verify_url) is not False:
587 continue
588 _log.error('bad MD5 of DICOM file at url [%s]: patient=%s, attachment_type=%s', verify_url, orthanc_id, attachment)
589 bad_data.append({'patient': orthanc_id, 'instance': instance_id, 'type': attachment, 'orthanc': '%s [%s]' % (self.server_identification, self.__server_url)})
590
591 return bad_data
592
593 #--------------------------------------------------------
595
596 if old_patient_id == new_patient_id:
597 return True
598
599 modify_data = {
600 'Replace': {
601 'PatientID': new_patient_id
602 #,u'0010,0021': praxis.name / "GNUmed vX.X.X"
603 #,u'0010,1002': series of (old) patient IDs
604 }
605 , 'Force': True
606 # "Keep" doesn't seem to do what it suggests ATM
607 #, u'Keep': True
608 }
609 o_pats = self.get_patients_by_external_id(external_id = old_patient_id)
610 all_modified = True
611 for o_pat in o_pats:
612 _log.info('modifying Orthanc patient [%s]: DICOM ID [%s] -> [%s]', o_pat['ID'], old_patient_id, new_patient_id)
613 if self.patient_is_protected(o_pat['ID']):
614 _log.debug('patient protected: %s, unprotecting for modification', o_pat['ID'])
615 if not self.unprotect_patient(o_pat['ID']):
616 _log.error('cannot unlock patient [%s], skipping', o_pat['ID'])
617 all_modified = False
618 continue
619 was_protected = True
620 else:
621 was_protected = False
622 pat_url = '%s/patients/%s' % (self.__server_url, o_pat['ID'])
623 modify_url = '%s/modify' % pat_url
624 result = self.__run_POST(modify_url, data = modify_data)
625 _log.debug('modified: %s', result)
626 if result is False:
627 _log.error('cannot modify patient [%s]', o_pat['ID'])
628 all_modified = False
629 continue
630 newly_created_patient_id = result['ID']
631 _log.debug('newly created Orthanc patient ID: %s', newly_created_patient_id)
632 _log.debug('deleting archived patient: %s', self.__run_DELETE(pat_url))
633 if was_protected:
634 if not self.protect_patient(newly_created_patient_id):
635 _log.error('cannot re-lock (new) patient [%s]', newly_created_patient_id)
636
637 return all_modified
638
639 #--------------------------------------------------------
640 # upload API
641 #--------------------------------------------------------
643 if gmTools.fname_stem(filename) == 'DICOMDIR':
644 _log.debug('ignoring [%s], no use uploading DICOMDIR files to Orthanc', filename)
645 return True
646
647 if check_mime_type:
648 if gmMimeLib.guess_mimetype(filename) != 'application/dicom':
649 _log.error('not considered a DICOM file: %s', filename)
650 return False
651 try:
652 f = io.open(filename, 'rb')
653 except Exception:
654 _log.exception('cannot open [%s]', filename)
655 return False
656 dcm_data = f.read()
657 f.close()
658 _log.debug('uploading [%s]', filename)
659 upload_url = '%s/instances' % self.__server_url
660 uploaded = self.__run_POST(upload_url, data = dcm_data, content_type = 'application/dicom')
661 if uploaded is False:
662 _log.error('cannot upload [%s]', filename)
663 return False
664 _log.debug(uploaded)
665 if uploaded['Status'] == 'AlreadyStored':
666 # paranoia, as is our custom
667 available_fields_url = '%s%s/attachments/dicom' % (self.__server_url, uploaded['Path']) # u'Path': u'/instances/1440110e-9cd02a98-0b1c0452-087d35db-3fd5eb05'
668 available_fields = self.__run_GET(available_fields_url, allow_cached = True)
669 if 'md5' not in available_fields:
670 _log.debug('md5 of instance not available in Orthanc, cannot compare against file md5, trusting Orthanc')
671 return True
672 md5_url = '%s/md5' % available_fields_url
673 md5_db = self.__run_GET(md5_url)
674 md5_file = gmTools.file2md5(filename)
675 if md5_file != md5_db:
676 _log.error('local md5: %s', md5_file)
677 _log.error('in-db md5: %s', md5_db)
678 _log.error('MD5 mismatch !')
679 return False
680 _log.error('MD5 match between file and database')
681 return True
682
683 #--------------------------------------------------------
685 uploaded = []
686 not_uploaded = []
687 for filename in files:
688 success = self.upload_dicom_file(filename, check_mime_type = check_mime_type)
689 if success:
690 uploaded.append(filename)
691 continue
692 not_uploaded.append(filename)
693
694 if len(not_uploaded) > 0:
695 _log.error('not all files uploaded')
696 return (uploaded, not_uploaded)
697
698 #--------------------------------------------------------
699 - def upload_from_directory(self, directory=None, recursive=False, check_mime_type=False, ignore_other_files=True):
700
701 #--------------------
702 def _on_error(exc):
703 _log.error('DICOM (?) file not accessible: %s', exc.filename)
704 _log.error(exc)
705 #--------------------
706
707 _log.debug('uploading DICOM files from [%s]', directory)
708 if not recursive:
709 files2try = os.listdir(directory)
710 _log.debug('found %s files', len(files2try))
711 if ignore_other_files:
712 files2try = [ f for f in files2try if gmMimeLib.guess_mimetype(f) == 'application/dicom' ]
713 _log.debug('DICOM files therein: %s', len(files2try))
714 return self.upload_dicom_files(files = files2try, check_mime_type = check_mime_type)
715
716 _log.debug('recursing for DICOM files')
717 uploaded = []
718 not_uploaded = []
719 for curr_root, curr_root_subdirs, curr_root_files in os.walk(directory, onerror = _on_error):
720 _log.debug('recursing into [%s]', curr_root)
721 files2try = [ os.path.join(curr_root, f) for f in curr_root_files ]
722 _log.debug('found %s files', len(files2try))
723 if ignore_other_files:
724 files2try = [ f for f in files2try if gmMimeLib.guess_mimetype(f) == 'application/dicom' ]
725 _log.debug('DICOM files therein: %s', len(files2try))
726 up, not_up = self.upload_dicom_files (
727 files = files2try,
728 check_mime_type = check_mime_type
729 )
730 uploaded.extend(up)
731 not_uploaded.extend(not_up)
732
733 return (uploaded, not_uploaded)
734
735 #--------------------------------------------------------
738
739 #--------------------------------------------------------
740 # helper functions
741 #--------------------------------------------------------
743
744 study_keys2hide = ['ModifiedFrom', 'Type', 'ID', 'ParentPatient', 'Series']
745 series_keys2hide = ['ModifiedFrom', 'Type', 'ID', 'ParentStudy', 'Instances']
746
747 studies_by_patient = []
748 series_keys = {}
749 series_keys_m = {}
750
751 # loop over patients
752 for pat in orthanc_patients:
753 pat_dict = {
754 'orthanc_id': pat['ID'],
755 'name': None,
756 'external_id': None,
757 'date_of_birth': None,
758 'gender': None,
759 'studies': []
760 }
761 try:
762 pat_dict['name'] = pat['MainDicomTags']['PatientName'].strip()
763 except KeyError:
764 pass
765 try:
766 pat_dict['external_id'] = pat['MainDicomTags']['PatientID'].strip()
767 except KeyError:
768 pass
769 try:
770 pat_dict['date_of_birth'] = pat['MainDicomTags']['PatientBirthDate'].strip()
771 except KeyError:
772 pass
773 try:
774 pat_dict['gender'] = pat['MainDicomTags']['PatientSex'].strip()
775 except KeyError:
776 pass
777 for key in pat_dict:
778 if pat_dict[key] in ['unknown', '(null)', '']:
779 pat_dict[key] = None
780 pat_dict[key] = cleanup_dicom_string(pat_dict[key])
781 studies_by_patient.append(pat_dict)
782
783 # loop over studies of patient
784 orth_studies = self.__run_GET(url = '%s/patients/%s/studies' % (self.__server_url, pat['ID']))
785 if orth_studies is False:
786 _log.error('cannot retrieve studies')
787 return []
788 for orth_study in orth_studies:
789 study_dict = {
790 'orthanc_id': orth_study['ID'],
791 'date': None,
792 'time': None,
793 'description': None,
794 'referring_doc': None,
795 'requesting_doc': None,
796 'performing_doc': None,
797 'operator_name': None,
798 'radiographer_code': None,
799 'radiology_org': None,
800 'radiology_dept': None,
801 'radiology_org_addr': None,
802 'station_name': None,
803 'series': []
804 }
805 try:
806 study_dict['date'] = orth_study['MainDicomTags']['StudyDate'].strip()
807 except KeyError:
808 pass
809 try:
810 study_dict['time'] = orth_study['MainDicomTags']['StudyTime'].strip()
811 except KeyError:
812 pass
813 try:
814 study_dict['description'] = orth_study['MainDicomTags']['StudyDescription'].strip()
815 except KeyError:
816 pass
817 try:
818 study_dict['referring_doc'] = orth_study['MainDicomTags']['ReferringPhysicianName'].strip()
819 except KeyError:
820 pass
821 try:
822 study_dict['requesting_doc'] = orth_study['MainDicomTags']['RequestingPhysician'].strip()
823 except KeyError:
824 pass
825 try:
826 study_dict['radiology_org_addr'] = orth_study['MainDicomTags']['InstitutionAddress'].strip()
827 except KeyError:
828 pass
829 try:
830 study_dict['radiology_org'] = orth_study['MainDicomTags']['InstitutionName'].strip()
831 if study_dict['radiology_org_addr'] is not None:
832 if study_dict['radiology_org'] in study_dict['radiology_org_addr']:
833 study_dict['radiology_org'] = None
834 except KeyError:
835 pass
836 try:
837 study_dict['radiology_dept'] = orth_study['MainDicomTags']['InstitutionalDepartmentName'].strip()
838 if study_dict['radiology_org'] is not None:
839 if study_dict['radiology_dept'] in study_dict['radiology_org']:
840 study_dict['radiology_dept'] = None
841 if study_dict['radiology_org_addr'] is not None:
842 if study_dict['radiology_dept'] in study_dict['radiology_org_addr']:
843 study_dict['radiology_dept'] = None
844 except KeyError:
845 pass
846 try:
847 study_dict['station_name'] = orth_study['MainDicomTags']['StationName'].strip()
848 if study_dict['radiology_org'] is not None:
849 if study_dict['station_name'] in study_dict['radiology_org']:
850 study_dict['station_name'] = None
851 if study_dict['radiology_org_addr'] is not None:
852 if study_dict['station_name'] in study_dict['radiology_org_addr']:
853 study_dict['station_name'] = None
854 if study_dict['radiology_dept'] is not None:
855 if study_dict['station_name'] in study_dict['radiology_dept']:
856 study_dict['station_name'] = None
857 except KeyError:
858 pass
859 for key in study_dict:
860 if study_dict[key] in ['unknown', '(null)', '']:
861 study_dict[key] = None
862 study_dict[key] = cleanup_dicom_string(study_dict[key])
863 study_dict['all_tags'] = {}
864 try:
865 orth_study['PatientMainDicomTags']
866 except KeyError:
867 orth_study['PatientMainDicomTags'] = pat['MainDicomTags']
868 for key in orth_study.keys():
869 if key == 'MainDicomTags':
870 for mkey in orth_study['MainDicomTags'].keys():
871 study_dict['all_tags'][mkey] = orth_study['MainDicomTags'][mkey].strip()
872 continue
873 if key == 'PatientMainDicomTags':
874 for pkey in orth_study['PatientMainDicomTags'].keys():
875 study_dict['all_tags'][pkey] = orth_study['PatientMainDicomTags'][pkey].strip()
876 continue
877 study_dict['all_tags'][key] = orth_study[key]
878 _log.debug('study: %s', study_dict['all_tags'].keys())
879 for key in study_keys2hide:
880 try: del study_dict['all_tags'][key]
881 except KeyError: pass
882 pat_dict['studies'].append(study_dict)
883
884 # loop over series in study
885 for orth_series_id in orth_study['Series']:
886 orth_series = self.__run_GET(url = '%s/series/%s' % (self.__server_url, orth_series_id))
887 #slices = orth_series['Instances']
888 ordered_slices = self.__run_GET(url = '%s/series/%s/ordered-slices' % (self.__server_url, orth_series_id))
889 slices = [ s[0] for s in ordered_slices['SlicesShort'] ]
890 if orth_series is False:
891 _log.error('cannot retrieve series')
892 return []
893 series_dict = {
894 'orthanc_id': orth_series['ID'],
895 'instances': slices,
896 'modality': None,
897 'date': None,
898 'time': None,
899 'description': None,
900 'body_part': None,
901 'protocol': None,
902 'performed_procedure_step_description': None,
903 'acquisition_device_processing_description': None,
904 'operator_name': None,
905 'radiographer_code': None,
906 'performing_doc': None
907 }
908 try:
909 series_dict['modality'] = orth_series['MainDicomTags']['Modality'].strip()
910 except KeyError:
911 pass
912 try:
913 series_dict['date'] = orth_series['MainDicomTags']['SeriesDate'].strip()
914 except KeyError:
915 pass
916 try:
917 series_dict['description'] = orth_series['MainDicomTags']['SeriesDescription'].strip()
918 except KeyError:
919 pass
920 try:
921 series_dict['time'] = orth_series['MainDicomTags']['SeriesTime'].strip()
922 except KeyError:
923 pass
924 try:
925 series_dict['body_part'] = orth_series['MainDicomTags']['BodyPartExamined'].strip()
926 except KeyError:
927 pass
928 try:
929 series_dict['protocol'] = orth_series['MainDicomTags']['ProtocolName'].strip()
930 except KeyError:
931 pass
932 try:
933 series_dict['performed_procedure_step_description'] = orth_series['MainDicomTags']['PerformedProcedureStepDescription'].strip()
934 except KeyError:
935 pass
936 try:
937 series_dict['acquisition_device_processing_description'] = orth_series['MainDicomTags']['AcquisitionDeviceProcessingDescription'].strip()
938 except KeyError:
939 pass
940 try:
941 series_dict['operator_name'] = orth_series['MainDicomTags']['OperatorsName'].strip()
942 except KeyError:
943 pass
944 try:
945 series_dict['radiographer_code'] = orth_series['MainDicomTags']['RadiographersCode'].strip()
946 except KeyError:
947 pass
948 try:
949 series_dict['performing_doc'] = orth_series['MainDicomTags']['PerformingPhysicianName'].strip()
950 except KeyError:
951 pass
952 for key in series_dict:
953 if series_dict[key] in ['unknown', '(null)', '']:
954 series_dict[key] = None
955 if series_dict['description'] == series_dict['protocol']:
956 _log.debug('<series description> matches <series protocol>, ignoring protocol')
957 series_dict['protocol'] = None
958 if series_dict['performed_procedure_step_description'] in [series_dict['description'], series_dict['protocol']]:
959 series_dict['performed_procedure_step_description'] = None
960 if series_dict['performed_procedure_step_description'] is not None:
961 # weed out "numeric" only
962 if regex.match ('[.,/\|\-\s\d]+', series_dict['performed_procedure_step_description'], flags = regex.UNICODE):
963 series_dict['performed_procedure_step_description'] = None
964 if series_dict['acquisition_device_processing_description'] in [series_dict['description'], series_dict['protocol']]:
965 series_dict['acquisition_device_processing_description'] = None
966 if series_dict['acquisition_device_processing_description'] is not None:
967 # weed out "numeric" only
968 if regex.match ('[.,/\|\-\s\d]+', series_dict['acquisition_device_processing_description'], flags = regex.UNICODE):
969 series_dict['acquisition_device_processing_description'] = None
970 if series_dict['date'] == study_dict['date']:
971 _log.debug('<series date> matches <study date>, ignoring date')
972 series_dict['date'] = None
973 if series_dict['time'] == study_dict['time']:
974 _log.debug('<series time> matches <study time>, ignoring time')
975 series_dict['time'] = None
976 for key in series_dict:
977 series_dict[key] = cleanup_dicom_string(series_dict[key])
978 series_dict['all_tags'] = {}
979 for key in orth_series.keys():
980 if key == 'MainDicomTags':
981 for mkey in orth_series['MainDicomTags'].keys():
982 series_dict['all_tags'][mkey] = orth_series['MainDicomTags'][mkey].strip()
983 continue
984 series_dict['all_tags'][key] = orth_series[key]
985 _log.debug('series: %s', series_dict['all_tags'].keys())
986 for key in series_keys2hide:
987 try: del series_dict['all_tags'][key]
988 except KeyError: pass
989 study_dict['operator_name'] = series_dict['operator_name'] # will collapse all operators into that of the last series
990 study_dict['radiographer_code'] = series_dict['radiographer_code'] # will collapse all into that of the last series
991 study_dict['performing_doc'] = series_dict['performing_doc'] # will collapse all into that of the last series
992 study_dict['series'].append(series_dict)
993
994 return studies_by_patient
995
996 #--------------------------------------------------------
997 # generic REST helpers
998 #--------------------------------------------------------
1000 if data is None:
1001 data = {}
1002 headers = {}
1003 if not allow_cached:
1004 headers['cache-control'] = 'no-cache'
1005 params = ''
1006 if len(data.keys()) > 0:
1007 params = '?' + urlencode(data)
1008 url_with_params = url + params
1009
1010 try:
1011 response, content = self.__conn.request(url_with_params, 'GET', headers = headers)
1012 except (socket.error, http.client.ResponseNotReady, http.client.InvalidURL, OverflowError, httplib2.ServerNotFoundError):
1013 _log.exception('exception in GET')
1014 _log.debug(' url: %s', url_with_params)
1015 _log.debug(' headers: %s', headers)
1016 return False
1017
1018 if response.status not in [ 200 ]:
1019 _log.error('GET returned non-OK status: %s', response.status)
1020 _log.debug(' url: %s', url_with_params)
1021 _log.debug(' headers: %s', headers)
1022 _log.error(' response: %s', response)
1023 _log.debug(' content: %s', content)
1024 return False
1025
1026 # _log.error(' response: %s', response)
1027 # _log.error(' content type: %s', type(content))
1028
1029 if response['content-type'].startswith('text/plain'):
1030 # utf8 ?
1031 # urldecode ?
1032 # latin1 = Orthanc default = tools/default-encoding ?
1033 # ascii ?
1034 return content.decode('utf8')
1035
1036 if response['content-type'].startswith('application/json'):
1037 try:
1038 return json.loads(content)
1039 except Exception:
1040 return content
1041
1042 return content
1043
1044 #--------------------------------------------------------
1046
1047 body = data
1048 headers = {'content-type' : content_type}
1049 if isinstance(data, str):
1050 if content_type is None:
1051 headers['content-type'] = 'text/plain'
1052 elif isinstance(data, bytes):
1053 if content_type is None:
1054 headers['content-type'] = 'application/octet-stream'
1055 else:
1056 body = json.dumps(data)
1057 headers['content-type'] = 'application/json'
1058
1059 try:
1060 try:
1061 response, content = self.__conn.request(url, 'POST', body = body, headers = headers)
1062 except BrokenPipeError:
1063 response, content = self.__conn.request(url, 'POST', body = body, headers = headers)
1064 except (socket.error, http.client.ResponseNotReady, OverflowError):
1065 _log.exception('exception in POST')
1066 _log.debug(' url: %s', url)
1067 _log.debug(' headers: %s', headers)
1068 _log.debug(' body: %s', body[:16])
1069 return False
1070
1071 if response.status == 404:
1072 _log.debug('no data, response: %s', response)
1073 if output_file is None:
1074 return []
1075 return False
1076 if response.status not in [ 200, 302 ]:
1077 _log.error('POST returned non-OK status: %s', response.status)
1078 _log.debug(' url: %s', url)
1079 _log.debug(' headers: %s', headers)
1080 _log.debug(' body: %s', body[:16])
1081 _log.error(' response: %s', response)
1082 _log.debug(' content: %s', content)
1083 return False
1084
1085 try:
1086 content = json.loads(content)
1087 except Exception:
1088 pass
1089 if output_file is None:
1090 return content
1091 output_file.write(content)
1092 return True
1093
1094 #--------------------------------------------------------
1096
1097 body = data
1098 headers = {'content-type' : content_type}
1099 if isinstance(data, str):
1100 if content_type is None:
1101 headers['content-type'] = 'text/plain'
1102 elif isinstance(data, bytes):
1103 if content_type is None:
1104 headers['content-type'] = 'application/octet-stream'
1105 else:
1106 body = json.dumps(data)
1107 headers['content-type'] = 'application/json'
1108
1109 try:
1110 try:
1111 response, content = self.__conn.request(url, 'PUT', body = body, headers = headers)
1112 except BrokenPipeError:
1113 response, content = self.__conn.request(url, 'PUT', body = body, headers = headers)
1114 except (socket.error, http.client.ResponseNotReady, OverflowError):
1115 _log.exception('exception in PUT')
1116 _log.debug(' url: %s', url)
1117 _log.debug(' headers: %s', headers)
1118 _log.debug(' body: %s', body[:16])
1119 return False
1120
1121 if response.status == 404:
1122 _log.debug('no data, response: %s', response)
1123 return []
1124 if response.status not in [ 200, 302 ]:
1125 _log.error('PUT returned non-OK status: %s', response.status)
1126 _log.debug(' url: %s', url)
1127 _log.debug(' headers: %s', headers)
1128 _log.debug(' body: %s', body[:16])
1129 _log.error(' response: %s', response)
1130 _log.debug(' content: %s', content)
1131 return False
1132
1133 if response['content-type'].startswith('text/plain'):
1134 # utf8 ?
1135 # urldecode ?
1136 # latin1 = Orthanc default = tools/default-encoding ?
1137 # ascii ?
1138 return content.decode('utf8')
1139
1140 if response['content-type'].startswith('application/json'):
1141 try:
1142 return json.loads(content)
1143 except Exception:
1144 return content
1145
1146 return content
1147
1148 #--------------------------------------------------------
1150 try:
1151 response, content = self.__conn.request(url, 'DELETE')
1152 except (http.client.ResponseNotReady, socket.error, OverflowError):
1153 _log.exception('exception in DELETE')
1154 _log.debug(' url: %s', url)
1155 return False
1156
1157 if response.status not in [ 200 ]:
1158 _log.error('DELETE returned non-OK status: %s', response.status)
1159 _log.debug(' url: %s', url)
1160 _log.error(' response: %s', response)
1161 _log.debug(' content: %s', content)
1162 return False
1163
1164 if response['content-type'].startswith('text/plain'):
1165 # utf8 ?
1166 # urldecode ?
1167 # latin1 = Orthanc default = tools/default-encoding ?
1168 # ascii ?
1169 return content.decode('utf8')
1170
1171 if response['content-type'].startswith('application/json'):
1172 try:
1173 return json.loads(content)
1174 except Exception:
1175 return content
1176
1177 return content
1178
1179 #------------------------------------------------------------
1181 if not isinstance(dicom_str, str):
1182 return dicom_str
1183 dicom_str = regex.sub('\^+', ' ', dicom_str.strip('^'))
1184 #dicom_str = dicom_str.replace('\r\n', ' [CR] ')
1185 return dicom_str
1186
1187 #---------------------------------------------------------------------------
1189 assert (pdf_name is not None), '<pdfname> must not be None'
1190 assert (person is not None), '<person> must not be None'
1191
1192 if title is None:
1193 title = pdf_name
1194 if dcm_name is None:
1195 dcm_name = gmTools.get_unique_filename(suffix = '.dcm')
1196 name = person.active_name
1197 cmd_line = [
1198 'pdf2dcm',
1199 '--patient-id', person.suggest_external_id(target = 'PACS'),
1200 '--patient-name', ('%s^%s' % (name['lastnames'], name['firstnames'])).replace(' ', '^'),
1201 '--title', title,
1202 '--log-level', 'trace'
1203 ]
1204 if person['dob'] is not None:
1205 cmd_line.append('--patient-birthdate')
1206 cmd_line.append(person.get_formatted_dob(format = '%Y%m%d', honor_estimation = False))
1207 if person['gender'] is not None:
1208 cmd_line.append('--patient-sex')
1209 cmd_line.append(_map_gender_gm2dcm[person['gender']])
1210 cmd_line.append(pdf_name)
1211 cmd_line.append(dcm_name)
1212 success, exit_code, stdout = gmShellAPI.run_process(cmd_line = cmd_line, encoding = 'utf8', verbose = verbose)
1213 if success:
1214 return dcm_name
1215 return None
1216
1217 #============================================================
1218 # main
1219 #------------------------------------------------------------
1220 if __name__ == "__main__":
1221
1222 if len(sys.argv) == 1:
1223 sys.exit()
1224
1225 if sys.argv[1] != 'test':
1226 sys.exit()
1227
1228 # if __name__ == '__main__':
1229 # sys.path.insert(0, '../../')
1230 from Gnumed.pycommon import gmLog2
1231
1232 #--------------------------------------------------------
1234 orthanc = cOrthancServer()
1235 if not orthanc.connect(host, port, user = None, password = None): #, expected_aet = 'another AET'
1236 print('error connecting to server:', orthanc.connect_error)
1237 return False
1238 print('Connected to Orthanc server "%s" (AET [%s] - version [%s] - DB [%s] - API [%s])' % (
1239 orthanc.server_identification['Name'],
1240 orthanc.server_identification['DicomAet'],
1241 orthanc.server_identification['Version'],
1242 orthanc.server_identification['DatabaseVersion'],
1243 orthanc.server_identification['ApiVersion']
1244 ))
1245 print('')
1246 print('Please enter patient name parts, separated by SPACE.')
1247
1248 while True:
1249 entered_name = gmTools.prompted_input(prompt = "\nEnter person search term or leave blank to exit")
1250 if entered_name in ['exit', 'quit', 'bye', None]:
1251 print("user cancelled patient search")
1252 break
1253
1254 pats = orthanc.get_patients_by_external_id(external_id = entered_name)
1255 if len(pats) > 0:
1256 print('Patients found:')
1257 for pat in pats:
1258 print(' -> ', pat)
1259 continue
1260
1261 pats = orthanc.get_patients_by_name(name_parts = entered_name.split(), fuzzy = True)
1262 print('Patients found:')
1263 for pat in pats:
1264 print(' -> ', pat)
1265 print(' verifying ...')
1266 bad_data = orthanc.verify_patient_data(pat['ID'])
1267 print(' bad data:')
1268 for bad in bad_data:
1269 print(' -> ', bad)
1270 continue
1271
1272 continue
1273
1274 pats = orthanc.get_studies_list_by_patient_name(name_parts = entered_name.split(), fuzzy = True)
1275 print('Patients found from studies list:')
1276 for pat in pats:
1277 print(' -> ', pat['name'])
1278 for study in pat['studies']:
1279 print(' ', gmTools.format_dict_like(study, relevant_keys = ['orthanc_id', 'date', 'time'], template = 'study [%%(orthanc_id)s] at %%(date)s %%(time)s contains %s series' % len(study['series'])))
1280 # for series in study['series']:
1281 # print (
1282 # u' ',
1283 # gmTools.format_dict_like (
1284 # series,
1285 # relevant_keys = ['orthanc_id', 'date', 'time', 'modality', 'instances', 'body_part', 'protocol', 'description', 'station'],
1286 # template = u'series [%(orthanc_id)s] at %(date)s %(time)s: "%(description)s" %(modality)s@%(station)s (%(protocol)s) of body part "%(body_part)s" holds images:\n%(instances)s'
1287 # )
1288 # )
1289 # print(orthanc.get_studies_with_dicomdir(study_ids = [study['orthanc_id']], filename = 'study_%s.zip' % study['orthanc_id'], create_zip = True))
1290 #print(orthanc.get_study_as_zip(study_id = study['orthanc_id'], filename = 'study_%s.zip' % study['orthanc_id']))
1291 #print(orthanc.get_studies_as_zip_with_dicomdir(study_ids = [ s['orthanc_id'] for s in pat['studies'] ], filename = 'studies_of_%s.zip' % pat['orthanc_id']))
1292 print('--------')
1293
1294 #--------------------------------------------------------
1296 try:
1297 host = sys.argv[2]
1298 except IndexError:
1299 host = None
1300 try:
1301 port = sys.argv[3]
1302 except IndexError:
1303 port = '8042'
1304
1305 orthanc_console(host, port)
1306
1307 #--------------------------------------------------------
1309 try:
1310 host = sys.argv[2]
1311 port = sys.argv[3]
1312 except IndexError:
1313 host = None
1314 port = '8042'
1315 orthanc = cOrthancServer()
1316 if not orthanc.connect(host, port, user = None, password = None): #, expected_aet = 'another AET'
1317 print('error connecting to server:', orthanc.connect_error)
1318 return False
1319 print('Connected to Orthanc server "%s" (AET [%s] - version [%s] - DB [%s])' % (
1320 orthanc.server_identification['Name'],
1321 orthanc.server_identification['DicomAet'],
1322 orthanc.server_identification['Version'],
1323 orthanc.server_identification['DatabaseVersion']
1324 ))
1325 print('')
1326 print('Please enter patient name parts, separated by SPACE.')
1327
1328 entered_name = gmTools.prompted_input(prompt = "\nEnter person search term or leave blank to exit")
1329 if entered_name in ['exit', 'quit', 'bye', None]:
1330 print("user cancelled patient search")
1331 return
1332
1333 pats = orthanc.get_patients_by_name(name_parts = entered_name.split(), fuzzy = True)
1334 if len(pats) == 0:
1335 print('no patient found')
1336 return
1337
1338 pat = pats[0]
1339 print('test patient:')
1340 print(pat)
1341 old_id = pat['MainDicomTags']['PatientID']
1342 new_id = old_id + '-1'
1343 print('setting [%s] to [%s]:' % (old_id, new_id), orthanc.modify_patient_id(old_id, new_id))
1344
1345 #--------------------------------------------------------
1347 # try:
1348 # host = sys.argv[2]
1349 # port = sys.argv[3]
1350 # except IndexError:
1351 host = None
1352 port = '8042'
1353
1354 orthanc = cOrthancServer()
1355 if not orthanc.connect(host, port, user = None, password = None): #, expected_aet = 'another AET'
1356 print('error connecting to server:', orthanc.connect_error)
1357 return False
1358 print('Connected to Orthanc server "%s" (AET [%s] - version [%s] - DB [%s] - REST API [%s])' % (
1359 orthanc.server_identification['Name'],
1360 orthanc.server_identification['DicomAet'],
1361 orthanc.server_identification['Version'],
1362 orthanc.server_identification['DatabaseVersion'],
1363 orthanc.server_identification['ApiVersion']
1364 ))
1365 print('')
1366
1367 #orthanc.upload_dicom_file(sys.argv[2])
1368 orthanc.upload_from_directory(directory = sys.argv[2], recursive = True, check_mime_type = False, ignore_other_files = True)
1369
1370 #--------------------------------------------------------
1372 host = None
1373 port = '8042'
1374
1375 orthanc = cOrthancServer()
1376 if not orthanc.connect(host, port, user = None, password = None): #, expected_aet = 'another AET'
1377 print('error connecting to server:', orthanc.connect_error)
1378 return False
1379 print('Connected to Orthanc server "%s" (AET [%s] - version [%s] - DB [%s])' % (
1380 orthanc.server_identification['Name'],
1381 orthanc.server_identification['DicomAet'],
1382 orthanc.server_identification['Version'],
1383 orthanc.server_identification['DatabaseVersion']
1384 ))
1385 print('')
1386
1387 print(orthanc.get_instance_preview('f4f07d22-0d8265ef-112ea4e9-dc140e13-350c06d1'))
1388 print(orthanc.get_instance('f4f07d22-0d8265ef-112ea4e9-dc140e13-350c06d1'))
1389
1390 #--------------------------------------------------------
1411 #print(orthanc.get_instance_dicom_tags(instance_id, simplified = True))
1412
1413 #--------------------------------------------------------
1419
1420 #--------------------------------------------------------
1421 #run_console()
1422 #test_modify_patient_id()
1423 #test_upload_files()
1424 #test_get_instance_preview()
1425 #test_get_instance_tags()
1426 test_pdf2dcm()
1427
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Fri Jan 25 02:55:27 2019 | http://epydoc.sourceforge.net |