changeset 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
files OrthancServer/Sources/Database/Compatibility/GenericFind.cpp OrthancServer/Sources/Database/FindRequest.cpp OrthancServer/Sources/Database/FindRequest.h OrthancServer/Sources/Database/FindResponse.cpp OrthancServer/Sources/Database/FindResponse.h OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp OrthancServer/Sources/ResourceFinder.cpp OrthancServer/Sources/ResourceFinder.h
diffstat 8 files changed, 389 insertions(+), 172 deletions(-) [+]
line wrap: on
line diff
--- a/OrthancServer/Sources/Database/Compatibility/GenericFind.cpp	Wed May 08 13:37:23 2024 +0200
+++ b/OrthancServer/Sources/Database/Compatibility/GenericFind.cpp	Wed May 08 18:28:36 2024 +0200
@@ -279,48 +279,27 @@
         resource->AddChildrenMetadata(*it, values);
       }
 
-      if (!request.GetRetrieveAttachmentOfOneInstance().empty())
+      if (request.IsRetrieveOneInstanceIdentifier())
       {
-        std::set<FileContentType> todo = request.GetRetrieveAttachmentOfOneInstance();
-        std::stack< std::pair<ResourceType, int64_t> > candidates;
-        candidates.push(std::make_pair(level, internalId));
-
-        while (!todo.empty() &&
-               !candidates.empty())
-        {
-          std::pair<ResourceType, int64_t> top = candidates.top();
-          candidates.pop();
-
-          if (top.first == ResourceType_Instance)
-          {
-            std::set<FileContentType> nextTodo;
+        int64_t currentId = internalId;
+        ResourceType currentLevel = level;
 
-            for (std::set<FileContentType>::const_iterator it = todo.begin(); it != todo.end(); ++it)
-            {
-              FileInfo attachment;
-              int64_t revision;
-              if (transaction_.LookupAttachment(attachment, revision, top.second, *it))
-              {
-                resource->AddAttachmentOfOneInstance(attachment);
-              }
-              else
-              {
-                nextTodo.insert(*it);
-              }
-            }
-
-            todo = nextTodo;
+        while (currentLevel != ResourceType_Instance)
+        {
+          std::list<int64_t> children;
+          transaction_.GetChildrenInternalId(children, currentId);
+          if (children.empty())
+          {
+            throw OrthancException(ErrorCode_DatabasePlugin);
           }
           else
           {
-            std::list<int64_t> children;
-            transaction_.GetChildrenInternalId(children, top.second);
-            for (std::list<int64_t>::const_iterator it = children.begin(); it != children.end(); ++it)
-            {
-              candidates.push(std::make_pair(GetChildResourceType(top.first), *it));
-            }
+            currentId = children.front();
+            currentLevel = GetChildResourceType(currentLevel);
           }
         }
+
+        resource->SetOneInstanceIdentifier(transaction_.GetPublicId(currentId));
       }
 
       response.Add(resource.release());
--- a/OrthancServer/Sources/Database/FindRequest.cpp	Wed May 08 13:37:23 2024 +0200
+++ b/OrthancServer/Sources/Database/FindRequest.cpp	Wed May 08 18:28:36 2024 +0200
@@ -45,7 +45,8 @@
     retrieveLabels_(false),
     retrieveAttachments_(false),
     retrieveParentIdentifier_(false),
-    retrieveChildrenIdentifiers_(false)
+    retrieveChildrenIdentifiers_(false),
+    retrieveOneInstanceIdentifier_(false)
   {
   }
 
@@ -60,6 +61,82 @@
     }
   }
 
+
+  void FindRequest::SetOrthancId(ResourceType level,
+                                 const std::string& id)
+  {
+    switch (level)
+    {
+      case ResourceType_Patient:
+        SetOrthancPatientId(id);
+        break;
+
+      case ResourceType_Study:
+        SetOrthancStudyId(id);
+        break;
+
+      case ResourceType_Series:
+        SetOrthancSeriesId(id);
+        break;
+
+      case ResourceType_Instance:
+        SetOrthancInstanceId(id);
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  void FindRequest::SetOrthancPatientId(const std::string& id)
+  {
+    orthancIdentifiers_.SetPatientId(id);
+  }
+
+
+  void FindRequest::SetOrthancStudyId(const std::string& id)
+  {
+    if (level_ == ResourceType_Patient)
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      orthancIdentifiers_.SetStudyId(id);
+    }
+  }
+
+
+  void FindRequest::SetOrthancSeriesId(const std::string& id)
+  {
+    if (level_ == ResourceType_Patient ||
+        level_ == ResourceType_Study)
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      orthancIdentifiers_.SetSeriesId(id);
+    }
+  }
+
+
+  void FindRequest::SetOrthancInstanceId(const std::string& id)
+  {
+    if (level_ == ResourceType_Patient ||
+        level_ == ResourceType_Study ||
+        level_ == ResourceType_Series)
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      orthancIdentifiers_.SetInstanceId(id);
+    }
+  }
+
+
   void FindRequest::AddDicomTagConstraint(const DicomTagConstraint& constraint)
   {
     dicomTagConstraints_.push_back(constraint);
@@ -289,17 +366,4 @@
       retrieveChildrenMetadata_.insert(metadata);
     }
   }
-
-
-  void FindRequest::AddRetrieveAttachmentOfOneInstance(FileContentType type)
-  {
-    if (retrieveAttachmentOfOneInstance_.find(type) == retrieveAttachmentOfOneInstance_.end())
-    {
-      retrieveAttachmentOfOneInstance_.insert(type);
-    }
-    else
-    {
-      throw OrthancException(ErrorCode_BadSequenceOfCalls);
-    }
-  }
 }
--- a/OrthancServer/Sources/Database/FindRequest.h	Wed May 08 13:37:23 2024 +0200
+++ b/OrthancServer/Sources/Database/FindRequest.h	Wed May 08 18:28:36 2024 +0200
@@ -179,7 +179,7 @@
     bool                                 retrieveParentIdentifier_;
     bool                                 retrieveChildrenIdentifiers_;
     std::set<MetadataType>               retrieveChildrenMetadata_;
-    std::set<FileContentType>            retrieveAttachmentOfOneInstance_;
+    bool                                 retrieveOneInstanceIdentifier_;
 
   public:
     explicit FindRequest(ResourceType level);
@@ -191,25 +191,16 @@
       return level_;
     }
 
-    void SetOrthancPatientId(const std::string& id)
-    {
-      orthancIdentifiers_.SetPatientId(id);
-    }
+    void SetOrthancId(ResourceType level,
+                      const std::string& id);
 
-    void SetOrthancStudyId(const std::string& id)
-    {
-      orthancIdentifiers_.SetStudyId(id);
-    }
+    void SetOrthancPatientId(const std::string& id);
 
-    void SetOrthancSeriesId(const std::string& id)
-    {
-      orthancIdentifiers_.SetSeriesId(id);
-    }
+    void SetOrthancStudyId(const std::string& id);
 
-    void SetOrthancInstanceId(const std::string& id)
-    {
-      orthancIdentifiers_.SetInstanceId(id);
-    }
+    void SetOrthancSeriesId(const std::string& id);
+
+    void SetOrthancInstanceId(const std::string& id);
 
     const OrthancIdentifiers& GetOrthancIdentifiers() const
     {
@@ -322,11 +313,14 @@
       return retrieveChildrenMetadata_;
     }
 
-    void AddRetrieveAttachmentOfOneInstance(FileContentType type);
+    void SetRetrieveOneInstanceIdentifier(bool retrieve)
+    {
+      retrieveOneInstanceIdentifier_ = retrieve;
+    }
 
-    const std::set<FileContentType>& GetRetrieveAttachmentOfOneInstance() const
+    bool IsRetrieveOneInstanceIdentifier() const
     {
-      return retrieveAttachmentOfOneInstance_;
+      return retrieveOneInstanceIdentifier_;
     }
   };
 }
--- a/OrthancServer/Sources/Database/FindResponse.cpp	Wed May 08 13:37:23 2024 +0200
+++ b/OrthancServer/Sources/Database/FindResponse.cpp	Wed May 08 18:28:36 2024 +0200
@@ -245,23 +245,6 @@
   }
 
 
-  const std::string& FindResponse::Resource::GetParentIdentifier() const
-  {
-    if (level_ == ResourceType_Patient)
-    {
-      throw OrthancException(ErrorCode_BadParameterType);
-    }
-    else if (HasParentIdentifier())
-    {
-      return *parentIdentifier_;
-    }
-    else
-    {
-      throw OrthancException(ErrorCode_BadSequenceOfCalls);
-    }
-  }
-
-
   FindResponse::Resource::~Resource()
   {
     for (ChildrenMetadata::iterator it = childrenMetadata_.begin(); it != childrenMetadata_.end(); ++it)
@@ -289,6 +272,23 @@
   }
 
 
+  const std::string& FindResponse::Resource::GetParentIdentifier() const
+  {
+    if (level_ == ResourceType_Patient)
+    {
+      throw OrthancException(ErrorCode_BadParameterType);
+    }
+    else if (HasParentIdentifier())
+    {
+      return *parentIdentifier_;
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+  }
+
+
   bool FindResponse::Resource::HasParentIdentifier() const
   {
     if (level_ == ResourceType_Patient)
@@ -374,11 +374,24 @@
   }
 
 
-  void FindResponse::Resource::AddAttachmentOfOneInstance(const FileInfo& info)
+  void FindResponse::Resource::SetOneInstanceIdentifier(const std::string& id)
   {
-    if (attachmentOfOneInstance_.find(info.GetContentType()) == attachmentOfOneInstance_.end())
+    if (HasOneInstanceIdentifier())
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+    else
     {
-      attachmentOfOneInstance_[info.GetContentType()] = info;
+      oneInstanceIdentifier_.reset(new std::string(id));
+    }
+  }
+
+
+  const std::string& FindResponse::Resource::GetOneInstanceIdentifier() const
+  {
+    if (HasOneInstanceIdentifier())
+    {
+      return *oneInstanceIdentifier_;
     }
     else
     {
@@ -387,20 +400,9 @@
   }
 
 
-  bool FindResponse::Resource::LookupAttachmentOfOneInstance(FileInfo& target,
-                                                             FileContentType type) const
+  bool FindResponse::Resource::HasOneInstanceIdentifier() const
   {
-    std::map<FileContentType, FileInfo>::const_iterator found = attachmentOfOneInstance_.find(type);
-
-    if (found == attachmentOfOneInstance_.end())
-    {
-      return false;
-    }
-    else
-    {
-      target = found->second;
-      return true;
-    }
+    return oneInstanceIdentifier_.get() != NULL;
   }
 
 
@@ -579,25 +581,9 @@
       }
     }
 
-    for (std::set<FileContentType>::const_iterator it = request.GetRetrieveAttachmentOfOneInstance().begin();
-         it != request.GetRetrieveAttachmentOfOneInstance().end(); ++it)
+    if (request.IsRetrieveOneInstanceIdentifier())
     {
-      FileInfo info;
-      if (LookupAttachmentOfOneInstance(info, *it))
-      {
-        if (info.GetContentType() == *it)
-        {
-          DebugAddAttachment(target["AttachmentOfOneInstance"], info);
-        }
-        else
-        {
-          throw OrthancException(ErrorCode_DatabasePlugin);
-        }
-      }
-      else
-      {
-        throw OrthancException(ErrorCode_DatabasePlugin);
-      }
+      target["OneInstance"] = GetOneInstanceIdentifier();
     }
   }
 
@@ -642,7 +628,7 @@
   }
 
 
-  const FindResponse::Resource& FindResponse::GetResource(size_t index) const
+  const FindResponse::Resource& FindResponse::GetResourceByIndex(size_t index) const
   {
     if (index >= items_.size())
     {
@@ -656,7 +642,7 @@
   }
 
 
-  FindResponse::Resource& FindResponse::GetResource(const std::string& id)
+  FindResponse::Resource& FindResponse::GetResourceByIdentifier(const std::string& id)
   {
     Index::const_iterator found = index_.find(id);
 
--- a/OrthancServer/Sources/Database/FindResponse.h	Wed May 08 13:37:23 2024 +0200
+++ b/OrthancServer/Sources/Database/FindResponse.h	Wed May 08 18:28:36 2024 +0200
@@ -88,7 +88,7 @@
       std::set<std::string>                 labels_;      
       std::map<FileContentType, FileInfo>   attachments_;
       ChildrenMetadata                      childrenMetadata_;
-      std::map<FileContentType, FileInfo>   attachmentOfOneInstance_;
+      std::unique_ptr<std::string>          oneInstanceIdentifier_;
 
       MainDicomTagsAtLevel& GetMainDicomTagsAtLevel(ResourceType level);
 
@@ -124,9 +124,9 @@
         return identifier_;
       }
 
-      const std::string& GetParentIdentifier() const;
+      void SetParentIdentifier(const std::string& id);
 
-      void SetParentIdentifier(const std::string& id);
+      const std::string& GetParentIdentifier() const;
 
       bool HasParentIdentifier() const;
 
@@ -201,10 +201,11 @@
       bool LookupChildrenMetadata(std::list<std::string>& values,
                                   MetadataType metadata) const;
 
-      void AddAttachmentOfOneInstance(const FileInfo& info);
+      const std::string& GetOneInstanceIdentifier() const;
 
-      bool LookupAttachmentOfOneInstance(FileInfo& target,
-                                         FileContentType type) const;
+      void SetOneInstanceIdentifier(const std::string& id);
+
+      bool HasOneInstanceIdentifier() const;
 
       void DebugExport(Json::Value& target,
                        const FindRequest& request) const;
@@ -226,13 +227,13 @@
       return items_.size();
     }
 
-    const Resource& GetResource(size_t index) const;
+    const Resource& GetResourceByIndex(size_t index) const;
 
-    Resource& GetResource(const std::string& id);
+    Resource& GetResourceByIdentifier(const std::string& id);
 
-    const Resource& GetResource(const std::string& id) const
+    const Resource& GetResourceByIdentifier(const std::string& id) const
     {
-      return const_cast<FindResponse&>(*this).GetResource(id);
+      return const_cast<FindResponse&>(*this).GetResourceByIdentifier(id);
     }
 
     bool HasResource(const std::string& id) const
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp	Wed May 08 13:37:23 2024 +0200
+++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp	Wed May 08 18:28:36 2024 +0200
@@ -241,7 +241,7 @@
       OrthancRestApi::GetRequestedTags(requestedTags, call);
 
       ResourceFinder finder(resourceType, expand);
-      finder.SetRequestedTags(requestedTags);
+      finder.AddRequestedTags(requestedTags);
       finder.SetFormat(OrthancRestApi::GetDicomFormat(call, DicomToJsonFormat_Human));
 
       if (call.HasArgument("limit") ||
--- a/OrthancServer/Sources/ResourceFinder.cpp	Wed May 08 13:37:23 2024 +0200
+++ b/OrthancServer/Sources/ResourceFinder.cpp	Wed May 08 18:28:36 2024 +0200
@@ -24,8 +24,10 @@
 #include "ResourceFinder.h"
 
 #include "../../OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h"
+#include "../../OrthancFramework/Sources/Logging.h"
 #include "../../OrthancFramework/Sources/OrthancException.h"
 #include "../../OrthancFramework/Sources/SerializationToolbox.h"
+#include "OrthancConfiguration.h"
 #include "ServerContext.h"
 #include "ServerIndex.h"
 
@@ -92,6 +94,34 @@
   }
 
 
+  static void InjectRequestedTags(DicomMap& requestedTags,
+                                  std::set<DicomTag>& missingTags /* out */,
+                                  const FindResponse::Resource& resource,
+                                  ResourceType level,
+                                  const std::set<DicomTag>& tags)
+  {
+    if (!tags.empty())
+    {
+      DicomMap m;
+      resource.GetMainDicomTags(m, level);
+
+      for (std::set<DicomTag>::const_iterator it = tags.begin(); it != tags.end(); ++it)
+      {
+        std::string value;
+        if (m.LookupStringValue(value, *it, false /* not binary */))
+        {
+          requestedTags.SetValue(*it, value, false /* not binary */);
+        }
+        else
+        {
+          // This is the case where the Housekeeper should be run
+          missingTags.insert(*it);
+        }
+      }
+    }
+  }
+
+
   void ResourceFinder::Expand(Json::Value& target,
                               const FindResponse::Resource& resource,
                               ServerIndex& index) const
@@ -106,11 +136,6 @@
       throw OrthancException(ErrorCode_InternalError);
     }
 
-    if (!requestedTags_.empty())
-    {
-      throw OrthancException(ErrorCode_NotImplemented);
-    }
-
     target = Json::objectValue;
 
     target["Type"] = GetResourceTypeText(resource.GetLevel(), false, true);
@@ -180,7 +205,7 @@
 
         target["Status"] = EnumerationToString(status);
 
-        static const char* EXPECTED_NUMBER_OF_INSTANCES = "ExpectedNumberOfInstances";
+        static const char* const EXPECTED_NUMBER_OF_INSTANCES = "ExpectedNumberOfInstances";
 
         if (status == SeriesStatus_Unknown)
         {
@@ -207,7 +232,7 @@
           throw OrthancException(ErrorCode_InternalError);
         }
 
-        static const char* INDEX_IN_SERIES = "IndexInSeries";
+        static const char* const INDEX_IN_SERIES = "IndexInSeries";
 
         std::string s;
         uint32_t index;
@@ -252,12 +277,40 @@
     }
 
     {
+      DicomMap allMainDicomTags;
+      resource.GetMainDicomTags(allMainDicomTags, resource.GetLevel());
+
+      /**
+       * This section was part of "StatelessDatabaseOperations::ExpandResource()"
+       * in Orthanc <= 1.12.3
+       **/
+
+      // read all main sequences from DB
+      std::string serializedSequences;
+      if (resource.LookupMetadata(serializedSequences, resource.GetLevel(), MetadataType_MainDicomSequences))
+      {
+        Json::Value jsonMetadata;
+        Toolbox::ReadJson(jsonMetadata, serializedSequences);
+
+        if (jsonMetadata["Version"].asInt() == 1)
+        {
+          allMainDicomTags.FromDicomAsJson(jsonMetadata["Sequences"], true /* append */, true /* parseSequences */);
+        }
+        else
+        {
+          throw OrthancException(ErrorCode_NotImplemented);
+        }
+      }
+
+      /**
+       * End of section from StatelessDatabaseOperations
+       **/
+
+
       static const char* const MAIN_DICOM_TAGS = "MainDicomTags";
       static const char* const PATIENT_MAIN_DICOM_TAGS = "PatientMainDicomTags";
 
-      // TODO-FIND : (expandFlags & ExpandResourceFlags_IncludeMainDicomTags)
-      DicomMap allMainDicomTags;
-      resource.GetMainDicomTags(allMainDicomTags, resource.GetLevel());
+      // TODO-FIND : Ignore "null" values
 
       DicomMap levelMainDicomTags;
       allMainDicomTags.ExtractResourceInformation(levelMainDicomTags, resource.GetLevel());
@@ -273,21 +326,6 @@
         target[PATIENT_MAIN_DICOM_TAGS] = Json::objectValue;
         FromDcmtkBridge::ToJson(target[PATIENT_MAIN_DICOM_TAGS], patientMainDicomTags, format_);
       }
-
-      /*
-        TODO-FIND
-
-        if (!requestedTags_.empty())
-        {
-        static const char* const REQUESTED_TAGS = "RequestedTags";
-
-        DicomMap tags;
-        resource.GetMainDicomTags().ExtractTags(tags, requestedTags);
-
-        target[REQUESTED_TAGS] = Json::objectValue;
-        FromDcmtkBridge::ToJson(target[REQUESTED_TAGS], tags, format);
-        }
-      */
     }
 
     {
@@ -323,6 +361,7 @@
     request_(level),
     expand_(expand),
     format_(DicomToJsonFormat_Human),
+    hasRequestedTags_(false),
     includeAllMetadata_(false)
   {
     if (expand)
@@ -353,6 +392,80 @@
   }
 
 
+  void ResourceFinder::AddRequestedTags(const DicomTag& tag)
+  {
+    if (DicomMap::IsMainDicomTag(tag, ResourceType_Patient))
+    {
+      request_.SetRetrieveMainDicomTags(ResourceType_Patient, true);
+      request_.SetRetrieveMetadata(ResourceType_Patient, true);
+      requestedPatientTags_.insert(tag);
+    }
+    else if (DicomMap::IsMainDicomTag(tag, ResourceType_Study))
+    {
+      if (request_.GetLevel() == ResourceType_Patient)
+      {
+        throw OrthancException(ErrorCode_ParameterOutOfRange, "Requested tag " + tag.Format() +
+                               " is only available at the study/series/instance levels");
+      }
+      else
+      {
+        request_.SetRetrieveMainDicomTags(ResourceType_Study, true);
+        request_.SetRetrieveMetadata(ResourceType_Study, true);
+        requestedStudyTags_.insert(tag);
+      }
+    }
+    else if (DicomMap::IsMainDicomTag(tag, ResourceType_Series))
+    {
+      if (request_.GetLevel() == ResourceType_Patient ||
+          request_.GetLevel() == ResourceType_Study)
+      {
+        throw OrthancException(ErrorCode_ParameterOutOfRange, "Requested tag " + tag.Format() +
+                               " is only available at the series/instance levels");
+      }
+      else
+      {
+        request_.SetRetrieveMainDicomTags(ResourceType_Series, true);
+        request_.SetRetrieveMetadata(ResourceType_Series, true);
+        requestedSeriesTags_.insert(tag);
+      }
+    }
+    else if (DicomMap::IsMainDicomTag(tag, ResourceType_Instance))
+    {
+      if (request_.GetLevel() == ResourceType_Patient ||
+          request_.GetLevel() == ResourceType_Study ||
+          request_.GetLevel() == ResourceType_Series)
+      {
+        throw OrthancException(ErrorCode_ParameterOutOfRange, "Requested tag " + tag.Format() +
+                               " is only available at the instance level");
+      }
+      else
+      {
+        // Main DICOM tags from the instance level will be retrieved anyway
+        assert(request_.IsRetrieveMainDicomTags(ResourceType_Instance));
+        assert(request_.IsRetrieveMetadata(ResourceType_Instance));
+        requestedInstanceTags_.insert(tag);
+      }
+    }
+    else
+    {
+      // This is not a main DICOM tag: We will be forced to access the DICOM file anyway
+      request_.SetRetrieveOneInstanceIdentifier(true);
+      requestedTagsFromFileStorage_.insert(tag);
+    }
+
+    hasRequestedTags_ = true;
+  }
+
+
+  void ResourceFinder::AddRequestedTags(const std::set<DicomTag>& tags)
+  {
+    for (std::set<DicomTag>::const_iterator it = tags.begin(); it != tags.end(); ++it)
+    {
+      AddRequestedTags(*it);
+    }
+  }
+
+
   void ResourceFinder::Execute(Json::Value& target,
                                ServerContext& context)
   {
@@ -361,20 +474,96 @@
 
     target = Json::arrayValue;
 
-    if (expand_)
+    for (size_t i = 0; i < response.GetSize(); i++)
     {
-      for (size_t i = 0; i < response.GetSize(); i++)
+      const FindResponse::Resource& resource = response.GetResourceByIndex(i);
+
+      if (expand_)
       {
         Json::Value item;
-        Expand(item, response.GetResource(i), context.GetIndex());
+        Expand(item, resource, context.GetIndex());
+
+        std::set<DicomTag> missingTags = requestedTagsFromFileStorage_;
+
+        DicomMap requestedTags;
+        InjectRequestedTags(requestedTags, missingTags, resource, ResourceType_Patient, requestedPatientTags_);
+        InjectRequestedTags(requestedTags, missingTags, resource, ResourceType_Study, requestedStudyTags_);
+        InjectRequestedTags(requestedTags, missingTags, resource, ResourceType_Series, requestedSeriesTags_);
+        InjectRequestedTags(requestedTags, missingTags, resource, ResourceType_Instance, requestedInstanceTags_);
+
+        if (!missingTags.empty())
+        {
+          OrthancConfiguration::ReaderLock lock;
+          if (lock.GetConfiguration().IsWarningEnabled(Warnings_001_TagsBeingReadFromStorage))
+          {
+            std::string missings;
+            FromDcmtkBridge::FormatListOfTags(missings, missingTags);
+
+            LOG(WARNING) << "W001: Accessing Dicom tags from storage when accessing "
+                         << Orthanc::GetResourceTypeText(resource.GetLevel(), false, false)
+                         << ": " << missings;
+          }
+
+          std::string instancePublicId;
+
+          if (request_.IsRetrieveOneInstanceIdentifier())
+          {
+            instancePublicId = resource.GetOneInstanceIdentifier();
+          }
+          else
+          {
+            FindRequest requestDicomAttachment(request_.GetLevel());
+            requestDicomAttachment.SetOrthancId(request_.GetLevel(), resource.GetIdentifier());
+            requestDicomAttachment.SetRetrieveOneInstanceIdentifier(true);
+
+            FindResponse responseDicomAttachment;
+            context.GetIndex().ExecuteFind(responseDicomAttachment, requestDicomAttachment);
+
+            if (responseDicomAttachment.GetSize() != 1 ||
+                !responseDicomAttachment.GetResourceByIndex(0).HasOneInstanceIdentifier())
+            {
+              throw OrthancException(ErrorCode_InexistentFile);
+            }
+            else
+            {
+              instancePublicId = responseDicomAttachment.GetResourceByIndex(0).GetOneInstanceIdentifier();
+            }
+          }
+
+          LOG(INFO) << "Will retrieve missing DICOM tags from instance: " << instancePublicId;
+
+          Json::Value tmpDicomAsJson;
+          context.ReadDicomAsJson(tmpDicomAsJson, instancePublicId, missingTags /* ignoreTagLength */);
+
+          DicomMap tmpDicomMap;
+          tmpDicomMap.FromDicomAsJson(tmpDicomAsJson, false /* append */, true /* parseSequences*/);
+
+          for (std::set<DicomTag>::const_iterator it = missingTags.begin(); it != missingTags.end(); ++it)
+          {
+            assert(!requestedTags.HasTag(*it));
+            if (tmpDicomMap.HasTag(*it))
+            {
+              requestedTags.SetValue(*it, tmpDicomMap.GetValue(*it));
+            }
+            else
+            {
+              requestedTags.SetNullValue(*it);  // TODO-FIND: Is this compatible with Orthanc <= 1.12.3?
+            }
+          }
+        }
+
+        if (hasRequestedTags_)
+        {
+          static const char* const REQUESTED_TAGS = "RequestedTags";
+          item[REQUESTED_TAGS] = Json::objectValue;
+          FromDcmtkBridge::ToJson(item[REQUESTED_TAGS], requestedTags, format_);
+        }
+
         target.append(item);
       }
-    }
-    else
-    {
-      for (size_t i = 0; i < response.GetSize(); i++)
+      else
       {
-        target.append(response.GetResource(i).GetIdentifier());
+        target.append(resource.GetIdentifier());
       }
     }
   }
--- a/OrthancServer/Sources/ResourceFinder.h	Wed May 08 13:37:23 2024 +0200
+++ b/OrthancServer/Sources/ResourceFinder.h	Wed May 08 18:28:36 2024 +0200
@@ -35,8 +35,13 @@
   private:
     FindRequest        request_;
     bool               expand_;
-    std::set<DicomTag> requestedTags_;
     DicomToJsonFormat  format_;
+    bool               hasRequestedTags_;
+    std::set<DicomTag> requestedPatientTags_;
+    std::set<DicomTag> requestedStudyTags_;
+    std::set<DicomTag> requestedSeriesTags_;
+    std::set<DicomTag> requestedInstanceTags_;
+    std::set<DicomTag> requestedTagsFromFileStorage_;
     bool               includeAllMetadata_;   // Same as: ExpandResourceFlags_IncludeAllMetadata
 
     SeriesStatus GetSeriesStatus(uint32_t& expectedNumberOfInstances,
@@ -50,11 +55,6 @@
     ResourceFinder(ResourceType level,
                    bool expand);
 
-    void SetRequestedTags(const std::set<DicomTag>& tags)
-    {
-      requestedTags_ = tags;
-    }
-
     void SetFormat(DicomToJsonFormat format)
     {
       format_ = format;
@@ -71,6 +71,10 @@
       includeAllMetadata_ = include;
     }
 
+    void AddRequestedTags(const DicomTag& tag);
+
+    void AddRequestedTags(const std::set<DicomTag>& tags);
+
     void Execute(Json::Value& target,
                  ServerContext& context);
   };