comparison OrthancServer/Sources/ServerToolbox.cpp @ 4044:d25f4c0fa160 framework

splitting code into OrthancFramework and OrthancServer
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 10 Jun 2020 20:30:34 +0200
parents OrthancServer/ServerToolbox.cpp@94f4a18a79cc
children 05b8fd21089c
comparison
equal deleted inserted replaced
4043:6c6239aec462 4044:d25f4c0fa160
1 /**
2 * Orthanc - A Lightweight, RESTful DICOM Store
3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
4 * Department, University Hospital of Liege, Belgium
5 * Copyright (C) 2017-2020 Osimis S.A., Belgium
6 *
7 * This program is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License as
9 * published by the Free Software Foundation, either version 3 of the
10 * License, or (at your option) any later version.
11 *
12 * In addition, as a special exception, the copyright holders of this
13 * program give permission to link the code of its release with the
14 * OpenSSL project's "OpenSSL" library (or with modified versions of it
15 * that use the same license as the "OpenSSL" library), and distribute
16 * the linked executables. You must obey the GNU General Public License
17 * in all respects for all of the code used other than "OpenSSL". If you
18 * modify file(s) with this exception, you may extend this exception to
19 * your version of the file(s), but you are not obligated to do so. If
20 * you do not wish to do so, delete this exception statement from your
21 * version. If you delete this exception statement from all source files
22 * in the program, then also delete it here.
23 *
24 * This program is distributed in the hope that it will be useful, but
25 * WITHOUT ANY WARRANTY; without even the implied warranty of
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
27 * General Public License for more details.
28 *
29 * You should have received a copy of the GNU General Public License
30 * along with this program. If not, see <http://www.gnu.org/licenses/>.
31 **/
32
33
34 #include "PrecompiledHeadersServer.h"
35 #include "ServerToolbox.h"
36
37 #include "../Core/DicomFormat/DicomArray.h"
38 #include "../Core/DicomParsing/ParsedDicomFile.h"
39 #include "../Core/FileStorage/StorageAccessor.h"
40 #include "../Core/Logging.h"
41 #include "../Core/OrthancException.h"
42 #include "Database/IDatabaseWrapper.h"
43 #include "Database/ResourcesContent.h"
44 #include "ServerContext.h"
45
46 #include <cassert>
47
48 namespace Orthanc
49 {
50 static const DicomTag PATIENT_IDENTIFIERS[] =
51 {
52 DICOM_TAG_PATIENT_ID,
53 DICOM_TAG_PATIENT_NAME,
54 DICOM_TAG_PATIENT_BIRTH_DATE
55 };
56
57 static const DicomTag STUDY_IDENTIFIERS[] =
58 {
59 DICOM_TAG_PATIENT_ID,
60 DICOM_TAG_PATIENT_NAME,
61 DICOM_TAG_PATIENT_BIRTH_DATE,
62 DICOM_TAG_STUDY_INSTANCE_UID,
63 DICOM_TAG_ACCESSION_NUMBER,
64 DICOM_TAG_STUDY_DESCRIPTION,
65 DICOM_TAG_STUDY_DATE
66 };
67
68 static const DicomTag SERIES_IDENTIFIERS[] =
69 {
70 DICOM_TAG_SERIES_INSTANCE_UID
71 };
72
73 static const DicomTag INSTANCE_IDENTIFIERS[] =
74 {
75 DICOM_TAG_SOP_INSTANCE_UID
76 };
77
78
79 static void StoreMainDicomTagsInternal(ResourcesContent& target,
80 int64_t resource,
81 const DicomMap& tags)
82 {
83 DicomArray flattened(tags);
84
85 for (size_t i = 0; i < flattened.GetSize(); i++)
86 {
87 const DicomElement& element = flattened.GetElement(i);
88 const DicomTag& tag = element.GetTag();
89 const DicomValue& value = element.GetValue();
90 if (!value.IsNull() &&
91 !value.IsBinary())
92 {
93 target.AddMainDicomTag(resource, tag, element.GetValue().GetContent());
94 }
95 }
96 }
97
98
99 static void StoreIdentifiers(ResourcesContent& target,
100 int64_t resource,
101 ResourceType level,
102 const DicomMap& map)
103 {
104 const DicomTag* tags;
105 size_t size;
106
107 ServerToolbox::LoadIdentifiers(tags, size, level);
108
109 for (size_t i = 0; i < size; i++)
110 {
111 // The identifiers tags are a subset of the main DICOM tags
112 assert(DicomMap::IsMainDicomTag(tags[i]));
113
114 const DicomValue* value = map.TestAndGetValue(tags[i]);
115 if (value != NULL &&
116 !value->IsNull() &&
117 !value->IsBinary())
118 {
119 std::string s = ServerToolbox::NormalizeIdentifier(value->GetContent());
120 target.AddIdentifierTag(resource, tags[i], s);
121 }
122 }
123 }
124
125
126 void ResourcesContent::AddResource(int64_t resource,
127 ResourceType level,
128 const DicomMap& dicomSummary)
129 {
130 StoreIdentifiers(*this, resource, level, dicomSummary);
131
132 DicomMap tags;
133
134 switch (level)
135 {
136 case ResourceType_Patient:
137 dicomSummary.ExtractPatientInformation(tags);
138 break;
139
140 case ResourceType_Study:
141 // Duplicate the patient tags at the study level (new in Orthanc 0.9.5 - db v6)
142 dicomSummary.ExtractPatientInformation(tags);
143 StoreMainDicomTagsInternal(*this, resource, tags);
144
145 dicomSummary.ExtractStudyInformation(tags);
146 break;
147
148 case ResourceType_Series:
149 dicomSummary.ExtractSeriesInformation(tags);
150 break;
151
152 case ResourceType_Instance:
153 dicomSummary.ExtractInstanceInformation(tags);
154 break;
155
156 default:
157 throw OrthancException(ErrorCode_InternalError);
158 }
159
160 StoreMainDicomTagsInternal(*this, resource, tags);
161 }
162
163
164 namespace ServerToolbox
165 {
166 void SimplifyTags(Json::Value& target,
167 const Json::Value& source,
168 DicomToJsonFormat format)
169 {
170 if (!source.isObject())
171 {
172 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
173 }
174
175 target = Json::objectValue;
176 Json::Value::Members members = source.getMemberNames();
177
178 for (size_t i = 0; i < members.size(); i++)
179 {
180 const Json::Value& v = source[members[i]];
181 const std::string& type = v["Type"].asString();
182
183 std::string name;
184 switch (format)
185 {
186 case DicomToJsonFormat_Human:
187 name = v["Name"].asString();
188 break;
189
190 case DicomToJsonFormat_Short:
191 name = members[i];
192 break;
193
194 default:
195 throw OrthancException(ErrorCode_ParameterOutOfRange);
196 }
197
198 if (type == "String")
199 {
200 target[name] = v["Value"].asString();
201 }
202 else if (type == "TooLong" ||
203 type == "Null")
204 {
205 target[name] = Json::nullValue;
206 }
207 else if (type == "Sequence")
208 {
209 const Json::Value& array = v["Value"];
210 assert(array.isArray());
211
212 Json::Value children = Json::arrayValue;
213 for (Json::Value::ArrayIndex i = 0; i < array.size(); i++)
214 {
215 Json::Value c;
216 SimplifyTags(c, array[i], format);
217 children.append(c);
218 }
219
220 target[name] = children;
221 }
222 else
223 {
224 assert(0);
225 }
226 }
227 }
228
229
230 bool FindOneChildInstance(int64_t& result,
231 IDatabaseWrapper& database,
232 int64_t resource,
233 ResourceType type)
234 {
235 for (;;)
236 {
237 if (type == ResourceType_Instance)
238 {
239 result = resource;
240 return true;
241 }
242
243 std::list<int64_t> children;
244 database.GetChildrenInternalId(children, resource);
245 if (children.empty())
246 {
247 return false;
248 }
249
250 resource = children.front();
251 type = GetChildResourceType(type);
252 }
253 }
254
255
256 void ReconstructMainDicomTags(IDatabaseWrapper& database,
257 IStorageArea& storageArea,
258 ResourceType level)
259 {
260 // WARNING: The database should be locked with a transaction!
261
262 // TODO: This function might consume much memory if level ==
263 // ResourceType_Instance. To improve this, first download the
264 // list of studies, then remove the instances for each single
265 // study (check out OrthancRestApi::InvalidateTags for an
266 // example). Take this improvement into consideration for the
267 // next upgrade of the database schema.
268
269 const char* plural = NULL;
270
271 switch (level)
272 {
273 case ResourceType_Patient:
274 plural = "patients";
275 break;
276
277 case ResourceType_Study:
278 plural = "studies";
279 break;
280
281 case ResourceType_Series:
282 plural = "series";
283 break;
284
285 case ResourceType_Instance:
286 plural = "instances";
287 break;
288
289 default:
290 throw OrthancException(ErrorCode_InternalError);
291 }
292
293 LOG(WARNING) << "Upgrade: Reconstructing the main DICOM tags of all the " << plural << "...";
294
295 std::list<std::string> resources;
296 database.GetAllPublicIds(resources, level);
297
298 for (std::list<std::string>::const_iterator
299 it = resources.begin(); it != resources.end(); ++it)
300 {
301 // Locate the resource and one of its child instances
302 int64_t resource, instance;
303 ResourceType tmp;
304
305 if (!database.LookupResource(resource, tmp, *it) ||
306 tmp != level ||
307 !FindOneChildInstance(instance, database, resource, level))
308 {
309 throw OrthancException(ErrorCode_InternalError,
310 "Cannot find an instance for " +
311 std::string(EnumerationToString(level)) +
312 " with identifier " + *it);
313 }
314
315 // Get the DICOM file attached to some instances in the resource
316 FileInfo attachment;
317 if (!database.LookupAttachment(attachment, instance, FileContentType_Dicom))
318 {
319 throw OrthancException(ErrorCode_InternalError,
320 "Cannot retrieve the DICOM file associated with instance " +
321 database.GetPublicId(instance));
322 }
323
324 try
325 {
326 // Read and parse the content of the DICOM file
327 StorageAccessor accessor(storageArea);
328
329 std::string content;
330 accessor.Read(content, attachment);
331
332 ParsedDicomFile dicom(content);
333
334 // Update the tags of this resource
335 DicomMap dicomSummary;
336 dicom.ExtractDicomSummary(dicomSummary);
337
338 database.ClearMainDicomTags(resource);
339
340 ResourcesContent tags;
341 tags.AddResource(resource, level, dicomSummary);
342 database.SetResourcesContent(tags);
343 }
344 catch (OrthancException&)
345 {
346 LOG(ERROR) << "Cannot decode the DICOM file with UUID " << attachment.GetUuid()
347 << " associated with instance " << database.GetPublicId(instance);
348 throw;
349 }
350 }
351 }
352
353
354 void LoadIdentifiers(const DicomTag*& tags,
355 size_t& size,
356 ResourceType level)
357 {
358 switch (level)
359 {
360 case ResourceType_Patient:
361 tags = PATIENT_IDENTIFIERS;
362 size = sizeof(PATIENT_IDENTIFIERS) / sizeof(DicomTag);
363 break;
364
365 case ResourceType_Study:
366 tags = STUDY_IDENTIFIERS;
367 size = sizeof(STUDY_IDENTIFIERS) / sizeof(DicomTag);
368 break;
369
370 case ResourceType_Series:
371 tags = SERIES_IDENTIFIERS;
372 size = sizeof(SERIES_IDENTIFIERS) / sizeof(DicomTag);
373 break;
374
375 case ResourceType_Instance:
376 tags = INSTANCE_IDENTIFIERS;
377 size = sizeof(INSTANCE_IDENTIFIERS) / sizeof(DicomTag);
378 break;
379
380 default:
381 throw OrthancException(ErrorCode_ParameterOutOfRange);
382 }
383 }
384
385
386 std::string NormalizeIdentifier(const std::string& value)
387 {
388 std::string t;
389 t.reserve(value.size());
390
391 for (size_t i = 0; i < value.size(); i++)
392 {
393 if (value[i] == '%' ||
394 value[i] == '_')
395 {
396 t.push_back(' '); // These characters might break wildcard queries in SQL
397 }
398 else if (isascii(value[i]) &&
399 !iscntrl(value[i]) &&
400 (!isspace(value[i]) || value[i] == ' '))
401 {
402 t.push_back(value[i]);
403 }
404 }
405
406 Toolbox::ToUpperCase(t);
407
408 return Toolbox::StripSpaces(t);
409 }
410
411
412 bool IsIdentifier(const DicomTag& tag,
413 ResourceType level)
414 {
415 const DicomTag* tags;
416 size_t size;
417
418 LoadIdentifiers(tags, size, level);
419
420 for (size_t i = 0; i < size; i++)
421 {
422 if (tag == tags[i])
423 {
424 return true;
425 }
426 }
427
428 return false;
429 }
430
431
432 void ReconstructResource(ServerContext& context,
433 const std::string& resource)
434 {
435 LOG(WARNING) << "Reconstructing resource " << resource;
436
437 std::list<std::string> instances;
438 context.GetIndex().GetChildInstances(instances, resource);
439
440 for (std::list<std::string>::const_iterator
441 it = instances.begin(); it != instances.end(); ++it)
442 {
443 ServerContext::DicomCacheLocker locker(context, *it);
444
445 Json::Value dicomAsJson;
446 locker.GetDicom().DatasetToJson(dicomAsJson);
447
448 std::string s = dicomAsJson.toStyledString();
449 context.AddAttachment(*it, FileContentType_DicomAsJson, s.c_str(), s.size());
450
451 context.GetIndex().ReconstructInstance(locker.GetDicom());
452 }
453 }
454 }
455 }