comparison OrthancServer/Sources/Database/Compatibility/DatabaseLookup.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/Database/Compatibility/DatabaseLookup.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 "DatabaseLookup.h"
36
37 #include "../../../Core/OrthancException.h"
38 #include "../../Search/DicomTagConstraint.h"
39 #include "../../ServerToolbox.h"
40 #include "SetOfResources.h"
41
42 namespace Orthanc
43 {
44 namespace Compatibility
45 {
46 namespace
47 {
48 // Anonymous namespace to avoid clashes between compiler modules
49 class MainTagsConstraints : boost::noncopyable
50 {
51 private:
52 std::vector<DicomTagConstraint*> constraints_;
53
54 public:
55 ~MainTagsConstraints()
56 {
57 for (size_t i = 0; i < constraints_.size(); i++)
58 {
59 assert(constraints_[i] != NULL);
60 delete constraints_[i];
61 }
62 }
63
64 void Reserve(size_t n)
65 {
66 constraints_.reserve(n);
67 }
68
69 size_t GetSize() const
70 {
71 return constraints_.size();
72 }
73
74 DicomTagConstraint& GetConstraint(size_t i) const
75 {
76 if (i >= constraints_.size())
77 {
78 throw OrthancException(ErrorCode_ParameterOutOfRange);
79 }
80 else
81 {
82 assert(constraints_[i] != NULL);
83 return *constraints_[i];
84 }
85 }
86
87 void Add(const DatabaseConstraint& constraint)
88 {
89 constraints_.push_back(new DicomTagConstraint(constraint));
90 }
91 };
92 }
93
94
95 static void ApplyIdentifierConstraint(SetOfResources& candidates,
96 ILookupResources& compatibility,
97 const DatabaseConstraint& constraint,
98 ResourceType level)
99 {
100 std::list<int64_t> matches;
101
102 switch (constraint.GetConstraintType())
103 {
104 case ConstraintType_Equal:
105 compatibility.LookupIdentifier(matches, level, constraint.GetTag(),
106 IdentifierConstraintType_Equal, constraint.GetSingleValue());
107 break;
108
109 case ConstraintType_SmallerOrEqual:
110 compatibility.LookupIdentifier(matches, level, constraint.GetTag(),
111 IdentifierConstraintType_SmallerOrEqual, constraint.GetSingleValue());
112 break;
113
114 case ConstraintType_GreaterOrEqual:
115 compatibility.LookupIdentifier(matches, level, constraint.GetTag(),
116 IdentifierConstraintType_GreaterOrEqual, constraint.GetSingleValue());
117
118 break;
119
120 case ConstraintType_Wildcard:
121 compatibility.LookupIdentifier(matches, level, constraint.GetTag(),
122 IdentifierConstraintType_Wildcard, constraint.GetSingleValue());
123
124 break;
125
126 case ConstraintType_List:
127 for (size_t i = 0; i < constraint.GetValuesCount(); i++)
128 {
129 std::list<int64_t> tmp;
130 compatibility.LookupIdentifier(tmp, level, constraint.GetTag(),
131 IdentifierConstraintType_Wildcard, constraint.GetValue(i));
132 matches.splice(matches.end(), tmp);
133 }
134
135 break;
136
137 default:
138 throw OrthancException(ErrorCode_InternalError);
139 }
140
141 candidates.Intersect(matches);
142 }
143
144
145 static void ApplyIdentifierRange(SetOfResources& candidates,
146 ILookupResources& compatibility,
147 const DatabaseConstraint& smaller,
148 const DatabaseConstraint& greater,
149 ResourceType level)
150 {
151 assert(smaller.GetConstraintType() == ConstraintType_SmallerOrEqual &&
152 greater.GetConstraintType() == ConstraintType_GreaterOrEqual &&
153 smaller.GetTag() == greater.GetTag() &&
154 ServerToolbox::IsIdentifier(smaller.GetTag(), level));
155
156 std::list<int64_t> matches;
157 compatibility.LookupIdentifierRange(matches, level, smaller.GetTag(),
158 greater.GetSingleValue(), smaller.GetSingleValue());
159 candidates.Intersect(matches);
160 }
161
162
163 static void ApplyLevel(SetOfResources& candidates,
164 IDatabaseWrapper& database,
165 ILookupResources& compatibility,
166 const std::vector<DatabaseConstraint>& lookup,
167 ResourceType level)
168 {
169 typedef std::set<const DatabaseConstraint*> SetOfConstraints;
170 typedef std::map<DicomTag, SetOfConstraints> Identifiers;
171
172 // (1) Select which constraints apply to this level, and split
173 // them between "identifier tags" constraints and "main DICOM
174 // tags" constraints
175
176 Identifiers identifiers;
177 SetOfConstraints mainTags;
178
179 for (size_t i = 0; i < lookup.size(); i++)
180 {
181 if (lookup[i].GetLevel() == level)
182 {
183 if (lookup[i].IsIdentifier())
184 {
185 identifiers[lookup[i].GetTag()].insert(&lookup[i]);
186 }
187 else
188 {
189 mainTags.insert(&lookup[i]);
190 }
191 }
192 }
193
194
195 // (2) Apply the constraints over the identifiers
196
197 for (Identifiers::const_iterator it = identifiers.begin();
198 it != identifiers.end(); ++it)
199 {
200 // Check whether some range constraint over identifiers is
201 // present at this level
202 const DatabaseConstraint* smaller = NULL;
203 const DatabaseConstraint* greater = NULL;
204
205 for (SetOfConstraints::const_iterator it2 = it->second.begin();
206 it2 != it->second.end(); ++it2)
207 {
208 assert(*it2 != NULL);
209
210 if ((*it2)->GetConstraintType() == ConstraintType_SmallerOrEqual)
211 {
212 smaller = *it2;
213 }
214
215 if ((*it2)->GetConstraintType() == ConstraintType_GreaterOrEqual)
216 {
217 greater = *it2;
218 }
219 }
220
221 if (smaller != NULL &&
222 greater != NULL)
223 {
224 // There is a range constraint: Apply it, as it is more efficient
225 ApplyIdentifierRange(candidates, compatibility, *smaller, *greater, level);
226 }
227 else
228 {
229 smaller = NULL;
230 greater = NULL;
231 }
232
233 for (SetOfConstraints::const_iterator it2 = it->second.begin();
234 it2 != it->second.end(); ++it2)
235 {
236 // Check to avoid applying twice the range constraint
237 if (*it2 != smaller &&
238 *it2 != greater)
239 {
240 ApplyIdentifierConstraint(candidates, compatibility, **it2, level);
241 }
242 }
243 }
244
245
246 // (3) Apply the constraints over the main DICOM tags (no index
247 // here, so this is less efficient than filtering over the
248 // identifiers)
249 if (!mainTags.empty())
250 {
251 MainTagsConstraints c;
252 c.Reserve(mainTags.size());
253
254 for (SetOfConstraints::const_iterator it = mainTags.begin();
255 it != mainTags.end(); ++it)
256 {
257 assert(*it != NULL);
258 c.Add(**it);
259 }
260
261 std::list<int64_t> source;
262 candidates.Flatten(compatibility, source);
263 candidates.Clear();
264
265 std::list<int64_t> filtered;
266 for (std::list<int64_t>::const_iterator candidate = source.begin();
267 candidate != source.end(); ++candidate)
268 {
269 DicomMap tags;
270 database.GetMainDicomTags(tags, *candidate);
271
272 bool match = true;
273
274 for (size_t i = 0; i < c.GetSize(); i++)
275 {
276 if (!c.GetConstraint(i).IsMatch(tags))
277 {
278 match = false;
279 break;
280 }
281 }
282
283 if (match)
284 {
285 filtered.push_back(*candidate);
286 }
287 }
288
289 candidates.Intersect(filtered);
290 }
291 }
292
293
294 static std::string GetOneInstance(IDatabaseWrapper& compatibility,
295 int64_t resource,
296 ResourceType level)
297 {
298 for (int i = level; i < ResourceType_Instance; i++)
299 {
300 assert(compatibility.GetResourceType(resource) == static_cast<ResourceType>(i));
301
302 std::list<int64_t> children;
303 compatibility.GetChildrenInternalId(children, resource);
304
305 if (children.empty())
306 {
307 throw OrthancException(ErrorCode_Database);
308 }
309
310 resource = children.front();
311 }
312
313 return compatibility.GetPublicId(resource);
314 }
315
316
317 void DatabaseLookup::ApplyLookupResources(std::list<std::string>& resourcesId,
318 std::list<std::string>* instancesId,
319 const std::vector<DatabaseConstraint>& lookup,
320 ResourceType queryLevel,
321 size_t limit)
322 {
323 // This is a re-implementation of
324 // "../../../Resources/Graveyard/DatabaseOptimizations/LookupResource.cpp"
325
326 assert(ResourceType_Patient < ResourceType_Study &&
327 ResourceType_Study < ResourceType_Series &&
328 ResourceType_Series < ResourceType_Instance);
329
330 ResourceType upperLevel = queryLevel;
331 ResourceType lowerLevel = queryLevel;
332
333 for (size_t i = 0; i < lookup.size(); i++)
334 {
335 ResourceType level = lookup[i].GetLevel();
336
337 if (level < upperLevel)
338 {
339 upperLevel = level;
340 }
341
342 if (level > lowerLevel)
343 {
344 lowerLevel = level;
345 }
346 }
347
348 assert(upperLevel <= queryLevel &&
349 queryLevel <= lowerLevel);
350
351 SetOfResources candidates(database_, upperLevel);
352
353 for (int level = upperLevel; level <= lowerLevel; level++)
354 {
355 ApplyLevel(candidates, database_, compatibility_, lookup, static_cast<ResourceType>(level));
356
357 if (level != lowerLevel)
358 {
359 candidates.GoDown();
360 }
361 }
362
363 std::list<int64_t> resources;
364 candidates.Flatten(compatibility_, resources);
365
366 // Climb up, up to queryLevel
367
368 for (int level = lowerLevel; level > queryLevel; level--)
369 {
370 std::list<int64_t> parents;
371 for (std::list<int64_t>::const_iterator
372 it = resources.begin(); it != resources.end(); ++it)
373 {
374 int64_t parent;
375 if (database_.LookupParent(parent, *it))
376 {
377 parents.push_back(parent);
378 }
379 }
380
381 resources.swap(parents);
382 }
383
384 // Apply the limit, if given
385
386 if (limit != 0 &&
387 resources.size() > limit)
388 {
389 resources.resize(limit);
390 }
391
392 // Get the public ID of all the selected resources
393
394 size_t pos = 0;
395
396 for (std::list<int64_t>::const_iterator
397 it = resources.begin(); it != resources.end(); ++it, pos++)
398 {
399 assert(database_.GetResourceType(*it) == queryLevel);
400
401 const std::string resource = database_.GetPublicId(*it);
402 resourcesId.push_back(resource);
403
404 if (instancesId != NULL)
405 {
406 if (queryLevel == ResourceType_Instance)
407 {
408 // The resource is itself the instance
409 instancesId->push_back(resource);
410 }
411 else
412 {
413 // Collect one child instance for each of the selected resources
414 instancesId->push_back(GetOneInstance(database_, *it, queryLevel));
415 }
416 }
417 }
418 }
419 }
420 }