comparison OrthancServer/Sources/ResourceFinder.cpp @ 5609:4690a0d2b01e find-refactoring

preliminary support of requestedTags
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 08 May 2024 18:28:36 +0200
parents 3d0aa94b44b3
children d4b570834d3a
comparison
equal deleted inserted replaced
5608:3d0aa94b44b3 5609:4690a0d2b01e
22 22
23 #include "PrecompiledHeadersServer.h" 23 #include "PrecompiledHeadersServer.h"
24 #include "ResourceFinder.h" 24 #include "ResourceFinder.h"
25 25
26 #include "../../OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h" 26 #include "../../OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h"
27 #include "../../OrthancFramework/Sources/Logging.h"
27 #include "../../OrthancFramework/Sources/OrthancException.h" 28 #include "../../OrthancFramework/Sources/OrthancException.h"
28 #include "../../OrthancFramework/Sources/SerializationToolbox.h" 29 #include "../../OrthancFramework/Sources/SerializationToolbox.h"
30 #include "OrthancConfiguration.h"
29 #include "ServerContext.h" 31 #include "ServerContext.h"
30 #include "ServerIndex.h" 32 #include "ServerIndex.h"
31 33
32 34
33 namespace Orthanc 35 namespace Orthanc
90 return SeriesStatus_Missing; 92 return SeriesStatus_Missing;
91 } 93 }
92 } 94 }
93 95
94 96
97 static void InjectRequestedTags(DicomMap& requestedTags,
98 std::set<DicomTag>& missingTags /* out */,
99 const FindResponse::Resource& resource,
100 ResourceType level,
101 const std::set<DicomTag>& tags)
102 {
103 if (!tags.empty())
104 {
105 DicomMap m;
106 resource.GetMainDicomTags(m, level);
107
108 for (std::set<DicomTag>::const_iterator it = tags.begin(); it != tags.end(); ++it)
109 {
110 std::string value;
111 if (m.LookupStringValue(value, *it, false /* not binary */))
112 {
113 requestedTags.SetValue(*it, value, false /* not binary */);
114 }
115 else
116 {
117 // This is the case where the Housekeeper should be run
118 missingTags.insert(*it);
119 }
120 }
121 }
122 }
123
124
95 void ResourceFinder::Expand(Json::Value& target, 125 void ResourceFinder::Expand(Json::Value& target,
96 const FindResponse::Resource& resource, 126 const FindResponse::Resource& resource,
97 ServerIndex& index) const 127 ServerIndex& index) const
98 { 128 {
99 /** 129 /**
104 if (resource.GetLevel() != request_.GetLevel()) 134 if (resource.GetLevel() != request_.GetLevel())
105 { 135 {
106 throw OrthancException(ErrorCode_InternalError); 136 throw OrthancException(ErrorCode_InternalError);
107 } 137 }
108 138
109 if (!requestedTags_.empty())
110 {
111 throw OrthancException(ErrorCode_NotImplemented);
112 }
113
114 target = Json::objectValue; 139 target = Json::objectValue;
115 140
116 target["Type"] = GetResourceTypeText(resource.GetLevel(), false, true); 141 target["Type"] = GetResourceTypeText(resource.GetLevel(), false, true);
117 target["ID"] = resource.GetIdentifier(); 142 target["ID"] = resource.GetIdentifier();
118 143
178 uint32_t expectedNumberOfInstances; 203 uint32_t expectedNumberOfInstances;
179 SeriesStatus status = GetSeriesStatus(expectedNumberOfInstances, resource); 204 SeriesStatus status = GetSeriesStatus(expectedNumberOfInstances, resource);
180 205
181 target["Status"] = EnumerationToString(status); 206 target["Status"] = EnumerationToString(status);
182 207
183 static const char* EXPECTED_NUMBER_OF_INSTANCES = "ExpectedNumberOfInstances"; 208 static const char* const EXPECTED_NUMBER_OF_INSTANCES = "ExpectedNumberOfInstances";
184 209
185 if (status == SeriesStatus_Unknown) 210 if (status == SeriesStatus_Unknown)
186 { 211 {
187 target[EXPECTED_NUMBER_OF_INSTANCES] = Json::nullValue; 212 target[EXPECTED_NUMBER_OF_INSTANCES] = Json::nullValue;
188 } 213 }
205 else 230 else
206 { 231 {
207 throw OrthancException(ErrorCode_InternalError); 232 throw OrthancException(ErrorCode_InternalError);
208 } 233 }
209 234
210 static const char* INDEX_IN_SERIES = "IndexInSeries"; 235 static const char* const INDEX_IN_SERIES = "IndexInSeries";
211 236
212 std::string s; 237 std::string s;
213 uint32_t index; 238 uint32_t index;
214 if (resource.LookupMetadata(s, ResourceType_Instance, MetadataType_Instance_IndexInSeries) && 239 if (resource.LookupMetadata(s, ResourceType_Instance, MetadataType_Instance_IndexInSeries) &&
215 SerializationToolbox::ParseUnsignedInteger32(index, s)) 240 SerializationToolbox::ParseUnsignedInteger32(index, s))
250 target["LastUpdate"] = s; 275 target["LastUpdate"] = s;
251 } 276 }
252 } 277 }
253 278
254 { 279 {
280 DicomMap allMainDicomTags;
281 resource.GetMainDicomTags(allMainDicomTags, resource.GetLevel());
282
283 /**
284 * This section was part of "StatelessDatabaseOperations::ExpandResource()"
285 * in Orthanc <= 1.12.3
286 **/
287
288 // read all main sequences from DB
289 std::string serializedSequences;
290 if (resource.LookupMetadata(serializedSequences, resource.GetLevel(), MetadataType_MainDicomSequences))
291 {
292 Json::Value jsonMetadata;
293 Toolbox::ReadJson(jsonMetadata, serializedSequences);
294
295 if (jsonMetadata["Version"].asInt() == 1)
296 {
297 allMainDicomTags.FromDicomAsJson(jsonMetadata["Sequences"], true /* append */, true /* parseSequences */);
298 }
299 else
300 {
301 throw OrthancException(ErrorCode_NotImplemented);
302 }
303 }
304
305 /**
306 * End of section from StatelessDatabaseOperations
307 **/
308
309
255 static const char* const MAIN_DICOM_TAGS = "MainDicomTags"; 310 static const char* const MAIN_DICOM_TAGS = "MainDicomTags";
256 static const char* const PATIENT_MAIN_DICOM_TAGS = "PatientMainDicomTags"; 311 static const char* const PATIENT_MAIN_DICOM_TAGS = "PatientMainDicomTags";
257 312
258 // TODO-FIND : (expandFlags & ExpandResourceFlags_IncludeMainDicomTags) 313 // TODO-FIND : Ignore "null" values
259 DicomMap allMainDicomTags;
260 resource.GetMainDicomTags(allMainDicomTags, resource.GetLevel());
261 314
262 DicomMap levelMainDicomTags; 315 DicomMap levelMainDicomTags;
263 allMainDicomTags.ExtractResourceInformation(levelMainDicomTags, resource.GetLevel()); 316 allMainDicomTags.ExtractResourceInformation(levelMainDicomTags, resource.GetLevel());
264 317
265 target[MAIN_DICOM_TAGS] = Json::objectValue; 318 target[MAIN_DICOM_TAGS] = Json::objectValue;
271 allMainDicomTags.ExtractPatientInformation(patientMainDicomTags); 324 allMainDicomTags.ExtractPatientInformation(patientMainDicomTags);
272 325
273 target[PATIENT_MAIN_DICOM_TAGS] = Json::objectValue; 326 target[PATIENT_MAIN_DICOM_TAGS] = Json::objectValue;
274 FromDcmtkBridge::ToJson(target[PATIENT_MAIN_DICOM_TAGS], patientMainDicomTags, format_); 327 FromDcmtkBridge::ToJson(target[PATIENT_MAIN_DICOM_TAGS], patientMainDicomTags, format_);
275 } 328 }
276
277 /*
278 TODO-FIND
279
280 if (!requestedTags_.empty())
281 {
282 static const char* const REQUESTED_TAGS = "RequestedTags";
283
284 DicomMap tags;
285 resource.GetMainDicomTags().ExtractTags(tags, requestedTags);
286
287 target[REQUESTED_TAGS] = Json::objectValue;
288 FromDcmtkBridge::ToJson(target[REQUESTED_TAGS], tags, format);
289 }
290 */
291 } 329 }
292 330
293 { 331 {
294 Json::Value labels = Json::arrayValue; 332 Json::Value labels = Json::arrayValue;
295 333
321 ResourceFinder::ResourceFinder(ResourceType level, 359 ResourceFinder::ResourceFinder(ResourceType level,
322 bool expand) : 360 bool expand) :
323 request_(level), 361 request_(level),
324 expand_(expand), 362 expand_(expand),
325 format_(DicomToJsonFormat_Human), 363 format_(DicomToJsonFormat_Human),
364 hasRequestedTags_(false),
326 includeAllMetadata_(false) 365 includeAllMetadata_(false)
327 { 366 {
328 if (expand) 367 if (expand)
329 { 368 {
330 request_.SetRetrieveMainDicomTags(level, true); 369 request_.SetRetrieveMainDicomTags(level, true);
351 } 390 }
352 } 391 }
353 } 392 }
354 393
355 394
395 void ResourceFinder::AddRequestedTags(const DicomTag& tag)
396 {
397 if (DicomMap::IsMainDicomTag(tag, ResourceType_Patient))
398 {
399 request_.SetRetrieveMainDicomTags(ResourceType_Patient, true);
400 request_.SetRetrieveMetadata(ResourceType_Patient, true);
401 requestedPatientTags_.insert(tag);
402 }
403 else if (DicomMap::IsMainDicomTag(tag, ResourceType_Study))
404 {
405 if (request_.GetLevel() == ResourceType_Patient)
406 {
407 throw OrthancException(ErrorCode_ParameterOutOfRange, "Requested tag " + tag.Format() +
408 " is only available at the study/series/instance levels");
409 }
410 else
411 {
412 request_.SetRetrieveMainDicomTags(ResourceType_Study, true);
413 request_.SetRetrieveMetadata(ResourceType_Study, true);
414 requestedStudyTags_.insert(tag);
415 }
416 }
417 else if (DicomMap::IsMainDicomTag(tag, ResourceType_Series))
418 {
419 if (request_.GetLevel() == ResourceType_Patient ||
420 request_.GetLevel() == ResourceType_Study)
421 {
422 throw OrthancException(ErrorCode_ParameterOutOfRange, "Requested tag " + tag.Format() +
423 " is only available at the series/instance levels");
424 }
425 else
426 {
427 request_.SetRetrieveMainDicomTags(ResourceType_Series, true);
428 request_.SetRetrieveMetadata(ResourceType_Series, true);
429 requestedSeriesTags_.insert(tag);
430 }
431 }
432 else if (DicomMap::IsMainDicomTag(tag, ResourceType_Instance))
433 {
434 if (request_.GetLevel() == ResourceType_Patient ||
435 request_.GetLevel() == ResourceType_Study ||
436 request_.GetLevel() == ResourceType_Series)
437 {
438 throw OrthancException(ErrorCode_ParameterOutOfRange, "Requested tag " + tag.Format() +
439 " is only available at the instance level");
440 }
441 else
442 {
443 // Main DICOM tags from the instance level will be retrieved anyway
444 assert(request_.IsRetrieveMainDicomTags(ResourceType_Instance));
445 assert(request_.IsRetrieveMetadata(ResourceType_Instance));
446 requestedInstanceTags_.insert(tag);
447 }
448 }
449 else
450 {
451 // This is not a main DICOM tag: We will be forced to access the DICOM file anyway
452 request_.SetRetrieveOneInstanceIdentifier(true);
453 requestedTagsFromFileStorage_.insert(tag);
454 }
455
456 hasRequestedTags_ = true;
457 }
458
459
460 void ResourceFinder::AddRequestedTags(const std::set<DicomTag>& tags)
461 {
462 for (std::set<DicomTag>::const_iterator it = tags.begin(); it != tags.end(); ++it)
463 {
464 AddRequestedTags(*it);
465 }
466 }
467
468
356 void ResourceFinder::Execute(Json::Value& target, 469 void ResourceFinder::Execute(Json::Value& target,
357 ServerContext& context) 470 ServerContext& context)
358 { 471 {
359 FindResponse response; 472 FindResponse response;
360 context.GetIndex().ExecuteFind(response, request_); 473 context.GetIndex().ExecuteFind(response, request_);
361 474
362 target = Json::arrayValue; 475 target = Json::arrayValue;
363 476
364 if (expand_) 477 for (size_t i = 0; i < response.GetSize(); i++)
365 { 478 {
366 for (size_t i = 0; i < response.GetSize(); i++) 479 const FindResponse::Resource& resource = response.GetResourceByIndex(i);
480
481 if (expand_)
367 { 482 {
368 Json::Value item; 483 Json::Value item;
369 Expand(item, response.GetResource(i), context.GetIndex()); 484 Expand(item, resource, context.GetIndex());
485
486 std::set<DicomTag> missingTags = requestedTagsFromFileStorage_;
487
488 DicomMap requestedTags;
489 InjectRequestedTags(requestedTags, missingTags, resource, ResourceType_Patient, requestedPatientTags_);
490 InjectRequestedTags(requestedTags, missingTags, resource, ResourceType_Study, requestedStudyTags_);
491 InjectRequestedTags(requestedTags, missingTags, resource, ResourceType_Series, requestedSeriesTags_);
492 InjectRequestedTags(requestedTags, missingTags, resource, ResourceType_Instance, requestedInstanceTags_);
493
494 if (!missingTags.empty())
495 {
496 OrthancConfiguration::ReaderLock lock;
497 if (lock.GetConfiguration().IsWarningEnabled(Warnings_001_TagsBeingReadFromStorage))
498 {
499 std::string missings;
500 FromDcmtkBridge::FormatListOfTags(missings, missingTags);
501
502 LOG(WARNING) << "W001: Accessing Dicom tags from storage when accessing "
503 << Orthanc::GetResourceTypeText(resource.GetLevel(), false, false)
504 << ": " << missings;
505 }
506
507 std::string instancePublicId;
508
509 if (request_.IsRetrieveOneInstanceIdentifier())
510 {
511 instancePublicId = resource.GetOneInstanceIdentifier();
512 }
513 else
514 {
515 FindRequest requestDicomAttachment(request_.GetLevel());
516 requestDicomAttachment.SetOrthancId(request_.GetLevel(), resource.GetIdentifier());
517 requestDicomAttachment.SetRetrieveOneInstanceIdentifier(true);
518
519 FindResponse responseDicomAttachment;
520 context.GetIndex().ExecuteFind(responseDicomAttachment, requestDicomAttachment);
521
522 if (responseDicomAttachment.GetSize() != 1 ||
523 !responseDicomAttachment.GetResourceByIndex(0).HasOneInstanceIdentifier())
524 {
525 throw OrthancException(ErrorCode_InexistentFile);
526 }
527 else
528 {
529 instancePublicId = responseDicomAttachment.GetResourceByIndex(0).GetOneInstanceIdentifier();
530 }
531 }
532
533 LOG(INFO) << "Will retrieve missing DICOM tags from instance: " << instancePublicId;
534
535 Json::Value tmpDicomAsJson;
536 context.ReadDicomAsJson(tmpDicomAsJson, instancePublicId, missingTags /* ignoreTagLength */);
537
538 DicomMap tmpDicomMap;
539 tmpDicomMap.FromDicomAsJson(tmpDicomAsJson, false /* append */, true /* parseSequences*/);
540
541 for (std::set<DicomTag>::const_iterator it = missingTags.begin(); it != missingTags.end(); ++it)
542 {
543 assert(!requestedTags.HasTag(*it));
544 if (tmpDicomMap.HasTag(*it))
545 {
546 requestedTags.SetValue(*it, tmpDicomMap.GetValue(*it));
547 }
548 else
549 {
550 requestedTags.SetNullValue(*it); // TODO-FIND: Is this compatible with Orthanc <= 1.12.3?
551 }
552 }
553 }
554
555 if (hasRequestedTags_)
556 {
557 static const char* const REQUESTED_TAGS = "RequestedTags";
558 item[REQUESTED_TAGS] = Json::objectValue;
559 FromDcmtkBridge::ToJson(item[REQUESTED_TAGS], requestedTags, format_);
560 }
561
370 target.append(item); 562 target.append(item);
371 } 563 }
372 } 564 else
373 else 565 {
374 { 566 target.append(resource.GetIdentifier());
375 for (size_t i = 0; i < response.GetSize(); i++)
376 {
377 target.append(response.GetResource(i).GetIdentifier());
378 } 567 }
379 } 568 }
380 } 569 }
381 } 570 }