Mercurial > hg > orthanc
comparison OrthancServer/OrthancFindRequestHandler.cpp @ 3015:abe49ca61cd5
On C-FIND, avoid accessing the storage area whenever possible
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Fri, 14 Dec 2018 12:10:03 +0100 |
parents | af1530b45290 |
children | 2cbafb5d5a62 |
comparison
equal
deleted
inserted
replaced
3014:b9f0b0c0b36f | 3015:abe49ca61cd5 |
---|---|
87 | 87 |
88 result.SetValue(tag, s, false); | 88 result.SetValue(tag, s, false); |
89 } | 89 } |
90 | 90 |
91 | 91 |
92 static void ExtractTagFromMainDicomTags(std::set<std::string>& target, | |
93 ServerIndex& index, | |
94 const DicomTag& tag, | |
95 const std::list<std::string>& resources, | |
96 ResourceType level) | |
97 { | |
98 for (std::list<std::string>::const_iterator | |
99 it = resources.begin(); it != resources.end(); ++it) | |
100 { | |
101 DicomMap tags; | |
102 if (index.GetMainDicomTags(tags, *it, level, level) && | |
103 tags.HasTag(tag)) | |
104 { | |
105 target.insert(tags.GetValue(tag).GetContent()); | |
106 } | |
107 } | |
108 } | |
109 | |
110 | |
111 static bool ExtractMetadata(std::set<std::string>& target, | |
112 ServerIndex& index, | |
113 MetadataType metadata, | |
114 const std::list<std::string>& resources) | |
115 { | |
116 for (std::list<std::string>::const_iterator | |
117 it = resources.begin(); it != resources.end(); ++it) | |
118 { | |
119 std::string value; | |
120 if (index.LookupMetadata(value, *it, metadata)) | |
121 { | |
122 target.insert(value); | |
123 } | |
124 else | |
125 { | |
126 // This metadata is unavailable for some resource, give up | |
127 return false; | |
128 } | |
129 } | |
130 | |
131 return true; | |
132 } | |
133 | |
134 | |
135 static void ExtractTagFromInstancesOnDisk(std::set<std::string>& target, | |
136 ServerContext& context, | |
137 const DicomTag& tag, | |
138 const std::list<std::string>& instances) | |
139 { | |
140 // WARNING: This function is slow, as it reads the JSON file | |
141 // summarizing each instance of interest from the hard drive. | |
142 | |
143 std::string formatted = tag.Format(); | |
144 | |
145 for (std::list<std::string>::const_iterator | |
146 it = instances.begin(); it != instances.end(); ++it) | |
147 { | |
148 Json::Value dicom; | |
149 context.ReadDicomAsJson(dicom, *it); | |
150 | |
151 if (dicom.isMember(formatted)) | |
152 { | |
153 const Json::Value& source = dicom[formatted]; | |
154 | |
155 if (source.type() == Json::objectValue && | |
156 source.isMember("Type") && | |
157 source.isMember("Value") && | |
158 source["Type"].asString() == "String" && | |
159 source["Value"].type() == Json::stringValue) | |
160 { | |
161 target.insert(source["Value"].asString()); | |
162 } | |
163 } | |
164 } | |
165 } | |
166 | |
167 | |
168 static void ComputePatientCounters(DicomMap& result, | 92 static void ComputePatientCounters(DicomMap& result, |
169 ServerIndex& index, | 93 ServerIndex& index, |
170 const std::string& patient, | 94 const std::string& patient, |
171 const DicomMap& query) | 95 const DicomMap& query) |
172 { | 96 { |
228 } | 152 } |
229 | 153 |
230 if (query.HasTag(DICOM_TAG_MODALITIES_IN_STUDY)) | 154 if (query.HasTag(DICOM_TAG_MODALITIES_IN_STUDY)) |
231 { | 155 { |
232 std::set<std::string> values; | 156 std::set<std::string> values; |
233 ExtractTagFromMainDicomTags(values, index, DICOM_TAG_MODALITY, series, ResourceType_Series); | 157 |
158 for (std::list<std::string>::const_iterator | |
159 it = series.begin(); it != series.end(); ++it) | |
160 { | |
161 DicomMap tags; | |
162 if (index.GetMainDicomTags(tags, *it, ResourceType_Series, ResourceType_Series)) | |
163 { | |
164 const DicomValue* value = tags.TestAndGetValue(DICOM_TAG_MODALITY); | |
165 | |
166 if (value != NULL && | |
167 !value->IsNull() && | |
168 !value->IsBinary()) | |
169 { | |
170 values.insert(value->GetContent()); | |
171 } | |
172 } | |
173 } | |
174 | |
234 StoreSetOfStrings(result, DICOM_TAG_MODALITIES_IN_STUDY, values); | 175 StoreSetOfStrings(result, DICOM_TAG_MODALITIES_IN_STUDY, values); |
235 } | 176 } |
236 | 177 |
237 if (!query.HasTag(DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES) && | 178 if (!query.HasTag(DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES) && |
238 !query.HasTag(DICOM_TAG_SOP_CLASSES_IN_STUDY)) | 179 !query.HasTag(DICOM_TAG_SOP_CLASSES_IN_STUDY)) |
251 | 192 |
252 if (query.HasTag(DICOM_TAG_SOP_CLASSES_IN_STUDY)) | 193 if (query.HasTag(DICOM_TAG_SOP_CLASSES_IN_STUDY)) |
253 { | 194 { |
254 std::set<std::string> values; | 195 std::set<std::string> values; |
255 | 196 |
256 if (ExtractMetadata(values, index, MetadataType_Instance_SopClassUid, instances)) | 197 for (std::list<std::string>::const_iterator |
257 { | 198 it = instances.begin(); it != instances.end(); ++it) |
258 // The metadata "SopClassUid" is available for each of these instances | 199 { |
259 StoreSetOfStrings(result, DICOM_TAG_SOP_CLASSES_IN_STUDY, values); | 200 std::string value; |
260 } | 201 if (context.LookupOrReconstructMetadata(value, *it, MetadataType_Instance_SopClassUid)) |
261 else | 202 { |
262 { | 203 values.insert(value); |
263 OrthancConfiguration::ReaderLock lock; | 204 } |
264 | 205 } |
265 if (lock.GetConfiguration().GetBooleanParameter("AllowFindSopClassesInStudy", false)) | 206 |
266 { | 207 StoreSetOfStrings(result, DICOM_TAG_SOP_CLASSES_IN_STUDY, values); |
267 ExtractTagFromInstancesOnDisk(values, context, DICOM_TAG_SOP_CLASS_UID, instances); | |
268 StoreSetOfStrings(result, DICOM_TAG_SOP_CLASSES_IN_STUDY, values); | |
269 } | |
270 else | |
271 { | |
272 result.SetValue(DICOM_TAG_SOP_CLASSES_IN_STUDY, "", false); | |
273 LOG(WARNING) << "The handling of \"SOP Classes in Study\" (0008,0062) " | |
274 << "in C-FIND requests is disabled"; | |
275 } | |
276 } | |
277 } | 208 } |
278 } | 209 } |
279 | 210 |
280 | 211 |
281 static void ComputeSeriesCounters(DicomMap& result, | 212 static void ComputeSeriesCounters(DicomMap& result, |
364 } | 295 } |
365 | 296 |
366 | 297 |
367 static void AddAnswer(DicomFindAnswers& answers, | 298 static void AddAnswer(DicomFindAnswers& answers, |
368 const DicomMap& mainDicomTags, | 299 const DicomMap& mainDicomTags, |
369 const Json::Value* dicomAsJson, // only used for sequences | 300 const Json::Value* dicomAsJson, |
370 const DicomArray& query, | 301 const DicomArray& query, |
371 const std::list<DicomTag>& sequencesToReturn, | 302 const std::list<DicomTag>& sequencesToReturn, |
372 const DicomMap* counters) | 303 const DicomMap* counters) |
373 { | 304 { |
305 DicomMap match; | |
306 | |
307 if (dicomAsJson != NULL) | |
308 { | |
309 match.FromDicomAsJson(*dicomAsJson); | |
310 } | |
311 else | |
312 { | |
313 match.Assign(mainDicomTags); | |
314 } | |
315 | |
374 DicomMap result; | 316 DicomMap result; |
375 | 317 |
376 for (size_t i = 0; i < query.GetSize(); i++) | 318 for (size_t i = 0; i < query.GetSize(); i++) |
377 { | 319 { |
378 if (query.GetElement(i).GetTag() == DICOM_TAG_QUERY_RETRIEVE_LEVEL) | 320 if (query.GetElement(i).GetTag() == DICOM_TAG_QUERY_RETRIEVE_LEVEL) |
382 } | 324 } |
383 else if (query.GetElement(i).GetTag() == DICOM_TAG_SPECIFIC_CHARACTER_SET) | 325 else if (query.GetElement(i).GetTag() == DICOM_TAG_SPECIFIC_CHARACTER_SET) |
384 { | 326 { |
385 // Do not include the encoding, this is handled by class ParsedDicomFile | 327 // Do not include the encoding, this is handled by class ParsedDicomFile |
386 } | 328 } |
387 else if (dicomAsJson != NULL) | 329 else |
388 { | 330 { |
389 std::string tag = query.GetElement(i).GetTag().Format(); | 331 const DicomTag& tag = query.GetElement(i).GetTag(); |
390 std::string value; | 332 const DicomValue* value = match.TestAndGetValue(tag); |
391 if (dicomAsJson->isMember(tag)) | 333 |
392 { | 334 if (value != NULL && |
393 value = dicomAsJson->get(tag, Json::arrayValue).get("Value", "").asString(); | 335 !value->IsNull() && |
394 result.SetValue(query.GetElement(i).GetTag(), value, false); | 336 !value->IsBinary()) |
337 { | |
338 result.SetValue(tag, value->GetContent(), false); | |
395 } | 339 } |
396 else | 340 else |
397 { | 341 { |
398 result.SetValue(query.GetElement(i).GetTag(), "", false); | 342 result.SetValue(tag, "", false); |
399 } | 343 } |
400 } | |
401 else | |
402 { | |
403 // Best-effort | |
404 // TODO | |
405 throw OrthancException(ErrorCode_NotImplemented); | |
406 } | 344 } |
407 } | 345 } |
408 | 346 |
409 if (counters != NULL) | 347 if (counters != NULL) |
410 { | 348 { |
418 if (result.GetSize() == 0 && | 356 if (result.GetSize() == 0 && |
419 sequencesToReturn.empty()) | 357 sequencesToReturn.empty()) |
420 { | 358 { |
421 LOG(WARNING) << "The C-FIND request does not return any DICOM tag"; | 359 LOG(WARNING) << "The C-FIND request does not return any DICOM tag"; |
422 } | 360 } |
423 else if (sequencesToReturn.empty() || | 361 else if (sequencesToReturn.empty()) |
424 dicomAsJson == NULL) | 362 { |
425 { | 363 answers.Add(result); |
364 } | |
365 else if (dicomAsJson == NULL) | |
366 { | |
367 LOG(WARNING) << "C-FIND query requesting a sequence, but reading JSON from disk is disabled"; | |
426 answers.Add(result); | 368 answers.Add(result); |
427 } | 369 } |
428 else | 370 else |
429 { | 371 { |
430 ParsedDicomFile dicom(result); | 372 ParsedDicomFile dicom(result); |
534 { | 476 { |
535 private: | 477 private: |
536 DicomFindAnswers& answers_; | 478 DicomFindAnswers& answers_; |
537 ServerContext& context_; | 479 ServerContext& context_; |
538 ResourceType level_; | 480 ResourceType level_; |
539 const DicomMap& filteredInput_; | 481 const DicomMap& query_; |
482 DicomArray queryAsArray_; | |
540 const std::list<DicomTag>& sequencesToReturn_; | 483 const std::list<DicomTag>& sequencesToReturn_; |
541 DicomArray query_; | |
542 | 484 |
543 public: | 485 public: |
544 LookupVisitor(DicomFindAnswers& answers, | 486 LookupVisitor(DicomFindAnswers& answers, |
545 ServerContext& context, | 487 ServerContext& context, |
546 ResourceType level, | 488 ResourceType level, |
547 const DicomMap& filteredInput, | 489 const DicomMap& query, |
548 const std::list<DicomTag>& sequencesToReturn) : | 490 const std::list<DicomTag>& sequencesToReturn) : |
549 answers_(answers), | 491 answers_(answers), |
550 context_(context), | 492 context_(context), |
551 level_(level), | 493 level_(level), |
552 filteredInput_(filteredInput), | 494 query_(query), |
553 sequencesToReturn_(sequencesToReturn), | 495 queryAsArray_(query), |
554 query_(filteredInput) | 496 sequencesToReturn_(sequencesToReturn) |
555 { | 497 { |
556 answers_.SetComplete(false); | 498 answers_.SetComplete(false); |
557 } | 499 } |
558 | 500 |
559 virtual bool IsDicomAsJsonNeeded() const | 501 virtual bool IsDicomAsJsonNeeded() const |
560 { | 502 { |
561 #if 1 | |
562 return true; | |
563 | |
564 #else | |
565 // TODO | |
566 | |
567 // Ask the "DICOM-as-JSON" attachment only if sequences are to | 503 // Ask the "DICOM-as-JSON" attachment only if sequences are to |
568 // be returned OR if "query_" contains non-main DICOM tags! | 504 // be returned OR if "query_" contains non-main DICOM tags! |
569 | 505 |
570 // TODO - configuration option | 506 DicomMap withoutSpecialTags; |
571 bool findFromDatabase; | 507 withoutSpecialTags.Assign(query_); |
508 | |
509 // Check out "ComputeCounters()" | |
510 withoutSpecialTags.Remove(DICOM_TAG_MODALITIES_IN_STUDY); | |
511 withoutSpecialTags.Remove(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_INSTANCES); | |
512 withoutSpecialTags.Remove(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_SERIES); | |
513 withoutSpecialTags.Remove(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_STUDIES); | |
514 withoutSpecialTags.Remove(DICOM_TAG_NUMBER_OF_SERIES_RELATED_INSTANCES); | |
515 withoutSpecialTags.Remove(DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES); | |
516 withoutSpecialTags.Remove(DICOM_TAG_NUMBER_OF_STUDY_RELATED_SERIES); | |
517 withoutSpecialTags.Remove(DICOM_TAG_SOP_CLASSES_IN_STUDY); | |
518 | |
519 // Check out "AddAnswer()" | |
520 withoutSpecialTags.Remove(DICOM_TAG_SPECIFIC_CHARACTER_SET); | |
521 withoutSpecialTags.Remove(DICOM_TAG_QUERY_RETRIEVE_LEVEL); | |
572 | 522 |
573 { | 523 return (!sequencesToReturn_.empty() || |
574 // New configuration option in 1.5.1 | 524 !withoutSpecialTags.HasOnlyMainDicomTags()); |
575 OrthancConfiguration::ReaderLock lock; | |
576 findFromDatabase = lock.GetConfiguration().GetUnsignedIntegerParameter("FindFromDatabase", false); | |
577 } | |
578 | |
579 return !sequencesToReturn_.empty(); | |
580 #endif | |
581 } | 525 } |
582 | 526 |
583 virtual void MarkAsComplete() | 527 virtual void MarkAsComplete() |
584 { | 528 { |
585 answers_.SetComplete(true); | 529 answers_.SetComplete(true); |
588 virtual void Visit(const std::string& publicId, | 532 virtual void Visit(const std::string& publicId, |
589 const std::string& instanceId, | 533 const std::string& instanceId, |
590 const DicomMap& mainDicomTags, | 534 const DicomMap& mainDicomTags, |
591 const Json::Value* dicomAsJson) | 535 const Json::Value* dicomAsJson) |
592 { | 536 { |
593 std::auto_ptr<DicomMap> counters(ComputeCounters(context_, instanceId, level_, filteredInput_)); | 537 std::auto_ptr<DicomMap> counters(ComputeCounters(context_, instanceId, level_, query_)); |
594 AddAnswer(answers_, mainDicomTags, dicomAsJson, query_, sequencesToReturn_, counters.get()); | 538 |
539 AddAnswer(answers_, mainDicomTags, dicomAsJson, | |
540 queryAsArray_, sequencesToReturn_, counters.get()); | |
595 } | 541 } |
596 }; | 542 }; |
597 | 543 |
598 | 544 |
599 void OrthancFindRequestHandler::Handle(DicomFindAnswers& answers, | 545 void OrthancFindRequestHandler::Handle(DicomFindAnswers& answers, |