changeset 5287:c04230962098 am-experimental

wip: 'dicomWeb' json format + 'include' get arguments
author Alain Mazy <am@osimis.io>
date Fri, 28 Apr 2023 10:42:27 +0200
parents 28f0e38e4082
children c9ea57d73603
files NEWS OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp OrthancFramework/Sources/Enumerations.cpp OrthancFramework/Sources/Enumerations.h OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp OrthancServer/Plugins/Samples/MultitenantDicom/OrthancFrameworkDependencies.cpp OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp OrthancServer/Sources/Database/StatelessDatabaseOperations.h OrthancServer/Sources/OrthancRestApi/OrthancRestApi.cpp OrthancServer/Sources/OrthancRestApi/OrthancRestApi.h OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp OrthancServer/Sources/OrthancWebDav.cpp OrthancServer/Sources/ServerContext.cpp OrthancServer/Sources/ServerContext.h
diffstat 14 files changed, 272 insertions(+), 62 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Mon Apr 24 18:13:48 2023 +0200
+++ b/NEWS	Fri Apr 28 10:42:27 2023 +0200
@@ -6,7 +6,10 @@
 
 * Fix decoding of YBR_FULL RLE images for which the "Planar Configuration" 
   tag (0028,0006) equals 1
-
+* 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 Apr 24 18:13:48 2023 +0200
+++ b/OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp	Fri Apr 28 10:42:27 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 Apr 24 18:13:48 2023 +0200
+++ b/OrthancFramework/Sources/Enumerations.cpp	Fri Apr 28 10:42:27 2023 +0200
@@ -1183,6 +1183,9 @@
       case DicomToJsonFormat_Short:
         return "Short";
 
+      case DicomToJsonFormat_DicomWeb:
+        return "DicomWeb";
+
       default:
         throw OrthancException(ErrorCode_ParameterOutOfRange);
     }
@@ -1884,6 +1887,10 @@
     {
       return DicomToJsonFormat_Human;
     }
+    else if (format == "DicomWeb")
+    {
+      return DicomToJsonFormat_DicomWeb;
+    }
     else
     {
       throw OrthancException(ErrorCode_ParameterOutOfRange);
--- a/OrthancFramework/Sources/Enumerations.h	Mon Apr 24 18:13:48 2023 +0200
+++ b/OrthancFramework/Sources/Enumerations.h	Fri Apr 28 10:42:27 2023 +0200
@@ -581,7 +581,8 @@
   {
     DicomToJsonFormat_Full,
     DicomToJsonFormat_Short,
-    DicomToJsonFormat_Human
+    DicomToJsonFormat_Human,
+    DicomToJsonFormat_DicomWeb
   };
 
   enum DicomToJsonFlags
--- a/OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp	Mon Apr 24 18:13:48 2023 +0200
+++ b/OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp	Fri Apr 28 10:42:27 2023 +0200
@@ -3216,7 +3216,128 @@
   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);
+    }
+
+    ASSERT_TRUE(result.isMember("00281053"));
+    ASSERT_EQ("0.0625", result["00281053"]["Value"].asString());
+  }
+}
 
 
 #if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
--- a/OrthancServer/Plugins/Samples/MultitenantDicom/OrthancFrameworkDependencies.cpp	Mon Apr 24 18:13:48 2023 +0200
+++ b/OrthancServer/Plugins/Samples/MultitenantDicom/OrthancFrameworkDependencies.cpp	Fri Apr 28 10:42:27 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 Apr 24 18:13:48 2023 +0200
+++ b/OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp	Fri Apr 28 10:42:27 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 Apr 24 18:13:48 2023 +0200
+++ b/OrthancServer/Sources/Database/StatelessDatabaseOperations.h	Fri Apr 28 10:42:27 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 Apr 24 18:13:48 2023 +0200
+++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestApi.cpp	Fri Apr 28 10:42:27 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 Apr 24 18:13:48 2023 +0200
+++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestApi.h	Fri Apr 28 10:42:27 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 Apr 24 18:13:48 2023 +0200
+++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp	Fri Apr 28 10:42:27 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);
     }
@@ -3355,12 +3355,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 +3480,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 +3887,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 +3912,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)
             {
--- a/OrthancServer/Sources/OrthancWebDav.cpp	Mon Apr 24 18:13:48 2023 +0200
+++ b/OrthancServer/Sources/OrthancWebDav.cpp	Fri Apr 28 10:42:27 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 Apr 24 18:13:48 2023 +0200
+++ b/OrthancServer/Sources/ServerContext.cpp	Fri Apr 28 10:42:27 2023 +0200
@@ -1987,7 +1987,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;
 
@@ -2015,42 +2016,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:
@@ -2113,26 +2117,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";
 
@@ -2144,6 +2151,7 @@
 
     }
 
+    if (expandFlags & ExpandResourceFlags_IncludeLabels)
     {
       Json::Value labels = Json::arrayValue;
 
@@ -2378,13 +2386,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,
@@ -2395,13 +2404,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 Apr 24 18:13:48 2023 +0200
+++ b/OrthancServer/Sources/ServerContext.h	Fri Apr 28 10:42:27 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,