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,