1
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
10 import io
11 import os
12 import sys
13 import re as regex
14 import logging
15 import http.client
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
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 gmDateTime
34
35
36
37 _log = logging.getLogger('gm.dicom')
38
39 _map_gender_gm2dcm = {
40 'm': 'M',
41 'f': 'F',
42 'tm': 'M',
43 'tf': 'F',
44 'h': 'O'
45 }
46
47
49
50
51
52
53
54
55
56
57
58
59 - def connect(self, host, port, user, password, expected_minimal_version=None, expected_name=None, expected_aet=None):
60 try:
61 int(port)
62 except Exception:
63 _log.error('invalid port [%s]', port)
64 return False
65 if (host is None) or (host.strip() == ''):
66 host = 'localhost'
67 try:
68 self.__server_url = str('http://%s:%s' % (host, port))
69 except Exception:
70 _log.exception('cannot create server url from: host [%s] and port [%s]', host, port)
71 return False
72 self.__user = user
73 self.__password = password
74 _log.info('connecting as [%s] to Orthanc server at [%s]', self.__user, self.__server_url)
75 cache_dir = os.path.join(gmTools.gmPaths().user_tmp_dir, '.orthanc2gm-cache')
76 gmTools.mkdir(cache_dir, 0o700)
77 _log.debug('using cache directory: %s', cache_dir)
78 self.__conn = httplib2.Http(cache = cache_dir)
79 self.__conn.add_credentials(self.__user, self.__password)
80 _log.debug('connected to server: %s', self.server_identification)
81 self.connect_error = ''
82 if self.server_identification is False:
83 self.connect_error += 'retrieving server identification failed'
84 return False
85 if expected_minimal_version is not None:
86 if version.LooseVersion(self.server_identification['Version']) < version.LooseVersion(expected_min_version):
87 _log.error('server too old, needed [%s]', expected_min_version)
88 self.connect_error += 'server too old, needed version [%s]' % expected_min_version
89 return False
90 if expected_name is not None:
91 if self.server_identification['Name'] != expected_name:
92 _log.error('wrong server name, expected [%s]', expected_name)
93 self.connect_error += 'wrong server name, expected [%s]' % expected_name
94 return False
95 if expected_aet is not None:
96 if self.server_identification['DicomAet'] != expected_name:
97 _log.error('wrong server AET, expected [%s]', expected_aet)
98 self.connect_error += 'wrong server AET, expected [%s]' % expected_aet
99 return False
100 return True
101
102
104 try:
105 return self.__server_identification
106 except AttributeError:
107 pass
108 system_data = self.__run_GET(url = '%s/system' % self.__server_url)
109 if system_data is False:
110 _log.error('unable to get server identification')
111 return False
112 _log.debug('server: %s', system_data)
113 self.__server_identification = system_data
114
115 self.__initial_orthanc_encoding = self.__run_GET(url = '%s/tools/default-encoding' % self.__server_url)
116 _log.debug('initial Orthanc encoding: %s', self.__initial_orthanc_encoding)
117
118
119 tolerance = 60
120 client_now_as_utc = pydt.datetime.utcnow()
121 start = time.time()
122 orthanc_now_str = self.__run_GET(url = '%s/tools/now' % self.__server_url)
123 end = time.time()
124 query_duration = end - start
125 orthanc_now_unknown_tz = pydt.datetime.strptime(orthanc_now_str, '%Y%m%dT%H%M%S')
126 _log.debug('GNUmed "now" (UTC): %s', client_now_as_utc)
127 _log.debug('Orthanc "now" (UTC): %s', orthanc_now_unknown_tz)
128 _log.debug('wire roundtrip (seconds): %s', query_duration)
129 _log.debug('maximum skew tolerance (seconds): %s', tolerance)
130 if query_duration > tolerance:
131 _log.info('useless to check GNUmed/Orthanc time skew, wire roundtrip (%s) > tolerance (%s)', query_duration, tolerance)
132 else:
133 if orthanc_now_unknown_tz > client_now_as_utc:
134 real_skew = orthanc_now_unknown_tz - client_now_as_utc
135 else:
136 real_skew = client_now_as_utc - orthanc_now_unknown_tz
137 _log.info('GNUmed/Orthanc time skew: %s', real_skew)
138 if real_skew > pydt.timedelta(seconds = tolerance):
139 _log.error('GNUmed/Orthanc time skew > tolerance (may be due to timezone differences on Orthanc < v1.3.2)')
140
141 return self.__server_identification
142
143 server_identification = property(_get_server_identification, lambda x:x)
144
145
147
148 return 'Orthanc::%(Name)s::%(DicomAet)s' % self.__server_identification
149
150 as_external_id_issuer = property(_get_as_external_id_issuer, lambda x:x)
151
152
154 if self.__user is None:
155 return self.__server_url
156 return self.__server_url.replace('http://', 'http://%s@' % self.__user)
157
158 url_browse_patients = property(_get_url_browse_patients, lambda x:x)
159
160
164
165
169
170
171
172
174 _log.info('searching for Orthanc patients matching %s', person)
175
176
177 pacs_ids = person.get_external_ids(id_type = 'PACS', issuer = self.as_external_id_issuer)
178 if len(pacs_ids) > 1:
179 _log.error('GNUmed patient has more than one ID for this PACS: %s', pacs_ids)
180 _log.error('the PACS ID is expected to be unique per PACS')
181 return []
182
183 pacs_ids2use = []
184
185 if len(pacs_ids) == 1:
186 pacs_ids2use.append(pacs_ids[0]['value'])
187 pacs_ids2use.extend(person.suggest_external_ids(target = 'PACS'))
188
189 for pacs_id in pacs_ids2use:
190 _log.debug('using PACS ID [%s]', pacs_id)
191 pats = self.get_patients_by_external_id(external_id = pacs_id)
192 if len(pats) > 1:
193 _log.warning('more than one Orthanc patient matches PACS ID: %s', pacs_id)
194 if len(pats) > 0:
195 return pats
196
197 _log.debug('no matching patient found in PACS')
198
199
200
201
202
203
204 return []
205
206
208 matching_patients = []
209 _log.info('searching for patients with external ID >>>%s<<<', external_id)
210
211
212 search_data = {
213 'Level': 'Patient',
214 'CaseSensitive': False,
215 'Expand': True,
216 'Query': {'PatientID': external_id.strip('*')}
217 }
218 _log.info('server-side C-FIND SCU over REST search, mogrified search data: %s', search_data)
219 matches = self.__run_POST(url = '%s/tools/find' % self.__server_url, data = search_data)
220
221
222 for match in matches:
223 self.protect_patient(orthanc_id = match['ID'])
224 return matches
225
226
227
228
229
230
231
232
233
234
235
236
237
238
240 _log.info('name parts %s, gender [%s], dob [%s], fuzzy: %s', name_parts, gender, dob, fuzzy)
241 if len(name_parts) > 1:
242 return self.get_patients_by_name_parts(name_parts = name_parts, gender = gender, dob = dob, fuzzy = fuzzy)
243 if not fuzzy:
244 search_term = name_parts[0].strip('*')
245 else:
246 search_term = name_parts[0]
247 if not search_term.endswith('*'):
248 search_term += '*'
249 search_data = {
250 'Level': 'Patient',
251 'CaseSensitive': False,
252 'Expand': True,
253 'Query': {'PatientName': search_term}
254 }
255 if gender is not None:
256 gender = _map_gender_gm2dcm[gender.lower()]
257 if gender is not None:
258 search_data['Query']['PatientSex'] = gender
259 if dob is not None:
260 search_data['Query']['PatientBirthDate'] = dob.strftime('%Y%m%d')
261 _log.info('server-side C-FIND SCU over REST search, mogrified search data: %s', search_data)
262 matches = self.__run_POST(url = '%s/tools/find' % self.__server_url, data = search_data)
263 return matches
264
265
267
268 matching_patients = []
269 clean_parts = []
270 for part in name_parts:
271 if part.strip() == '':
272 continue
273 clean_parts.append(part.lower().strip())
274 _log.info('client-side patient search, scrubbed search terms: %s', clean_parts)
275 pat_ids = self.__run_GET(url = '%s/patients' % self.__server_url)
276 if pat_ids is False:
277 _log.error('cannot retrieve patients')
278 return []
279 for pat_id in pat_ids:
280 orthanc_pat = self.__run_GET(url = '%s/patients/%s' % (self.__server_url, pat_id))
281 if orthanc_pat is False:
282 _log.error('cannot retrieve patient')
283 continue
284 orthanc_name = orthanc_pat['MainDicomTags']['PatientName'].lower().strip()
285 if not fuzzy:
286 orthanc_name = orthanc_name.replace(' ', ',').replace('^', ',').split(',')
287 parts_in_orthanc_name = 0
288 for part in clean_parts:
289 if part in orthanc_name:
290 parts_in_orthanc_name += 1
291 if parts_in_orthanc_name == len(clean_parts):
292 _log.debug('name match: "%s" contains all of %s', orthanc_name, clean_parts)
293 if gender is not None:
294 gender = _map_gender_gm2dcm[gender.lower()]
295 if gender is not None:
296 if orthanc_pat['MainDicomTags']['PatientSex'].lower() != gender:
297 _log.debug('gender mismatch: dicom=[%s] gnumed=[%s], skipping', orthanc_pat['MainDicomTags']['PatientSex'], gender)
298 continue
299 if dob is not None:
300 if orthanc_pat['MainDicomTags']['PatientBirthDate'] != dob.strftime('%Y%m%d'):
301 _log.debug('dob mismatch: dicom=[%s] gnumed=[%s], skipping', orthanc_pat['MainDicomTags']['PatientBirthDate'], dob)
302 continue
303 matching_patients.append(orthanc_pat)
304 else:
305 _log.debug('name mismatch: "%s" does not contain all of %s', orthanc_name, clean_parts)
306 return matching_patients
307
308
313
314
319
320
329
330
339
340
350
351
353
354 if filename is None:
355 filename = gmTools.get_unique_filename(prefix = r'DCM-', suffix = r'.zip', tmp_dir = target_dir)
356
357
358 if study_ids is None:
359 _log.info('exporting all studies of patient [%s] into [%s]', patient_id, filename)
360 f = io.open(filename, 'wb')
361 url = '%s/patients/%s/media' % (self.__server_url, str(patient_id))
362 _log.debug(url)
363 f.write(self.__run_GET(url = url, allow_cached = True))
364 f.close()
365 if create_zip:
366 return filename
367 if target_dir is None:
368 target_dir = gmTools.mk_sandbox_dir(prefix = 'dcm-')
369 if not gmTools.unzip_archive(filename, target_dir = target_dir, remove_archive = True):
370 return False
371 return target_dir
372
373
374 dicomdir_cmd = 'gm-create_dicomdir'
375 found, external_cmd = gmShellAPI.detect_external_binary(dicomdir_cmd)
376 if not found:
377 _log.error('[%s] not found', dicomdir_cmd)
378 return False
379
380 if create_zip:
381 sandbox_dir = gmTools.mk_sandbox_dir(prefix = 'dcm-')
382 _log.info('exporting studies [%s] into [%s] (sandbox [%s])', study_ids, filename, sandbox_dir)
383 else:
384 sandbox_dir = target_dir
385 _log.info('exporting studies [%s] into [%s]', study_ids, sandbox_dir)
386 _log.debug('sandbox dir: %s', sandbox_dir)
387 idx = 0
388 for study_id in study_ids:
389 study_zip_name = gmTools.get_unique_filename(prefix = 'dcm-', suffix = '.zip')
390
391 study_zip_name = self.get_study_as_zip_with_dicomdir(study_id = study_id, filename = study_zip_name)
392
393 idx += 1
394 study_unzip_dir = os.path.join(sandbox_dir, 'STUDY%s' % idx)
395 _log.debug('study [%s] -> %s -> %s', study_id, study_zip_name, study_unzip_dir)
396
397
398 if not gmTools.unzip_archive(study_zip_name, target_dir = study_unzip_dir, remove_archive = True):
399 return False
400
401
402
403 target_dicomdir_name = os.path.join(sandbox_dir, 'DICOMDIR')
404 gmTools.remove_file(target_dicomdir_name, log_error = False)
405 _log.debug('generating [%s]', target_dicomdir_name)
406 cmd = '%(cmd)s %(DICOMDIR)s %(startdir)s' % {
407 'cmd': external_cmd,
408 'DICOMDIR': target_dicomdir_name,
409 'startdir': sandbox_dir
410 }
411 success = gmShellAPI.run_command_in_shell (
412 command = cmd,
413 blocking = True
414 )
415 if not success:
416 _log.error('problem running [gm-create_dicomdir]')
417 return False
418
419 try:
420 io.open(target_dicomdir_name)
421 except Exception:
422 _log.error('[%s] not generated, aborting', target_dicomdir_name)
423 return False
424
425
426 if not create_zip:
427 return sandbox_dir
428
429
430 studies_zip = shutil.make_archive (
431 gmTools.fname_stem_with_path(filename),
432 'zip',
433 root_dir = gmTools.parent_dir(sandbox_dir),
434 base_dir = gmTools.dirname_stem(sandbox_dir),
435 logger = _log
436 )
437 _log.debug('archived all studies with one DICOMDIR into: %s', studies_zip)
438
439 gmTools.rmdir(sandbox_dir)
440 return studies_zip
441
442
444
445 if filename is None:
446 filename = gmTools.get_unique_filename(prefix = r'DCM-', suffix = r'.zip', tmp_dir = target_dir)
447
448
449 if study_ids is None:
450 if patient_id is None:
451 raise ValueError('<patient_id> must be defined if <study_ids> is None')
452 _log.info('exporting all studies of patient [%s] into [%s]', patient_id, filename)
453 f = io.open(filename, 'wb')
454 url = '%s/patients/%s/media' % (self.__server_url, str(patient_id))
455 _log.debug(url)
456 f.write(self.__run_GET(url = url, allow_cached = True))
457 f.close()
458 if create_zip:
459 return filename
460 if target_dir is None:
461 target_dir = gmTools.mk_sandbox_dir(prefix = 'dcm-')
462 if not gmTools.unzip_archive(filename, target_dir = target_dir, remove_archive = True):
463 return False
464 return target_dir
465
466
467 _log.info('exporting %s studies into [%s]', len(study_ids), filename)
468 _log.debug('studies: %s', study_ids)
469 f = io.open(filename, 'wb')
470
471
472
473
474
475 url = '%s/tools/create-media-extended' % self.__server_url
476 _log.debug(url)
477 try:
478 downloaded = self.__run_POST(url = url, data = study_ids, output_file = f)
479 if not downloaded:
480 _log.error('this Orthanc version probably does not support "create-media-extended"')
481 except TypeError:
482 f.close()
483 _log.exception('cannot retrieve multiple studies as one archive with DICOMDIR, probably not supported by this Orthanc version')
484 return False
485
486 if not downloaded:
487 url = '%s/tools/create-media' % self.__server_url
488 _log.debug('retrying: %s', url)
489 try:
490 downloaded = self.__run_POST(url = url, data = study_ids, output_file = f)
491 if not downloaded:
492 return False
493 except TypeError:
494 _log.exception('cannot retrieve multiple studies as one archive with DICOMDIR, probably not supported by this Orthanc version')
495 return False
496 finally:
497 f.close()
498 if create_zip:
499 return filename
500 if target_dir is None:
501 target_dir = gmTools.mk_sandbox_dir(prefix = 'dcm-')
502 _log.debug('exporting studies into [%s]', target_dir)
503 if not gmTools.unzip_archive(filename, target_dir = target_dir, remove_archive = True):
504 return False
505 return target_dir
506
507
515
516
527
528
539
540
541
542
544 url = '%s/patients/%s/protected' % (self.__server_url, str(orthanc_id))
545 if self.__run_GET(url) == 1:
546 _log.debug('patient already protected: %s', orthanc_id)
547 return True
548 _log.warning('patient [%s] not protected against recycling, enabling protection now', orthanc_id)
549 self.__run_PUT(url = url, data = '1')
550 if self.__run_GET(url) == 1:
551 return True
552 _log.error('cannot protect patient [%s] against recycling', orthanc_id)
553 return False
554
555
557 url = '%s/patients/%s/protected' % (self.__server_url, str(orthanc_id))
558 if self.__run_GET(url) == 0:
559 return True
560 _log.info('patient [%s] protected against recycling, disabling protection now', orthanc_id)
561 self.__run_PUT(url = url, data = '0')
562 if self.__run_GET(url) == 0:
563 return True
564 _log.error('cannot unprotect patient [%s] against recycling', orthanc_id)
565 return False
566
567
569 url = '%s/patients/%s/protected' % (self.__server_url, str(orthanc_id))
570 return (self.__run_GET(url) == 1)
571
572
574 _log.info('verifying DICOM data of patient [%s]', orthanc_id)
575 bad_data = []
576 instances_url = '%s/patients/%s/instances' % (self.__server_url, orthanc_id)
577 instances = self.__run_GET(instances_url)
578 for instance in instances:
579 instance_id = instance['ID']
580 attachments_url = '%s/instances/%s/attachments' % (self.__server_url, instance_id)
581 attachments = self.__run_GET(attachments_url, allow_cached = True)
582 for attachment in attachments:
583 verify_url = '%s/%s/verify-md5' % (attachments_url, attachment)
584
585
586
587 if self.__run_POST(verify_url) is not False:
588 continue
589 _log.error('bad MD5 of DICOM file at url [%s]: patient=%s, attachment_type=%s', verify_url, orthanc_id, attachment)
590 bad_data.append({'patient': orthanc_id, 'instance': instance_id, 'type': attachment, 'orthanc': '%s [%s]' % (self.server_identification, self.__server_url)})
591
592 return bad_data
593
594
596
597 if old_patient_id == new_patient_id:
598 return True
599
600 modify_data = {
601 'Replace': {
602 'PatientID': new_patient_id
603
604
605 }
606 , 'Force': True
607
608
609 }
610 o_pats = self.get_patients_by_external_id(external_id = old_patient_id)
611 all_modified = True
612 for o_pat in o_pats:
613 _log.info('modifying Orthanc patient [%s]: DICOM ID [%s] -> [%s]', o_pat['ID'], old_patient_id, new_patient_id)
614 if self.patient_is_protected(o_pat['ID']):
615 _log.debug('patient protected: %s, unprotecting for modification', o_pat['ID'])
616 if not self.unprotect_patient(o_pat['ID']):
617 _log.error('cannot unlock patient [%s], skipping', o_pat['ID'])
618 all_modified = False
619 continue
620 was_protected = True
621 else:
622 was_protected = False
623 pat_url = '%s/patients/%s' % (self.__server_url, o_pat['ID'])
624 modify_url = '%s/modify' % pat_url
625 result = self.__run_POST(modify_url, data = modify_data)
626 _log.debug('modified: %s', result)
627 if result is False:
628 _log.error('cannot modify patient [%s]', o_pat['ID'])
629 all_modified = False
630 continue
631 newly_created_patient_id = result['ID']
632 _log.debug('newly created Orthanc patient ID: %s', newly_created_patient_id)
633 _log.debug('deleting archived patient: %s', self.__run_DELETE(pat_url))
634 if was_protected:
635 if not self.protect_patient(newly_created_patient_id):
636 _log.error('cannot re-lock (new) patient [%s]', newly_created_patient_id)
637
638 return all_modified
639
640
641
642
644 if gmTools.fname_stem(filename) == 'DICOMDIR':
645 _log.debug('ignoring [%s], no use uploading DICOMDIR files to Orthanc', filename)
646 return True
647
648 if check_mime_type:
649 if gmMimeLib.guess_mimetype(filename) != 'application/dicom':
650 _log.error('not considered a DICOM file: %s', filename)
651 return False
652 try:
653 f = io.open(filename, 'rb')
654 except Exception:
655 _log.exception('cannot open [%s]', filename)
656 return False
657
658 dcm_data = f.read()
659 f.close()
660 _log.debug('uploading [%s]', filename)
661 upload_url = '%s/instances' % self.__server_url
662 uploaded = self.__run_POST(upload_url, data = dcm_data, content_type = 'application/dicom')
663 if uploaded is False:
664 _log.error('cannot upload [%s]', filename)
665 return False
666
667 _log.debug(uploaded)
668 if uploaded['Status'] == 'AlreadyStored':
669
670 available_fields_url = '%s%s/attachments/dicom' % (self.__server_url, uploaded['Path'])
671 available_fields = self.__run_GET(available_fields_url, allow_cached = True)
672 if 'md5' not in available_fields:
673 _log.debug('md5 of instance not available in Orthanc, cannot compare against file md5, trusting Orthanc')
674 return True
675 md5_url = '%s/md5' % available_fields_url
676 md5_db = self.__run_GET(md5_url)
677 md5_file = gmTools.file2md5(filename)
678 if md5_file != md5_db:
679 _log.error('local md5: %s', md5_file)
680 _log.error('in-db md5: %s', md5_db)
681 _log.error('MD5 mismatch !')
682 return False
683
684 _log.error('MD5 match between file and database')
685
686 return True
687
688
690 uploaded = []
691 not_uploaded = []
692 for filename in files:
693 success = self.upload_dicom_file(filename, check_mime_type = check_mime_type)
694 if success:
695 uploaded.append(filename)
696 continue
697 not_uploaded.append(filename)
698
699 if len(not_uploaded) > 0:
700 _log.error('not all files uploaded')
701 return (uploaded, not_uploaded)
702
703
704 - def upload_from_directory(self, directory=None, recursive=False, check_mime_type=False, ignore_other_files=True):
705
706
707 def _on_error(exc):
708 _log.error('DICOM (?) file not accessible: %s', exc.filename)
709 _log.error(exc)
710
711
712 _log.debug('uploading DICOM files from [%s]', directory)
713 if not recursive:
714 files2try = os.listdir(directory)
715 _log.debug('found %s files', len(files2try))
716 if ignore_other_files:
717 files2try = [ f for f in files2try if gmMimeLib.guess_mimetype(f) == 'application/dicom' ]
718 _log.debug('DICOM files therein: %s', len(files2try))
719 return self.upload_dicom_files(files = files2try, check_mime_type = check_mime_type)
720
721 _log.debug('recursing for DICOM files')
722 uploaded = []
723 not_uploaded = []
724 for curr_root, curr_root_subdirs, curr_root_files in os.walk(directory, onerror = _on_error):
725 _log.debug('recursing into [%s]', curr_root)
726 files2try = [ os.path.join(curr_root, f) for f in curr_root_files ]
727 _log.debug('found %s files', len(files2try))
728 if ignore_other_files:
729 files2try = [ f for f in files2try if gmMimeLib.guess_mimetype(f) == 'application/dicom' ]
730 _log.debug('DICOM files therein: %s', len(files2try))
731 up, not_up = self.upload_dicom_files (
732 files = files2try,
733 check_mime_type = check_mime_type
734 )
735 uploaded.extend(up)
736 not_uploaded.extend(not_up)
737
738 return (uploaded, not_uploaded)
739
740
743
744
745
746
748
749 study_keys2hide = ['ModifiedFrom', 'Type', 'ID', 'ParentPatient', 'Series']
750 series_keys2hide = ['ModifiedFrom', 'Type', 'ID', 'ParentStudy', 'Instances']
751
752 studies_by_patient = []
753 series_keys = {}
754 series_keys_m = {}
755
756
757 for pat in orthanc_patients:
758 pat_dict = {
759 'orthanc_id': pat['ID'],
760 'name': None,
761 'external_id': None,
762 'date_of_birth': None,
763 'gender': None,
764 'studies': []
765 }
766 try:
767 pat_dict['name'] = pat['MainDicomTags']['PatientName'].strip()
768 except KeyError:
769 pass
770 try:
771 pat_dict['external_id'] = pat['MainDicomTags']['PatientID'].strip()
772 except KeyError:
773 pass
774 try:
775 pat_dict['date_of_birth'] = pat['MainDicomTags']['PatientBirthDate'].strip()
776 except KeyError:
777 pass
778 try:
779 pat_dict['gender'] = pat['MainDicomTags']['PatientSex'].strip()
780 except KeyError:
781 pass
782 for key in pat_dict:
783 if pat_dict[key] in ['unknown', '(null)', '']:
784 pat_dict[key] = None
785 pat_dict[key] = cleanup_dicom_string(pat_dict[key])
786 studies_by_patient.append(pat_dict)
787
788
789 orth_studies = self.__run_GET(url = '%s/patients/%s/studies' % (self.__server_url, pat['ID']))
790 if orth_studies is False:
791 _log.error('cannot retrieve studies')
792 return []
793 for orth_study in orth_studies:
794 study_dict = {
795 'orthanc_id': orth_study['ID'],
796 'date': None,
797 'time': None,
798 'description': None,
799 'referring_doc': None,
800 'requesting_doc': None,
801 'performing_doc': None,
802 'operator_name': None,
803 'radiographer_code': None,
804 'radiology_org': None,
805 'radiology_dept': None,
806 'radiology_org_addr': None,
807 'station_name': None,
808 'series': []
809 }
810 try:
811 study_dict['date'] = orth_study['MainDicomTags']['StudyDate'].strip()
812 except KeyError:
813 pass
814 try:
815 study_dict['time'] = orth_study['MainDicomTags']['StudyTime'].strip()
816 except KeyError:
817 pass
818 try:
819 study_dict['description'] = orth_study['MainDicomTags']['StudyDescription'].strip()
820 except KeyError:
821 pass
822 try:
823 study_dict['referring_doc'] = orth_study['MainDicomTags']['ReferringPhysicianName'].strip()
824 except KeyError:
825 pass
826 try:
827 study_dict['requesting_doc'] = orth_study['MainDicomTags']['RequestingPhysician'].strip()
828 except KeyError:
829 pass
830 try:
831 study_dict['radiology_org_addr'] = orth_study['MainDicomTags']['InstitutionAddress'].strip()
832 except KeyError:
833 pass
834 try:
835 study_dict['radiology_org'] = orth_study['MainDicomTags']['InstitutionName'].strip()
836 if study_dict['radiology_org_addr'] is not None:
837 if study_dict['radiology_org'] in study_dict['radiology_org_addr']:
838 study_dict['radiology_org'] = None
839 except KeyError:
840 pass
841 try:
842 study_dict['radiology_dept'] = orth_study['MainDicomTags']['InstitutionalDepartmentName'].strip()
843 if study_dict['radiology_org'] is not None:
844 if study_dict['radiology_dept'] in study_dict['radiology_org']:
845 study_dict['radiology_dept'] = None
846 if study_dict['radiology_org_addr'] is not None:
847 if study_dict['radiology_dept'] in study_dict['radiology_org_addr']:
848 study_dict['radiology_dept'] = None
849 except KeyError:
850 pass
851 try:
852 study_dict['station_name'] = orth_study['MainDicomTags']['StationName'].strip()
853 if study_dict['radiology_org'] is not None:
854 if study_dict['station_name'] in study_dict['radiology_org']:
855 study_dict['station_name'] = None
856 if study_dict['radiology_org_addr'] is not None:
857 if study_dict['station_name'] in study_dict['radiology_org_addr']:
858 study_dict['station_name'] = None
859 if study_dict['radiology_dept'] is not None:
860 if study_dict['station_name'] in study_dict['radiology_dept']:
861 study_dict['station_name'] = None
862 except KeyError:
863 pass
864 for key in study_dict:
865 if study_dict[key] in ['unknown', '(null)', '']:
866 study_dict[key] = None
867 study_dict[key] = cleanup_dicom_string(study_dict[key])
868 study_dict['all_tags'] = {}
869 try:
870 orth_study['PatientMainDicomTags']
871 except KeyError:
872 orth_study['PatientMainDicomTags'] = pat['MainDicomTags']
873 for key in orth_study.keys():
874 if key == 'MainDicomTags':
875 for mkey in orth_study['MainDicomTags'].keys():
876 study_dict['all_tags'][mkey] = orth_study['MainDicomTags'][mkey].strip()
877 continue
878 if key == 'PatientMainDicomTags':
879 for pkey in orth_study['PatientMainDicomTags'].keys():
880 study_dict['all_tags'][pkey] = orth_study['PatientMainDicomTags'][pkey].strip()
881 continue
882 study_dict['all_tags'][key] = orth_study[key]
883 _log.debug('study: %s', study_dict['all_tags'].keys())
884 for key in study_keys2hide:
885 try: del study_dict['all_tags'][key]
886 except KeyError: pass
887 pat_dict['studies'].append(study_dict)
888
889
890 for orth_series_id in orth_study['Series']:
891 orth_series = self.__run_GET(url = '%s/series/%s' % (self.__server_url, orth_series_id))
892 ordered_slices = self.__run_GET(url = '%s/series/%s/ordered-slices' % (self.__server_url, orth_series_id))
893 if ordered_slices is False:
894 slices = orth_series['Instances']
895 else:
896 slices = [ s[0] for s in ordered_slices['SlicesShort'] ]
897 if orth_series is False:
898 _log.error('cannot retrieve series')
899 return []
900 series_dict = {
901 'orthanc_id': orth_series['ID'],
902 'instances': slices,
903 'modality': None,
904 'date': None,
905 'time': None,
906 'description': None,
907 'body_part': None,
908 'protocol': None,
909 'performed_procedure_step_description': None,
910 'acquisition_device_processing_description': None,
911 'operator_name': None,
912 'radiographer_code': None,
913 'performing_doc': None
914 }
915 try:
916 series_dict['modality'] = orth_series['MainDicomTags']['Modality'].strip()
917 except KeyError:
918 pass
919 try:
920 series_dict['date'] = orth_series['MainDicomTags']['SeriesDate'].strip()
921 except KeyError:
922 pass
923 try:
924 series_dict['description'] = orth_series['MainDicomTags']['SeriesDescription'].strip()
925 except KeyError:
926 pass
927 try:
928 series_dict['time'] = orth_series['MainDicomTags']['SeriesTime'].strip()
929 except KeyError:
930 pass
931 try:
932 series_dict['body_part'] = orth_series['MainDicomTags']['BodyPartExamined'].strip()
933 except KeyError:
934 pass
935 try:
936 series_dict['protocol'] = orth_series['MainDicomTags']['ProtocolName'].strip()
937 except KeyError:
938 pass
939 try:
940 series_dict['performed_procedure_step_description'] = orth_series['MainDicomTags']['PerformedProcedureStepDescription'].strip()
941 except KeyError:
942 pass
943 try:
944 series_dict['acquisition_device_processing_description'] = orth_series['MainDicomTags']['AcquisitionDeviceProcessingDescription'].strip()
945 except KeyError:
946 pass
947 try:
948 series_dict['operator_name'] = orth_series['MainDicomTags']['OperatorsName'].strip()
949 except KeyError:
950 pass
951 try:
952 series_dict['radiographer_code'] = orth_series['MainDicomTags']['RadiographersCode'].strip()
953 except KeyError:
954 pass
955 try:
956 series_dict['performing_doc'] = orth_series['MainDicomTags']['PerformingPhysicianName'].strip()
957 except KeyError:
958 pass
959 for key in series_dict:
960 if series_dict[key] in ['unknown', '(null)', '']:
961 series_dict[key] = None
962 if series_dict['description'] == series_dict['protocol']:
963 _log.debug('<series description> matches <series protocol>, ignoring protocol')
964 series_dict['protocol'] = None
965 if series_dict['performed_procedure_step_description'] in [series_dict['description'], series_dict['protocol']]:
966 series_dict['performed_procedure_step_description'] = None
967 if series_dict['performed_procedure_step_description'] is not None:
968
969 if regex.match ('[.,/\|\-\s\d]+', series_dict['performed_procedure_step_description'], flags = regex.UNICODE):
970 series_dict['performed_procedure_step_description'] = None
971 if series_dict['acquisition_device_processing_description'] in [series_dict['description'], series_dict['protocol']]:
972 series_dict['acquisition_device_processing_description'] = None
973 if series_dict['acquisition_device_processing_description'] is not None:
974
975 if regex.match ('[.,/\|\-\s\d]+', series_dict['acquisition_device_processing_description'], flags = regex.UNICODE):
976 series_dict['acquisition_device_processing_description'] = None
977 if series_dict['date'] == study_dict['date']:
978 _log.debug('<series date> matches <study date>, ignoring date')
979 series_dict['date'] = None
980 if series_dict['time'] == study_dict['time']:
981 _log.debug('<series time> matches <study time>, ignoring time')
982 series_dict['time'] = None
983 for key in series_dict:
984 series_dict[key] = cleanup_dicom_string(series_dict[key])
985 series_dict['all_tags'] = {}
986 for key in orth_series.keys():
987 if key == 'MainDicomTags':
988 for mkey in orth_series['MainDicomTags'].keys():
989 series_dict['all_tags'][mkey] = orth_series['MainDicomTags'][mkey].strip()
990 continue
991 series_dict['all_tags'][key] = orth_series[key]
992 _log.debug('series: %s', series_dict['all_tags'].keys())
993 for key in series_keys2hide:
994 try: del series_dict['all_tags'][key]
995 except KeyError: pass
996 study_dict['operator_name'] = series_dict['operator_name']
997 study_dict['radiographer_code'] = series_dict['radiographer_code']
998 study_dict['performing_doc'] = series_dict['performing_doc']
999 study_dict['series'].append(series_dict)
1000
1001 return studies_by_patient
1002
1003
1004
1005
1006 - def __run_GET(self, url=None, data=None, allow_cached=False):
1007 if data is None:
1008 data = {}
1009 headers = {}
1010 if not allow_cached:
1011 headers['cache-control'] = 'no-cache'
1012 params = ''
1013 if len(data.keys()) > 0:
1014 params = '?' + urlencode(data)
1015 url_with_params = url + params
1016
1017 try:
1018 response, content = self.__conn.request(url_with_params, 'GET', headers = headers)
1019 except (socket.error, http.client.ResponseNotReady, http.client.InvalidURL, OverflowError, httplib2.ServerNotFoundError):
1020 _log.exception('exception in GET')
1021 _log.debug(' url: %s', url_with_params)
1022 _log.debug(' headers: %s', headers)
1023 return False
1024
1025 if response.status not in [ 200 ]:
1026 _log.error('GET returned non-OK status: %s', response.status)
1027 _log.debug(' url: %s', url_with_params)
1028 _log.debug(' headers: %s', headers)
1029 _log.error(' response: %s', response)
1030 _log.debug(' content: %s', content)
1031 return False
1032
1033
1034
1035
1036 if response['content-type'].startswith('text/plain'):
1037
1038
1039
1040
1041 return content.decode('utf8')
1042
1043 if response['content-type'].startswith('application/json'):
1044 try:
1045 return json.loads(content)
1046 except Exception:
1047 return content
1048
1049 return content
1050
1051
1052 - def __run_POST(self, url=None, data=None, content_type=None, output_file=None):
1053
1054 body = data
1055 headers = {'content-type' : content_type}
1056 if isinstance(data, str):
1057 if content_type is None:
1058 headers['content-type'] = 'text/plain'
1059 elif isinstance(data, bytes):
1060 if content_type is None:
1061 headers['content-type'] = 'application/octet-stream'
1062 else:
1063 body = json.dumps(data)
1064 headers['content-type'] = 'application/json'
1065
1066 try:
1067 try:
1068 response, content = self.__conn.request(url, 'POST', body = body, headers = headers)
1069 except BrokenPipeError:
1070 response, content = self.__conn.request(url, 'POST', body = body, headers = headers)
1071 except (socket.error, http.client.ResponseNotReady, OverflowError):
1072 _log.exception('exception in POST')
1073 _log.debug(' url: %s', url)
1074 _log.debug(' headers: %s', headers)
1075 _log.debug(' body: %s', body[:16])
1076 return False
1077
1078 if response.status == 404:
1079 _log.debug('no data, response: %s', response)
1080 if output_file is None:
1081 return []
1082 return False
1083 if response.status not in [ 200, 302 ]:
1084 _log.error('POST returned non-OK status: %s', response.status)
1085 _log.debug(' url: %s', url)
1086 _log.debug(' headers: %s', headers)
1087 _log.debug(' body: %s', body[:16])
1088 _log.error(' response: %s', response)
1089 _log.debug(' content: %s', content)
1090 return False
1091
1092 try:
1093 content = json.loads(content)
1094 except Exception:
1095 pass
1096 if output_file is None:
1097 return content
1098 output_file.write(content)
1099 return True
1100
1101
1102 - def __run_PUT(self, url=None, data=None, content_type=None):
1103
1104 body = data
1105 headers = {'content-type' : content_type}
1106 if isinstance(data, str):
1107 if content_type is None:
1108 headers['content-type'] = 'text/plain'
1109 elif isinstance(data, bytes):
1110 if content_type is None:
1111 headers['content-type'] = 'application/octet-stream'
1112 else:
1113 body = json.dumps(data)
1114 headers['content-type'] = 'application/json'
1115
1116 try:
1117 try:
1118 response, content = self.__conn.request(url, 'PUT', body = body, headers = headers)
1119 except BrokenPipeError:
1120 response, content = self.__conn.request(url, 'PUT', body = body, headers = headers)
1121 except (socket.error, http.client.ResponseNotReady, OverflowError):
1122 _log.exception('exception in PUT')
1123 _log.debug(' url: %s', url)
1124 _log.debug(' headers: %s', headers)
1125 _log.debug(' body: %s', body[:16])
1126 return False
1127
1128 if response.status == 404:
1129 _log.debug('no data, response: %s', response)
1130 return []
1131 if response.status not in [ 200, 302 ]:
1132 _log.error('PUT returned non-OK status: %s', response.status)
1133 _log.debug(' url: %s', url)
1134 _log.debug(' headers: %s', headers)
1135 _log.debug(' body: %s', body[:16])
1136 _log.error(' response: %s', response)
1137 _log.debug(' content: %s', content)
1138 return False
1139
1140 if response['content-type'].startswith('text/plain'):
1141
1142
1143
1144
1145 return content.decode('utf8')
1146
1147 if response['content-type'].startswith('application/json'):
1148 try:
1149 return json.loads(content)
1150 except Exception:
1151 return content
1152
1153 return content
1154
1155
1157 try:
1158 response, content = self.__conn.request(url, 'DELETE')
1159 except (http.client.ResponseNotReady, socket.error, OverflowError):
1160 _log.exception('exception in DELETE')
1161 _log.debug(' url: %s', url)
1162 return False
1163
1164 if response.status not in [ 200 ]:
1165 _log.error('DELETE returned non-OK status: %s', response.status)
1166 _log.debug(' url: %s', url)
1167 _log.error(' response: %s', response)
1168 _log.debug(' content: %s', content)
1169 return False
1170
1171 if response['content-type'].startswith('text/plain'):
1172
1173
1174
1175
1176 return content.decode('utf8')
1177
1178 if response['content-type'].startswith('application/json'):
1179 try:
1180 return json.loads(content)
1181 except Exception:
1182 return content
1183
1184 return content
1185
1186
1188 if not isinstance(dicom_str, str):
1189 return dicom_str
1190 dicom_str = regex.sub('\^+', ' ', dicom_str.strip('^'))
1191
1192 return dicom_str
1193
1194
1195 -def dicomize_file(filename, title=None, person=None, dcm_name=None, verbose=False, dcm_template_file=None, dcm_transfer_series=True):
1196 assert (filename is not None), '<filename> must not be None'
1197 assert (not ((person is None) and (dcm_template_file is None))), '<person> or <dcm_template_file> must not be None'
1198
1199
1200 if gmMimeLib.guess_mimetype(filename) == 'application/dicom':
1201 _log.error('already a DICOM file: %s', filename)
1202 if dcm_name is None:
1203 return filename
1204 return shutil.copy2(filename, dcm_name)
1205
1206 dcm_fname = dicomize_pdf (
1207 pdf_name = filename,
1208 title = title,
1209 person = person,
1210 dcm_name = dcm_name,
1211 verbose = verbose,
1212 dcm_template_file = dcm_template_file,
1213 dcm_transfer_series = dcm_transfer_series
1214 )
1215 if dcm_fname is not None:
1216 return dcm_fname
1217
1218 _log.debug('does not seem to be a PDF: %s', filename)
1219 converted_fname = gmMimeLib.convert_file(filename = filename, target_mime = 'image/jpeg')
1220 if converted_fname is None:
1221 _log.error('cannot convert to JPG: %s', filename)
1222 return None
1223
1224 dcm_name = dicomize_jpg (
1225 jpg_name = converted_fname,
1226 title = title,
1227 person = person,
1228 dcm_name = dcm_name,
1229 verbose = verbose,
1230 dcm_template_file = dcm_template_file,
1231 dcm_transfer_series = dcm_transfer_series
1232 )
1233 return dcm_name
1234
1235
1236 -def dicomize_pdf(pdf_name=None, title=None, person=None, dcm_name=None, verbose=False, dcm_template_file=None, dcm_transfer_series=True):
1237 assert (pdf_name is not None), '<pdf_name> must not be None'
1238 assert (not ((person is None) and (dcm_template_file is None))), '<person> or <dcm_template_file> must not be None'
1239
1240 if dcm_name is None:
1241 dcm_name = gmTools.get_unique_filename(suffix = '.dcm')
1242 _log.debug('%s -> %s', pdf_name, dcm_name)
1243 if title is None:
1244 title = pdf_name
1245 now = gmDateTime.pydt_now_here()
1246 cmd_line = [
1247 'pdf2dcm',
1248 '--title', title,
1249 '--key', '0008,0020=%s' % now.strftime('%Y%m%d'),
1250 '--key', '0008,0021=%s' % now.strftime('%Y%m%d'),
1251 '--key', '0008,0023=%s' % now.strftime('%Y%m%d'),
1252 '--key', '0008,0030=%s' % now.strftime('%H%M%s.0'),
1253 '--key', '0008,0031=%s' % now.strftime('%H%M%s.0'),
1254 '--key', '0008,0033=%s' % now.strftime('%H%M%s.0')
1255 ]
1256 if dcm_template_file is None:
1257 name = person.active_name
1258 cmd_line.append('--patient-id')
1259 cmd_line.append(person.suggest_external_id(target = 'PACS'))
1260 cmd_line.append('--patient-name')
1261 cmd_line.append(('%s^%s' % (name['lastnames'], name['firstnames'])).replace(' ', '^'))
1262 if person['dob'] is not None:
1263 cmd_line.append('--patient-birthdate')
1264 cmd_line.append(person.get_formatted_dob(format = '%Y%m%d', honor_estimation = False))
1265 if person['gender'] is not None:
1266 cmd_line.append('--patient-sex')
1267 cmd_line.append(_map_gender_gm2dcm[person['gender']])
1268 else:
1269 _log.debug('DCM template file: %s', dcm_template_file)
1270 if dcm_transfer_series:
1271 cmd_line.append('--series-from')
1272 else:
1273 cmd_line.append('--study-from')
1274 cmd_line.append(dcm_template_file)
1275 if verbose:
1276 cmd_line.append('--log-level')
1277 cmd_line.append('trace')
1278 cmd_line.append(pdf_name)
1279 cmd_line.append(dcm_name)
1280 success, exit_code, stdout = gmShellAPI.run_process(cmd_line = cmd_line, encoding = 'utf8', verbose = verbose)
1281 if success:
1282 return dcm_name
1283
1284 return None
1285
1286
1287 -def dicomize_jpg(jpg_name=None, title=None, person=None, dcm_name=None, verbose=False, dcm_template_file=None, dcm_transfer_series=True):
1288 assert (jpg_name is not None), '<jpg_name> must not be None'
1289 assert (not ((person is None) and (dcm_template_file is None))), 'both <person> and <dcm_template_file> are None, but one is needed'
1290
1291 if dcm_name is None:
1292 dcm_name = gmTools.get_unique_filename(suffix = '.dcm')
1293 _log.debug('%s -> %s', jpg_name, dcm_name)
1294 now = gmDateTime.pydt_now_here()
1295 cmd_line = [
1296 'img2dcm',
1297 '--keep-appn',
1298 '--insist-on-jfif',
1299 '--key', '0008,0021=%s' % now.strftime('%Y%m%d'),
1300 '--key', '0008,0031=%s' % now.strftime('%H%M%s.0'),
1301 '--key', '0008,0023=%s' % now.strftime('%Y%m%d'),
1302 '--key', '0008,0033=%s' % now.strftime('%H%M%s.0')
1303 ]
1304 if title is not None:
1305
1306 if title is not None:
1307 cmd_line.append('--key')
1308 cmd_line.append('0008,103E=%s' % title)
1309 if dcm_template_file is None:
1310
1311 if title is not None:
1312 cmd_line.append('--key')
1313 cmd_line.append('0008,1030=%s' % title)
1314
1315 cmd_line.append('--key')
1316 cmd_line.append('0008,0020=%s' % now.strftime('%Y%m%d'))
1317
1318 cmd_line.append('--key')
1319 cmd_line.append('0008,0030=%s' % now.strftime('%H%M%s.0'))
1320
1321 name = person.active_name
1322 cmd_line.append('--key')
1323 cmd_line.append('0010,0010=%s' % ('%s^%s' % (
1324 name['lastnames'],
1325 name['firstnames'])
1326 ).replace(' ', '^'))
1327
1328 cmd_line.append('--key')
1329 cmd_line.append('0010,0020=%s' % person.suggest_external_id(target = 'PACS'))
1330
1331 cmd_line.append('--key')
1332 cmd_line.append('0010,0030=%s' % person.get_formatted_dob(format = '%Y%m%d', honor_estimation = False))
1333
1334 if person['gender'] is not None:
1335 cmd_line.append('--key')
1336 cmd_line.append('0010,0040=%s' % _map_gender_gm2dcm[person['gender']])
1337 else:
1338 _log.debug('DCM template file: %s', dcm_template_file)
1339 if dcm_transfer_series:
1340 cmd_line.append('--series-from')
1341 else:
1342 cmd_line.append('--study-from')
1343 cmd_line.append(dcm_template_file)
1344 if verbose:
1345 cmd_line.append('--log-level')
1346 cmd_line.append('trace')
1347 cmd_line.append(jpg_name)
1348 cmd_line.append(dcm_name)
1349 success, exit_code, stdout = gmShellAPI.run_process(cmd_line = cmd_line, encoding = 'utf8', verbose = verbose)
1350 if success:
1351 return dcm_name
1352
1353 return None
1354
1355
1356
1357
1358 if __name__ == "__main__":
1359
1360 if len(sys.argv) == 1:
1361 sys.exit()
1362
1363 if sys.argv[1] != 'test':
1364 sys.exit()
1365
1366
1367
1368 from Gnumed.pycommon import gmLog2
1369
1370
1372 orthanc = cOrthancServer()
1373 if not orthanc.connect(host, port, user = None, password = None):
1374 print('error connecting to server:', orthanc.connect_error)
1375 return False
1376 print('Connected to Orthanc server "%s" (AET [%s] - version [%s] - DB [%s] - API [%s])' % (
1377 orthanc.server_identification['Name'],
1378 orthanc.server_identification['DicomAet'],
1379 orthanc.server_identification['Version'],
1380 orthanc.server_identification['DatabaseVersion'],
1381 orthanc.server_identification['ApiVersion']
1382 ))
1383 print('')
1384 print('Please enter patient name parts, separated by SPACE.')
1385
1386 while True:
1387 entered_name = gmTools.prompted_input(prompt = "\nEnter person search term or leave blank to exit")
1388 if entered_name in ['exit', 'quit', 'bye', None]:
1389 print("user cancelled patient search")
1390 break
1391
1392 pats = orthanc.get_patients_by_external_id(external_id = entered_name)
1393 if len(pats) > 0:
1394 print('Patients found:')
1395 for pat in pats:
1396 print(' -> ', pat)
1397 continue
1398
1399 pats = orthanc.get_patients_by_name(name_parts = entered_name.split(), fuzzy = True)
1400 print('Patients found:')
1401 for pat in pats:
1402 print(' -> ', pat)
1403 print(' verifying ...')
1404 bad_data = orthanc.verify_patient_data(pat['ID'])
1405 print(' bad data:')
1406 for bad in bad_data:
1407 print(' -> ', bad)
1408 continue
1409
1410 continue
1411
1412 pats = orthanc.get_studies_list_by_patient_name(name_parts = entered_name.split(), fuzzy = True)
1413 print('Patients found from studies list:')
1414 for pat in pats:
1415 print(' -> ', pat['name'])
1416 for study in pat['studies']:
1417 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'])))
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430 print('--------')
1431
1432
1434 try:
1435 host = sys.argv[2]
1436 except IndexError:
1437 host = None
1438 try:
1439 port = sys.argv[3]
1440 except IndexError:
1441 port = '8042'
1442
1443 orthanc_console(host, port)
1444
1445
1447 try:
1448 host = sys.argv[2]
1449 port = sys.argv[3]
1450 except IndexError:
1451 host = None
1452 port = '8042'
1453 orthanc = cOrthancServer()
1454 if not orthanc.connect(host, port, user = None, password = None):
1455 print('error connecting to server:', orthanc.connect_error)
1456 return False
1457 print('Connected to Orthanc server "%s" (AET [%s] - version [%s] - DB [%s])' % (
1458 orthanc.server_identification['Name'],
1459 orthanc.server_identification['DicomAet'],
1460 orthanc.server_identification['Version'],
1461 orthanc.server_identification['DatabaseVersion']
1462 ))
1463 print('')
1464 print('Please enter patient name parts, separated by SPACE.')
1465
1466 entered_name = gmTools.prompted_input(prompt = "\nEnter person search term or leave blank to exit")
1467 if entered_name in ['exit', 'quit', 'bye', None]:
1468 print("user cancelled patient search")
1469 return
1470
1471 pats = orthanc.get_patients_by_name(name_parts = entered_name.split(), fuzzy = True)
1472 if len(pats) == 0:
1473 print('no patient found')
1474 return
1475
1476 pat = pats[0]
1477 print('test patient:')
1478 print(pat)
1479 old_id = pat['MainDicomTags']['PatientID']
1480 new_id = old_id + '-1'
1481 print('setting [%s] to [%s]:' % (old_id, new_id), orthanc.modify_patient_id(old_id, new_id))
1482
1483
1485
1486
1487
1488
1489 host = None
1490 port = '8042'
1491
1492 orthanc = cOrthancServer()
1493 if not orthanc.connect(host, port, user = None, password = None):
1494 print('error connecting to server:', orthanc.connect_error)
1495 return False
1496 print('Connected to Orthanc server "%s" (AET [%s] - version [%s] - DB [%s] - REST API [%s])' % (
1497 orthanc.server_identification['Name'],
1498 orthanc.server_identification['DicomAet'],
1499 orthanc.server_identification['Version'],
1500 orthanc.server_identification['DatabaseVersion'],
1501 orthanc.server_identification['ApiVersion']
1502 ))
1503 print('')
1504
1505
1506 orthanc.upload_from_directory(directory = sys.argv[2], recursive = True, check_mime_type = False, ignore_other_files = True)
1507
1508
1527
1528
1549
1550
1551
1560
1561
1563
1564 from Gnumed.business import gmPerson
1565 pers = gmPerson.cPerson(12)
1566 try:
1567 print(dicomize_jpg(jpg_name = sys.argv[2], person = pers, dcm_name = sys.argv[2]+'.dcm', verbose = True, dcm_template_file = sys.argv[3]))
1568 except IndexError:
1569 print(dicomize_jpg(jpg_name = sys.argv[2], person = pers, dcm_name = sys.argv[2]+'.dcm', verbose = True))
1570
1571
1573 from Gnumed.business import gmPersonSearch
1574 person = gmPersonSearch.ask_for_patient()
1575 if person is None:
1576 return
1577 print(person)
1578 try:
1579 print(dicomize_file(filename = sys.argv[2], person = person, dcm_name = sys.argv[2]+'.dcm', verbose = True, dcm_template_file = sys.argv[3], title = sys.argv[4]))
1580 except IndexError:
1581 pass
1582 try:
1583 print(dicomize_file(filename = sys.argv[2], person = person, dcm_name = sys.argv[2]+'.dcm', verbose = True, title = sys.argv[3]))
1584 except IndexError:
1585 print(dicomize_file(filename = sys.argv[2], person = person, dcm_name = sys.argv[2]+'.dcm', verbose = True))
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595 test_file2dcm()
1596