Mercurial > hg > orthanc
comparison OrthancServer/OrthancFindRequestHandler.cpp @ 1364:111e23bb4904 query-retrieve
integration mainline->query-retrieve
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Thu, 21 May 2015 16:58:30 +0200 |
parents | c2c28dd17e87 94ffb597d297 |
children | 5c11c4e728eb |
comparison
equal
deleted
inserted
replaced
953:f894be6e7cc1 | 1364:111e23bb4904 |
---|---|
1 /** | 1 /** |
2 * Orthanc - A Lightweight, RESTful DICOM Store | 2 * Orthanc - A Lightweight, RESTful DICOM Store |
3 * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, | 3 * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics |
4 * Belgium | 4 * Department, University Hospital of Liege, Belgium |
5 * | 5 * |
6 * This program is free software: you can redistribute it and/or | 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 | 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 | 8 * published by the Free Software Foundation, either version 3 of the |
9 * License, or (at your option) any later version. | 9 * License, or (at your option) any later version. |
39 #include "../Core/DicomFormat/DicomArray.h" | 39 #include "../Core/DicomFormat/DicomArray.h" |
40 #include "ServerToolbox.h" | 40 #include "ServerToolbox.h" |
41 #include "OrthancInitialization.h" | 41 #include "OrthancInitialization.h" |
42 #include "FromDcmtkBridge.h" | 42 #include "FromDcmtkBridge.h" |
43 | 43 |
44 #include "ResourceFinder.h" | |
45 #include "DicomFindQuery.h" | |
46 | |
47 | |
44 namespace Orthanc | 48 namespace Orthanc |
45 { | 49 { |
46 static bool IsWildcard(const std::string& constraint) | |
47 { | |
48 return (constraint.find('-') != std::string::npos || | |
49 constraint.find('*') != std::string::npos || | |
50 constraint.find('\\') != std::string::npos || | |
51 constraint.find('?') != std::string::npos); | |
52 } | |
53 | |
54 static bool ApplyRangeConstraint(const std::string& value, | |
55 const std::string& constraint) | |
56 { | |
57 size_t separator = constraint.find('-'); | |
58 std::string lower, upper, v; | |
59 Toolbox::ToLowerCase(lower, constraint.substr(0, separator)); | |
60 Toolbox::ToLowerCase(upper, constraint.substr(separator + 1)); | |
61 Toolbox::ToLowerCase(v, value); | |
62 | |
63 if (lower.size() == 0 && upper.size() == 0) | |
64 { | |
65 return false; | |
66 } | |
67 | |
68 if (lower.size() == 0) | |
69 { | |
70 return v <= upper; | |
71 } | |
72 | |
73 if (upper.size() == 0) | |
74 { | |
75 return v >= lower; | |
76 } | |
77 | |
78 return (v >= lower && v <= upper); | |
79 } | |
80 | |
81 | |
82 static bool ApplyListConstraint(const std::string& value, | |
83 const std::string& constraint) | |
84 { | |
85 std::string v1; | |
86 Toolbox::ToLowerCase(v1, value); | |
87 | |
88 std::vector<std::string> items; | |
89 Toolbox::TokenizeString(items, constraint, '\\'); | |
90 | |
91 for (size_t i = 0; i < items.size(); i++) | |
92 { | |
93 std::string lower; | |
94 Toolbox::ToLowerCase(lower, items[i]); | |
95 if (lower == v1) | |
96 { | |
97 return true; | |
98 } | |
99 } | |
100 | |
101 return false; | |
102 } | |
103 | |
104 | |
105 static bool Matches(const std::string& value, | |
106 const std::string& constraint) | |
107 { | |
108 // http://www.itk.org/Wiki/DICOM_QueryRetrieve_Explained | |
109 // http://dicomiseasy.blogspot.be/2012/01/dicom-queryretrieve-part-i.html | |
110 | |
111 if (constraint.find('-') != std::string::npos) | |
112 { | |
113 return ApplyRangeConstraint(value, constraint); | |
114 } | |
115 | |
116 if (constraint.find('\\') != std::string::npos) | |
117 { | |
118 return ApplyListConstraint(value, constraint); | |
119 } | |
120 | |
121 if (constraint.find('*') != std::string::npos || | |
122 constraint.find('?') != std::string::npos) | |
123 { | |
124 // TODO - Cache the constructed regular expression | |
125 boost::regex pattern(Toolbox::WildcardToRegularExpression(constraint), | |
126 boost::regex::icase /* case insensitive search */); | |
127 return boost::regex_match(value, pattern); | |
128 } | |
129 else | |
130 { | |
131 std::string v, c; | |
132 Toolbox::ToLowerCase(v, value); | |
133 Toolbox::ToLowerCase(c, constraint); | |
134 return v == c; | |
135 } | |
136 } | |
137 | |
138 | |
139 static bool LookupOneInstance(std::string& result, | |
140 ServerIndex& index, | |
141 const std::string& id, | |
142 ResourceType type) | |
143 { | |
144 if (type == ResourceType_Instance) | |
145 { | |
146 result = id; | |
147 return true; | |
148 } | |
149 | |
150 std::string childId; | |
151 | |
152 { | |
153 std::list<std::string> children; | |
154 index.GetChildInstances(children, id); | |
155 | |
156 if (children.empty()) | |
157 { | |
158 return false; | |
159 } | |
160 | |
161 childId = children.front(); | |
162 } | |
163 | |
164 return LookupOneInstance(result, index, childId, GetChildResourceType(type)); | |
165 } | |
166 | |
167 | |
168 static bool Matches(const Json::Value& resource, | |
169 const DicomArray& query) | |
170 { | |
171 for (size_t i = 0; i < query.GetSize(); i++) | |
172 { | |
173 if (query.GetElement(i).GetValue().IsNull() || | |
174 query.GetElement(i).GetTag() == DICOM_TAG_QUERY_RETRIEVE_LEVEL || | |
175 query.GetElement(i).GetTag() == DICOM_TAG_SPECIFIC_CHARACTER_SET || | |
176 query.GetElement(i).GetTag() == DICOM_TAG_MODALITIES_IN_STUDY) | |
177 { | |
178 continue; | |
179 } | |
180 | |
181 std::string tag = query.GetElement(i).GetTag().Format(); | |
182 std::string value; | |
183 if (resource.isMember(tag)) | |
184 { | |
185 value = resource.get(tag, Json::arrayValue).get("Value", "").asString(); | |
186 } | |
187 | |
188 if (!Matches(value, query.GetElement(i).GetValue().AsString())) | |
189 { | |
190 return false; | |
191 } | |
192 } | |
193 | |
194 return true; | |
195 } | |
196 | |
197 | |
198 static void AddAnswer(DicomFindAnswers& answers, | 50 static void AddAnswer(DicomFindAnswers& answers, |
199 const Json::Value& resource, | 51 const Json::Value& resource, |
200 const DicomArray& query) | 52 const DicomArray& query) |
201 { | 53 { |
202 DicomMap result; | 54 DicomMap result; |
203 | 55 |
204 for (size_t i = 0; i < query.GetSize(); i++) | 56 for (size_t i = 0; i < query.GetSize(); i++) |
205 { | 57 { |
206 if (query.GetElement(i).GetTag() != DICOM_TAG_QUERY_RETRIEVE_LEVEL && | 58 // Fix issue 30 (QR response missing "Query/Retrieve Level" (008,0052)) |
207 query.GetElement(i).GetTag() != DICOM_TAG_SPECIFIC_CHARACTER_SET) | 59 if (query.GetElement(i).GetTag() == DICOM_TAG_QUERY_RETRIEVE_LEVEL) |
60 { | |
61 result.SetValue(query.GetElement(i).GetTag(), query.GetElement(i).GetValue()); | |
62 } | |
63 else if (query.GetElement(i).GetTag() == DICOM_TAG_SPECIFIC_CHARACTER_SET) | |
64 { | |
65 } | |
66 else | |
208 { | 67 { |
209 std::string tag = query.GetElement(i).GetTag().Format(); | 68 std::string tag = query.GetElement(i).GetTag().Format(); |
210 std::string value; | 69 std::string value; |
211 if (resource.isMember(tag)) | 70 if (resource.isMember(tag)) |
212 { | 71 { |
218 result.SetValue(query.GetElement(i).GetTag(), ""); | 77 result.SetValue(query.GetElement(i).GetTag(), ""); |
219 } | 78 } |
220 } | 79 } |
221 } | 80 } |
222 | 81 |
223 answers.Add(result); | 82 if (result.GetSize() == 0) |
83 { | |
84 LOG(WARNING) << "The C-FIND request does not return any DICOM tag"; | |
85 } | |
86 else | |
87 { | |
88 answers.Add(result); | |
89 } | |
224 } | 90 } |
225 | 91 |
226 | 92 |
227 static bool ApplyModalitiesInStudyFilter(std::list<std::string>& filteredStudies, | 93 namespace |
228 const std::list<std::string>& studies, | |
229 const DicomMap& input, | |
230 ServerIndex& index) | |
231 { | 94 { |
232 filteredStudies.clear(); | 95 class CFindQuery : public DicomFindQuery |
233 | 96 { |
234 const DicomValue& v = input.GetValue(DICOM_TAG_MODALITIES_IN_STUDY); | 97 private: |
235 if (v.IsNull()) | 98 DicomFindAnswers& answers_; |
236 { | 99 ServerIndex& index_; |
237 return false; | 100 const DicomArray& query_; |
238 } | 101 bool hasModalitiesInStudy_; |
239 | 102 std::set<std::string> modalitiesInStudy_; |
240 // Move the allowed modalities into a "std::set" | 103 |
241 std::vector<std::string> tmp; | 104 public: |
242 Toolbox::TokenizeString(tmp, v.AsString(), '\\'); | 105 CFindQuery(DicomFindAnswers& answers, |
243 | 106 ServerIndex& index, |
244 std::set<std::string> modalities; | 107 const DicomArray& query) : |
245 for (size_t i = 0; i < tmp.size(); i++) | 108 answers_(answers), |
246 { | 109 index_(index), |
247 modalities.insert(tmp[i]); | 110 query_(query), |
248 } | 111 hasModalitiesInStudy_(false) |
249 | 112 { |
250 // Loop over the studies | 113 } |
251 for (std::list<std::string>::const_iterator | 114 |
252 it = studies.begin(); it != studies.end(); ++it) | 115 void SetModalitiesInStudy(const std::string& value) |
253 { | 116 { |
254 try | 117 hasModalitiesInStudy_ = true; |
255 { | 118 |
256 // We are considering a single study. Check whether one of | 119 std::vector<std::string> tmp; |
257 // its child series matches one of the modalities. | 120 Toolbox::TokenizeString(tmp, value, '\\'); |
258 Json::Value study; | 121 |
259 if (index.LookupResource(study, *it, ResourceType_Study)) | 122 for (size_t i = 0; i < tmp.size(); i++) |
260 { | 123 { |
261 // Loop over the series of the considered study. | 124 modalitiesInStudy_.insert(tmp[i]); |
262 for (Json::Value::ArrayIndex j = 0; j < study["Series"].size(); j++) // (*) | 125 } |
126 } | |
127 | |
128 virtual bool HasMainDicomTagsFilter(ResourceType level) const | |
129 { | |
130 if (DicomFindQuery::HasMainDicomTagsFilter(level)) | |
131 { | |
132 return true; | |
133 } | |
134 | |
135 return (level == ResourceType_Study && | |
136 hasModalitiesInStudy_); | |
137 } | |
138 | |
139 virtual bool FilterMainDicomTags(const std::string& resourceId, | |
140 ResourceType level, | |
141 const DicomMap& mainTags) const | |
142 { | |
143 if (!DicomFindQuery::FilterMainDicomTags(resourceId, level, mainTags)) | |
144 { | |
145 return false; | |
146 } | |
147 | |
148 if (level != ResourceType_Study || | |
149 !hasModalitiesInStudy_) | |
150 { | |
151 return true; | |
152 } | |
153 | |
154 try | |
155 { | |
156 // We are considering a single study, and the | |
157 // "MODALITIES_IN_STUDY" tag is set in the C-Find. Check | |
158 // whether one of its child series matches one of the | |
159 // modalities. | |
160 | |
161 Json::Value study; | |
162 if (index_.LookupResource(study, resourceId, ResourceType_Study)) | |
263 { | 163 { |
264 Json::Value series; | 164 // Loop over the series of the considered study. |
265 if (index.LookupResource(series, study["Series"][j].asString(), ResourceType_Series)) | 165 for (Json::Value::ArrayIndex j = 0; j < study["Series"].size(); j++) |
266 { | 166 { |
267 // Get the modality of this series | 167 Json::Value series; |
268 if (series["MainDicomTags"].isMember("Modality")) | 168 if (index_.LookupResource(series, study["Series"][j].asString(), ResourceType_Series)) |
269 { | 169 { |
270 std::string modality = series["MainDicomTags"]["Modality"].asString(); | 170 // Get the modality of this series |
271 if (modalities.find(modality) != modalities.end()) | 171 if (series["MainDicomTags"].isMember("Modality")) |
272 { | 172 { |
273 // This series of the considered study matches one | 173 std::string modality = series["MainDicomTags"]["Modality"].asString(); |
274 // of the required modalities. Take the study into | 174 if (modalitiesInStudy_.find(modality) != modalitiesInStudy_.end()) |
275 // consideration for future filtering. | 175 { |
276 filteredStudies.push_back(*it); | 176 // This series of the considered study matches one |
277 | 177 // of the required modalities. Take the study into |
278 // We have finished considering this study. Break the study loop at (*). | 178 // consideration for future filtering. |
279 break; | 179 return true; |
180 } | |
280 } | 181 } |
281 } | 182 } |
282 } | 183 } |
283 } | 184 } |
284 } | 185 } |
285 } | 186 catch (OrthancException&) |
286 catch (OrthancException&) | 187 { |
287 { | 188 // This resource has probably been deleted during the find request |
288 // This resource has probably been deleted during the find request | 189 } |
289 } | 190 |
290 } | 191 return false; |
291 | 192 } |
292 return true; | 193 |
293 } | 194 virtual bool HasInstanceFilter() const |
294 | 195 { |
295 | 196 return true; |
296 namespace | 197 } |
297 { | 198 |
298 class CandidateResources | 199 virtual bool FilterInstance(const std::string& instanceId, |
299 { | 200 const Json::Value& content) const |
300 private: | 201 { |
301 ServerIndex& index_; | 202 bool ok = DicomFindQuery::FilterInstance(instanceId, content); |
302 ModalityManufacturer manufacturer_; | 203 |
303 ResourceType level_; | 204 if (ok) |
304 bool isFilterApplied_; | 205 { |
305 std::set<std::string> filtered_; | 206 // Add this resource to the answers |
306 | 207 AddAnswer(answers_, content, query_); |
307 static void ListToSet(std::set<std::string>& target, | 208 } |
308 const std::list<std::string>& source) | 209 |
309 { | 210 return ok; |
310 for (std::list<std::string>::const_iterator | |
311 it = source.begin(); it != source.end(); ++it) | |
312 { | |
313 target.insert(*it); | |
314 } | |
315 } | |
316 | |
317 void ApplyExactFilter(const DicomTag& tag, const std::string& value) | |
318 { | |
319 LOG(INFO) << "Applying exact filter on tag " | |
320 << FromDcmtkBridge::GetName(tag) << " (value: " << value << ")"; | |
321 | |
322 std::list<std::string> resources; | |
323 index_.LookupTagValue(resources, tag, value, level_); | |
324 | |
325 if (isFilterApplied_) | |
326 { | |
327 std::set<std::string> s; | |
328 ListToSet(s, resources); | |
329 | |
330 std::set<std::string> tmp = filtered_; | |
331 filtered_.clear(); | |
332 | |
333 for (std::set<std::string>::const_iterator | |
334 it = tmp.begin(); it != tmp.end(); ++it) | |
335 { | |
336 if (s.find(*it) != s.end()) | |
337 { | |
338 filtered_.insert(*it); | |
339 } | |
340 } | |
341 } | |
342 else | |
343 { | |
344 assert(filtered_.empty()); | |
345 isFilterApplied_ = true; | |
346 ListToSet(filtered_, resources); | |
347 } | |
348 } | |
349 | |
350 public: | |
351 CandidateResources(ServerIndex& index, | |
352 ModalityManufacturer manufacturer) : | |
353 index_(index), | |
354 manufacturer_(manufacturer), | |
355 level_(ResourceType_Patient), | |
356 isFilterApplied_(false) | |
357 { | |
358 } | |
359 | |
360 ResourceType GetLevel() const | |
361 { | |
362 return level_; | |
363 } | |
364 | |
365 void GoDown() | |
366 { | |
367 assert(level_ != ResourceType_Instance); | |
368 | |
369 if (isFilterApplied_) | |
370 { | |
371 std::set<std::string> tmp = filtered_; | |
372 | |
373 filtered_.clear(); | |
374 | |
375 for (std::set<std::string>::const_iterator | |
376 it = tmp.begin(); it != tmp.end(); ++it) | |
377 { | |
378 std::list<std::string> children; | |
379 index_.GetChildren(children, *it); | |
380 ListToSet(filtered_, children); | |
381 } | |
382 } | |
383 | |
384 switch (level_) | |
385 { | |
386 case ResourceType_Patient: | |
387 level_ = ResourceType_Study; | |
388 break; | |
389 | |
390 case ResourceType_Study: | |
391 level_ = ResourceType_Series; | |
392 break; | |
393 | |
394 case ResourceType_Series: | |
395 level_ = ResourceType_Instance; | |
396 break; | |
397 | |
398 default: | |
399 throw OrthancException(ErrorCode_InternalError); | |
400 } | |
401 } | |
402 | |
403 void Flatten(std::list<std::string>& resources) const | |
404 { | |
405 resources.clear(); | |
406 | |
407 if (isFilterApplied_) | |
408 { | |
409 for (std::set<std::string>::const_iterator | |
410 it = filtered_.begin(); it != filtered_.end(); ++it) | |
411 { | |
412 resources.push_back(*it); | |
413 } | |
414 } | |
415 else | |
416 { | |
417 Json::Value tmp; | |
418 index_.GetAllUuids(tmp, level_); | |
419 for (Json::Value::ArrayIndex i = 0; i < tmp.size(); i++) | |
420 { | |
421 resources.push_back(tmp[i].asString()); | |
422 } | |
423 } | |
424 } | |
425 | |
426 void ApplyFilter(const DicomTag& tag, const DicomMap& query) | |
427 { | |
428 if (query.HasTag(tag)) | |
429 { | |
430 const DicomValue& value = query.GetValue(tag); | |
431 if (!value.IsNull()) | |
432 { | |
433 std::string value = query.GetValue(tag).AsString(); | |
434 if (!IsWildcard(value)) | |
435 { | |
436 ApplyExactFilter(tag, value); | |
437 } | |
438 } | |
439 } | |
440 } | 211 } |
441 }; | 212 }; |
442 } | 213 } |
443 | 214 |
444 | |
445 bool OrthancFindRequestHandler::HasReachedLimit(const DicomFindAnswers& answers, | |
446 ResourceType level) const | |
447 { | |
448 switch (level) | |
449 { | |
450 case ResourceType_Patient: | |
451 case ResourceType_Study: | |
452 case ResourceType_Series: | |
453 return (maxResults_ != 0 && answers.GetSize() >= maxResults_); | |
454 | |
455 case ResourceType_Instance: | |
456 return (maxInstances_ != 0 && answers.GetSize() >= maxInstances_); | |
457 | |
458 default: | |
459 throw OrthancException(ErrorCode_InternalError); | |
460 } | |
461 } | |
462 | 215 |
463 | 216 |
464 bool OrthancFindRequestHandler::Handle(DicomFindAnswers& answers, | 217 bool OrthancFindRequestHandler::Handle(DicomFindAnswers& answers, |
465 const DicomMap& input, | 218 const DicomMap& input, |
466 const std::string& callingAETitle) | 219 const std::string& callingAETitle) |
467 { | 220 { |
468 /** | 221 /** |
469 * Retrieve the manufacturer of this modality. | 222 * Ensure that the calling modality is known to Orthanc. |
470 **/ | 223 **/ |
471 | 224 |
472 ModalityManufacturer manufacturer; | 225 RemoteModalityParameters modality; |
473 | 226 |
474 { | 227 if (!Configuration::LookupDicomModalityUsingAETitle(modality, callingAETitle)) |
475 RemoteModalityParameters modality; | 228 { |
476 | 229 throw OrthancException("Unknown modality"); |
477 if (!Configuration::LookupDicomModalityUsingAETitle(modality, callingAETitle)) | 230 } |
478 { | 231 |
479 throw OrthancException("Unknown modality"); | 232 // ModalityManufacturer manufacturer = modality.GetManufacturer(); |
480 } | |
481 | |
482 manufacturer = modality.GetManufacturer(); | |
483 } | |
484 | 233 |
485 | 234 |
486 /** | 235 /** |
487 * Retrieve the query level. | 236 * Retrieve the query level. |
488 **/ | 237 **/ |
517 } | 266 } |
518 } | 267 } |
519 | 268 |
520 | 269 |
521 /** | 270 /** |
522 * Retrieve the candidate resources for this query level. Whenever | 271 * Build up the query object. |
523 * possible, we avoid returning ALL the resources for this query | |
524 * level, as it would imply reading the JSON file on the harddisk | |
525 * for each of them. | |
526 **/ | 272 **/ |
527 | 273 |
528 CandidateResources candidates(context_.GetIndex(), manufacturer); | 274 CFindQuery findQuery(answers, context_.GetIndex(), query); |
529 | 275 findQuery.SetLevel(level); |
530 for (;;) | 276 |
531 { | 277 for (size_t i = 0; i < query.GetSize(); i++) |
532 switch (candidates.GetLevel()) | 278 { |
533 { | 279 const DicomTag tag = query.GetElement(i).GetTag(); |
534 case ResourceType_Patient: | 280 |
535 candidates.ApplyFilter(DICOM_TAG_PATIENT_ID, input); | 281 if (query.GetElement(i).GetValue().IsNull() || |
536 break; | 282 tag == DICOM_TAG_QUERY_RETRIEVE_LEVEL || |
537 | 283 tag == DICOM_TAG_SPECIFIC_CHARACTER_SET) |
538 case ResourceType_Study: | 284 { |
539 candidates.ApplyFilter(DICOM_TAG_STUDY_INSTANCE_UID, input); | 285 continue; |
540 candidates.ApplyFilter(DICOM_TAG_ACCESSION_NUMBER, input); | 286 } |
541 break; | 287 |
542 | 288 std::string value = query.GetElement(i).GetValue().AsString(); |
543 case ResourceType_Series: | 289 |
544 candidates.ApplyFilter(DICOM_TAG_SERIES_INSTANCE_UID, input); | 290 if (tag == DICOM_TAG_MODALITIES_IN_STUDY) |
545 break; | 291 { |
546 | 292 findQuery.SetModalitiesInStudy(value); |
547 case ResourceType_Instance: | 293 } |
548 candidates.ApplyFilter(DICOM_TAG_SOP_INSTANCE_UID, input); | 294 else |
549 break; | 295 { |
550 | 296 findQuery.SetConstraint(tag, value); |
551 default: | 297 } |
552 throw OrthancException(ErrorCode_InternalError); | 298 } |
553 } | 299 |
554 | 300 |
555 if (candidates.GetLevel() == level) | 301 /** |
556 { | 302 * Run the query. |
303 **/ | |
304 | |
305 ResourceFinder finder(context_); | |
306 | |
307 switch (level) | |
308 { | |
309 case ResourceType_Patient: | |
310 case ResourceType_Study: | |
311 case ResourceType_Series: | |
312 finder.SetMaxResults(maxResults_); | |
557 break; | 313 break; |
558 } | 314 |
559 | 315 case ResourceType_Instance: |
560 candidates.GoDown(); | 316 finder.SetMaxResults(maxInstances_); |
561 } | 317 break; |
562 | 318 |
563 std::list<std::string> resources; | 319 default: |
564 candidates.Flatten(resources); | 320 throw OrthancException(ErrorCode_InternalError); |
565 | 321 } |
566 LOG(INFO) << "Number of candidate resources after exact filtering: " << resources.size(); | 322 |
567 | 323 std::list<std::string> tmp; |
568 /** | 324 bool finished = finder.Apply(tmp, findQuery); |
569 * Apply filtering on modalities for studies, if asked (this is an | 325 |
570 * extension to standard DICOM) | 326 LOG(INFO) << "Number of matching resources: " << tmp.size(); |
571 * http://www.medicalconnections.co.uk/kb/Filtering_on_and_Retrieving_the_Modality_in_a_C_FIND | 327 |
572 **/ | 328 return finished; |
573 | |
574 if (level == ResourceType_Study && | |
575 input.HasTag(DICOM_TAG_MODALITIES_IN_STUDY)) | |
576 { | |
577 std::list<std::string> filtered; | |
578 if (ApplyModalitiesInStudyFilter(filtered, resources, input, context_.GetIndex())) | |
579 { | |
580 resources = filtered; | |
581 } | |
582 } | |
583 | |
584 | |
585 /** | |
586 * Loop over all the resources for this query level. | |
587 **/ | |
588 | |
589 for (std::list<std::string>::const_iterator | |
590 resource = resources.begin(); resource != resources.end(); ++resource) | |
591 { | |
592 try | |
593 { | |
594 std::string instance; | |
595 if (LookupOneInstance(instance, context_.GetIndex(), *resource, level)) | |
596 { | |
597 Json::Value info; | |
598 context_.ReadJson(info, instance); | |
599 | |
600 if (Matches(info, query)) | |
601 { | |
602 if (HasReachedLimit(answers, level)) | |
603 { | |
604 // Too many results, stop before recording this new match | |
605 return false; | |
606 } | |
607 | |
608 AddAnswer(answers, info, query); | |
609 } | |
610 } | |
611 } | |
612 catch (OrthancException&) | |
613 { | |
614 // This resource has probably been deleted during the find request | |
615 } | |
616 } | |
617 | |
618 return true; // All the matching resources have been returned | |
619 } | 329 } |
620 } | 330 } |
621 | |
622 | |
623 | |
624 /** | |
625 * TODO : Case-insensitive match for PN value representation (Patient | |
626 * Name). Case-senstive match for all the other value representations. | |
627 * | |
628 * Reference: DICOM PS 3.4 | |
629 * - C.2.2.2.1 ("Single Value Matching") | |
630 * - C.2.2.2.4 ("Wild Card Matching") | |
631 * http://medical.nema.org/Dicom/2011/11_04pu.pdf ( | |
632 **/ |