Mercurial > hg > orthanc
annotate OrthancServer/ResourceFinder.cpp @ 1382:1cd2e09cb0e5
case insensitive search by default
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Fri, 29 May 2015 14:46:06 +0200 |
parents | 94ffb597d297 |
children | d6971e18a324 |
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 |
36 #include "FromDcmtkBridge.h" | |
37 #include "ServerContext.h" | |
38 | |
39 #include <glog/logging.h> | |
40 #include <boost/algorithm/string/predicate.hpp> | |
1358 | 41 |
42 namespace Orthanc | |
43 { | |
1360 | 44 class ResourceFinder::CandidateResources |
1358 | 45 { |
1359 | 46 private: |
47 typedef std::map<DicomTag, std::string> Query; | |
48 | |
1360 | 49 ResourceFinder& finder_; |
1359 | 50 ServerIndex& index_; |
51 ResourceType level_; | |
52 bool isFilterApplied_; | |
53 std::set<std::string> filtered_; | |
54 | |
55 | |
56 static void ListToSet(std::set<std::string>& target, | |
57 const std::list<std::string>& source) | |
58 { | |
59 for (std::list<std::string>::const_iterator | |
60 it = source.begin(); it != source.end(); ++it) | |
61 { | |
62 target.insert(*it); | |
63 } | |
64 } | |
65 | |
66 | |
67 public: | |
1360 | 68 CandidateResources(ResourceFinder& finder) : |
1359 | 69 finder_(finder), |
70 index_(finder.context_.GetIndex()), | |
71 level_(ResourceType_Patient), | |
72 isFilterApplied_(false) | |
73 { | |
74 } | |
75 | |
76 ResourceType GetLevel() const | |
1358 | 77 { |
1359 | 78 return level_; |
79 } | |
80 | |
81 void GoDown() | |
82 { | |
83 assert(level_ != ResourceType_Instance); | |
84 | |
85 if (isFilterApplied_) | |
86 { | |
87 std::set<std::string> tmp = filtered_; | |
88 | |
89 filtered_.clear(); | |
90 | |
91 for (std::set<std::string>::const_iterator | |
92 it = tmp.begin(); it != tmp.end(); ++it) | |
93 { | |
94 std::list<std::string> children; | |
95 try | |
96 { | |
97 index_.GetChildren(children, *it); | |
98 ListToSet(filtered_, children); | |
99 } | |
100 catch (OrthancException&) | |
101 { | |
102 // The resource was removed in the meantime | |
103 } | |
104 } | |
105 } | |
106 | |
107 switch (level_) | |
1358 | 108 { |
1359 | 109 case ResourceType_Patient: |
110 level_ = ResourceType_Study; | |
111 break; | |
112 | |
113 case ResourceType_Study: | |
114 level_ = ResourceType_Series; | |
115 break; | |
116 | |
117 case ResourceType_Series: | |
118 level_ = ResourceType_Instance; | |
119 break; | |
120 | |
121 default: | |
122 throw OrthancException(ErrorCode_InternalError); | |
123 } | |
124 } | |
125 | |
126 | |
127 void Flatten(std::list<std::string>& resources) const | |
128 { | |
129 resources.clear(); | |
130 | |
131 if (isFilterApplied_) | |
132 { | |
133 for (std::set<std::string>::const_iterator | |
134 it = filtered_.begin(); it != filtered_.end(); ++it) | |
135 { | |
136 resources.push_back(*it); | |
137 } | |
138 } | |
139 else | |
140 { | |
141 index_.GetAllUuids(resources, level_); | |
142 } | |
143 } | |
144 | |
145 | |
1360 | 146 void RestrictIdentifier(const IQuery& query, |
147 const DicomTag& tag) | |
1359 | 148 { |
1360 | 149 assert((level_ == ResourceType_Patient && tag == DICOM_TAG_PATIENT_ID) || |
150 (level_ == ResourceType_Study && tag == DICOM_TAG_STUDY_INSTANCE_UID) || | |
151 (level_ == ResourceType_Study && tag == DICOM_TAG_ACCESSION_NUMBER) || | |
152 (level_ == ResourceType_Series && tag == DICOM_TAG_SERIES_INSTANCE_UID) || | |
153 (level_ == ResourceType_Instance && tag == DICOM_TAG_SOP_INSTANCE_UID)); | |
154 | |
155 std::string value; | |
156 if (!query.RestrictIdentifier(value, tag)) | |
157 { | |
158 return; | |
159 } | |
160 | |
161 LOG(INFO) << "Lookup for identifier tag " | |
162 << FromDcmtkBridge::GetName(tag) << " (value: " << value << ")"; | |
163 | |
164 std::list<std::string> resources; | |
165 index_.LookupIdentifier(resources, tag, value, level_); | |
166 | |
167 if (isFilterApplied_) | |
1359 | 168 { |
1360 | 169 std::set<std::string> s; |
170 ListToSet(s, resources); | |
171 | |
172 std::set<std::string> tmp = filtered_; | |
173 filtered_.clear(); | |
174 | |
175 for (std::set<std::string>::const_iterator | |
176 it = tmp.begin(); it != tmp.end(); ++it) | |
177 { | |
178 if (s.find(*it) != s.end()) | |
179 { | |
180 filtered_.insert(*it); | |
181 } | |
182 } | |
183 } | |
184 else | |
185 { | |
186 assert(filtered_.empty()); | |
187 isFilterApplied_ = true; | |
188 ListToSet(filtered_, resources); | |
1359 | 189 } |
190 } | |
191 | |
192 | |
1360 | 193 void RestrictMainDicomTags(const IQuery& query) |
1359 | 194 { |
1360 | 195 if (!query.HasMainDicomTagsFilter(level_)) |
1359 | 196 { |
197 return; | |
1358 | 198 } |
199 | |
1359 | 200 std::list<std::string> resources; |
201 Flatten(resources); | |
202 | |
203 isFilterApplied_ = true; | |
204 filtered_.clear(); | |
205 | |
206 for (std::list<std::string>::const_iterator | |
207 it = resources.begin(); it != resources.end(); it++) | |
208 { | |
209 DicomMap mainTags; | |
210 if (index_.GetMainDicomTags(mainTags, *it, level_)) | |
211 { | |
1361
94ffb597d297
refactoring of C-Find SCP
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
1360
diff
changeset
|
212 if (query.FilterMainDicomTags(*it, level_, mainTags)) |
1359 | 213 { |
214 filtered_.insert(*it); | |
215 } | |
216 } | |
217 } | |
218 } | |
219 }; | |
220 | |
221 | |
1360 | 222 ResourceFinder::ResourceFinder(ServerContext& context) : |
1359 | 223 context_(context), |
224 maxResults_(0) | |
225 { | |
226 } | |
227 | |
228 | |
1360 | 229 void ResourceFinder::ApplyAtLevel(CandidateResources& candidates, |
230 const IQuery& query, | |
231 ResourceType level) | |
1359 | 232 { |
233 if (level != ResourceType_Patient) | |
234 { | |
235 candidates.GoDown(); | |
236 } | |
237 | |
1360 | 238 switch (level) |
1359 | 239 { |
240 case ResourceType_Patient: | |
241 { | |
1360 | 242 candidates.RestrictIdentifier(query, DICOM_TAG_PATIENT_ID); |
1359 | 243 break; |
244 } | |
245 | |
246 case ResourceType_Study: | |
247 { | |
1360 | 248 candidates.RestrictIdentifier(query, DICOM_TAG_STUDY_INSTANCE_UID); |
249 candidates.RestrictIdentifier(query, DICOM_TAG_ACCESSION_NUMBER); | |
1359 | 250 break; |
251 } | |
252 | |
253 case ResourceType_Series: | |
254 { | |
1360 | 255 candidates.RestrictIdentifier(query, DICOM_TAG_SERIES_INSTANCE_UID); |
1359 | 256 break; |
257 } | |
258 | |
259 case ResourceType_Instance: | |
260 { | |
1360 | 261 candidates.RestrictIdentifier(query, DICOM_TAG_SOP_INSTANCE_UID); |
1359 | 262 break; |
263 } | |
264 | |
265 default: | |
266 throw OrthancException(ErrorCode_InternalError); | |
267 } | |
268 | |
1360 | 269 candidates.RestrictMainDicomTags(query); |
1359 | 270 } |
271 | |
272 | |
273 | |
274 static bool LookupOneInstance(std::string& result, | |
275 ServerIndex& index, | |
276 const std::string& id, | |
277 ResourceType type) | |
278 { | |
279 if (type == ResourceType_Instance) | |
280 { | |
281 result = id; | |
282 return true; | |
283 } | |
284 | |
285 std::string childId; | |
1358 | 286 |
1359 | 287 { |
288 std::list<std::string> children; | |
289 index.GetChildInstances(children, id); | |
290 | |
291 if (children.empty()) | |
292 { | |
293 return false; | |
294 } | |
295 | |
296 childId = children.front(); | |
297 } | |
298 | |
299 return LookupOneInstance(result, index, childId, GetChildResourceType(type)); | |
1358 | 300 } |
301 | |
1359 | 302 |
1360 | 303 bool ResourceFinder::Apply(std::list<std::string>& result, |
304 const IQuery& query) | |
1359 | 305 { |
306 CandidateResources candidates(*this); | |
307 | |
1360 | 308 ApplyAtLevel(candidates, query, ResourceType_Patient); |
309 | |
310 const ResourceType level = query.GetLevel(); | |
1359 | 311 |
1360 | 312 if (level == ResourceType_Study || |
313 level == ResourceType_Series || | |
314 level == ResourceType_Instance) | |
1359 | 315 { |
1360 | 316 ApplyAtLevel(candidates, query, ResourceType_Study); |
1359 | 317 } |
318 | |
1360 | 319 if (level == ResourceType_Series || |
320 level == ResourceType_Instance) | |
1359 | 321 { |
1360 | 322 ApplyAtLevel(candidates, query, ResourceType_Series); |
1359 | 323 } |
324 | |
1360 | 325 if (level == ResourceType_Instance) |
1359 | 326 { |
1360 | 327 ApplyAtLevel(candidates, query, ResourceType_Instance); |
1359 | 328 } |
329 | |
1360 | 330 if (!query.HasInstanceFilter()) |
1359 | 331 { |
332 candidates.Flatten(result); | |
333 | |
334 if (maxResults_ != 0 && | |
335 result.size() >= maxResults_) | |
336 { | |
337 result.resize(maxResults_); | |
338 return false; | |
339 } | |
340 else | |
341 { | |
342 return true; | |
343 } | |
344 } | |
345 else | |
346 { | |
347 std::list<std::string> tmp; | |
348 candidates.Flatten(tmp); | |
349 | |
350 result.clear(); | |
351 for (std::list<std::string>::const_iterator | |
352 resource = tmp.begin(); resource != tmp.end(); ++resource) | |
353 { | |
354 try | |
355 { | |
356 std::string instance; | |
1360 | 357 if (LookupOneInstance(instance, context_.GetIndex(), *resource, level)) |
1359 | 358 { |
359 Json::Value content; | |
360 context_.ReadJson(content, instance); | |
1360 | 361 if (query.FilterInstance(*resource, content)) |
1359 | 362 { |
363 result.push_back(*resource); | |
364 | |
365 if (maxResults_ != 0 && | |
366 result.size() >= maxResults_) | |
367 { | |
368 // Too many results, stop before recording this new match | |
369 return false; | |
370 } | |
371 } | |
372 } | |
373 } | |
374 catch (OrthancException&) | |
375 { | |
376 // This resource has been deleted since the search was started | |
377 } | |
378 } | |
379 } | |
380 | |
381 return true; // All the matching resources have been returned | |
382 } | |
1358 | 383 } |