comparison OrthancServer/Resources/Graveyard/DatabaseOptimizations/LookupResource.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 Resources/Graveyard/DatabaseOptimizations/LookupResource.cpp@94f4a18a79cc
children d9473bd5ed43
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 "LookupResource.h"
36
37 #include "../../Core/OrthancException.h"
38 #include "../../Core/FileStorage/StorageAccessor.h"
39 #include "../ServerToolbox.h"
40 #include "../../Core/DicomParsing/FromDcmtkBridge.h"
41
42
43 namespace Orthanc
44 {
45 static bool DoesDicomMapMatch(const DicomMap& dicom,
46 const DicomTag& tag,
47 const IFindConstraint& constraint)
48 {
49 const DicomValue* value = dicom.TestAndGetValue(tag);
50
51 return (value != NULL &&
52 !value->IsNull() &&
53 !value->IsBinary() &&
54 constraint.Match(value->GetContent()));
55 }
56
57
58 LookupResource::Level::Level(ResourceType level) : level_(level)
59 {
60 const DicomTag* tags = NULL;
61 size_t size;
62
63 ServerToolbox::LoadIdentifiers(tags, size, level);
64
65 for (size_t i = 0; i < size; i++)
66 {
67 identifiers_.insert(tags[i]);
68 }
69
70 DicomMap::LoadMainDicomTags(tags, size, level);
71
72 for (size_t i = 0; i < size; i++)
73 {
74 if (identifiers_.find(tags[i]) == identifiers_.end())
75 {
76 mainTags_.insert(tags[i]);
77 }
78 }
79 }
80
81 LookupResource::Level::~Level()
82 {
83 for (Constraints::iterator it = mainTagsConstraints_.begin();
84 it != mainTagsConstraints_.end(); ++it)
85 {
86 delete it->second;
87 }
88
89 for (Constraints::iterator it = identifiersConstraints_.begin();
90 it != identifiersConstraints_.end(); ++it)
91 {
92 delete it->second;
93 }
94 }
95
96 bool LookupResource::Level::Add(const DicomTag& tag,
97 std::auto_ptr<IFindConstraint>& constraint)
98 {
99 if (identifiers_.find(tag) != identifiers_.end())
100 {
101 if (level_ == ResourceType_Patient)
102 {
103 // The filters on the patient level must be cloned to the study level
104 identifiersConstraints_[tag] = constraint->Clone();
105 }
106 else
107 {
108 identifiersConstraints_[tag] = constraint.release();
109 }
110
111 return true;
112 }
113 else if (mainTags_.find(tag) != mainTags_.end())
114 {
115 if (level_ == ResourceType_Patient)
116 {
117 // The filters on the patient level must be cloned to the study level
118 mainTagsConstraints_[tag] = constraint->Clone();
119 }
120 else
121 {
122 mainTagsConstraints_[tag] = constraint.release();
123 }
124
125 return true;
126 }
127 else
128 {
129 // This is not a main DICOM tag
130 return false;
131 }
132 }
133
134
135 bool LookupResource::Level::IsMatch(const DicomMap& dicom) const
136 {
137 for (Constraints::const_iterator it = identifiersConstraints_.begin();
138 it != identifiersConstraints_.end(); ++it)
139 {
140 assert(it->second != NULL);
141
142 if (!DoesDicomMapMatch(dicom, it->first, *it->second))
143 {
144 return false;
145 }
146 }
147
148 for (Constraints::const_iterator it = mainTagsConstraints_.begin();
149 it != mainTagsConstraints_.end(); ++it)
150 {
151 assert(it->second != NULL);
152
153 if (!DoesDicomMapMatch(dicom, it->first, *it->second))
154 {
155 return false;
156 }
157 }
158
159 return true;
160 }
161
162
163 LookupResource::LookupResource(ResourceType level) : level_(level)
164 {
165 switch (level)
166 {
167 case ResourceType_Patient:
168 levels_[ResourceType_Patient] = new Level(ResourceType_Patient);
169 break;
170
171 case ResourceType_Instance:
172 levels_[ResourceType_Instance] = new Level(ResourceType_Instance);
173 // Do not add "break" here
174
175 case ResourceType_Series:
176 levels_[ResourceType_Series] = new Level(ResourceType_Series);
177 // Do not add "break" here
178
179 case ResourceType_Study:
180 levels_[ResourceType_Study] = new Level(ResourceType_Study);
181 break;
182
183 default:
184 throw OrthancException(ErrorCode_InternalError);
185 }
186 }
187
188
189 LookupResource::~LookupResource()
190 {
191 for (Levels::iterator it = levels_.begin();
192 it != levels_.end(); ++it)
193 {
194 delete it->second;
195 }
196
197 for (Constraints::iterator it = unoptimizedConstraints_.begin();
198 it != unoptimizedConstraints_.end(); ++it)
199 {
200 delete it->second;
201 }
202 }
203
204
205
206 bool LookupResource::AddInternal(ResourceType level,
207 const DicomTag& tag,
208 std::auto_ptr<IFindConstraint>& constraint)
209 {
210 Levels::iterator it = levels_.find(level);
211 if (it != levels_.end())
212 {
213 if (it->second->Add(tag, constraint))
214 {
215 return true;
216 }
217 }
218
219 return false;
220 }
221
222
223 void LookupResource::Add(const DicomTag& tag,
224 IFindConstraint* constraint)
225 {
226 std::auto_ptr<IFindConstraint> c(constraint);
227
228 if (!AddInternal(ResourceType_Patient, tag, c) &&
229 !AddInternal(ResourceType_Study, tag, c) &&
230 !AddInternal(ResourceType_Series, tag, c) &&
231 !AddInternal(ResourceType_Instance, tag, c))
232 {
233 unoptimizedConstraints_[tag] = c.release();
234 }
235 }
236
237
238 static bool Match(const DicomMap& tags,
239 const DicomTag& tag,
240 const IFindConstraint& constraint)
241 {
242 const DicomValue* value = tags.TestAndGetValue(tag);
243
244 if (value == NULL ||
245 value->IsNull() ||
246 value->IsBinary())
247 {
248 return false;
249 }
250 else
251 {
252 return constraint.Match(value->GetContent());
253 }
254 }
255
256
257 void LookupResource::Level::Apply(SetOfResources& candidates,
258 IDatabaseWrapper& database) const
259 {
260 // First, use the indexed identifiers
261 LookupIdentifierQuery query(level_);
262
263 for (Constraints::const_iterator it = identifiersConstraints_.begin();
264 it != identifiersConstraints_.end(); ++it)
265 {
266 it->second->Setup(query, it->first);
267 }
268
269 query.Apply(candidates, database);
270
271 /*{
272 query.Print(std::cout);
273 std::list<int64_t> source;
274 candidates.Flatten(source);
275 printf("=> %d\n", source.size());
276 }*/
277
278 // Secondly, filter using the main DICOM tags
279 if (!identifiersConstraints_.empty() ||
280 !mainTagsConstraints_.empty())
281 {
282 std::list<int64_t> source;
283 candidates.Flatten(source);
284 candidates.Clear();
285
286 std::list<int64_t> filtered;
287 for (std::list<int64_t>::const_iterator candidate = source.begin();
288 candidate != source.end(); ++candidate)
289 {
290 DicomMap tags;
291 database.GetMainDicomTags(tags, *candidate);
292
293 bool match = true;
294
295 // Re-apply the identifier constraints, as their "Setup"
296 // method is less restrictive than their "Match" method
297 for (Constraints::const_iterator it = identifiersConstraints_.begin();
298 match && it != identifiersConstraints_.end(); ++it)
299 {
300 if (!Match(tags, it->first, *it->second))
301 {
302 match = false;
303 }
304 }
305
306 for (Constraints::const_iterator it = mainTagsConstraints_.begin();
307 match && it != mainTagsConstraints_.end(); ++it)
308 {
309 if (!Match(tags, it->first, *it->second))
310 {
311 match = false;
312 }
313 }
314
315 if (match)
316 {
317 filtered.push_back(*candidate);
318 }
319 }
320
321 candidates.Intersect(filtered);
322 }
323 }
324
325
326
327 bool LookupResource::IsMatch(const DicomMap& dicom) const
328 {
329 for (Levels::const_iterator it = levels_.begin(); it != levels_.end(); ++it)
330 {
331 if (!it->second->IsMatch(dicom))
332 {
333 return false;
334 }
335 }
336
337 for (Constraints::const_iterator it = unoptimizedConstraints_.begin();
338 it != unoptimizedConstraints_.end(); ++it)
339 {
340 assert(it->second != NULL);
341
342 if (!DoesDicomMapMatch(dicom, it->first, *it->second))
343 {
344 return false;
345 }
346 }
347
348 return true;
349 }
350
351
352 void LookupResource::ApplyLevel(SetOfResources& candidates,
353 ResourceType level,
354 IDatabaseWrapper& database) const
355 {
356 Levels::const_iterator it = levels_.find(level);
357 if (it != levels_.end())
358 {
359 it->second->Apply(candidates, database);
360 }
361
362 if (level == ResourceType_Study &&
363 modalitiesInStudy_.get() != NULL)
364 {
365 // There is a constraint on the "ModalitiesInStudy" DICOM
366 // extension. Check out whether one child series has one of the
367 // allowed modalities
368 std::list<int64_t> allStudies, matchingStudies;
369 candidates.Flatten(allStudies);
370
371 for (std::list<int64_t>::const_iterator
372 study = allStudies.begin(); study != allStudies.end(); ++study)
373 {
374 std::list<int64_t> childrenSeries;
375 database.GetChildrenInternalId(childrenSeries, *study);
376
377 for (std::list<int64_t>::const_iterator
378 series = childrenSeries.begin(); series != childrenSeries.end(); ++series)
379 {
380 DicomMap tags;
381 database.GetMainDicomTags(tags, *series);
382
383 const DicomValue* value = tags.TestAndGetValue(DICOM_TAG_MODALITY);
384 if (value != NULL &&
385 !value->IsNull() &&
386 !value->IsBinary())
387 {
388 if (modalitiesInStudy_->Match(value->GetContent()))
389 {
390 matchingStudies.push_back(*study);
391 break;
392 }
393 }
394 }
395 }
396
397 candidates.Intersect(matchingStudies);
398 }
399 }
400
401
402 void LookupResource::FindCandidates(std::list<int64_t>& result,
403 IDatabaseWrapper& database) const
404 {
405 ResourceType startingLevel;
406 if (level_ == ResourceType_Patient)
407 {
408 startingLevel = ResourceType_Patient;
409 }
410 else
411 {
412 startingLevel = ResourceType_Study;
413 }
414
415 SetOfResources candidates(database, startingLevel);
416
417 switch (level_)
418 {
419 case ResourceType_Patient:
420 ApplyLevel(candidates, ResourceType_Patient, database);
421 break;
422
423 case ResourceType_Study:
424 ApplyLevel(candidates, ResourceType_Study, database);
425 break;
426
427 case ResourceType_Series:
428 ApplyLevel(candidates, ResourceType_Study, database);
429 candidates.GoDown();
430 ApplyLevel(candidates, ResourceType_Series, database);
431 break;
432
433 case ResourceType_Instance:
434 ApplyLevel(candidates, ResourceType_Study, database);
435 candidates.GoDown();
436 ApplyLevel(candidates, ResourceType_Series, database);
437 candidates.GoDown();
438 ApplyLevel(candidates, ResourceType_Instance, database);
439 break;
440
441 default:
442 throw OrthancException(ErrorCode_InternalError);
443 }
444
445 candidates.Flatten(result);
446 }
447
448
449 void LookupResource::SetModalitiesInStudy(const std::string& modalities)
450 {
451 modalitiesInStudy_.reset(new ListConstraint(true /* case sensitive */));
452
453 std::vector<std::string> items;
454 Toolbox::TokenizeString(items, modalities, '\\');
455
456 for (size_t i = 0; i < items.size(); i++)
457 {
458 modalitiesInStudy_->AddAllowedValue(items[i]);
459 }
460 }
461
462
463 void LookupResource::AddDicomConstraint(const DicomTag& tag,
464 const std::string& dicomQuery,
465 bool caseSensitive)
466 {
467 // http://www.itk.org/Wiki/DICOM_QueryRetrieve_Explained
468 // http://dicomiseasy.blogspot.be/2012/01/dicom-queryretrieve-part-i.html
469 if (tag == DICOM_TAG_MODALITIES_IN_STUDY)
470 {
471 SetModalitiesInStudy(dicomQuery);
472 }
473 else
474 {
475 Add(tag, IFindConstraint::ParseDicomConstraint(tag, dicomQuery, caseSensitive));
476 }
477 }
478
479 }