changeset 5301:f26ed26a7793 am-experimental

merge
author Alain Mazy <am@osimis.io>
date Wed, 24 May 2023 08:56:41 +0200
parents 7d913ee2f665 (diff) 76dc541c5dda (current diff)
children 9504de20d43d
files NEWS OrthancFramework/Sources/Enumerations.cpp OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp OrthancServer/Sources/ServerContext.cpp
diffstat 14 files changed, 332 insertions(+), 74 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Mon May 22 17:58:53 2023 +0200
+++ b/NEWS	Wed May 24 08:56:41 2023 +0200
@@ -1,6 +1,11 @@
 Pending changes in the mainline
 ===============================
 
+REST API
+--------
+
+* New URI /instances/{id}/file-until-pixel-data
+
 Maintenance
 -----------
 
@@ -14,6 +19,10 @@
   (https://discourse.orthanc-server.org/t/issue-with-deleting-incoming-dicoms-when-maximumstoragesize-is-reached/3510)
 * When deleting a resource, its parents LastUpdate metadata are now updated.
 
+* WIP: new dicomWeb Json format for some of the Rest API routes.
+* WIP: new 'include' get arguments for some of the Rest API routes to define
+  the content of the response.  Useful since the dicomWeb format is very slow
+  to serialize.
 
 Version 1.12.0 (2023-04-14)
 ===========================
--- a/OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp	Mon May 22 17:58:53 2023 +0200
+++ b/OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp	Wed May 24 08:56:41 2023 +0200
@@ -41,6 +41,9 @@
 #include "../Logging.h"
 #include "../Toolbox.h"
 #include "../OrthancException.h"
+#include "DicomWebJsonVisitor.h"
+#include "ParsedDicomFile.h"
+#include <boost/date_time/posix_time/posix_time.hpp>
 
 #if ORTHANC_SANDBOXED == 0
 #  include "../TemporaryFile.h"
@@ -1444,6 +1447,16 @@
 
     result.clear();
 
+    if (format == DicomToJsonFormat_DicomWeb)
+    {
+      DicomWebJsonVisitor visitor;
+
+      ParsedDicomFile dicom(values, Encoding_Utf8, true);
+      dicom.Apply(visitor);
+      result = visitor.GetResult();
+      return;
+    }
+
     for (DicomMap::Content::const_iterator 
            it = values.content_.begin(); it != values.content_.end(); ++it)
     {
--- a/OrthancFramework/Sources/Enumerations.cpp	Mon May 22 17:58:53 2023 +0200
+++ b/OrthancFramework/Sources/Enumerations.cpp	Wed May 24 08:56:41 2023 +0200
@@ -1184,6 +1184,9 @@
       case DicomToJsonFormat_Short:
         return "Short";
 
+      case DicomToJsonFormat_DicomWeb:
+        return "DicomWeb";
+
       default:
         throw OrthancException(ErrorCode_ParameterOutOfRange);
     }
@@ -1885,6 +1888,10 @@
     {
       return DicomToJsonFormat_Human;
     }
+    else if (format == "DicomWeb")
+    {
+      return DicomToJsonFormat_DicomWeb;
+    }
     else
     {
       throw OrthancException(ErrorCode_ParameterOutOfRange);
--- a/OrthancFramework/Sources/Enumerations.h	Mon May 22 17:58:53 2023 +0200
+++ b/OrthancFramework/Sources/Enumerations.h	Wed May 24 08:56:41 2023 +0200
@@ -581,7 +581,8 @@
   {
     DicomToJsonFormat_Full,
     DicomToJsonFormat_Short,
-    DicomToJsonFormat_Human
+    DicomToJsonFormat_Human,
+    DicomToJsonFormat_DicomWeb
   };
 
   enum DicomToJsonFlags
--- a/OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp	Mon May 22 17:58:53 2023 +0200
+++ b/OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp	Wed May 24 08:56:41 2023 +0200
@@ -3220,7 +3220,129 @@
   m.FromDicomWeb(v);
 }
 
-
+TEST(DicomMap, MainDicomTagsDicomWebJson)
+{
+  std::unique_ptr<ParsedDicomFile> dicom;
+
+  {
+    std::string source = "{"
+      "\"0008,0005\" : {"
+         "\"Name\" : \"SpecificCharacterSet\","
+         "\"Type\" : \"String\","
+         "\"Value\" : \"ISO_IR 192\""
+      "},"
+      "\"0008,0008\" : {"
+         "\"Name\" : \"ImageType\","
+         "\"Type\" : \"String\","
+         "\"Value\" : \"DERIVED\\\\PRIMARY\\\\AXIAL\""
+      "},"
+      "\"0008,0016\" : {"
+         "\"Name\" : \"SOPClassUID\","
+         "\"Type\" : \"String\","
+         "\"Value\" : \"1.2.840.10008.5.1.4.1.1.2\""
+      "},"
+      "\"0008,0018\" : {"
+         "\"Name\" : \"SOPInstanceUID\","
+         "\"Type\" : \"String\","
+         "\"Value\" : \"1.2.276.0.7230010.3.1.4.1215942821.4756.1664833117.11987\""
+      "},"
+      "\"0018,0050\" : {"
+         "\"Name\" : \"SliceThickness\","
+         "\"Type\" : \"String\","
+         "\"Value\" : \"0.19816064757161\""
+      "},"
+      "\"0020,0013\" : {"
+         "\"Name\" : \"InstanceNumber\","
+         "\"Type\" : \"String\","
+         "\"Value\" : \"0\""
+      "},"
+      "\"0020,0032\" : {"
+         "\"Name\" : \"ImagePositionPatient\","
+         "\"Type\" : \"String\","
+         "\"Value\" : \"-79.462419676214\\\\-79.462419676214\\\\-59.150953300125\""
+      "},"
+      "\"0020,0037\" : {"
+         "\"Name\" : \"ImageOrientationPatient\","
+         "\"Type\" : \"String\","
+         "\"Value\" : \"1\\\\-0\\\\0\\\\-0\\\\1\\\\-0\""
+      "},"
+      "\"0028,0004\" : {"
+         "\"Name\" : \"PhotometricInterpretation\","
+         "\"Type\" : \"String\","
+         "\"Value\" : \"MONOCHROME2\""
+      "},"
+      "\"0028,0010\" : {"
+         "\"Name\" : \"Rows\","
+         "\"Type\" : \"String\","
+         "\"Value\" : \"803\""
+      "},"
+      "\"0028,0011\" : {"
+         "\"Name\" : \"Columns\","
+         "\"Type\" : \"String\","
+         "\"Value\" : \"803\""
+      "},"
+      "\"0028,0030\" : {"
+         "\"Name\" : \"PixelSpacing\","
+         "\"Type\" : \"String\","
+         "\"Value\" : \"0.19816064757161\\\\0.19816064757161\""
+      "},"
+      "\"0028,0100\" : {"
+         "\"Name\" : \"BitsAllocated\","
+         "\"Type\" : \"String\","
+         "\"Value\" : \"16\""
+      "},"
+      "\"0028,0101\" : {"
+         "\"Name\" : \"BitsStored\","
+         "\"Type\" : \"String\","
+         "\"Value\" : \"16\""
+      "},"
+      "\"0028,0103\" : {"
+         "\"Name\" : \"PixelRepresentation\","
+         "\"Type\" : \"String\","
+         "\"Value\" : \"0\""
+      "},"
+      "\"0028,1050\" : {"
+         "\"Name\" : \"WindowCenter\","
+         "\"Type\" : \"String\","
+         "\"Value\" : \"1023.96875\""
+      "},"
+      "\"0028,1051\" : {"
+         "\"Name\" : \"WindowWidth\","
+         "\"Type\" : \"String\","
+         "\"Value\" : \"4095.9375\""
+      "},"
+      "\"0028,1052\" : {"
+         "\"Name\" : \"RescaleIntercept\","
+         "\"Type\" : \"String\","
+         "\"Value\" : \"-1024\""
+      "},"
+      "\"0028,1053\" : {"
+         "\"Name\" : \"RescaleSlope\","
+         "\"Type\" : \"String\","
+         "\"Value\" : \"0.0625\""
+      "}"
+   "}";
+
+    Json::Value v;
+    Orthanc::Toolbox::ReadJson(v, source);
+
+//    LOG(INFO) << source;
+//    LOG(INFO) << v.toStyledString();
+    Json::Value result = Json::objectValue;
+    DicomMap map;
+    map.FromDicomAsJson(v, false, true);
+
+// for profiling    for (int i=0; i < 100; ++i)
+    {
+      result = Json::objectValue;
+      FromDcmtkBridge::ToJson(result, map, Orthanc::DicomToJsonFormat_DicomWeb);
+    }
+
+    LOG(INFO) << result.toStyledString();
+    ASSERT_TRUE(result.isMember("00281053"));
+    ASSERT_DOUBLE_EQ(0.0625, result["00281053"]["Value"][0].asDouble());
+  }
+}
 
 
 #if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
--- a/OrthancServer/Plugins/Samples/MultitenantDicom/OrthancFrameworkDependencies.cpp	Mon May 22 17:58:53 2023 +0200
+++ b/OrthancServer/Plugins/Samples/MultitenantDicom/OrthancFrameworkDependencies.cpp	Wed May 24 08:56:41 2023 +0200
@@ -64,6 +64,7 @@
 #include "../../../../OrthancFramework/Sources/DicomNetworking/Internals/StoreScp.cpp"
 #include "../../../../OrthancFramework/Sources/DicomNetworking/RemoteModalityParameters.cpp"
 #include "../../../../OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp"
+#include "../../../../OrthancFramework/Sources/DicomParsing/DicomWebJsonVisitor.cpp"
 #include "../../../../OrthancFramework/Sources/DicomParsing/Internals/DicomFrameIndex.cpp"
 #include "../../../../OrthancFramework/Sources/DicomParsing/Internals/DicomImageDecoder.cpp"
 #include "../../../../OrthancFramework/Sources/DicomParsing/ParsedDicomFile.cpp"
--- a/OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp	Mon May 22 17:58:53 2023 +0200
+++ b/OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp	Wed May 24 08:56:41 2023 +0200
@@ -868,7 +868,8 @@
             LookupStringMetadata(target.mainDicomTagsSignature_, target.metadata_, MetadataType_MainDicomTagsSignature);
           }
 
-          if (expandFlags & ExpandResourceFlags_IncludeMainDicomTags)
+          if (expandFlags & ExpandResourceFlags_IncludeMainDicomTags
+              || expandFlags & ExpandResourceFlags_IncludeRequestedTags)
           {
             // read all tags from DB
             transaction.GetMainDicomTags(target.GetMainDicomTags(), internalId);
--- a/OrthancServer/Sources/Database/StatelessDatabaseOperations.h	Mon May 22 17:58:53 2023 +0200
+++ b/OrthancServer/Sources/Database/StatelessDatabaseOperations.h	Wed May 24 08:56:41 2023 +0200
@@ -115,11 +115,13 @@
     ExpandResourceFlags_IncludeChildren         = (1 << 1),
     ExpandResourceFlags_IncludeMainDicomTags    = (1 << 2),
     ExpandResourceFlags_IncludeLabels           = (1 << 3),
+    ExpandResourceFlags_IncludeRequestedTags    = (1 << 3),
 
     ExpandResourceFlags_Default = (ExpandResourceFlags_IncludeMetadata |
                                      ExpandResourceFlags_IncludeChildren |
                                      ExpandResourceFlags_IncludeMainDicomTags |
-                                     ExpandResourceFlags_IncludeLabels)
+                                     ExpandResourceFlags_IncludeLabels |
+                                     ExpandResourceFlags_IncludeRequestedTags)
   };
 
   class StatelessDatabaseOperations : public boost::noncopyable
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestApi.cpp	Mon May 22 17:58:53 2023 +0200
+++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestApi.cpp	Wed May 24 08:56:41 2023 +0200
@@ -489,7 +489,9 @@
   static const std::string GET_SIMPLIFY = "simplify";
   static const std::string GET_FULL = "full";
   static const std::string GET_SHORT = "short";
+  static const std::string GET_DICOM_WEB = "dicomWeb";
   static const std::string GET_REQUESTED_TAGS = "requestedTags";
+  static const std::string GET_INCLUDE = "include";
 
   static const std::string POST_SIMPLIFY = "Simplify";
   static const std::string POST_FULL = "Full";
@@ -506,6 +508,45 @@
     "report the DICOM tags in full format (tags indexed by their hexadecimal "
     "format, associated with their symbolic name and their value)";
 
+  ExpandResourceFlags OrthancRestApi::GetResponseContent(const RestApiGetCall& call,
+                                                         ExpandResourceFlags defaultContent)
+  {
+    if (call.HasArgument(GET_INCLUDE))
+    {
+      std::vector<std::string> includes;
+      Toolbox::TokenizeString(includes, call.GetArgument(GET_INCLUDE, ""), ',');
+
+      unsigned int result = static_cast<unsigned int>(ExpandResourceFlags_None);
+      for (size_t i = 0; i < includes.size(); ++i)
+      {
+        if (includes[i] == "MainDicomTags")
+        {
+          result |= static_cast<unsigned int>(ExpandResourceFlags_IncludeMainDicomTags);
+        }
+        else if (includes[i] == "RequestedTags")
+        {
+          result |= static_cast<unsigned int>(ExpandResourceFlags_IncludeRequestedTags);
+        }
+        else if (includes[i] == "Labels")
+        {
+          result |= static_cast<unsigned int>(ExpandResourceFlags_IncludeLabels);
+        }
+        else if (includes[i] == "Children")
+        {
+          result |= static_cast<unsigned int>(ExpandResourceFlags_IncludeChildren);
+        }
+        else if (includes[i] == "Metadata")
+        {
+          result |= static_cast<unsigned int>(ExpandResourceFlags_IncludeMetadata);
+        }
+      }
+
+      return static_cast<ExpandResourceFlags>(result);
+    }
+
+    return defaultContent;
+  }
+
 
   DicomToJsonFormat OrthancRestApi::GetDicomFormat(const RestApiGetCall& call,
                                                    DicomToJsonFormat defaultFormat)
@@ -522,6 +563,10 @@
     {
       return DicomToJsonFormat_Full;
     }
+    else if (call.HasArgument(GET_DICOM_WEB))
+    {
+      return DicomToJsonFormat_DicomWeb;
+    }
     else
     {
       return defaultFormat;
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestApi.h	Mon May 22 17:58:53 2023 +0200
+++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestApi.h	Wed May 24 08:56:41 2023 +0200
@@ -28,7 +28,7 @@
 #include "../../../OrthancFramework/Sources/RestApi/RestApi.h"
 #include "../ServerJobs/ThreadedSetOfInstancesJob.h"
 #include "../ServerEnumerations.h"
-
+#include "../Database/StatelessDatabaseOperations.h"
 #include <set>
 
 namespace Orthanc
@@ -147,6 +147,9 @@
     static DicomToJsonFormat GetDicomFormat(const Json::Value& body,
                                             DicomToJsonFormat defaultFormat);
 
+    static ExpandResourceFlags GetResponseContent(const RestApiGetCall& call,
+                                                  ExpandResourceFlags defaultContent);
+
     static void DocumentDicomFormat(RestApiGetCall& call,
                                     DicomToJsonFormat defaultFormat);
 
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp	Mon May 22 17:58:53 2023 +0200
+++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp	Wed May 24 08:56:41 2023 +0200
@@ -158,11 +158,11 @@
                                  *(mainDicomTags->second.get()),
                                  instanceId->second,
                                  dicomAsJson->second.get(),
-                                 level, format, requestedTags, allowStorageAccess);
+                                 level, format, requestedTags, ExpandResourceFlags_Default, allowStorageAccess);
         }
         else
         {
-          context.ExpandResource(expanded, *resource, level, format, requestedTags, allowStorageAccess);
+          context.ExpandResource(expanded, *resource, level, format, requestedTags, ExpandResourceFlags_Default, allowStorageAccess);
         }
 
         if (expanded.type() == Json::objectValue)
@@ -288,7 +288,7 @@
 
     Json::Value json;
     if (OrthancRestApi::GetContext(call).ExpandResource(
-          json, call.GetUriComponent("id", ""), resourceType, format, requestedTags, true /* allowStorageAccess */))
+          json, call.GetUriComponent("id", ""), resourceType, format, requestedTags, ExpandResourceFlags_Default, true /* allowStorageAccess */))
     {
       call.GetOutput().AnswerJson(json);
     }
@@ -421,6 +421,30 @@
   }
 
 
+  static void GetInstanceFileUntiPixelData(RestApiGetCall& call)
+  {
+    if (call.IsDocumentation())
+    {
+      call.GetDocumentation()
+        .SetTag("Instances")
+        .SetSummary("Download DICOM Header (without the pixel data)")
+        .SetDescription("Download one DICOM instance header")
+        .SetUriArgument("id", "Orthanc identifier of the DICOM instance of interest")
+        .SetHttpHeader("Accept", "This HTTP header can be set to retrieve the DICOM instance in DICOMweb format")
+        .AddAnswerType(MimeType_Dicom, "The DICOM instance");
+      return;
+    }
+
+    ServerContext& context = OrthancRestApi::GetContext(call);
+
+    std::string publicId = call.GetUriComponent("id", "");
+
+    std::string buffer;
+    context.ReadDicomUntilPixelData(buffer, publicId);
+    call.GetOutput().AnswerBuffer(buffer, MimeType_Binary);
+  }
+
+
   static void ExportInstanceFile(RestApiPostCall& call)
   {
     if (call.IsDocumentation())
@@ -3355,12 +3379,13 @@
     Json::Value result = Json::arrayValue;
 
     const DicomToJsonFormat format = OrthancRestApi::GetDicomFormat(call, DicomToJsonFormat_Human);
+    ExpandResourceFlags expandResource = OrthancRestApi::GetResponseContent(call, ExpandResourceFlags_Default);
 
     for (std::list<std::string>::const_iterator
            it = a.begin(); it != a.end(); ++it)
     {
       Json::Value resource;
-      if (OrthancRestApi::GetContext(call).ExpandResource(resource, *it, end, format, requestedTags, true /* allowStorageAccess */))
+      if (OrthancRestApi::GetContext(call).ExpandResource(resource, *it, end, format, requestedTags, expandResource, true /* allowStorageAccess */))
       {
         result.append(resource);
       }
@@ -3479,7 +3504,7 @@
     const DicomToJsonFormat format = OrthancRestApi::GetDicomFormat(call, DicomToJsonFormat_Human);
 
     Json::Value resource;
-    if (OrthancRestApi::GetContext(call).ExpandResource(resource, current, end, format, requestedTags, true /* allowStorageAccess */))
+    if (OrthancRestApi::GetContext(call).ExpandResource(resource, current, end, format, requestedTags, ExpandResourceFlags_Default, true /* allowStorageAccess */))
     {
       call.GetOutput().AnswerJson(resource);
     }
@@ -3886,7 +3911,7 @@
           Json::Value item;
           std::set<DicomTag> emptyRequestedTags;  // not supported for bulk content
 
-          if (OrthancRestApi::GetContext(call).ExpandResource(item, *it, level, format, emptyRequestedTags, true /* allowStorageAccess */))
+          if (OrthancRestApi::GetContext(call).ExpandResource(item, *it, level, format, emptyRequestedTags, ExpandResourceFlags_Default, true /* allowStorageAccess */))
           {
             if (metadata)
             {
@@ -3911,7 +3936,7 @@
           std::set<DicomTag> emptyRequestedTags;  // not supported for bulk content
 
           if (index.LookupResourceType(level, *it) &&
-              OrthancRestApi::GetContext(call).ExpandResource(item, *it, level, format, emptyRequestedTags, true /* allowStorageAccess */))
+              OrthancRestApi::GetContext(call).ExpandResource(item, *it, level, format, emptyRequestedTags, ExpandResourceFlags_Default, true /* allowStorageAccess */))
           {
             if (metadata)
             {
@@ -4010,6 +4035,7 @@
     Register("/studies/{id}/module-patient", GetModule<ResourceType_Study, DicomModule_Patient>);
 
     Register("/instances/{id}/file", GetInstanceFile);
+    Register("/instances/{id}/file-until-pixel-data", GetInstanceFileUntiPixelData);
     Register("/instances/{id}/export", ExportInstanceFile);
     Register("/instances/{id}/tags", GetInstanceTags);
     Register("/instances/{id}/simplified-tags", GetInstanceSimplifiedTags);
--- a/OrthancServer/Sources/OrthancWebDav.cpp	Mon May 22 17:58:53 2023 +0200
+++ b/OrthancServer/Sources/OrthancWebDav.cpp	Wed May 24 08:56:41 2023 +0200
@@ -261,7 +261,7 @@
       Json::Value resource;
       std::set<DicomTag> emptyRequestedTags;  // not supported for webdav
 
-      if (context_.ExpandResource(resource, publicId, level_, DicomToJsonFormat_Human, emptyRequestedTags, true /* allowStorageAccess */))
+      if (context_.ExpandResource(resource, publicId, level_, DicomToJsonFormat_Human, emptyRequestedTags, ExpandResourceFlags_Default, true /* allowStorageAccess */))
       {
         if (success_)
         {
--- a/OrthancServer/Sources/ServerContext.cpp	Mon May 22 17:58:53 2023 +0200
+++ b/OrthancServer/Sources/ServerContext.cpp	Wed May 24 08:56:41 2023 +0200
@@ -1133,32 +1133,47 @@
   void ServerContext::ReadDicomForHeader(std::string& dicom,
                                          const std::string& instancePublicId)
   {
-    if (!ReadDicomUntilPixelData(dicom, instancePublicId))
-    {
-      ReadDicom(dicom, instancePublicId);
-    }
+    ReadDicomUntilPixelData(dicom, instancePublicId);
   }
 
   bool ServerContext::ReadDicomUntilPixelData(std::string& dicom,
                                               const std::string& instancePublicId)
   {
+    FileInfo attachment;
+    int64_t revision;  // Ignored
+
+    // if the attachment exists as such return it directly
+    if (index_.LookupAttachment(attachment, revision, instancePublicId, FileContentType_DicomUntilPixelData))
+    {
+      StorageAccessor accessor(area_, storageCache_, GetMetricsRegistry());
+      accessor.Read(dicom, attachment);
+      return true;
+    }
+
+    // if the storage area can not read part of files, return the whole file
     if (!area_.HasReadRange())
     {
-      return false;
+      ReadDicom(dicom, instancePublicId);
+      return true;
     }
-    
-    FileInfo attachment;
-    int64_t revision;  // Ignored
+
+    // else, read the start of the dicom file
+
     if (!index_.LookupAttachment(attachment, revision, instancePublicId, FileContentType_Dicom))
     {
       throw OrthancException(ErrorCode_InternalError,
-                             "Unable to read the DICOM file of instance " + instancePublicId);
+                            "Unable to read the DICOM file of instance " + instancePublicId);
+    }
+
+    // if the attachment is compressed, return the whole file
+    if (attachment.GetCompressionType() != CompressionType_None)
+    {
+      ReadDicom(dicom, instancePublicId);
+      return true;
     }
 
     std::string s;
-
-    if (attachment.GetCompressionType() == CompressionType_None &&
-        index_.LookupMetadata(s, revision, instancePublicId, ResourceType_Instance,
+    if (index_.LookupMetadata(s, revision, instancePublicId, ResourceType_Instance,
                               MetadataType_Instance_PixelDataOffset) &&
         !s.empty())
     {
@@ -1179,7 +1194,8 @@
       }
     }
 
-    return false;
+    ReadDicom(dicom, instancePublicId);
+    return true;
   }
   
 
@@ -1991,7 +2007,8 @@
   static void SerializeExpandedResource(Json::Value& target,
                                         const ExpandedResource& resource,
                                         DicomToJsonFormat format,
-                                        const std::set<DicomTag>& requestedTags)
+                                        const std::set<DicomTag>& requestedTags,
+                                        ExpandResourceFlags expandFlags)
   {
     target = Json::objectValue;
 
@@ -2019,42 +2036,45 @@
         throw OrthancException(ErrorCode_InternalError);
     }
 
-    switch (resource.GetLevel())
+    if (expandFlags & ExpandResourceFlags_IncludeChildren)
     {
-      case ResourceType_Patient:
-      case ResourceType_Study:
-      case ResourceType_Series:
+      switch (resource.GetLevel())
       {
-        Json::Value c = Json::arrayValue;
+        case ResourceType_Patient:
+        case ResourceType_Study:
+        case ResourceType_Series:
+        {
+          Json::Value c = Json::arrayValue;
+
+          for (std::list<std::string>::const_iterator
+                  it = resource.childrenIds_.begin(); it != resource.childrenIds_.end(); ++it)
+          {
+            c.append(*it);
+          }
 
-        for (std::list<std::string>::const_iterator
-                it = resource.childrenIds_.begin(); it != resource.childrenIds_.end(); ++it)
-        {
-          c.append(*it);
+          if (resource.GetLevel() == ResourceType_Patient)
+          {
+            target["Studies"] = c;
+          }
+          else if (resource.GetLevel() == ResourceType_Study)
+          {
+            target["Series"] = c;
+          }
+          else
+          {
+            target["Instances"] = c;
+          }
+          break;
         }
 
-        if (resource.GetLevel() == ResourceType_Patient)
-        {
-          target["Studies"] = c;
-        }
-        else if (resource.GetLevel() == ResourceType_Study)
-        {
-          target["Series"] = c;
-        }
-        else
-        {
-          target["Instances"] = c;
-        }
-        break;
+        case ResourceType_Instance:
+          break;
+
+        default:
+          throw OrthancException(ErrorCode_InternalError);
       }
-
-      case ResourceType_Instance:
-        break;
-
-      default:
-        throw OrthancException(ErrorCode_InternalError);
     }
-
+    
     switch (resource.GetLevel())
     {
       case ResourceType_Patient:
@@ -2117,26 +2137,29 @@
     }
 
     // serialize tags
+    if (expandFlags & ExpandResourceFlags_IncludeMainDicomTags)
+    {
+      static const char* const MAIN_DICOM_TAGS = "MainDicomTags";
+      static const char* const PATIENT_MAIN_DICOM_TAGS = "PatientMainDicomTags";
 
-    static const char* const MAIN_DICOM_TAGS = "MainDicomTags";
-    static const char* const PATIENT_MAIN_DICOM_TAGS = "PatientMainDicomTags";
-
-    DicomMap mainDicomTags;
-    resource.GetMainDicomTags().ExtractResourceInformation(mainDicomTags, resource.GetLevel());
+      DicomMap mainDicomTags;
+      resource.GetMainDicomTags().ExtractResourceInformation(mainDicomTags, resource.GetLevel());
 
-    target[MAIN_DICOM_TAGS] = Json::objectValue;
-    FromDcmtkBridge::ToJson(target[MAIN_DICOM_TAGS], mainDicomTags, format);
-    
-    if (resource.GetLevel() == ResourceType_Study)
-    {
-      DicomMap patientMainDicomTags;
-      resource.GetMainDicomTags().ExtractPatientInformation(patientMainDicomTags);
+      target[MAIN_DICOM_TAGS] = Json::objectValue;
+      FromDcmtkBridge::ToJson(target[MAIN_DICOM_TAGS], mainDicomTags, format);
+      
+      if (resource.GetLevel() == ResourceType_Study)
+      {
+        DicomMap patientMainDicomTags;
+        resource.GetMainDicomTags().ExtractPatientInformation(patientMainDicomTags);
 
-      target[PATIENT_MAIN_DICOM_TAGS] = Json::objectValue;
-      FromDcmtkBridge::ToJson(target[PATIENT_MAIN_DICOM_TAGS], patientMainDicomTags, format);
+        target[PATIENT_MAIN_DICOM_TAGS] = Json::objectValue;
+        FromDcmtkBridge::ToJson(target[PATIENT_MAIN_DICOM_TAGS], patientMainDicomTags, format);
+      }
     }
 
-    if (requestedTags.size() > 0)
+    if ((expandFlags & ExpandResourceFlags_IncludeRequestedTags)
+        && requestedTags.size() > 0)
     {
       static const char* const REQUESTED_TAGS = "RequestedTags";
 
@@ -2148,6 +2171,7 @@
 
     }
 
+    if (expandFlags & ExpandResourceFlags_IncludeLabels)
     {
       Json::Value labels = Json::arrayValue;
 
@@ -2382,13 +2406,14 @@
                                      ResourceType level,
                                      DicomToJsonFormat format,
                                      const std::set<DicomTag>& requestedTags,
+                                     ExpandResourceFlags expandFlags,
                                      bool allowStorageAccess)
   {
     std::string unusedInstanceId;
     Json::Value* unusedDicomAsJson = NULL;
     DicomMap unusedMainDicomTags;
 
-    return ExpandResource(target, publicId, unusedMainDicomTags, unusedInstanceId, unusedDicomAsJson, level, format, requestedTags, allowStorageAccess);
+    return ExpandResource(target, publicId, unusedMainDicomTags, unusedInstanceId, unusedDicomAsJson, level, format, requestedTags, expandFlags, allowStorageAccess);
   }
 
   bool ServerContext::ExpandResource(Json::Value& target,
@@ -2399,13 +2424,14 @@
                                      ResourceType level,
                                      DicomToJsonFormat format,
                                      const std::set<DicomTag>& requestedTags,
+                                     ExpandResourceFlags expandFlags,
                                      bool allowStorageAccess)
   {
     ExpandedResource resource;
 
-    if (ExpandResource(resource, publicId, mainDicomTags, instanceId, dicomAsJson, level, requestedTags, ExpandResourceFlags_Default, allowStorageAccess))
+    if (ExpandResource(resource, publicId, mainDicomTags, instanceId, dicomAsJson, level, requestedTags, expandFlags, allowStorageAccess))
     {
-      SerializeExpandedResource(target, resource, format, requestedTags);
+      SerializeExpandedResource(target, resource, format, requestedTags, expandFlags);
       return true;
     }
 
--- a/OrthancServer/Sources/ServerContext.h	Mon May 22 17:58:53 2023 +0200
+++ b/OrthancServer/Sources/ServerContext.h	Wed May 24 08:56:41 2023 +0200
@@ -566,6 +566,7 @@
                         ResourceType level,
                         DicomToJsonFormat format,
                         const std::set<DicomTag>& requestedTags,
+                        ExpandResourceFlags expandFlags,
                         bool allowStorageAccess);
 
     bool ExpandResource(Json::Value& target,
@@ -576,6 +577,7 @@
                         ResourceType level,
                         DicomToJsonFormat format,
                         const std::set<DicomTag>& requestedTags,
+                        ExpandResourceFlags expandFlags,
                         bool allowStorageAccess);
 
     bool ExpandResource(ExpandedResource& target,