Mercurial > hg > orthanc
annotate OrthancServer/ResourceFinder.cpp @ 1677:a903d57d9f0c db-changes
adaptation of search with patient tags at study level
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Mon, 05 Oct 2015 16:40:14 +0200 |
parents | f967bdf8534e |
children | 21d31da73374 |
rev | line source |
---|---|
1358 | 1 /** |
2 * Orthanc - A Lightweight, RESTful DICOM Store | |
3 * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics | |
4 * Department, University Hospital of Liege, Belgium | |
5 * | |
6 * This program is free software: you can redistribute it and/or | |
7 * modify it under the terms of the GNU General Public License as | |
8 * published by the Free Software Foundation, either version 3 of the | |
9 * License, or (at your option) any later version. | |
10 * | |
11 * In addition, as a special exception, the copyright holders of this | |
12 * program give permission to link the code of its release with the | |
13 * OpenSSL project's "OpenSSL" library (or with modified versions of it | |
14 * that use the same license as the "OpenSSL" library), and distribute | |
15 * the linked executables. You must obey the GNU General Public License | |
16 * in all respects for all of the code used other than "OpenSSL". If you | |
17 * modify file(s) with this exception, you may extend this exception to | |
18 * your version of the file(s), but you are not obligated to do so. If | |
19 * you do not wish to do so, delete this exception statement from your | |
20 * version. If you delete this exception statement from all source files | |
21 * in the program, then also delete it here. | |
22 * | |
23 * This program is distributed in the hope that it will be useful, but | |
24 * WITHOUT ANY WARRANTY; without even the implied warranty of | |
25 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
26 * General Public License for more details. | |
27 * | |
28 * You should have received a copy of the GNU General Public License | |
29 * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
30 **/ | |
31 | |
32 | |
33 #include "PrecompiledHeadersServer.h" | |
1360 | 34 #include "ResourceFinder.h" |
1359 | 35 |
1486
f967bdf8534e
refactoring to Logging.h
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
1384
diff
changeset
|
36 #include "../Core/Logging.h" |
1359 | 37 #include "FromDcmtkBridge.h" |
38 #include "ServerContext.h" | |
39 | |
1358 | 40 namespace Orthanc |
41 { | |
1360 | 42 class ResourceFinder::CandidateResources |
1358 | 43 { |
1359 | 44 private: |
45 typedef std::map<DicomTag, std::string> Query; | |
46 | |
1360 | 47 ResourceFinder& finder_; |
1359 | 48 ServerIndex& index_; |
49 ResourceType level_; | |
50 bool isFilterApplied_; | |
51 std::set<std::string> filtered_; | |
52 | |
53 | |
54 static void ListToSet(std::set<std::string>& target, | |
55 const std::list<std::string>& source) | |
56 { | |
57 for (std::list<std::string>::const_iterator | |
58 it = source.begin(); it != source.end(); ++it) | |
59 { | |
60 target.insert(*it); | |
61 } | |
62 } | |
63 | |
64 | |
65 public: | |
1360 | 66 CandidateResources(ResourceFinder& finder) : |
1359 | 67 finder_(finder), |
68 index_(finder.context_.GetIndex()), | |
69 level_(ResourceType_Patient), | |
70 isFilterApplied_(false) | |
71 { | |
72 } | |
73 | |
74 ResourceType GetLevel() const | |
1358 | 75 { |
1359 | 76 return level_; |
77 } | |
78 | |
79 void GoDown() | |
80 { | |
81 assert(level_ != ResourceType_Instance); | |
82 | |
83 if (isFilterApplied_) | |
84 { | |
85 std::set<std::string> tmp = filtered_; | |
86 | |
87 filtered_.clear(); | |
88 | |
89 for (std::set<std::string>::const_iterator | |
90 it = tmp.begin(); it != tmp.end(); ++it) | |
91 { | |
92 std::list<std::string> children; | |
93 try | |
94 { | |
95 index_.GetChildren(children, *it); | |
96 ListToSet(filtered_, children); | |
97 } | |
98 catch (OrthancException&) | |
99 { | |
100 // The resource was removed in the meantime | |
101 } | |
102 } | |
103 } | |
104 | |
105 switch (level_) | |
1358 | 106 { |
1359 | 107 case ResourceType_Patient: |
108 level_ = ResourceType_Study; | |
109 break; | |
110 | |
111 case ResourceType_Study: | |
112 level_ = ResourceType_Series; | |
113 break; | |
114 | |
115 case ResourceType_Series: | |
116 level_ = ResourceType_Instance; | |
117 break; | |
118 | |
119 default: | |
120 throw OrthancException(ErrorCode_InternalError); | |
121 } | |
122 } | |
123 | |
124 | |
125 void Flatten(std::list<std::string>& resources) const | |
126 { | |
127 resources.clear(); | |
128 | |
129 if (isFilterApplied_) | |
130 { | |
131 for (std::set<std::string>::const_iterator | |
132 it = filtered_.begin(); it != filtered_.end(); ++it) | |
133 { | |
134 resources.push_back(*it); | |
135 } | |
136 } | |
137 else | |
138 { | |
139 index_.GetAllUuids(resources, level_); | |
140 } | |
141 } | |
142 | |
143 | |
1360 | 144 void RestrictIdentifier(const IQuery& query, |
145 const DicomTag& tag) | |
1359 | 146 { |
1360 | 147 assert((level_ == ResourceType_Patient && tag == DICOM_TAG_PATIENT_ID) || |
148 (level_ == ResourceType_Study && tag == DICOM_TAG_STUDY_INSTANCE_UID) || | |
149 (level_ == ResourceType_Study && tag == DICOM_TAG_ACCESSION_NUMBER) || | |
150 (level_ == ResourceType_Series && tag == DICOM_TAG_SERIES_INSTANCE_UID) || | |
151 (level_ == ResourceType_Instance && tag == DICOM_TAG_SOP_INSTANCE_UID)); | |
152 | |
153 std::string value; | |
154 if (!query.RestrictIdentifier(value, tag)) | |
155 { | |
156 return; | |
157 } | |
158 | |
159 LOG(INFO) << "Lookup for identifier tag " | |
160 << FromDcmtkBridge::GetName(tag) << " (value: " << value << ")"; | |
161 | |
162 std::list<std::string> resources; | |
163 index_.LookupIdentifier(resources, tag, value, level_); | |
164 | |
165 if (isFilterApplied_) | |
1359 | 166 { |
1360 | 167 std::set<std::string> s; |
168 ListToSet(s, resources); | |
169 | |
170 std::set<std::string> tmp = filtered_; | |
171 filtered_.clear(); | |
172 | |
173 for (std::set<std::string>::const_iterator | |
174 it = tmp.begin(); it != tmp.end(); ++it) | |
175 { | |
176 if (s.find(*it) != s.end()) | |
177 { | |
178 filtered_.insert(*it); | |
179 } | |
180 } | |
181 } | |
182 else | |
183 { | |
184 assert(filtered_.empty()); | |
185 isFilterApplied_ = true; | |
186 ListToSet(filtered_, resources); | |
1359 | 187 } |
188 } | |
189 | |
190 | |
1677
a903d57d9f0c
adaptation of search with patient tags at study level
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
1486
diff
changeset
|
191 void RestrictMainDicomTags(const IQuery& query, |
a903d57d9f0c
adaptation of search with patient tags at study level
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
1486
diff
changeset
|
192 bool filterPatientTagsAtStudyLevel) |
1359 | 193 { |
1677
a903d57d9f0c
adaptation of search with patient tags at study level
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
1486
diff
changeset
|
194 if (filterPatientTagsAtStudyLevel && |
a903d57d9f0c
adaptation of search with patient tags at study level
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
1486
diff
changeset
|
195 level_ == ResourceType_Patient) |
a903d57d9f0c
adaptation of search with patient tags at study level
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
1486
diff
changeset
|
196 { |
a903d57d9f0c
adaptation of search with patient tags at study level
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
1486
diff
changeset
|
197 return; |
a903d57d9f0c
adaptation of search with patient tags at study level
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
1486
diff
changeset
|
198 } |
a903d57d9f0c
adaptation of search with patient tags at study level
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
1486
diff
changeset
|
199 |
1360 | 200 if (!query.HasMainDicomTagsFilter(level_)) |
1359 | 201 { |
202 return; | |
1358 | 203 } |
204 | |
1359 | 205 std::list<std::string> resources; |
206 Flatten(resources); | |
207 | |
208 isFilterApplied_ = true; | |
209 filtered_.clear(); | |
210 | |
211 for (std::list<std::string>::const_iterator | |
1384 | 212 it = resources.begin(); it != resources.end(); ++it) |
1359 | 213 { |
214 DicomMap mainTags; | |
1677
a903d57d9f0c
adaptation of search with patient tags at study level
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
1486
diff
changeset
|
215 if (!index_.GetMainDicomTags(mainTags, *it, level_, level_) || |
a903d57d9f0c
adaptation of search with patient tags at study level
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
1486
diff
changeset
|
216 !query.FilterMainDicomTags(*it, level_, mainTags)) |
1359 | 217 { |
1677
a903d57d9f0c
adaptation of search with patient tags at study level
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
1486
diff
changeset
|
218 continue; |
a903d57d9f0c
adaptation of search with patient tags at study level
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
1486
diff
changeset
|
219 } |
a903d57d9f0c
adaptation of search with patient tags at study level
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
1486
diff
changeset
|
220 |
a903d57d9f0c
adaptation of search with patient tags at study level
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
1486
diff
changeset
|
221 if (filterPatientTagsAtStudyLevel && |
a903d57d9f0c
adaptation of search with patient tags at study level
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
1486
diff
changeset
|
222 level_ == ResourceType_Study) |
a903d57d9f0c
adaptation of search with patient tags at study level
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
1486
diff
changeset
|
223 { |
a903d57d9f0c
adaptation of search with patient tags at study level
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
1486
diff
changeset
|
224 if (!index_.GetMainDicomTags(mainTags, *it, ResourceType_Study, ResourceType_Patient) || |
a903d57d9f0c
adaptation of search with patient tags at study level
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
1486
diff
changeset
|
225 !query.FilterMainDicomTags(*it, ResourceType_Patient, mainTags)) |
1359 | 226 { |
1677
a903d57d9f0c
adaptation of search with patient tags at study level
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
1486
diff
changeset
|
227 continue; |
a903d57d9f0c
adaptation of search with patient tags at study level
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
1486
diff
changeset
|
228 } |
1359 | 229 } |
1677
a903d57d9f0c
adaptation of search with patient tags at study level
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
1486
diff
changeset
|
230 |
a903d57d9f0c
adaptation of search with patient tags at study level
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
1486
diff
changeset
|
231 filtered_.insert(*it); |
1359 | 232 } |
233 } | |
234 }; | |
235 | |
236 | |
1360 | 237 ResourceFinder::ResourceFinder(ServerContext& context) : |
1359 | 238 context_(context), |
239 maxResults_(0) | |
240 { | |
241 } | |
242 | |
243 | |
1360 | 244 void ResourceFinder::ApplyAtLevel(CandidateResources& candidates, |
245 const IQuery& query, | |
246 ResourceType level) | |
1359 | 247 { |
248 if (level != ResourceType_Patient) | |
249 { | |
250 candidates.GoDown(); | |
251 } | |
252 | |
1360 | 253 switch (level) |
1359 | 254 { |
255 case ResourceType_Patient: | |
256 { | |
1360 | 257 candidates.RestrictIdentifier(query, DICOM_TAG_PATIENT_ID); |
1359 | 258 break; |
259 } | |
260 | |
261 case ResourceType_Study: | |
262 { | |
1360 | 263 candidates.RestrictIdentifier(query, DICOM_TAG_STUDY_INSTANCE_UID); |
264 candidates.RestrictIdentifier(query, DICOM_TAG_ACCESSION_NUMBER); | |
1359 | 265 break; |
266 } | |
267 | |
268 case ResourceType_Series: | |
269 { | |
1360 | 270 candidates.RestrictIdentifier(query, DICOM_TAG_SERIES_INSTANCE_UID); |
1359 | 271 break; |
272 } | |
273 | |
274 case ResourceType_Instance: | |
275 { | |
1360 | 276 candidates.RestrictIdentifier(query, DICOM_TAG_SOP_INSTANCE_UID); |
1359 | 277 break; |
278 } | |
279 | |
280 default: | |
281 throw OrthancException(ErrorCode_InternalError); | |
282 } | |
283 | |
1677
a903d57d9f0c
adaptation of search with patient tags at study level
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
1486
diff
changeset
|
284 if (query.GetLevel() == ResourceType_Patient) |
a903d57d9f0c
adaptation of search with patient tags at study level
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
1486
diff
changeset
|
285 { |
a903d57d9f0c
adaptation of search with patient tags at study level
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
1486
diff
changeset
|
286 candidates.RestrictMainDicomTags(query, false); |
a903d57d9f0c
adaptation of search with patient tags at study level
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
1486
diff
changeset
|
287 } |
a903d57d9f0c
adaptation of search with patient tags at study level
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
1486
diff
changeset
|
288 else |
a903d57d9f0c
adaptation of search with patient tags at study level
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
1486
diff
changeset
|
289 { |
a903d57d9f0c
adaptation of search with patient tags at study level
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
1486
diff
changeset
|
290 candidates.RestrictMainDicomTags(query, true); |
a903d57d9f0c
adaptation of search with patient tags at study level
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
1486
diff
changeset
|
291 } |
1359 | 292 } |
293 | |
294 | |
295 | |
296 static bool LookupOneInstance(std::string& result, | |
297 ServerIndex& index, | |
298 const std::string& id, | |
299 ResourceType type) | |
300 { | |
301 if (type == ResourceType_Instance) | |
302 { | |
303 result = id; | |
304 return true; | |
305 } | |
306 | |
307 std::string childId; | |
1358 | 308 |
1359 | 309 { |
310 std::list<std::string> children; | |
311 index.GetChildInstances(children, id); | |
312 | |
313 if (children.empty()) | |
314 { | |
315 return false; | |
316 } | |
317 | |
318 childId = children.front(); | |
319 } | |
320 | |
321 return LookupOneInstance(result, index, childId, GetChildResourceType(type)); | |
1358 | 322 } |
323 | |
1359 | 324 |
1360 | 325 bool ResourceFinder::Apply(std::list<std::string>& result, |
326 const IQuery& query) | |
1359 | 327 { |
328 CandidateResources candidates(*this); | |
329 | |
1360 | 330 ApplyAtLevel(candidates, query, ResourceType_Patient); |
331 | |
332 const ResourceType level = query.GetLevel(); | |
1359 | 333 |
1360 | 334 if (level == ResourceType_Study || |
335 level == ResourceType_Series || | |
336 level == ResourceType_Instance) | |
1359 | 337 { |
1360 | 338 ApplyAtLevel(candidates, query, ResourceType_Study); |
1359 | 339 } |
340 | |
1360 | 341 if (level == ResourceType_Series || |
342 level == ResourceType_Instance) | |
1359 | 343 { |
1360 | 344 ApplyAtLevel(candidates, query, ResourceType_Series); |
1359 | 345 } |
346 | |
1360 | 347 if (level == ResourceType_Instance) |
1359 | 348 { |
1360 | 349 ApplyAtLevel(candidates, query, ResourceType_Instance); |
1359 | 350 } |
351 | |
1360 | 352 if (!query.HasInstanceFilter()) |
1359 | 353 { |
354 candidates.Flatten(result); | |
355 | |
356 if (maxResults_ != 0 && | |
357 result.size() >= maxResults_) | |
358 { | |
359 result.resize(maxResults_); | |
360 return false; | |
361 } | |
362 else | |
363 { | |
364 return true; | |
365 } | |
366 } | |
367 else | |
368 { | |
369 std::list<std::string> tmp; | |
370 candidates.Flatten(tmp); | |
371 | |
372 result.clear(); | |
373 for (std::list<std::string>::const_iterator | |
374 resource = tmp.begin(); resource != tmp.end(); ++resource) | |
375 { | |
376 try | |
377 { | |
378 std::string instance; | |
1360 | 379 if (LookupOneInstance(instance, context_.GetIndex(), *resource, level)) |
1359 | 380 { |
381 Json::Value content; | |
382 context_.ReadJson(content, instance); | |
1360 | 383 if (query.FilterInstance(*resource, content)) |
1359 | 384 { |
385 result.push_back(*resource); | |
386 | |
387 if (maxResults_ != 0 && | |
388 result.size() >= maxResults_) | |
389 { | |
390 // Too many results, stop before recording this new match | |
391 return false; | |
392 } | |
393 } | |
394 } | |
395 } | |
396 catch (OrthancException&) | |
397 { | |
398 // This resource has been deleted since the search was started | |
399 } | |
400 } | |
401 } | |
402 | |
403 return true; // All the matching resources have been returned | |
404 } | |
1358 | 405 } |