comparison OrthancServer/Database/Compatibility/DatabaseLookup.cpp @ 3093:2e1808b6146a db-changes

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