Mercurial > hg > orthanc
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 } |