changeset 368:979776c2f75b

simplification in wado-rs retrieve metadata
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 27 Sep 2019 16:24:38 +0200
parents 96dc376e4679
children 331c30ae41c3
files Plugin/WadoRs.cpp Plugin/WadoRs.h Plugin/WadoRsRetrieveFrames.cpp Plugin/WadoRsRetrieveRendered.cpp
diffstat 4 files changed, 259 insertions(+), 329 deletions(-) [+]
line wrap: on
line diff
--- a/Plugin/WadoRs.cpp	Fri Sep 27 12:50:30 2019 +0200
+++ b/Plugin/WadoRs.cpp	Fri Sep 27 16:24:38 2019 +0200
@@ -254,297 +254,82 @@
 
 
 
-static void CopyDictionary(Json::Value& target,
-                           const Json::Value& source)
-{
-  if (target.type() != Json::objectValue ||
-      source.type() != Json::objectValue)
-  {
-    throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-  }
-  
-  Json::Value::Members members = source.getMemberNames();
-
-  for (size_t i = 0; i < members.size(); i++)
-  {
-    if (source[members[i]].type() != Json::stringValue)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-    }
-    else
-    {
-      target[members[i]] = source[members[i]].asString();
-    }
-  }
-}
-
-
 static void WriteInstanceMetadata(OrthancPlugins::DicomWebFormatter::HttpWriter& writer,
                                   const std::string& orthancId,
+                                  const std::string& studyInstanceUid,
+                                  const std::string& seriesInstanceUid,
+                                  const std::string& sopInstanceUid,
                                   const std::string& wadoBase)
 {
-  std::string studyInstanceUid, seriesInstanceUid, sopInstanceUid;
-  bool found;
+  assert(!orthancId.empty() &&
+         !studyInstanceUid.empty() &&
+         !seriesInstanceUid.empty() &&
+         !sopInstanceUid.empty() &&
+         !wadoBase.empty());
+  
+  const std::string bulkRoot = (wadoBase +
+                                "studies/" + studyInstanceUid +
+                                "/series/" + seriesInstanceUid + 
+                                "/instances/" + sopInstanceUid + "/bulk");
   
 #if 0
-  // This version is slow, as one access is done to the filesystem
-  // (cf. "/instances/.../tags"). It was in use in versions <= 1.0 of
-  // the DICOMweb plugin.
-
-  static const char* const STUDY_INSTANCE_UID = "0020,000d";
-  static const char* const SERIES_INSTANCE_UID = "0020,000e";
-  static const char* const SOP_INSTANCE_UID = "0008,0018";
-  
-  Json::Value dicom;
-  if (OrthancPlugins::RestApiGet(dicom, "/instances/" + orthancId + "/tags?short", false) &&
-      dicom.isMember(STUDY_INSTANCE_UID) &&
-      dicom.isMember(SERIES_INSTANCE_UID) &&
-      dicom.isMember(SOP_INSTANCE_UID) &&
-      dicom[STUDY_INSTANCE_UID].type() == Json::stringValue &&
-      dicom[SERIES_INSTANCE_UID].type() == Json::stringValue &&
-      dicom[SOP_INSTANCE_UID].type() == Json::stringValue)
+  // On a SSD drive, this version is twice slower than if using
+  // cache (see below)
+    
+  OrthancPlugins::MemoryBuffer dicom;
+  if (dicom.RestApiGet("/instances/" + orthancId + "/file", false))
   {
-    studyInstanceUid = dicom[STUDY_INSTANCE_UID].asString();
-    seriesInstanceUid = dicom[SERIES_INSTANCE_UID].asString();
-    sopInstanceUid = dicom[SOP_INSTANCE_UID].asString();
-    found = true;
+    writer.AddDicom(dicom.GetData(), dicom.GetSize(), bulkRoot);
   }
-  else
-  {
-    found = false;
-  }
+#else
 
-#else
-  // This version is faster, as it only queries the database (no file
-  // is opened on the filesystem, besides the SQLite database).
-
-  Json::Value patient, study, series, instance;
-
-  static const char* const MAIN_DICOM_TAGS = "MainDicomTags";
-  static const char* const STUDY_INSTANCE_UID = "StudyInstanceUID";
-  static const char* const SERIES_INSTANCE_UID = "SeriesInstanceUID";
-  static const char* const SOP_INSTANCE_UID = "SOPInstanceUID";
+  // TODO - Have a global setting to enable/disable caching of DICOMweb
 
-  if (OrthancPlugins::RestApiGet(patient, "/instances/" + orthancId + "/patient", false) &&
-      OrthancPlugins::RestApiGet(study, "/instances/" + orthancId + "/study", false) &&
-      OrthancPlugins::RestApiGet(series, "/instances/" + orthancId + "/series", false) &&
-      OrthancPlugins::RestApiGet(instance, "/instances/" + orthancId, false) &&
-      patient.isMember(MAIN_DICOM_TAGS) &&
-      study.isMember(MAIN_DICOM_TAGS) &&
-      series.isMember(MAIN_DICOM_TAGS) &&
-      instance.isMember(MAIN_DICOM_TAGS) &&
-      patient[MAIN_DICOM_TAGS].type() == Json::objectValue &&
-      study[MAIN_DICOM_TAGS].type() == Json::objectValue &&
-      series[MAIN_DICOM_TAGS].type() == Json::objectValue &&
-      instance[MAIN_DICOM_TAGS].type() == Json::objectValue &&
-      study[MAIN_DICOM_TAGS].isMember(STUDY_INSTANCE_UID) &&
-      series[MAIN_DICOM_TAGS].isMember(SERIES_INSTANCE_UID) &&
-      instance[MAIN_DICOM_TAGS].isMember(SOP_INSTANCE_UID) &&
-      study[MAIN_DICOM_TAGS][STUDY_INSTANCE_UID].type() == Json::stringValue &&
-      series[MAIN_DICOM_TAGS][SERIES_INSTANCE_UID].type() == Json::stringValue &&
-      instance[MAIN_DICOM_TAGS][SOP_INSTANCE_UID].type() == Json::stringValue)
-  {
-    studyInstanceUid = study[MAIN_DICOM_TAGS][STUDY_INSTANCE_UID].asString();
-    seriesInstanceUid = series[MAIN_DICOM_TAGS][SERIES_INSTANCE_UID].asString();
-    sopInstanceUid = instance[MAIN_DICOM_TAGS][SOP_INSTANCE_UID].asString();
-    found = true;
+  // TODO - Have a way to clear the "4444" attachments if Orthanc
+  // version changes => Store Orthanc core version in a prefix or in
+  // another attachment?
+    
+  OrthancPlugins::MemoryBuffer buffer;
 
-#if 0
-    // This is a FAST version, that does not access the filesystem. It
-    // only uses the main DICOM tags as stored in the database, so
-    // less common tags will be missing (this is INCOMPATIBLE with Web
-    // Viewer 2). This is important if the DICOM/JSON files are stored
-    // on low-speed filesystem (e.g. AWS S3). TODO - Add a
-    // configuration option in the plugin
-    const std::string bulkRoot = (wadoBase +
-                                  "studies/" + studyInstanceUid +
-                                  "/series/" + seriesInstanceUid + 
-                                  "/instances/" + sopInstanceUid + "/bulk");
-      
-    Json::Value combined = Json::objectValue;
-    CopyDictionary(combined, patient[MAIN_DICOM_TAGS]);
-    CopyDictionary(combined, study[MAIN_DICOM_TAGS]);
-    CopyDictionary(combined, series[MAIN_DICOM_TAGS]);
-    CopyDictionary(combined, instance[MAIN_DICOM_TAGS]);
-
-    OrthancPlugins::MemoryBuffer dicom;
-    dicom.CreateDicom(combined, OrthancPluginCreateDicomFlags_None);
-    writer.AddDicom(dicom.GetData(), dicom.GetSize(), bulkRoot);
-    return;
-#endif
+  if (writer.IsXml())
+  {
+    // DICOMweb XML is not cached
+    if (buffer.RestApiGet("/instances/" + orthancId + "/file", false))
+    {
+      writer.AddDicom(buffer.GetData(), buffer.GetSize(), bulkRoot);
+    }
   }
   else
   {
-    found = false;
+    if (buffer.RestApiGet("/instances/" + orthancId + "/attachments/4444/data", false))
+    {
+      writer.AddDicomWebSerializedJson(buffer.GetData(), buffer.GetSize());
+    }
+    else if (buffer.RestApiGet("/instances/" + orthancId + "/file", false))
+    {
+      // "Ignore binary mode" in DICOMweb conversion if caching is
+      // enabled, as the bulk root can change across executions
+
+      std::string dicomweb;
+      {
+        // TODO - Avoid a global mutex => Need to change Orthanc SDK
+        OrthancPlugins::DicomWebFormatter::Locker locker(OrthancPluginDicomWebBinaryMode_Ignore, "");
+        locker.Apply(dicomweb, OrthancPlugins::GetGlobalContext(),
+                     buffer.GetData(), buffer.GetSize(), false /* JSON */);
+      }
+
+      buffer.RestApiPut("/instances/" + orthancId + "/attachments/4444", dicomweb, false);
+      writer.AddDicomWebSerializedJson(dicomweb.c_str(), dicomweb.size());
+    }
   }
 #endif
-
-  if (found)
-  {
-    const std::string bulkRoot = (wadoBase +
-                                  "studies/" + studyInstanceUid +
-                                  "/series/" + seriesInstanceUid + 
-                                  "/instances/" + sopInstanceUid + "/bulk");
-
-#if 1
-    // On a SSD drive, this version is twice slower than if using
-    // cache (see below)
-    
-    OrthancPlugins::MemoryBuffer dicom;
-    if (dicom.RestApiGet("/instances/" + orthancId + "/file", false))
-    {
-      writer.AddDicom(dicom.GetData(), dicom.GetSize(), bulkRoot);
-    }
-#else
-
-    // TODO - Have a global setting to enable/disable caching of DICOMweb
-
-    // TODO - Have a way to clear the "4444" attachments if Orthanc
-    // version changes => Store Orthanc core version in a prefix or in
-    // another attachments?
-    
-    OrthancPlugins::MemoryBuffer buffer;
-
-    if (writer.IsXml())
-    {
-      // DICOMweb XML is not cached
-      if (buffer.RestApiGet("/instances/" + orthancId + "/file", false))
-      {
-        writer.AddDicom(buffer.GetData(), buffer.GetSize(), bulkRoot);
-      }
-    }
-    else
-    {
-      if (buffer.RestApiGet("/instances/" + orthancId + "/attachments/4444/data", false))
-      {
-        writer.AddDicomWebSerializedJson(buffer.GetData(), buffer.GetSize());
-      }
-      else if (buffer.RestApiGet("/instances/" + orthancId + "/file", false))
-      {
-        // "Ignore binary mode" in DICOMweb conversion if caching is
-        // enabled, as the bulk root can change across executions
-
-        std::string dicomweb;
-        {
-          // TODO - Avoid a global mutex => Need to change Orthanc SDK
-          OrthancPlugins::DicomWebFormatter::Locker locker(OrthancPluginDicomWebBinaryMode_Ignore, "");
-          locker.Apply(dicomweb, OrthancPlugins::GetGlobalContext(),
-                       buffer.GetData(), buffer.GetSize(), false /* JSON */);
-        }
-
-        buffer.RestApiPut("/instances/" + orthancId + "/attachments/4444", dicomweb, false);
-        writer.AddDicomWebSerializedJson(dicomweb.c_str(), dicomweb.size());
-      }
-    }
-#endif
-  }
-}
-
-
-
-
-static void AnswerMetadata(OrthancPluginRestOutput* output,
-                           const OrthancPluginHttpRequest* request,
-                           Orthanc::ResourceType level,
-                           const std::string& resource,
-                           bool isXml)
-{
-  static const char* const ID = "ID";
-  static const char* const INSTANCES = "Instances";
-  
-  OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
-
-  std::list<std::string> instances;
-
-  switch (level)
-  {
-    case Orthanc::ResourceType_Instance:
-      instances.push_back(resource);
-      break;
-
-    case Orthanc::ResourceType_Series:
-    {
-      Json::Value series;
-      if (!OrthancPlugins::RestApiGet(series, GetResourceUri(level, resource), false))
-      {
-        // Internal error
-        OrthancPluginSendHttpStatusCode(context, output, 400);
-        return;
-      }
-
-      if (series.type() != Json::objectValue ||
-          !series.isMember(INSTANCES) ||
-          series[INSTANCES].type() != Json::arrayValue)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
-      }
-          
-      for (Json::Value::ArrayIndex i = 0; i < series[INSTANCES].size(); i++)
-      {
-        if (series[INSTANCES][i].type() != Json::stringValue)
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
-        }
-        else
-        {
-          instances.push_back(series[INSTANCES][i].asString());
-        }
-      }
-      break;
-    }
-
-    case Orthanc::ResourceType_Study:
-    {
-      Json::Value children;
-      if (!OrthancPlugins::RestApiGet(children, GetResourceUri(level, resource) + "/instances", false))
-      {
-        // Internal error
-        OrthancPluginSendHttpStatusCode(context, output, 400);
-        return;
-      }
-
-      if (children.type() != Json::arrayValue)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
-      }
-
-      for (Json::Value::ArrayIndex i = 0; i < children.size(); i++)
-      {
-        if (children[i].type() != Json::objectValue ||
-            !children[i].isMember(ID) ||
-            children[i][ID].type() != Json::stringValue)
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
-        }
-        else
-        {
-          instances.push_back(children[i][ID].asString());
-        }
-      }
-      break;
-    }
-
-    default:
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-  }
-
-  const std::string wadoBase = OrthancPlugins::Configuration::GetBaseUrl(request);
-
-  OrthancPlugins::DicomWebFormatter::HttpWriter writer(output, isXml);
-
-  for (std::list<std::string>::const_iterator
-         it = instances.begin(); it != instances.end(); ++it)
-  {
-    WriteInstanceMetadata(writer, *it, wadoBase);
-  }
-
-  writer.Send();
 }
 
 
 
 static bool LocateStudy(OrthancPluginRestOutput* output,
-                        std::string& publicId,
+                        std::string& orthancId,
+                        std::string& studyInstanceUid,
                         const OrthancPluginHttpRequest* request)
 {
   OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
@@ -555,14 +340,16 @@
     return false;
   }
 
+  studyInstanceUid = request->groups[0];
+
   try
   {
     OrthancPlugins::OrthancString tmp;
-    tmp.Assign(OrthancPluginLookupStudy(context, request->groups[0]));
+    tmp.Assign(OrthancPluginLookupStudy(context, studyInstanceUid.c_str()));
 
     if (tmp.GetContent() != NULL)
     {
-      tmp.ToString(publicId);
+      tmp.ToString(orthancId);
       return true;
     }
   }
@@ -571,13 +358,14 @@
   }
 
   throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentItem, 
-                                  "Accessing an inexistent study with WADO-RS: " +
-                                  std::string(request->groups[0]));
+                                  "Accessing an inexistent study with WADO-RS: " + studyInstanceUid);
 }
 
 
 bool LocateSeries(OrthancPluginRestOutput* output,
-                  std::string& publicId,
+                  std::string& orthancId,
+                  std::string& studyInstanceUid,
+                  std::string& seriesInstanceUid,
                   const OrthancPluginHttpRequest* request)
 {
   OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
@@ -588,16 +376,19 @@
     return false;
   }
 
+  studyInstanceUid = request->groups[0];
+  seriesInstanceUid = request->groups[1];
+
   bool found = false;
 
   try
   {
     OrthancPlugins::OrthancString tmp;
-    tmp.Assign(OrthancPluginLookupSeries(context, request->groups[1]));
+    tmp.Assign(OrthancPluginLookupSeries(context, seriesInstanceUid.c_str()));
 
     if (tmp.GetContent() != NULL)
     {
-      tmp.ToString(publicId);
+      tmp.ToString(orthancId);
       found = true;
     }
   }
@@ -608,21 +399,19 @@
   if (!found)
   {
     throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentItem, 
-                                    "Accessing an inexistent series with WADO-RS: " +
-                                    std::string(request->groups[1]));
+                                    "Accessing an inexistent series with WADO-RS: " + seriesInstanceUid);
   }
   
   Json::Value study;
-  if (!OrthancPlugins::RestApiGet(study, "/series/" + publicId + "/study", false))
+  if (!OrthancPlugins::RestApiGet(study, "/series/" + orthancId + "/study", false))
   {
     OrthancPluginSendHttpStatusCode(context, output, 404);
     return false;
   }
-  else if (study["MainDicomTags"]["StudyInstanceUID"].asString() != std::string(request->groups[0]))
+  else if (study["MainDicomTags"]["StudyInstanceUID"].asString() != studyInstanceUid)
   {
     throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentItem, 
-                                    "No series " + std::string(request->groups[1]) + 
-                                    " in study " + std::string(request->groups[0]));
+                                    "No series " + seriesInstanceUid + " in study " + studyInstanceUid);
   }
   else
   {
@@ -632,7 +421,10 @@
 
 
 bool LocateInstance(OrthancPluginRestOutput* output,
-                    std::string& publicId,
+                    std::string& orthancId,
+                    std::string& studyInstanceUid,
+                    std::string& seriesInstanceUid,
+                    std::string& sopInstanceUid,
                     const OrthancPluginHttpRequest* request)
 {
   OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
@@ -643,34 +435,37 @@
     return false;
   }
 
+  studyInstanceUid = request->groups[0];
+  seriesInstanceUid = request->groups[1];
+  sopInstanceUid = request->groups[2];
+  
   {
     OrthancPlugins::OrthancString tmp;
-    tmp.Assign(OrthancPluginLookupInstance(context, request->groups[2]));
+    tmp.Assign(OrthancPluginLookupInstance(context, sopInstanceUid.c_str()));
 
     if (tmp.GetContent() == NULL)
     {
       throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentItem,
-                                      "Accessing an inexistent instance with WADO-RS: " + 
-                                      std::string(request->groups[2]));
+                                      "Accessing an inexistent instance with WADO-RS: " + sopInstanceUid);
     }
 
-    tmp.ToString(publicId);
+    tmp.ToString(orthancId);
   }
   
   Json::Value study, series;
-  if (!OrthancPlugins::RestApiGet(series, "/instances/" + publicId + "/series", false) ||
-      !OrthancPlugins::RestApiGet(study, "/instances/" + publicId + "/study", false))
+  if (!OrthancPlugins::RestApiGet(series, "/instances/" + orthancId + "/series", false) ||
+      !OrthancPlugins::RestApiGet(study, "/instances/" + orthancId + "/study", false))
   {
     OrthancPluginSendHttpStatusCode(context, output, 404);
     return false;
   }
-  else if (study["MainDicomTags"]["StudyInstanceUID"].asString() != std::string(request->groups[0]) ||
-           series["MainDicomTags"]["SeriesInstanceUID"].asString() != std::string(request->groups[1]))
+  else if (study["MainDicomTags"]["StudyInstanceUID"].asString() != studyInstanceUid ||
+           series["MainDicomTags"]["SeriesInstanceUID"].asString() != seriesInstanceUid)
   {
     throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentItem,
-                                    "Instance " + std::string(request->groups[2]) + 
-                                    " is not both in study " + std::string(request->groups[0]) +
-                                    " and in series " + std::string(request->groups[1]));
+                                    "Instance " + sopInstanceUid + 
+                                    " is not both in study " + studyInstanceUid +
+                                    " and in series " + seriesInstanceUid);
   }
   else
   {
@@ -689,10 +484,10 @@
   }
   else
   {
-    std::string publicId;
-    if (LocateStudy(output, publicId, request))
+    std::string orthancId, studyInstanceUid;
+    if (LocateStudy(output, orthancId, studyInstanceUid, request))
     {
-      AnswerListOfDicomInstances(output, Orthanc::ResourceType_Study, publicId);
+      AnswerListOfDicomInstances(output, Orthanc::ResourceType_Study, orthancId);
     }
   }
 }
@@ -708,10 +503,10 @@
   }
   else
   {
-    std::string publicId;
-    if (LocateSeries(output, publicId, request))
+    std::string orthancId, studyInstanceUid, seriesInstanceUid;
+    if (LocateSeries(output, orthancId, studyInstanceUid, seriesInstanceUid, request))
     {
-      AnswerListOfDicomInstances(output, Orthanc::ResourceType_Series, publicId);
+      AnswerListOfDicomInstances(output, Orthanc::ResourceType_Series, orthancId);
     }
   }
 }
@@ -730,8 +525,8 @@
   }
   else
   {
-    std::string publicId;
-    if (LocateInstance(output, publicId, request))
+    std::string orthancId, studyInstanceUid, seriesInstanceUid, sopInstanceUid;
+    if (LocateInstance(output, orthancId, studyInstanceUid, seriesInstanceUid, sopInstanceUid, request))
     {
       if (OrthancPluginStartMultipartAnswer(context, output, "related", "application/dicom"))
       {
@@ -739,7 +534,7 @@
       }
 
       OrthancPlugins::MemoryBuffer dicom;
-      if (dicom.RestApiGet("/instances/" + publicId + "/file", false) &&
+      if (dicom.RestApiGet("/instances/" + orthancId + "/file", false) &&
           OrthancPluginSendMultipartItem(context, output, dicom.GetData(), dicom.GetSize()) != 0)
       {
         throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
@@ -750,6 +545,99 @@
 
 
 
+namespace
+{
+  class Identifier
+  {
+  private:
+    std::string  orthancId_;
+    std::string  dicomUid_;
+
+  public:
+    Identifier(const std::string& orthancId,
+               const std::string& dicomUid) :
+      orthancId_(orthancId),
+      dicomUid_(dicomUid)
+    {
+    }
+
+    const std::string& GetOrthancId() const
+    {
+      return orthancId_;
+    }
+
+    const std::string& GetDicomUid() const
+    {
+      return dicomUid_;
+    }
+  };
+}
+
+
+static void GetChildrenIdentifiers(std::list<Identifier>& target,
+                                   Orthanc::ResourceType level,
+                                   const std::string& orthancId)
+{
+  static const char* const ID = "ID";
+  static const char* const MAIN_DICOM_TAGS = "MainDicomTags";
+  static const char* const SERIES_INSTANCE_UID = "SeriesInstanceUID";
+  static const char* const SOP_INSTANCE_UID = "SOPInstanceUID";
+
+  target.clear();
+
+  const char* tag = NULL;
+  std::string uri;
+
+  switch (level)
+  {
+    case Orthanc::ResourceType_Study:
+      uri = "/studies/" + orthancId + "/series";
+      tag = SERIES_INSTANCE_UID;
+      break;
+       
+    case Orthanc::ResourceType_Series:
+      uri = "/series/" + orthancId + "/instances";
+      tag = SOP_INSTANCE_UID;
+      break;
+
+    default:
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+  }
+
+  assert(tag != NULL);
+  
+  Json::Value children;
+  if (OrthancPlugins::RestApiGet(children, uri, false))
+  {
+    if (children.type() != Json::arrayValue)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+    }
+
+    for (Json::Value::ArrayIndex i = 0; i < children.size(); i++)
+    {
+      if (children[i].type() != Json::objectValue ||
+          !children[i].isMember(ID) ||
+          !children[i].isMember(MAIN_DICOM_TAGS) ||
+          children[i][ID].type() != Json::stringValue ||
+          children[i][MAIN_DICOM_TAGS].type() != Json::objectValue ||
+          !children[i][MAIN_DICOM_TAGS].isMember(tag) ||
+          children[i][MAIN_DICOM_TAGS][tag].type() != Json::stringValue)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+      }
+      else
+      {
+        target.push_back(Identifier(children[i][ID].asString(),
+                                    children[i][MAIN_DICOM_TAGS][tag].asString()));
+                                    
+      }
+    }
+  }  
+}
+
+
+
 void RetrieveStudyMetadata(OrthancPluginRestOutput* output,
                            const char* url,
                            const OrthancPluginHttpRequest* request)
@@ -761,10 +649,29 @@
   }
   else
   {
-    std::string publicId;
-    if (LocateStudy(output, publicId, request))
+    std::string studyOrthancId, studyInstanceUid;
+    if (LocateStudy(output, studyOrthancId, studyInstanceUid, request))
     {
-      AnswerMetadata(output, request, Orthanc::ResourceType_Study, publicId, isXml);
+      OrthancPlugins::DicomWebFormatter::HttpWriter writer(output, isXml);
+      const std::string& wadoBase = OrthancPlugins::Configuration::GetBaseUrl(request);
+
+      std::list<Identifier> series;
+      GetChildrenIdentifiers(series, Orthanc::ResourceType_Study, studyOrthancId);
+
+      for (std::list<Identifier>::const_iterator a = series.begin(); a != series.end(); ++a)
+      {
+        std::list<Identifier> instances;
+        GetChildrenIdentifiers(instances, Orthanc::ResourceType_Series, a->GetOrthancId());
+
+        for (std::list<Identifier>::const_iterator b = instances.begin(); b != instances.end(); ++b)
+        {
+          WriteInstanceMetadata(writer, b->GetOrthancId(), studyInstanceUid, a->GetDicomUid(), b->GetDicomUid(),
+                                OrthancPlugins::Configuration::GetBaseUrl(request));
+
+        }
+      }
+
+      writer.Send();
     }
   }
 }
@@ -774,17 +681,32 @@
                             const char* url,
                             const OrthancPluginHttpRequest* request)
 {
+  OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
+  
   bool isXml;
   if (!AcceptMetadata(request, isXml))
   {
-    OrthancPluginSendHttpStatusCode(OrthancPlugins::GetGlobalContext(), output, 400 /* Bad request */);
+    OrthancPluginSendHttpStatusCode(context, output, 400 /* Bad request */);
   }
   else
   {
-    std::string publicId;
-    if (LocateSeries(output, publicId, request))
+    std::string seriesOrthancId, studyInstanceUid, seriesInstanceUid;
+    if (LocateSeries(output, seriesOrthancId, studyInstanceUid, seriesInstanceUid, request))
     {
-      AnswerMetadata(output, request, Orthanc::ResourceType_Series, publicId, isXml);
+      OrthancPlugins::DicomWebFormatter::HttpWriter writer(output, isXml);
+      const std::string& wadoBase = OrthancPlugins::Configuration::GetBaseUrl(request);
+      
+      std::list<Identifier> instances;
+      GetChildrenIdentifiers(instances, Orthanc::ResourceType_Series, seriesOrthancId);
+
+      for (std::list<Identifier>::const_iterator a = instances.begin(); a != instances.end(); ++a)
+      {
+        WriteInstanceMetadata(writer, a->GetOrthancId(), studyInstanceUid, seriesOrthancId, a->GetDicomUid(),
+                              OrthancPlugins::Configuration::GetBaseUrl(request));
+
+      }
+
+      writer.Send();
     }
   }
 }
@@ -801,10 +723,13 @@
   }
   else
   {
-    std::string publicId;
-    if (LocateInstance(output, publicId, request))
+    std::string orthancId, studyInstanceUid, seriesInstanceUid, sopInstanceUid;
+    if (LocateInstance(output, orthancId, studyInstanceUid, seriesInstanceUid, sopInstanceUid, request))
     {
-      AnswerMetadata(output, request, Orthanc::ResourceType_Instance, publicId, isXml);
+      OrthancPlugins::DicomWebFormatter::HttpWriter writer(output, isXml);
+      WriteInstanceMetadata(writer, orthancId, studyInstanceUid, seriesInstanceUid, sopInstanceUid,
+                            OrthancPlugins::Configuration::GetBaseUrl(request));
+      writer.Send();      
     }
   }
 }
@@ -822,10 +747,10 @@
     return;
   }
 
-  std::string publicId;
+  std::string orthancId, studyInstanceUid, seriesInstanceUid, sopInstanceUid;
   OrthancPlugins::MemoryBuffer content;
-  if (LocateInstance(output, publicId, request) &&
-      content.RestApiGet("/instances/" + publicId + "/file", false))
+  if (LocateInstance(output, orthancId, studyInstanceUid, seriesInstanceUid, sopInstanceUid, request) &&
+      content.RestApiGet("/instances/" + orthancId + "/file", false))
   {
     std::string bulk(request->groups[3]);
 
@@ -833,7 +758,7 @@
     Orthanc::Toolbox::TokenizeString(path, bulk, '/');
 
     // Map the bulk data URI to the Orthanc "/instances/.../content/..." built-in URI
-    std::string orthanc = "/instances/" + publicId + "/content";
+    std::string orthanc = "/instances/" + orthancId + "/content";
 
     Orthanc::DicomTag tmp(0, 0);
     
--- a/Plugin/WadoRs.h	Fri Sep 27 12:50:30 2019 +0200
+++ b/Plugin/WadoRs.h	Fri Sep 27 16:24:38 2019 +0200
@@ -26,10 +26,15 @@
 
 bool LocateSeries(OrthancPluginRestOutput* output,
                   std::string& uri,
+                  std::string& studyInstanceUid,
+                  std::string& seriesInstanceUid,
                   const OrthancPluginHttpRequest* request);
 
 bool LocateInstance(OrthancPluginRestOutput* output,
                     std::string& uri,
+                    std::string& studyInstanceUid,
+                    std::string& seriesInstanceUid,
+                    std::string& sopInstanceUid,
                     const OrthancPluginHttpRequest* request);
 
 void RetrieveDicomStudy(OrthancPluginRestOutput* output,
--- a/Plugin/WadoRsRetrieveFrames.cpp	Fri Sep 27 12:50:30 2019 +0200
+++ b/Plugin/WadoRsRetrieveFrames.cpp	Fri Sep 27 16:24:38 2019 +0200
@@ -501,14 +501,14 @@
   ParseFrameList(frames, request);
 
   Json::Value header;
-  std::string publicId;
+  std::string orthancId, studyInstanceUid, seriesInstanceUid, sopInstanceUid;
   OrthancPlugins::MemoryBuffer content;
-  if (LocateInstance(output, publicId, request) &&
-      content.RestApiGet("/instances/" + publicId + "/file", false) &&
-      OrthancPlugins::RestApiGet(header, "/instances/" + publicId + "/header?simplify", false))
+  if (LocateInstance(output, orthancId, studyInstanceUid, seriesInstanceUid, sopInstanceUid, request) &&
+      content.RestApiGet("/instances/" + orthancId + "/file", false) &&
+      OrthancPlugins::RestApiGet(header, "/instances/" + orthancId + "/header?simplify", false))
   {
     {
-      std::string s = "DICOMweb RetrieveFrames on " + publicId + ", frames: ";
+      std::string s = "DICOMweb RetrieveFrames on " + orthancId + ", frames: ";
       for (std::list<unsigned int>::const_iterator 
              frame = frames.begin(); frame != frames.end(); ++frame)
       {
@@ -551,7 +551,7 @@
       // Need to convert the transfer syntax
 
       {
-        OrthancPlugins::LogInfo("DICOMweb RetrieveFrames: Transcoding instance " + publicId + 
+        OrthancPlugins::LogInfo("DICOMweb RetrieveFrames: Transcoding instance " + orthancId + 
                                 " from transfer syntax " + std::string(sourceSyntax.GetString()) + 
                                 " to " + std::string(targetSyntax.GetString()));
       }
--- a/Plugin/WadoRsRetrieveRendered.cpp	Fri Sep 27 12:50:30 2019 +0200
+++ b/Plugin/WadoRsRetrieveRendered.cpp	Fri Sep 27 16:24:38 2019 +0200
@@ -850,8 +850,8 @@
   }
   else
   {
-    std::string instanceId;
-    if (LocateInstance(output, instanceId, request))
+    std::string instanceId, studyInstanceUid, seriesInstanceUid, sopInstanceUid;
+    if (LocateInstance(output, instanceId, studyInstanceUid, seriesInstanceUid, sopInstanceUid, request))
     {
       AnswerFrameRendered(output, instanceId, frame, request);
     }
@@ -897,11 +897,11 @@
   }
   else
   {
-    std::string seriesId;
-    if (LocateSeries(output, seriesId, request))
+    std::string orthancId, studyInstanceUid, seriesInstanceUid;
+    if (LocateSeries(output, orthancId, studyInstanceUid, seriesInstanceUid, request))
     {
       Json::Value series;
-      if (OrthancPlugins::RestApiGet(series, "/series/" + seriesId, false) &&
+      if (OrthancPlugins::RestApiGet(series, "/series/" + orthancId, false) &&
           series.type() == Json::objectValue &&
           series.isMember(INSTANCES) &&
           series[INSTANCES].type() == Json::arrayValue &&