Mercurial > hg > orthanc
annotate OrthancServer/ResourceFinder.cpp @ 1724:7e0b5e413c7c db-changes
C-Move SCP for studies using AccessionNumber tag
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Tue, 20 Oct 2015 11:02:06 +0200 |
parents | 21d31da73374 |
children | 1ae29c5e52fb |
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 |
1683 | 200 bool hasTagsAtThisLevel = query.HasMainDicomTagsFilter(level_); |
201 bool hasTagsAtPatientLevel = (filterPatientTagsAtStudyLevel && | |
202 level_ == ResourceType_Study && | |
203 query.HasMainDicomTagsFilter(ResourceType_Patient)); | |
204 | |
205 if (!hasTagsAtThisLevel && !hasTagsAtPatientLevel) | |
1359 | 206 { |
207 return; | |
1358 | 208 } |
209 | |
1359 | 210 std::list<std::string> resources; |
211 Flatten(resources); | |
212 | |
213 isFilterApplied_ = true; | |
214 filtered_.clear(); | |
215 | |
216 for (std::list<std::string>::const_iterator | |
1384 | 217 it = resources.begin(); it != resources.end(); ++it) |
1359 | 218 { |
219 DicomMap mainTags; | |
1683 | 220 |
221 if (hasTagsAtThisLevel && | |
222 (!index_.GetMainDicomTags(mainTags, *it, level_, level_) || | |
223 !query.FilterMainDicomTags(*it, level_, mainTags))) | |
1359 | 224 { |
1677
a903d57d9f0c
adaptation of search with patient tags at study level
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
1486
diff
changeset
|
225 continue; |
a903d57d9f0c
adaptation of search with patient tags at study level
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
1486
diff
changeset
|
226 } |
a903d57d9f0c
adaptation of search with patient tags at study level
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
1486
diff
changeset
|
227 |
1683 | 228 if (hasTagsAtPatientLevel && |
229 (!index_.GetMainDicomTags(mainTags, *it, ResourceType_Study, ResourceType_Patient) || | |
230 !query.FilterMainDicomTags(*it, ResourceType_Patient, mainTags))) | |
1677
a903d57d9f0c
adaptation of search with patient tags at study level
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
1486
diff
changeset
|
231 { |
1683 | 232 continue; |
1359 | 233 } |
1677
a903d57d9f0c
adaptation of search with patient tags at study level
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
1486
diff
changeset
|
234 |
a903d57d9f0c
adaptation of search with patient tags at study level
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
1486
diff
changeset
|
235 filtered_.insert(*it); |
1359 | 236 } |
237 } | |
238 }; | |
239 | |
240 | |
1360 | 241 ResourceFinder::ResourceFinder(ServerContext& context) : |
1359 | 242 context_(context), |
243 maxResults_(0) | |
244 { | |
245 } | |
246 | |
247 | |
1360 | 248 void ResourceFinder::ApplyAtLevel(CandidateResources& candidates, |
249 const IQuery& query, | |
250 ResourceType level) | |
1359 | 251 { |
252 if (level != ResourceType_Patient) | |
253 { | |
254 candidates.GoDown(); | |
255 } | |
256 | |
1360 | 257 switch (level) |
1359 | 258 { |
259 case ResourceType_Patient: | |
260 { | |
1360 | 261 candidates.RestrictIdentifier(query, DICOM_TAG_PATIENT_ID); |
1359 | 262 break; |
263 } | |
264 | |
265 case ResourceType_Study: | |
266 { | |
1360 | 267 candidates.RestrictIdentifier(query, DICOM_TAG_STUDY_INSTANCE_UID); |
268 candidates.RestrictIdentifier(query, DICOM_TAG_ACCESSION_NUMBER); | |
1359 | 269 break; |
270 } | |
271 | |
272 case ResourceType_Series: | |
273 { | |
1360 | 274 candidates.RestrictIdentifier(query, DICOM_TAG_SERIES_INSTANCE_UID); |
1359 | 275 break; |
276 } | |
277 | |
278 case ResourceType_Instance: | |
279 { | |
1360 | 280 candidates.RestrictIdentifier(query, DICOM_TAG_SOP_INSTANCE_UID); |
1359 | 281 break; |
282 } | |
283 | |
284 default: | |
285 throw OrthancException(ErrorCode_InternalError); | |
286 } | |
287 | |
1677
a903d57d9f0c
adaptation of search with patient tags at study level
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
1486
diff
changeset
|
288 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
|
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, false); |
a903d57d9f0c
adaptation of search with patient tags at study level
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
1486
diff
changeset
|
291 } |
a903d57d9f0c
adaptation of search with patient tags at study level
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
1486
diff
changeset
|
292 else |
a903d57d9f0c
adaptation of search with patient tags at study level
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
1486
diff
changeset
|
293 { |
a903d57d9f0c
adaptation of search with patient tags at study level
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
1486
diff
changeset
|
294 candidates.RestrictMainDicomTags(query, true); |
a903d57d9f0c
adaptation of search with patient tags at study level
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
1486
diff
changeset
|
295 } |
1359 | 296 } |
297 | |
298 | |
299 | |
300 static bool LookupOneInstance(std::string& result, | |
301 ServerIndex& index, | |
302 const std::string& id, | |
303 ResourceType type) | |
304 { | |
305 if (type == ResourceType_Instance) | |
306 { | |
307 result = id; | |
308 return true; | |
309 } | |
310 | |
311 std::string childId; | |
1358 | 312 |
1359 | 313 { |
314 std::list<std::string> children; | |
315 index.GetChildInstances(children, id); | |
316 | |
317 if (children.empty()) | |
318 { | |
319 return false; | |
320 } | |
321 | |
322 childId = children.front(); | |
323 } | |
324 | |
325 return LookupOneInstance(result, index, childId, GetChildResourceType(type)); | |
1358 | 326 } |
327 | |
1359 | 328 |
1360 | 329 bool ResourceFinder::Apply(std::list<std::string>& result, |
330 const IQuery& query) | |
1359 | 331 { |
332 CandidateResources candidates(*this); | |
333 | |
1360 | 334 ApplyAtLevel(candidates, query, ResourceType_Patient); |
335 | |
336 const ResourceType level = query.GetLevel(); | |
1359 | 337 |
1360 | 338 if (level == ResourceType_Study || |
339 level == ResourceType_Series || | |
340 level == ResourceType_Instance) | |
1359 | 341 { |
1360 | 342 ApplyAtLevel(candidates, query, ResourceType_Study); |
1359 | 343 } |
344 | |
1360 | 345 if (level == ResourceType_Series || |
346 level == ResourceType_Instance) | |
1359 | 347 { |
1360 | 348 ApplyAtLevel(candidates, query, ResourceType_Series); |
1359 | 349 } |
350 | |
1360 | 351 if (level == ResourceType_Instance) |
1359 | 352 { |
1360 | 353 ApplyAtLevel(candidates, query, ResourceType_Instance); |
1359 | 354 } |
355 | |
1360 | 356 if (!query.HasInstanceFilter()) |
1359 | 357 { |
358 candidates.Flatten(result); | |
359 | |
360 if (maxResults_ != 0 && | |
361 result.size() >= maxResults_) | |
362 { | |
363 result.resize(maxResults_); | |
364 return false; | |
365 } | |
366 else | |
367 { | |
368 return true; | |
369 } | |
370 } | |
371 else | |
372 { | |
373 std::list<std::string> tmp; | |
374 candidates.Flatten(tmp); | |
375 | |
376 result.clear(); | |
377 for (std::list<std::string>::const_iterator | |
378 resource = tmp.begin(); resource != tmp.end(); ++resource) | |
379 { | |
380 try | |
381 { | |
382 std::string instance; | |
1360 | 383 if (LookupOneInstance(instance, context_.GetIndex(), *resource, level)) |
1359 | 384 { |
385 Json::Value content; | |
386 context_.ReadJson(content, instance); | |
1360 | 387 if (query.FilterInstance(*resource, content)) |
1359 | 388 { |
389 result.push_back(*resource); | |
390 | |
391 if (maxResults_ != 0 && | |
392 result.size() >= maxResults_) | |
393 { | |
394 // Too many results, stop before recording this new match | |
395 return false; | |
396 } | |
397 } | |
398 } | |
399 } | |
400 catch (OrthancException&) | |
401 { | |
402 // This resource has been deleted since the search was started | |
403 } | |
404 } | |
405 } | |
406 | |
407 return true; // All the matching resources have been returned | |
408 } | |
1358 | 409 } |