comparison OrthancServer/Resources/Samples/ImportDicomFiles/OrthancImport.py @ 4337:7707fa761b71

OrthancImport.py sample
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 02 Dec 2020 11:01:59 +0100
parents
children d9473bd5ed43
comparison
equal deleted inserted replaced
4336:9b38aadd4a99 4337:7707fa761b71
1 #!/usr/bin/env python
2
3 # Orthanc - A Lightweight, RESTful DICOM Store
4 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
5 # Department, University Hospital of Liege, Belgium
6 # Copyright (C) 2017-2020 Osimis S.A., Belgium
7 #
8 # This program is free software: you can redistribute it and/or
9 # modify it under the terms of the GNU General Public License as
10 # published by the Free Software Foundation, either version 3 of the
11 # License, or (at your option) any later version.
12 #
13 # This program is distributed in the hope that it will be useful, but
14 # WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 # General Public License for more details.
17 #
18 # You should have received a copy of the GNU General Public License
19 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20
21
22 import argparse
23 import bz2
24 import gzip
25 import os
26 import requests
27 import sys
28 import tarfile
29 import zipfile
30
31 from requests.auth import HTTPBasicAuth
32
33
34
35 parser = argparse.ArgumentParser(description = 'Command-line tool to import files or archives into Orthanc.')
36 parser.add_argument('--url',
37 default = 'http://localhost:8042',
38 help = 'URL to the REST API of the Orthanc server')
39 parser.add_argument('--username',
40 default = 'orthanc',
41 help = 'Username to the REST API')
42 parser.add_argument('--password',
43 default = 'orthanc',
44 help = 'Password to the REST API')
45 parser.add_argument('--force', help = 'Do not warn the user about deletion',
46 action = 'store_true')
47 parser.add_argument('--clear', help = 'Remove the content of the Orthanc database',
48 action = 'store_true')
49 parser.add_argument('--verbose', help = 'Be verbose',
50 action = 'store_true')
51 parser.add_argument('--ignore-errors', help = 'Do not stop if encountering non-DICOM files',
52 action = 'store_true')
53 parser.add_argument('files', metavar = 'N', nargs = '*',
54 help = 'Files to import')
55
56
57 args = parser.parse_args()
58
59 if args.clear and not args.force:
60 print("""
61 WARNING: This script will remove all the content of your
62 Orthanc instance running on %s!
63
64 Are you sure ["yes" to go on]?""" % args.server)
65
66 if sys.stdin.readline().strip() != 'yes':
67 print('Aborting...')
68 exit(0)
69
70
71
72 IMPORTED_STUDIES = set()
73 COUNT_ERROR = 0
74 COUNT_SUCCESS = 0
75
76 def UploadBuffer(dicom):
77 global IMPORTED_STUDIES
78 global COUNT_ERROR
79 global COUNT_SUCCESS
80
81 auth = HTTPBasicAuth(args.username, args.password)
82 r = requests.post('%s/instances' % args.url, auth = auth, data = dicom)
83
84 try:
85 r.raise_for_status()
86 except:
87 COUNT_ERROR += 1
88 if args.ignore_errors:
89 if args.verbose:
90 print(' not a valid DICOM file, ignoring it')
91 return
92 else:
93 raise
94
95 info = r.json()
96 COUNT_SUCCESS += 1
97
98 if not info['ParentStudy'] in IMPORTED_STUDIES:
99 IMPORTED_STUDIES.add(info['ParentStudy'])
100
101 r2 = requests.get('%s/instances/%s/tags?short' % (args.url, info['ID']),
102 auth = auth)
103 r2.raise_for_status()
104 tags = r2.json()
105
106 print('')
107 print('New imported study:')
108 print(' Orthanc ID of the patient: %s' % info['ParentPatient'])
109 print(' Orthanc ID of the study: %s' % info['ParentStudy'])
110 print(' DICOM Patient ID: %s' % tags['0010,0020'])
111 print(' DICOM Study Instance UID: %s' % tags['0020,000d'])
112 print('')
113
114
115 def UploadFile(path):
116 with open(path, 'rb') as f:
117 dicom = f.read()
118 if args.verbose:
119 print('Uploading: %s (%dMB)' % (path, len(dicom) / (1024 * 1024)))
120
121 UploadBuffer(dicom)
122
123
124 def UploadBzip2(path):
125 with bz2.BZ2File(path, 'rb') as f:
126 dicom = f.read()
127 if args.verbose:
128 print('Uploading: %s (%dMB)' % (path, len(dicom) / (1024 * 1024)))
129
130 UploadBuffer(dicom)
131
132
133 def UploadGzip(path):
134 with gzip.open(path, 'rb') as f:
135 dicom = f.read()
136 if args.verbose:
137 print('Uploading: %s (%dMB)' % (path, len(dicom) / (1024 * 1024)))
138
139 UploadBuffer(dicom)
140
141
142 def UploadTar(path, decoder):
143 if args.verbose:
144 print('Uncompressing tar archive: %s' % path)
145 with tarfile.open(path, decoder) as tar:
146 for item in tar:
147 if item.isreg():
148 f = tar.extractfile(item)
149 dicom = f.read()
150 f.close()
151
152 if args.verbose:
153 print('Uploading: %s (%dMB)' % (item.name, len(dicom) / (1024 * 1024)))
154
155 UploadBuffer(dicom)
156
157
158 def UploadZip(path):
159 if args.verbose:
160 print('Uncompressing ZIP archive: %s' % path)
161 with zipfile.ZipFile(path, 'r') as zip:
162 for item in zip.infolist():
163 # WARNING - "item.is_dir()" would be better, but is not available in Python 2.7
164 if item.file_size > 0:
165 dicom = zip.read(item.filename)
166
167 if args.verbose:
168 print('Uploading: %s (%dMB)' % (item.filename, len(dicom) / (1024 * 1024)))
169
170 UploadBuffer(dicom)
171
172
173 def DecodeFile(path):
174 extension = os.path.splitext(path) [1]
175
176 if path.endswith('.tar.bz2'):
177 UploadTar(path, 'r:bz2')
178 elif path.endswith('.tar.gz'):
179 UploadTar(path, 'r:gz')
180 elif extension == '.zip':
181 UploadZip(path)
182 elif extension == '.tar':
183 UploadTar(path, 'r')
184 elif extension == '.bz2':
185 UploadBzip2(path)
186 elif extension == '.gz':
187 UploadGzip(path)
188 else:
189 UploadFile(path)
190
191
192 if args.clear:
193 print('Removing the content of Orthanc')
194
195 auth = HTTPBasicAuth(args.username, args.password)
196 r = requests.get('%s/studies' % args.url, auth = auth)
197 r.raise_for_status()
198
199 print(' %d studies are being removed...' % len(r.json()))
200
201 for study in r.json():
202 requests.delete('%s/studies/%s' % (args.url, study), auth = auth).raise_for_status()
203
204 print('Orthanc is now empty')
205 print('')
206
207
208 for path in args.files:
209 if os.path.isfile(path):
210 DecodeFile(path)
211 elif os.path.isdir(path):
212 for root, dirs, files in os.walk(path):
213 for name in files:
214 DecodeFile(os.path.join(root, name))
215 else:
216 raise Exception('Missing file or directory: %s' % path)
217
218
219 print('')
220 print('Status:')
221 print(' %d DICOM instances properly imported' % COUNT_SUCCESS)
222 print(' %d DICOM studies properly imported' % len(IMPORTED_STUDIES))
223 print(' Error in %d files' % COUNT_ERROR)
224 print('')