changeset 5596:81a29ad7fb4b find-refactoring

added possibility to retrieve main DICOM tags and metadata at any level
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 07 May 2024 18:44:53 +0200
parents a87f2a56257d
children 8796c100aaf8
files OrthancServer/Sources/Database/Compatibility/GenericFind.cpp OrthancServer/Sources/Database/Compatibility/GenericFind.h OrthancServer/Sources/Database/FindRequest.cpp OrthancServer/Sources/Database/FindRequest.h OrthancServer/Sources/Database/FindResponse.cpp OrthancServer/Sources/Database/FindResponse.h OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp OrthancServer/Sources/ServerContext.cpp
diffstat 9 files changed, 794 insertions(+), 198 deletions(-) [+]
line wrap: on
line diff
--- a/OrthancServer/Sources/Database/Compatibility/GenericFind.cpp	Tue May 07 12:53:12 2024 +0200
+++ b/OrthancServer/Sources/Database/Compatibility/GenericFind.cpp	Tue May 07 18:44:53 2024 +0200
@@ -25,6 +25,8 @@
 #include "../../../../OrthancFramework/Sources/DicomFormat/DicomArray.h"
 #include "../../../../OrthancFramework/Sources/OrthancException.h"
 
+#include <stack>
+
 
 namespace Orthanc
 {
@@ -59,6 +61,91 @@
     }
 
 
+    void GenericFind::RetrieveMainDicomTags(FindResponse::Resource& target,
+                                            ResourceType level,
+                                            int64_t internalId)
+    {
+      DicomMap m;
+      transaction_.GetMainDicomTags(m, internalId);
+
+      DicomArray a(m);
+      for (size_t i = 0; i < a.GetSize(); i++)
+      {
+        const DicomElement& element = a.GetElement(i);
+        if (element.GetValue().IsString())
+        {
+          target.AddStringDicomTag(level, element.GetTag().GetGroup(),
+                                   element.GetTag().GetElement(), element.GetValue().GetContent());
+        }
+        else
+        {
+          throw OrthancException(ErrorCode_BadParameterType);
+        }
+      }
+    }
+
+
+    static ResourceType GetTopLevelOfInterest(const FindRequest& request)
+    {
+      switch (request.GetLevel())
+      {
+        case ResourceType_Patient:
+          return ResourceType_Patient;
+
+        case ResourceType_Study:
+          if (request.IsRetrieveMainDicomTags(ResourceType_Patient) ||
+              request.IsRetrieveMetadata(ResourceType_Patient))
+          {
+            return ResourceType_Patient;
+          }
+          else
+          {
+            return ResourceType_Study;
+          }
+
+        case ResourceType_Series:
+          if (request.IsRetrieveMainDicomTags(ResourceType_Patient) ||
+              request.IsRetrieveMetadata(ResourceType_Patient))
+          {
+            return ResourceType_Patient;
+          }
+          else if (request.IsRetrieveMainDicomTags(ResourceType_Study) ||
+                   request.IsRetrieveMetadata(ResourceType_Study))
+          {
+            return ResourceType_Study;
+          }
+          else
+          {
+            return ResourceType_Series;
+          }
+
+        case ResourceType_Instance:
+          if (request.IsRetrieveMainDicomTags(ResourceType_Patient) ||
+              request.IsRetrieveMetadata(ResourceType_Patient))
+          {
+            return ResourceType_Patient;
+          }
+          else if (request.IsRetrieveMainDicomTags(ResourceType_Study) ||
+                   request.IsRetrieveMetadata(ResourceType_Study))
+          {
+            return ResourceType_Study;
+          }
+          else if (request.IsRetrieveMainDicomTags(ResourceType_Series) ||
+                   request.IsRetrieveMetadata(ResourceType_Series))
+          {
+            return ResourceType_Series;
+          }
+          else
+          {
+            return ResourceType_Instance;
+          }
+
+        default:
+          throw OrthancException(ErrorCode_ParameterOutOfRange);
+      }
+    }
+
+
     void GenericFind::ExecuteExpand(FindResponse& response,
                                     const FindRequest& request,
                                     const std::string& identifier)
@@ -110,32 +197,43 @@
         resource->SetParentIdentifier(parent);
       }
 
-      if (request.IsRetrieveMainDicomTags())
       {
-        DicomMap m;
-        transaction_.GetMainDicomTags(m, internalId);
+        int64_t currentId = internalId;
+        ResourceType currentLevel = level;
+        const ResourceType topLevel = GetTopLevelOfInterest(request);
 
-        DicomArray a(m);
-        for (size_t i = 0; i < a.GetSize(); i++)
+        for (;;)
         {
-          const DicomElement& element = a.GetElement(i);
-          if (element.GetValue().IsString())
+          if (request.IsRetrieveMainDicomTags(currentLevel))
+          {
+            RetrieveMainDicomTags(*resource, currentLevel, currentId);
+          }
+
+          if (request.IsRetrieveMetadata(currentLevel))
           {
-            resource->AddStringDicomTag(element.GetTag().GetGroup(), element.GetTag().GetElement(),
-                                        element.GetValue().GetContent());
+            transaction_.GetAllMetadata(resource->GetMetadata(currentLevel), currentId);
+          }
+
+          if (currentLevel == topLevel)
+          {
+            break;
           }
           else
           {
-            throw OrthancException(ErrorCode_BadParameterType);
+            int64_t parentId;
+            if (transaction_.LookupParent(parentId, currentId))
+            {
+              currentId = parentId;
+              currentLevel = GetParentResourceType(currentLevel);
+            }
+            else
+            {
+              throw OrthancException(ErrorCode_DatabasePlugin);
+            }
           }
         }
       }
 
-      if (request.IsRetrieveMetadata())
-      {
-        transaction_.GetAllMetadata(resource->GetMetadata(), internalId);
-      }
-
       if (request.IsRetrieveLabels())
       {
         transaction_.ListLabels(resource->GetLabels(), internalId);
@@ -169,7 +267,7 @@
 
         for (std::list<std::string>::const_iterator it = children.begin(); it != children.end(); ++it)
         {
-          resource->AddChildIdentifier(GetChildResourceType(level), *it);
+          resource->AddChildIdentifier(*it);
         }
       }
 
@@ -181,6 +279,50 @@
         resource->AddChildrenMetadata(*it, values);
       }
 
+      if (!request.GetRetrieveAttachmentOfOneInstance().empty())
+      {
+        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;
+
+            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;
+          }
+          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));
+            }
+          }
+        }
+      }
+
       response.Add(resource.release());
     }
   }
--- a/OrthancServer/Sources/Database/Compatibility/GenericFind.h	Tue May 07 12:53:12 2024 +0200
+++ b/OrthancServer/Sources/Database/Compatibility/GenericFind.h	Tue May 07 18:44:53 2024 +0200
@@ -33,6 +33,10 @@
     private:
       IDatabaseWrapper::ITransaction&  transaction_;
 
+      void RetrieveMainDicomTags(FindResponse::Resource& target,
+                                 ResourceType level,
+                                 int64_t internalId);
+
     public:
       GenericFind(IDatabaseWrapper::ITransaction& transaction) :
         transaction_(transaction)
--- a/OrthancServer/Sources/Database/FindRequest.cpp	Tue May 07 12:53:12 2024 +0200
+++ b/OrthancServer/Sources/Database/FindRequest.cpp	Tue May 07 18:44:53 2024 +0200
@@ -34,8 +34,14 @@
     hasLimits_(false),
     limitsSince_(0),
     limitsCount_(0),
-    retrieveMainDicomTags_(false),
-    retrieveMetadata_(false),
+    retrieveMainDicomTagsPatients_(false),
+    retrieveMainDicomTagsStudies_(false),
+    retrieveMainDicomTagsSeries_(false),
+    retrieveMainDicomTagsInstances_(false),
+    retrieveMetadataPatients_(false),
+    retrieveMetadataStudies_(false),
+    retrieveMetadataSeries_(false),
+    retrieveMetadataInstances_(false),
     retrieveLabels_(false),
     retrieveAttachments_(false),
     retrieveParentIdentifier_(false),
@@ -128,6 +134,124 @@
   }
 
 
+  void FindRequest::SetRetrieveMainDicomTags(ResourceType level,
+                                             bool retrieve)
+  {
+    if (!IsResourceLevelAboveOrEqual(level, level_))
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    switch (level)
+    {
+      case ResourceType_Patient:
+        retrieveMainDicomTagsPatients_ = retrieve;
+        break;
+
+      case ResourceType_Study:
+        retrieveMainDicomTagsStudies_ = retrieve;
+        break;
+
+      case ResourceType_Series:
+        retrieveMainDicomTagsSeries_ = retrieve;
+        break;
+
+      case ResourceType_Instance:
+        retrieveMainDicomTagsInstances_ = retrieve;
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_InternalError);
+    }
+  }
+
+
+  bool FindRequest::IsRetrieveMainDicomTags(ResourceType level) const
+  {
+    if (!IsResourceLevelAboveOrEqual(level, level_))
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    switch (level)
+    {
+      case ResourceType_Patient:
+        return retrieveMainDicomTagsPatients_;
+
+      case ResourceType_Study:
+        return retrieveMainDicomTagsStudies_;
+
+      case ResourceType_Series:
+        return retrieveMainDicomTagsSeries_;
+
+      case ResourceType_Instance:
+        return retrieveMainDicomTagsInstances_;
+
+      default:
+        throw OrthancException(ErrorCode_InternalError);
+    }
+  }
+
+
+  void FindRequest::SetRetrieveMetadata(ResourceType level,
+                                        bool retrieve)
+  {
+    if (!IsResourceLevelAboveOrEqual(level, level_))
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    switch (level)
+    {
+      case ResourceType_Patient:
+        retrieveMetadataPatients_ = retrieve;
+        break;
+
+      case ResourceType_Study:
+        retrieveMetadataStudies_ = retrieve;
+        break;
+
+      case ResourceType_Series:
+        retrieveMetadataSeries_ = retrieve;
+        break;
+
+      case ResourceType_Instance:
+        retrieveMetadataInstances_ = retrieve;
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_InternalError);
+    }
+  }
+
+
+  bool FindRequest::IsRetrieveMetadata(ResourceType level) const
+  {
+    if (!IsResourceLevelAboveOrEqual(level, level_))
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    switch (level)
+    {
+      case ResourceType_Patient:
+        return retrieveMetadataPatients_;
+
+      case ResourceType_Study:
+        return retrieveMetadataStudies_;
+
+      case ResourceType_Series:
+        return retrieveMetadataSeries_;
+
+      case ResourceType_Instance:
+        return retrieveMetadataInstances_;
+
+      default:
+        throw OrthancException(ErrorCode_InternalError);
+    }
+  }
+
+
   void FindRequest::SetRetrieveParentIdentifier(bool retrieve)
   {
     if (level_ == ResourceType_Patient)
@@ -158,11 +282,24 @@
   {
     if (IsRetrieveChildrenMetadata(metadata))
     {
-      throw OrthancException(ErrorCode_BadParameterType);
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
     }
     else
     {
       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	Tue May 07 12:53:12 2024 +0200
+++ b/OrthancServer/Sources/Database/FindRequest.h	Tue May 07 18:44:53 2024 +0200
@@ -166,13 +166,20 @@
     LabelsConstraint                     labelsContraint_;
     std::deque<Ordering*>                ordering_;             // The ordering criteria (note: the order is important !)
 
-    bool                                 retrieveMainDicomTags_;
-    bool                                 retrieveMetadata_;
+    bool                                 retrieveMainDicomTagsPatients_;
+    bool                                 retrieveMainDicomTagsStudies_;
+    bool                                 retrieveMainDicomTagsSeries_;
+    bool                                 retrieveMainDicomTagsInstances_;
+    bool                                 retrieveMetadataPatients_;
+    bool                                 retrieveMetadataStudies_;
+    bool                                 retrieveMetadataSeries_;
+    bool                                 retrieveMetadataInstances_;
     bool                                 retrieveLabels_;
     bool                                 retrieveAttachments_;
     bool                                 retrieveParentIdentifier_;
     bool                                 retrieveChildrenIdentifiers_;
     std::set<MetadataType>               retrieveChildrenMetadata_;
+    std::set<FileContentType>            retrieveAttachmentOfOneInstance_;
 
   public:
     explicit FindRequest(ResourceType level);
@@ -259,25 +266,15 @@
       return labelsContraint_;
     }
 
-    void SetRetrieveMetadata(bool retrieve)
-    {
-      retrieveMetadata_ = retrieve;
-    }
+    void SetRetrieveMainDicomTags(ResourceType level,
+                                  bool retrieve);
 
-    bool IsRetrieveMainDicomTags() const
-    {
-      return retrieveMainDicomTags_;
-    }
+    bool IsRetrieveMainDicomTags(ResourceType level) const;
 
-    void SetRetrieveMainDicomTags(bool retrieve)
-    {
-      retrieveMainDicomTags_ = retrieve;
-    }
+    void SetRetrieveMetadata(ResourceType level,
+                             bool retrieve);
 
-    bool IsRetrieveMetadata() const
-    {
-      return retrieveMetadata_;
-    }
+    bool IsRetrieveMetadata(ResourceType level) const;
 
     void SetRetrieveLabels(bool retrieve)
     {
@@ -324,5 +321,12 @@
     {
       return retrieveChildrenMetadata_;
     }
+
+    void AddRetrieveAttachmentOfOneInstance(FileContentType type);
+
+    const std::set<FileContentType>& GetRetrieveAttachmentOfOneInstance() const
+    {
+      return retrieveAttachmentOfOneInstance_;
+    }
   };
 }
--- a/OrthancServer/Sources/Database/FindResponse.cpp	Tue May 07 12:53:12 2024 +0200
+++ b/OrthancServer/Sources/Database/FindResponse.cpp	Tue May 07 18:44:53 2024 +0200
@@ -22,16 +22,17 @@
 
 #include "FindResponse.h"
 
-#include "../../../OrthancFramework/Sources/DicomFormat/DicomInstanceHasher.h"
+#include "../../../OrthancFramework/Sources/DicomFormat/DicomArray.h"
 #include "../../../OrthancFramework/Sources/OrthancException.h"
 #include "../../../OrthancFramework/Sources/SerializationToolbox.h"
 
+#include <boost/lexical_cast.hpp>
 #include <cassert>
 
 
 namespace Orthanc
 {
-  class FindResponse::Resource::DicomValue : public boost::noncopyable
+  class FindResponse::MainDicomTagsAtLevel::DicomValue : public boost::noncopyable
   {
   public:
     enum ValueType
@@ -74,8 +75,18 @@
   };
 
 
-  void FindResponse::Resource::AddNullDicomTag(uint16_t group,
-                                               uint16_t element)
+  FindResponse::MainDicomTagsAtLevel::~MainDicomTagsAtLevel()
+  {
+    for (MainDicomTags::iterator it = mainDicomTags_.begin(); it != mainDicomTags_.end(); ++it)
+    {
+      assert(it->second != NULL);
+      delete it->second;
+    }
+  }
+
+
+  void FindResponse::MainDicomTagsAtLevel::AddNullDicomTag(uint16_t group,
+                                                           uint16_t element)
   {
     const DicomTag tag(group, element);
 
@@ -90,9 +101,9 @@
   }
 
 
-  void FindResponse::Resource::AddStringDicomTag(uint16_t group,
-                                                 uint16_t element,
-                                                 const std::string& value)
+  void FindResponse::MainDicomTagsAtLevel::AddStringDicomTag(uint16_t group,
+                                                             uint16_t element,
+                                                             const std::string& value)
   {
     const DicomTag tag(group, element);
 
@@ -107,7 +118,7 @@
   }
 
 
-  void FindResponse::Resource::GetMainDicomTags(DicomMap& target) const
+  void FindResponse::MainDicomTagsAtLevel::Export(DicomMap& target) const
   {
     for (MainDicomTags::const_iterator it = mainDicomTags_.begin(); it != mainDicomTags_.end(); ++it)
     {
@@ -130,11 +141,11 @@
   }
 
 
-  void FindResponse::ChildrenAtLevel::AddIdentifier(const std::string& identifier)
+  void FindResponse::Resource::AddChildIdentifier(const std::string& identifier)
   {
-    if (identifiers_.find(identifier) == identifiers_.end())
+    if (childrenIdentifiers_.find(identifier) == childrenIdentifiers_.end())
     {
-      identifiers_.insert(identifier);
+      childrenIdentifiers_.insert(identifier);
     }
     else
     {
@@ -143,42 +154,26 @@
   }
 
 
-  FindResponse::ChildrenAtLevel& FindResponse::Resource::GetChildrenAtLevel(ResourceType level)
+  FindResponse::MainDicomTagsAtLevel& FindResponse::Resource::GetMainDicomTagsAtLevel(ResourceType level)
   {
+    if (!IsResourceLevelAboveOrEqual(level, level_))
+    {
+      throw OrthancException(ErrorCode_BadParameterType);
+    }
+
     switch (level)
     {
+      case ResourceType_Patient:
+        return mainDicomTagsPatient_;
+
       case ResourceType_Study:
-        if (level_ == ResourceType_Patient)
-        {
-          return childrenStudies_;
-        }
-        else
-        {
-          throw OrthancException(ErrorCode_BadParameterType);
-        }
+        return mainDicomTagsStudy_;
 
       case ResourceType_Series:
-        if (level_ == ResourceType_Patient ||
-            level_ == ResourceType_Study)
-        {
-          return childrenSeries_;
-        }
-        else
-        {
-          throw OrthancException(ErrorCode_BadParameterType);
-        }
+        return mainDicomTagsSeries_;
 
       case ResourceType_Instance:
-        if (level_ == ResourceType_Patient ||
-            level_ == ResourceType_Study ||
-            level_ == ResourceType_Series)
-        {
-          return childrenInstances_;
-        }
-        else
-        {
-          throw OrthancException(ErrorCode_BadParameterType);
-        }
+        return mainDicomTagsInstance_;
 
       default:
         throw OrthancException(ErrorCode_ParameterOutOfRange);
@@ -186,26 +181,59 @@
   }
 
 
-  void FindResponse::Resource::AddMetadata(MetadataType metadata,
+  void FindResponse::Resource::AddMetadata(ResourceType level,
+                                           MetadataType metadata,
                                            const std::string& value)
   {
-    if (metadata_.find(metadata) != metadata_.end())
+    std::map<MetadataType, std::string>& m = GetMetadata(level);
+
+    if (m.find(metadata) != m.end())
     {
       throw OrthancException(ErrorCode_BadSequenceOfCalls);  // Metadata already present
     }
     else
     {
-      metadata_[metadata] = value;
+      m[metadata] = value;
+    }
+  }
+
+
+  std::map<MetadataType, std::string>& FindResponse::Resource::GetMetadata(ResourceType level)
+  {
+    if (!IsResourceLevelAboveOrEqual(level, level_))
+    {
+      throw OrthancException(ErrorCode_BadParameterType);
+    }
+
+    switch (level)
+    {
+      case ResourceType_Patient:
+        return metadataPatient_;
+
+      case ResourceType_Study:
+        return metadataStudy_;
+
+      case ResourceType_Series:
+        return metadataSeries_;
+
+      case ResourceType_Instance:
+        return metadataInstance_;
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
     }
   }
 
 
   bool FindResponse::Resource::LookupMetadata(std::string& value,
+                                              ResourceType level,
                                               MetadataType metadata) const
   {
-    std::map<MetadataType, std::string>::const_iterator found = metadata_.find(metadata);
+    const std::map<MetadataType, std::string>& m = GetMetadata(level);
 
-    if (found == metadata_.end())
+    std::map<MetadataType, std::string>::const_iterator found = m.find(metadata);
+
+    if (found == m.end())
     {
       return false;
     }
@@ -217,16 +245,6 @@
   }
 
 
-  void FindResponse::Resource::ListMetadata(std::set<MetadataType>& target) const
-  {
-    target.clear();
-
-    for (std::map<MetadataType, std::string>::const_iterator it = metadata_.begin(); it != metadata_.end(); ++it)
-    {
-      target.insert(it->first);
-    }
-  }
-
   const std::string& FindResponse::Resource::GetParentIdentifier() const
   {
     if (level_ == ResourceType_Patient)
@@ -246,12 +264,6 @@
 
   FindResponse::Resource::~Resource()
   {
-    for (MainDicomTags::iterator it = mainDicomTags_.begin(); it != mainDicomTags_.end(); ++it)
-    {
-      assert(it->second != NULL);
-      delete it->second;
-    }
-
     for (ChildrenMetadata::iterator it = childrenMetadata_.begin(); it != childrenMetadata_.end(); ++it)
     {
       assert(it->second != NULL);
@@ -362,6 +374,36 @@
   }
 
 
+  void FindResponse::Resource::AddAttachmentOfOneInstance(const FileInfo& info)
+  {
+    if (attachmentOfOneInstance_.find(info.GetContentType()) == attachmentOfOneInstance_.end())
+    {
+      attachmentOfOneInstance_[info.GetContentType()] = info;
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+  }
+
+
+  bool FindResponse::Resource::LookupAttachmentOfOneInstance(FileInfo& target,
+                                                             FileContentType type) const
+  {
+    std::map<FileContentType, FileInfo>::const_iterator found = attachmentOfOneInstance_.find(type);
+
+    if (found == attachmentOfOneInstance_.end())
+    {
+      return false;
+    }
+    else
+    {
+      target = found->second;
+      return true;
+    }
+  }
+
+
   SeriesStatus FindResponse::Resource::GetSeriesStatus(uint32_t& expectedNumberOfInstances) const
   {
     if (level_ != ResourceType_Series)
@@ -370,7 +412,7 @@
     }
 
     std::string s;
-    if (!LookupMetadata(s, MetadataType_Series_ExpectedNumberOfInstances) ||
+    if (!LookupMetadata(s, ResourceType_Series, MetadataType_Series_ExpectedNumberOfInstances) ||
         !SerializationToolbox::ParseUnsignedInteger32(expectedNumberOfInstances, s))
     {
       return SeriesStatus_Unknown;
@@ -422,7 +464,8 @@
 
 
   void FindResponse::Resource::Expand(Json::Value& target,
-                                      const FindRequest& request) const
+                                      const FindRequest& request,
+                                      bool includeAllMetadata) const
   {
     /**
 
@@ -444,35 +487,15 @@
 
      **/
 
+    /**
+     * This method closely follows "SerializeExpandedResource()" in
+     * "ServerContext.cpp" from Orthanc 1.12.3.
+     **/
+
     target = Json::objectValue;
     target["ID"] = identifier_;
     target["Type"] = GetResourceTypeText(level_, false, true);
 
-    if (request.IsRetrieveMetadata())
-    {
-      Json::Value metadata = Json::objectValue;
-
-      for (std::map<MetadataType, std::string>::const_iterator
-             it = metadata_.begin(); it != metadata_.end(); ++it)
-      {
-        metadata[EnumerationToString(it->first)] = it->second;
-      }
-
-      target["Metadata"] = metadata;
-    }
-
-    if (request.IsRetrieveLabels())
-    {
-      Json::Value labels = Json::arrayValue;
-
-      for (std::set<std::string>::const_iterator it = labels_.begin(); it != labels_.end(); ++it)
-      {
-        labels.append(*it);
-      }
-
-      target["Labels"] = labels;
-    }
-
     if (request.IsRetrieveParentIdentifier())
     {
       switch (level_)
@@ -499,10 +522,9 @@
 
     if (request.IsRetrieveChildrenIdentifiers())
     {
-      Json::Value c = Json::arrayValue;
+      const std::set<std::string>& children = GetChildrenIdentifiers();
 
-      const std::set<std::string>& children = GetChildrenAtLevel(GetChildResourceType(level_)).GetIdentifiers();
-
+      Json::Value c = Json::arrayValue;
       for (std::set<std::string>::const_iterator
              it = children.begin(); it != children.end(); ++it)
       {
@@ -527,6 +549,269 @@
           throw OrthancException(ErrorCode_InternalError);
       }
     }
+
+    if (request.IsRetrieveMetadata(level_))
+    {
+      switch (level_)
+      {
+        case ResourceType_Patient:
+        case ResourceType_Study:
+          break;
+
+        case ResourceType_Series:
+        {
+          uint32_t expectedNumberOfInstances;
+          SeriesStatus status = GetSeriesStatus(expectedNumberOfInstances);
+
+          target["Status"] = EnumerationToString(status);
+
+          if (status == SeriesStatus_Unknown)
+          {
+            target["ExpectedNumberOfInstances"] = Json::nullValue;
+          }
+          else
+          {
+            target["ExpectedNumberOfInstances"] = Json::nullValue;
+          }
+
+          break;
+        }
+
+        case ResourceType_Instance:
+        {
+          // TODO-FIND
+          break;
+        }
+
+        default:
+          throw OrthancException(ErrorCode_InternalError);
+      }
+    }
+
+    if (request.IsRetrieveLabels())
+    {
+      Json::Value labels = Json::arrayValue;
+
+      for (std::set<std::string>::const_iterator it = labels_.begin(); it != labels_.end(); ++it)
+      {
+        labels.append(*it);
+      }
+
+      target["Labels"] = labels;
+    }
+
+    if (request.IsRetrieveMetadata(level_) &&
+        includeAllMetadata)
+    {
+      const std::map<MetadataType, std::string>& m = GetMetadata(level_);
+
+      Json::Value metadata = Json::objectValue;
+
+      for (std::map<MetadataType, std::string>::const_iterator it = m.begin(); it != m.end(); ++it)
+      {
+        metadata[EnumerationToString(it->first)] = it->second;
+      }
+
+      target["Metadata"] = metadata;
+    }
+  }
+
+
+  static void DebugDicomMap(Json::Value& target,
+                            const DicomMap& m)
+  {
+    DicomArray a(m);
+    for (size_t i = 0; i < a.GetSize(); i++)
+    {
+      if (a.GetElement(i).GetValue().IsNull())
+      {
+        target[a.GetElement(i).GetTag().Format()] = Json::nullValue;
+      }
+      else if (a.GetElement(i).GetValue().IsString())
+      {
+        target[a.GetElement(i).GetTag().Format()] = a.GetElement(i).GetValue().GetContent();
+      }
+      else
+      {
+        throw OrthancException(ErrorCode_InternalError);
+      }
+    }
+  }
+
+
+  static void DebugMetadata(Json::Value& target,
+                            const std::map<MetadataType, std::string>& m)
+  {
+    target = Json::objectValue;
+
+    for (std::map<MetadataType, std::string>::const_iterator it = m.begin(); it != m.end(); ++it)
+    {
+      target[EnumerationToString(it->first)] = it->second;
+    }
+  }
+
+
+  static void DebugAddAttachment(Json::Value& target,
+                                 const FileInfo& info)
+  {
+    Json::Value u = Json::arrayValue;
+    u.append(info.GetUuid());
+    u.append(info.GetUncompressedSize());
+    target[EnumerationToString(info.GetContentType())] = u;
+  }
+
+  void FindResponse::Resource::DebugExport(Json::Value& target,
+                                           const FindRequest& request) const
+  {
+    target = Json::objectValue;
+
+    target["Level"] = EnumerationToString(GetLevel());
+    target["ID"] = GetIdentifier();
+
+    if (request.IsRetrieveParentIdentifier())
+    {
+      target["ParentID"] = GetParentIdentifier();
+    }
+
+    if (request.IsRetrieveMainDicomTags(ResourceType_Patient))
+    {
+      DicomMap m;
+      GetMainDicomTags(m, ResourceType_Patient);
+      DebugDicomMap(target["Patient"]["MainDicomTags"], m);
+    }
+
+    if (request.IsRetrieveMetadata(ResourceType_Patient))
+    {
+      DebugMetadata(target["Patient"]["Metadata"], GetMetadata(ResourceType_Patient));
+    }
+
+    if (request.GetLevel() != ResourceType_Patient)
+    {
+      if (request.IsRetrieveMainDicomTags(ResourceType_Study))
+      {
+        DicomMap m;
+        GetMainDicomTags(m, ResourceType_Study);
+        DebugDicomMap(target["Study"]["MainDicomTags"], m);
+      }
+
+      if (request.IsRetrieveMetadata(ResourceType_Study))
+      {
+        DebugMetadata(target["Study"]["Metadata"], GetMetadata(ResourceType_Study));
+      }
+    }
+
+    if (request.GetLevel() != ResourceType_Patient &&
+        request.GetLevel() != ResourceType_Study)
+    {
+      if (request.IsRetrieveMainDicomTags(ResourceType_Series))
+      {
+        DicomMap m;
+        GetMainDicomTags(m, ResourceType_Series);
+        DebugDicomMap(target["Series"]["MainDicomTags"], m);
+      }
+
+      if (request.IsRetrieveMetadata(ResourceType_Series))
+      {
+        DebugMetadata(target["Series"]["Metadata"], GetMetadata(ResourceType_Series));
+      }
+    }
+
+    if (request.GetLevel() != ResourceType_Patient &&
+        request.GetLevel() != ResourceType_Study &&
+        request.GetLevel() != ResourceType_Series)
+    {
+      if (request.IsRetrieveMainDicomTags(ResourceType_Instance))
+      {
+        DicomMap m;
+        GetMainDicomTags(m, ResourceType_Instance);
+        DebugDicomMap(target["Instance"]["MainDicomTags"], m);
+      }
+
+      if (request.IsRetrieveMetadata(ResourceType_Instance))
+      {
+        DebugMetadata(target["Instance"]["Metadata"], GetMetadata(ResourceType_Instance));
+      }
+    }
+
+    if (request.IsRetrieveChildrenIdentifiers())
+    {
+      Json::Value v = Json::arrayValue;
+      for (std::set<std::string>::const_iterator it = childrenIdentifiers_.begin();
+           it != childrenIdentifiers_.end(); ++it)
+      {
+        v.append(*it);
+      }
+      target["Children"] = v;
+    }
+
+    if (request.IsRetrieveLabels())
+    {
+      Json::Value v = Json::arrayValue;
+      for (std::set<std::string>::const_iterator it = labels_.begin();
+           it != labels_.end(); ++it)
+      {
+        v.append(*it);
+      }
+      target["Labels"] = v;
+    }
+
+    if (request.IsRetrieveAttachments())
+    {
+      Json::Value v = Json::objectValue;
+      for (std::map<FileContentType, FileInfo>::const_iterator it = attachments_.begin();
+           it != attachments_.end(); ++it)
+      {
+        if (it->first != it->second.GetContentType())
+        {
+          throw OrthancException(ErrorCode_DatabasePlugin);
+        }
+        else
+        {
+          DebugAddAttachment(v, it->second);
+        }
+      }
+      target["Attachments"] = v;
+    }
+
+    for (std::set<MetadataType>::const_iterator it = request.GetRetrieveChildrenMetadata().begin();
+         it != request.GetRetrieveChildrenMetadata().end(); ++it)
+    {
+      std::list<std::string> l;
+      if (LookupChildrenMetadata(l, *it))
+      {
+        Json::Value v = Json::arrayValue;
+        for (std::list<std::string>::const_iterator it2 = l.begin(); it2 != l.end(); ++it2)
+        {
+          v.append(*it2);
+        }
+        target["ChildrenMetadata"][EnumerationToString(*it)] = v;
+      }
+      else
+      {
+        throw OrthancException(ErrorCode_DatabasePlugin);
+      }
+    }
+
+    for (std::set<FileContentType>::const_iterator it = request.GetRetrieveAttachmentOfOneInstance().begin();
+         it != request.GetRetrieveAttachmentOfOneInstance().end(); ++it)
+    {
+      FileInfo info;
+      if (LookupAttachmentOfOneInstance(info, *it))
+      {
+        if (info.GetContentType() == *it)
+        {
+          DebugAddAttachment(target["AttachmentOfOneInstance"], info);
+        }
+        else
+        {
+          throw OrthancException(ErrorCode_DatabasePlugin);
+        }
+      }
+      else
+      {
+        throw OrthancException(ErrorCode_DatabasePlugin);
+      }
+    }
   }
 
 
--- a/OrthancServer/Sources/Database/FindResponse.h	Tue May 07 12:53:12 2024 +0200
+++ b/OrthancServer/Sources/Database/FindResponse.h	Tue May 07 18:44:53 2024 +0200
@@ -41,18 +41,28 @@
   class FindResponse : public boost::noncopyable
   {
   private:
-    class ChildrenAtLevel : public boost::noncopyable
+    class MainDicomTagsAtLevel : public boost::noncopyable
     {
     private:
-      std::set<std::string>  identifiers_;
+      class DicomValue;
+
+      typedef std::map<DicomTag, DicomValue*>  MainDicomTags;
+
+      MainDicomTags  mainDicomTags_;
 
     public:
-      void AddIdentifier(const std::string& identifier);
+      ~MainDicomTagsAtLevel();
+
+      void AddStringDicomTag(uint16_t group,
+                             uint16_t element,
+                             const std::string& value);
 
-      const std::set<std::string>& GetIdentifiers() const
-      {
-        return identifiers_;
-      }
+      // The "Null" value could be used in the future to indicate a
+      // value that is not available, typically a new "ExtraMainDicomTag"
+      void AddNullDicomTag(uint16_t group,
+                           uint16_t element);
+
+      void Export(DicomMap& target) const;
     };
 
 
@@ -60,28 +70,30 @@
     class Resource : public boost::noncopyable
     {
     private:
-      class DicomValue;
-
-      typedef std::map<DicomTag, DicomValue*>                  MainDicomTags;
       typedef std::map<MetadataType, std::list<std::string>*>  ChildrenMetadata;
 
       ResourceType                          level_;
       std::string                           identifier_;
       std::unique_ptr<std::string>          parentIdentifier_;
-      MainDicomTags                         mainDicomTags_;
-      ChildrenAtLevel                       childrenStudies_;
-      ChildrenAtLevel                       childrenSeries_;
-      ChildrenAtLevel                       childrenInstances_;
+      MainDicomTagsAtLevel                  mainDicomTagsPatient_;
+      MainDicomTagsAtLevel                  mainDicomTagsStudy_;
+      MainDicomTagsAtLevel                  mainDicomTagsSeries_;
+      MainDicomTagsAtLevel                  mainDicomTagsInstance_;
+      std::map<MetadataType, std::string>   metadataPatient_;
+      std::map<MetadataType, std::string>   metadataStudy_;
+      std::map<MetadataType, std::string>   metadataSeries_;
+      std::map<MetadataType, std::string>   metadataInstance_;
+      std::set<std::string>                 childrenIdentifiers_;
       std::set<std::string>                 labels_;      
-      std::map<MetadataType, std::string>   metadata_;
       std::map<FileContentType, FileInfo>   attachments_;
       ChildrenMetadata                      childrenMetadata_;
+      std::map<FileContentType, FileInfo>   attachmentOfOneInstance_;
 
-      ChildrenAtLevel& GetChildrenAtLevel(ResourceType level);
+      MainDicomTagsAtLevel& GetMainDicomTagsAtLevel(ResourceType level);
 
-      const ChildrenAtLevel& GetChildrenAtLevel(ResourceType level) const
+      const MainDicomTagsAtLevel& GetMainDicomTagsAtLevel(ResourceType level) const
       {
-        return const_cast<Resource&>(*this).GetChildrenAtLevel(level);
+        return const_cast<Resource&>(*this).GetMainDicomTagsAtLevel(level);
       }
 
     public:
@@ -110,26 +122,47 @@
 
       bool HasParentIdentifier() const;
 
-      void AddStringDicomTag(uint16_t group,
+      void AddStringDicomTag(ResourceType level,
+                             uint16_t group,
                              uint16_t element,
-                             const std::string& value);
+                             const std::string& value)
+      {
+        GetMainDicomTagsAtLevel(level).AddStringDicomTag(group, element, value);
+      }
 
-      // The "Null" value could be used in the future to indicate a
-      // value that is not available, typically a new "ExtraMainDicomTag"
-      void AddNullDicomTag(uint16_t group,
-                           uint16_t element);
+      void AddNullDicomTag(ResourceType level,
+                           uint16_t group,
+                           uint16_t element)
+      {
+        GetMainDicomTagsAtLevel(level).AddNullDicomTag(group, element);
+      }
 
-      void GetMainDicomTags(DicomMap& target) const;
-
-      void AddChildIdentifier(ResourceType level,
-                              const std::string& childId)
+      void GetMainDicomTags(DicomMap& target,
+                            ResourceType level) const
       {
-        GetChildrenAtLevel(level).AddIdentifier(childId);
+        GetMainDicomTagsAtLevel(level).Export(target);
       }
 
-      const std::set<std::string>& GetChildrenIdentifiers(ResourceType level) const
+      void AddMetadata(ResourceType level,
+                       MetadataType metadata,
+                       const std::string& value);
+
+      std::map<MetadataType, std::string>& GetMetadata(ResourceType level);
+
+      const std::map<MetadataType, std::string>& GetMetadata(ResourceType level) const
       {
-        return const_cast<Resource&>(*this).GetChildrenAtLevel(level).GetIdentifiers();
+        return const_cast<Resource&>(*this).GetMetadata(level);
+      }
+
+      bool LookupMetadata(std::string& value,
+                          ResourceType level,
+                          MetadataType metadata) const;
+
+      void AddChildIdentifier(const std::string& childId);
+
+      const std::set<std::string>& GetChildrenIdentifiers() const
+      {
+        return childrenIdentifiers_;
       }
 
       void AddLabel(const std::string& label);
@@ -144,44 +177,35 @@
         return labels_;
       }
 
-      void AddMetadata(MetadataType metadata,
-                       const std::string& value);
-
-      std::map<MetadataType, std::string>& GetMetadata()
-      {
-        return metadata_;
-      }
-
-      const std::map<MetadataType, std::string>& GetMetadata() const
-      {
-        return metadata_;
-      }
-
-      bool HasMetadata(MetadataType metadata) const
-      {
-        return metadata_.find(metadata) != metadata_.end();
-      }
-
-      bool LookupMetadata(std::string& value,
-                          MetadataType metadata) const;
-
-      void ListMetadata(std::set<MetadataType>& metadata) const;
-
       void AddAttachment(const FileInfo& attachment);
 
       bool LookupAttachment(FileInfo& target,
                             FileContentType type) const;
 
+      const std::map<FileContentType, FileInfo>& GetAttachments() const
+      {
+        return attachments_;
+      }
+
       void AddChildrenMetadata(MetadataType metadata,
                                const std::list<std::string>& values);
 
       bool LookupChildrenMetadata(std::list<std::string>& values,
                                   MetadataType metadata) const;
 
+      void AddAttachmentOfOneInstance(const FileInfo& info);
+
+      bool LookupAttachmentOfOneInstance(FileInfo& target,
+                                         FileContentType type) const;
+
       SeriesStatus GetSeriesStatus(uint32_t& expecterNumberOfInstances) const;
 
       void Expand(Json::Value& target,
-                  const FindRequest& request) const;
+                  const FindRequest& request,
+                  bool includeAllMetadata) const;
+
+      void DebugExport(Json::Value& target,
+                       const FindRequest& request) const;
     };
 
   private:
--- a/OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp	Tue May 07 12:53:12 2024 +0200
+++ b/OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp	Tue May 07 18:44:53 2024 +0200
@@ -3928,15 +3928,15 @@
       throw OrthancException(ErrorCode_InternalError);
     }
 
-    if (request.IsRetrieveMainDicomTags())
+    if (request.IsRetrieveMainDicomTags(request.GetLevel()))
     {
-      resource.GetMainDicomTags(tags_);
+      resource.GetMainDicomTags(tags_, request.GetLevel());
     }
 
     if (request.IsRetrieveChildrenIdentifiers())
     {
-      const std::set<std::string>& s = resource.GetChildrenIdentifiers(GetChildResourceType(request.GetLevel()));
-      for (std::set<std::string>::const_iterator it = s.begin(); it != s.end(); ++it)
+      const std::set<std::string>& children = resource.GetChildrenIdentifiers();
+      for (std::set<std::string>::const_iterator it = children.begin(); it != children.end(); ++it)
       {
         childrenIds_.push_back(*it);
       }
@@ -3947,36 +3947,36 @@
       parentId_ = resource.GetParentIdentifier();
     }
 
-    if (request.IsRetrieveMetadata())
+    if (request.IsRetrieveMetadata(request.GetLevel()))
     {
-      metadata_ = resource.GetMetadata();
+      metadata_ = resource.GetMetadata(request.GetLevel());
       std::string value;
-      if (resource.LookupMetadata(value, MetadataType_MainDicomTagsSignature))
+      if (resource.LookupMetadata(value, request.GetLevel(), MetadataType_MainDicomTagsSignature))
       {
         mainDicomTagsSignature_ = value;
       }
-      if (resource.LookupMetadata(value, MetadataType_AnonymizedFrom))
+      if (resource.LookupMetadata(value, request.GetLevel(), MetadataType_AnonymizedFrom))
       {
         anonymizedFrom_ = value;
       }
-      if (resource.LookupMetadata(value, MetadataType_ModifiedFrom))
+      if (resource.LookupMetadata(value, request.GetLevel(), MetadataType_ModifiedFrom))
       {
         modifiedFrom_ = value;
       }
-      if (resource.LookupMetadata(value, MetadataType_LastUpdate))
+      if (resource.LookupMetadata(value, request.GetLevel(), MetadataType_LastUpdate))
       {
         lastUpdate_ = value;
       }
       if (request.GetLevel() == ResourceType_Series)
       {
-        if (resource.LookupMetadata(value, MetadataType_Series_ExpectedNumberOfInstances))
+        if (resource.LookupMetadata(value, request.GetLevel(), MetadataType_Series_ExpectedNumberOfInstances))
         {
           expectedNumberOfInstances_ = boost::lexical_cast<int>(value);
         }
       }
       if (request.GetLevel() == ResourceType_Instance)
       {
-        if (resource.LookupMetadata(value, MetadataType_Instance_IndexInSeries))
+        if (resource.LookupMetadata(value, request.GetLevel(), MetadataType_Instance_IndexInSeries))
         {
           indexInSeries_ = boost::lexical_cast<int>(value);
         }
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp	Tue May 07 12:53:12 2024 +0200
+++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp	Tue May 07 18:44:53 2024 +0200
@@ -245,8 +245,8 @@
       if (expand)
       {
         // compatibility with default expand option
-        request.SetRetrieveMainDicomTags(true);
-        request.SetRetrieveMetadata(true);
+        request.SetRetrieveMainDicomTags(resourceType, true);
+        request.SetRetrieveMetadata(resourceType, true);
         request.SetRetrieveLabels(true);
 
         if (resourceType == ResourceType_Series)
--- a/OrthancServer/Sources/ServerContext.cpp	Tue May 07 12:53:12 2024 +0200
+++ b/OrthancServer/Sources/ServerContext.cpp	Tue May 07 18:44:53 2024 +0200
@@ -2732,11 +2732,11 @@
     {
       expandFlags = static_cast<ExpandResourceFlags>(expandFlags | ExpandResourceFlags_IncludeChildren);
     }
-    if (request.IsRetrieveMetadata())
+    if (request.IsRetrieveMetadata(request.GetLevel()))
     {
       expandFlags = static_cast<ExpandResourceFlags>(expandFlags | ExpandResourceFlags_IncludeAllMetadata | ExpandResourceFlags_IncludeMetadata );
     }
-    if (request.IsRetrieveMainDicomTags())
+    if (request.IsRetrieveMainDicomTags(request.GetLevel()))
     {
       expandFlags = static_cast<ExpandResourceFlags>(expandFlags | ExpandResourceFlags_IncludeMainDicomTags);
     }