Mercurial > hg > orthanc
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 } |