changeset 1778:776573e592da

create media refactoring
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 13 Nov 2015 13:12:21 +0100
parents 0f5c416969dc
children c24dac8c1d4e
files OrthancServer/OrthancRestApi/OrthancRestArchive.cpp OrthancServer/ServerContext.cpp OrthancServer/ServerContext.h
diffstat 3 files changed, 329 insertions(+), 322 deletions(-) [+]
line wrap: on
line diff
--- a/OrthancServer/OrthancRestApi/OrthancRestArchive.cpp	Fri Nov 13 11:51:19 2015 +0100
+++ b/OrthancServer/OrthancRestApi/OrthancRestArchive.cpp	Fri Nov 13 13:12:21 2015 +0100
@@ -54,197 +54,6 @@
 {
   // Download of ZIP files ----------------------------------------------------
  
-  static std::string GetDirectoryNameInArchive(const Json::Value& resource,
-                                               ResourceType resourceType)
-  {
-    std::string s;
-    const Json::Value& tags = resource["MainDicomTags"];
-
-    switch (resourceType)
-    {
-      case ResourceType_Patient:
-      {
-        std::string p = tags["PatientID"].asString();
-        std::string n = tags["PatientName"].asString();
-        s = p + " " + n;
-        break;
-      }
-
-      case ResourceType_Study:
-      {
-        std::string p;
-        if (tags.isMember("AccessionNumber"))
-        {
-          p = tags["AccessionNumber"].asString() + " ";
-        }
-
-        s = p + tags["StudyDescription"].asString();
-        break;
-      }
-        
-      case ResourceType_Series:
-      {
-        std::string d = tags["SeriesDescription"].asString();
-        std::string m = tags["Modality"].asString();
-        s = m + " " + d;
-        break;
-      }
-        
-      default:
-        throw OrthancException(ErrorCode_InternalError);
-    }
-
-    // Get rid of special characters
-    return Toolbox::ConvertToAscii(s);
-  }
-
-  static bool CreateRootDirectoryInArchive(HierarchicalZipWriter& writer,
-                                           ServerContext& context,
-                                           const Json::Value& resource,
-                                           ResourceType resourceType)
-  {
-    if (resourceType == ResourceType_Patient)
-    {
-      return true;
-    }
-
-    ResourceType parentType = GetParentResourceType(resourceType);
-    Json::Value parent;
-
-    switch (resourceType)
-    {
-      case ResourceType_Study:
-      {
-        if (!context.GetIndex().LookupResource(parent, resource["ParentPatient"].asString(), parentType))
-        {
-          return false;
-        }
-
-        break;
-      }
-        
-      case ResourceType_Series:
-        if (!context.GetIndex().LookupResource(parent, resource["ParentStudy"].asString(), parentType) ||
-            !CreateRootDirectoryInArchive(writer, context, parent, parentType))
-        {
-          return false;
-        }
-        break;
-        
-      default:
-        throw OrthancException(ErrorCode_NotImplemented);
-    }
-
-    writer.OpenDirectory(GetDirectoryNameInArchive(parent, parentType).c_str());
-    return true;
-  }
-
-  static bool ArchiveInstance(HierarchicalZipWriter& writer,
-                              ServerContext& context,
-                              const std::string& instancePublicId,
-                              const char* filename)
-  {
-    writer.OpenFile(filename);
-
-    std::string dicom;
-    context.ReadFile(dicom, instancePublicId, FileContentType_Dicom);
-    writer.Write(dicom);
-
-    return true;
-  }
-
-  static bool ArchiveInternal(HierarchicalZipWriter& writer,
-                              ServerContext& context,
-                              const std::string& publicId,
-                              ResourceType resourceType,
-                              bool isFirstLevel)
-  { 
-    Json::Value resource;
-    if (!context.GetIndex().LookupResource(resource, publicId, resourceType))
-    {
-      return false;
-    }    
-
-    if (isFirstLevel && 
-        !CreateRootDirectoryInArchive(writer, context, resource, resourceType))
-    {
-      return false;
-    }
-
-    writer.OpenDirectory(GetDirectoryNameInArchive(resource, resourceType).c_str());
-
-    switch (resourceType)
-    {
-      case ResourceType_Patient:
-        for (Json::Value::ArrayIndex i = 0; i < resource["Studies"].size(); i++)
-        {
-          std::string studyId = resource["Studies"][i].asString();
-          if (!ArchiveInternal(writer, context, studyId, ResourceType_Study, false))
-          {
-            return false;
-          }
-        }
-        break;
-
-      case ResourceType_Study:
-        for (Json::Value::ArrayIndex i = 0; i < resource["Series"].size(); i++)
-        {
-          std::string seriesId = resource["Series"][i].asString();
-          if (!ArchiveInternal(writer, context, seriesId, ResourceType_Series, false))
-          {
-            return false;
-          }
-        }
-        break;
-
-      case ResourceType_Series:
-      {
-        // Create a filename prefix, depending on the modality
-        char format[24] = "%08d.dcm";
-
-        if (resource["MainDicomTags"].isMember("Modality"))
-        {
-          std::string modality = resource["MainDicomTags"]["Modality"].asString();
-
-          if (modality.size() == 1)
-          {
-            snprintf(format, sizeof(format) - 1, "%c%%07d.dcm", toupper(modality[0]));
-          }
-          else if (modality.size() >= 2)
-          {
-            snprintf(format, sizeof(format) - 1, "%c%c%%06d.dcm", toupper(modality[0]), toupper(modality[1]));
-          }
-        }
-
-        char filename[24];
-
-        for (Json::Value::ArrayIndex i = 0; i < resource["Instances"].size(); i++)
-        {
-          snprintf(filename, sizeof(filename) - 1, format, i);
-
-          std::string publicId = resource["Instances"][i].asString();
-
-          // This was the implementation up to Orthanc 0.7.0:
-          // std::string filename = instance["MainDicomTags"]["SOPInstanceUID"].asString() + ".dcm";
-
-          if (!ArchiveInstance(writer, context, publicId, filename))
-          {
-            return false;
-          }
-        }
-
-        break;
-      }
-
-      default:
-        throw OrthancException(ErrorCode_InternalError);
-    }
-
-    writer.CloseDirectory();
-    return true;
-  }                                 
-
-
   static bool IsZip64Required(uint64_t uncompressedSize,
                               unsigned int countInstances)
   {
@@ -268,120 +77,6 @@
   }
 
 
-  static bool IsZip64Required(ServerIndex& index,
-                              const std::string& id)
-  {
-    uint64_t uncompressedSize;
-    uint64_t compressedSize;
-    unsigned int countStudies;
-    unsigned int countSeries;
-    unsigned int countInstances;
-
-    index.GetStatistics(compressedSize, uncompressedSize, 
-                        countStudies, countSeries, countInstances, id);
-
-    return IsZip64Required(uncompressedSize, countInstances);
-  }
-                              
-
-  template <enum ResourceType resourceType>
-  static void GetArchive(RestApiGetCall& call)
-  {
-    ServerContext& context = OrthancRestApi::GetContext(call);
-
-    std::string id = call.GetUriComponent("id", "");
-    bool isZip64 = IsZip64Required(context.GetIndex(), id);
-
-    // Create a RAII for the temporary file to manage the ZIP file
-    Toolbox::TemporaryFile tmp;
-
-    {
-      // Create a ZIP writer
-      HierarchicalZipWriter writer(tmp.GetPath().c_str());
-      writer.SetZip64(isZip64);
-
-      // Store the requested resource into the ZIP
-      if (!ArchiveInternal(writer, context, id, resourceType, true))
-      {
-        return;
-      }
-    }
-
-    // Prepare the sending of the ZIP file
-    FilesystemHttpSender sender(tmp.GetPath());
-    sender.SetContentType("application/zip");
-    sender.SetContentFilename(id + ".zip");
-
-    // Send the ZIP
-    call.GetOutput().AnswerStream(sender);
-
-    // The temporary file is automatically removed thanks to the RAII
-  }
-
-
-  static void GetMediaArchive(RestApiGetCall& call)
-  {
-    ServerContext& context = OrthancRestApi::GetContext(call);
-
-    std::string id = call.GetUriComponent("id", "");
-    bool isZip64 = IsZip64Required(context.GetIndex(), id);
-
-    // Create a RAII for the temporary file to manage the ZIP file
-    Toolbox::TemporaryFile tmp;
-
-    {
-      // Create a ZIP writer
-      HierarchicalZipWriter writer(tmp.GetPath().c_str());
-      writer.SetZip64(isZip64);
-      writer.OpenDirectory("IMAGES");
-
-      // Create the DICOMDIR writer
-      DicomDirWriter dicomDir;
-
-      // Retrieve the list of the instances
-      std::list<std::string> instances;
-      context.GetIndex().GetChildInstances(instances, id);
-
-      size_t pos = 0;
-      for (std::list<std::string>::const_iterator
-             it = instances.begin(); it != instances.end(); ++it, ++pos)
-      {
-        // "DICOM restricts the filenames on DICOM media to 8
-        // characters (some systems wrongly use 8.3, but this does not
-        // conform to the standard)."
-        std::string filename = "IM" + boost::lexical_cast<std::string>(pos);
-        writer.OpenFile(filename.c_str());
-
-        std::string dicom;
-        context.ReadFile(dicom, *it, FileContentType_Dicom);
-        writer.Write(dicom);
-
-        ParsedDicomFile parsed(dicom);
-        dicomDir.Add("IMAGES", filename, parsed);
-      }
-
-      // Add the DICOMDIR
-      writer.CloseDirectory();
-      writer.OpenFile("DICOMDIR");
-      std::string s;
-      dicomDir.Encode(s);
-      writer.Write(s);
-    }
-
-    // Prepare the sending of the ZIP file
-    FilesystemHttpSender sender(tmp.GetPath());
-    sender.SetContentType("application/zip");
-    sender.SetContentFilename(id + ".zip");
-
-    // Send the ZIP
-    call.GetOutput().AnswerStream(sender);
-
-    // The temporary file is automatically removed thanks to the RAII
-  }
-
-
-
-
   namespace
   {
     class ResourceIdentifiers
@@ -726,10 +421,255 @@
         out_ << "      " << instanceId << std::endl;
       }
     };
+
+
+    class ArchiveWriterVisitor : public IArchiveVisitor
+    {
+    private:
+      HierarchicalZipWriter&  writer_;
+      ServerContext&            context_;
+      char                    instanceFormat_[24];
+      unsigned int            countInstances_;
+
+      static std::string GetTag(const DicomMap& tags,
+                                const DicomTag& tag)
+      {
+        const DicomValue* v = tags.TestAndGetValue(tag);
+        if (v != NULL &&
+            !v->IsBinary() &&
+            !v->IsNull())
+        {
+          return v->GetContent();
+        }
+        else
+        {
+          return "";
+        }
+      }
+
+    public:
+      ArchiveWriterVisitor(HierarchicalZipWriter& writer,
+                           ServerContext& context) :
+        writer_(writer),
+        context_(context)
+      {
+      }
+
+      virtual void Open(ResourceType level,
+                        const std::string& publicId)
+      {
+        std::string path;
+
+        DicomMap tags;
+        if (context_.GetIndex().GetMainDicomTags(tags, publicId, level, level))
+        {
+          switch (level)
+          {
+            case ResourceType_Patient:
+              path = GetTag(tags, DICOM_TAG_PATIENT_ID) + " " + GetTag(tags, DICOM_TAG_PATIENT_NAME);
+              break;
+
+            case ResourceType_Study:
+              path = GetTag(tags, DICOM_TAG_ACCESSION_NUMBER) + " " + GetTag(tags, DICOM_TAG_STUDY_DESCRIPTION);
+              break;
+
+            case ResourceType_Series:
+            {
+              std::string modality = GetTag(tags, DICOM_TAG_MODALITY);
+              path = modality + " " + GetTag(tags, DICOM_TAG_SERIES_DESCRIPTION);
+
+              if (modality.size() == 0)
+              {
+                snprintf(instanceFormat_, sizeof(instanceFormat_) - 1, "%%08d.dcm");
+              }
+              else if (modality.size() == 1)
+              {
+                snprintf(instanceFormat_, sizeof(instanceFormat_) - 1, "%c%%07d.dcm", 
+                         toupper(modality[0]));
+              }
+              else if (modality.size() >= 2)
+              {
+                snprintf(instanceFormat_, sizeof(instanceFormat_) - 1, "%c%c%%06d.dcm", 
+                         toupper(modality[0]), toupper(modality[1]));
+              }
+
+              countInstances_ = 0;
+
+              break;
+            }
+
+            default:
+              throw OrthancException(ErrorCode_InternalError);
+          }
+        }
+
+        path = Toolbox::StripSpaces(Toolbox::ConvertToAscii(path));
+
+        if (path.empty())
+        {
+          path = std::string("Unknown ") + EnumerationToString(level);
+        }
+
+        writer_.OpenDirectory(path.c_str());
+      }
+
+      virtual void Close()
+      {
+        writer_.CloseDirectory();
+      }
+
+      virtual void AddInstance(const std::string& instanceId,
+                               const FileInfo& dicom)
+      {
+        std::string content;
+        context_.ReadFile(content, dicom);
+
+        char filename[24];
+        snprintf(filename, sizeof(filename) - 1, instanceFormat_, countInstances_);
+        countInstances_ ++;
+
+        writer_.OpenFile(filename);
+        writer_.Write(content);
+      }
+
+      static void Apply(RestApiOutput& output,
+                        ServerContext& context,
+                        ArchiveIndex& archive,
+                        const std::string& filename)
+      {
+        archive.Expand(context.GetIndex());
+
+        StatisticsVisitor stats;
+        archive.Apply(stats);
+
+        const bool isZip64 = IsZip64Required(stats.GetUncompressedSize(), stats.GetInstancesCount());
+
+        // Create a RAII for the temporary file to manage the ZIP file
+        Toolbox::TemporaryFile tmp;
+
+        {
+          // Create a ZIP writer
+          HierarchicalZipWriter writer(tmp.GetPath().c_str());
+          writer.SetZip64(isZip64);
+
+          ArchiveWriterVisitor v(writer, context);
+          archive.Apply(v);
+        }
+
+        // Prepare the sending of the ZIP file
+        FilesystemHttpSender sender(tmp.GetPath());
+        sender.SetContentType("application/zip");
+        sender.SetContentFilename(filename);
+
+        // Send the ZIP
+        output.AnswerStream(sender);
+
+        // The temporary file is automatically removed thanks to the RAII
+      }
+    };
+
+    
+    class MediaWriterVisitor : public IArchiveVisitor
+    {
+    private:
+      HierarchicalZipWriter&  writer_;
+      DicomDirWriter          dicomDir_;
+      ServerContext&          context_;
+      unsigned int            countInstances_;
+
+    public:
+      MediaWriterVisitor(HierarchicalZipWriter& writer,
+                         ServerContext& context) :
+        writer_(writer),
+        context_(context),
+        countInstances_(0)
+      {
+      }
+
+      void EncodeDicomDir(std::string& result)
+      {
+        dicomDir_.Encode(result);
+      }
+
+      virtual void Open(ResourceType level,
+                        const std::string& publicId)
+      {
+      }
+
+      virtual void Close()
+      {
+      }
+
+      virtual void AddInstance(const std::string& instanceId,
+                               const FileInfo& dicom)
+      {
+        // "DICOM restricts the filenames on DICOM media to 8
+        // characters (some systems wrongly use 8.3, but this does not
+        // conform to the standard)."
+        std::string filename = "IM" + boost::lexical_cast<std::string>(countInstances_);
+        writer_.OpenFile(filename.c_str());
+
+        std::string content;
+        context_.ReadFile(content, dicom);
+        writer_.Write(content);
+
+        ParsedDicomFile parsed(content);
+        dicomDir_.Add("IMAGES", filename, parsed);
+
+        countInstances_ ++;
+      }
+
+      static void Apply(RestApiOutput& output,
+                        ServerContext& context,
+                        ArchiveIndex& archive,
+                        const std::string& filename)
+      {
+        archive.Expand(context.GetIndex());
+
+        StatisticsVisitor stats;
+        archive.Apply(stats);
+
+        const bool isZip64 = IsZip64Required(stats.GetUncompressedSize(), stats.GetInstancesCount());
+
+        // Create a RAII for the temporary file to manage the ZIP file
+        Toolbox::TemporaryFile tmp;
+
+        {
+          // Create a ZIP writer
+          HierarchicalZipWriter writer(tmp.GetPath().c_str());
+          writer.SetZip64(isZip64);
+          writer.OpenDirectory("IMAGES");
+
+          // Create the DICOMDIR writer
+          DicomDirWriter dicomDir;
+
+          MediaWriterVisitor v(writer, context);
+          archive.Apply(v);
+
+          // Add the DICOMDIR
+          writer.CloseDirectory();
+          writer.OpenFile("DICOMDIR");
+          std::string s;
+          v.EncodeDicomDir(s);
+          writer.Write(s);
+        }
+
+        // Prepare the sending of the ZIP file
+        FilesystemHttpSender sender(tmp.GetPath());
+        sender.SetContentType("application/zip");
+        sender.SetContentFilename(filename);
+
+        // Send the ZIP
+        output.AnswerStream(sender);
+
+        // The temporary file is automatically removed thanks to the RAII
+      }
+    };
   }
 
 
-  static void CreateBatchArchive(RestApiPostCall& call)
+  static bool AddResourcesOfInterest(ArchiveIndex& archive,
+                                     RestApiPostCall& call)
   {
     ServerIndex& index = OrthancRestApi::GetIndex(call);
 
@@ -737,43 +677,99 @@
     if (call.ParseJsonRequest(resources) &&
         resources.type() == Json::arrayValue)
     {
-      ArchiveIndex archive(ResourceType_Patient);  // root
-
       for (Json::Value::ArrayIndex i = 0; i < resources.size(); i++)
       {
         if (resources[i].type() != Json::stringValue)
         {
-          return;   // Bad request
+          return false;   // Bad request
         }
 
         ResourceIdentifiers resource(index, resources[i].asString());
         archive.Add(index, resource);
       }
 
-      archive.Expand(index);
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
 
-      PrintVisitor v(std::cout);
-      archive.Apply(v);
+
+  static void CreateBatchArchive(RestApiPostCall& call)
+  {
+    ArchiveIndex archive(ResourceType_Patient);  // root
 
-      StatisticsVisitor s;
-      archive.Apply(s);
+    if (AddResourcesOfInterest(archive, call))
+    {
+      ArchiveWriterVisitor::Apply(call.GetOutput(),
+                                  OrthancRestApi::GetContext(call),
+                                  archive,
+                                  "Archive.zip");
+    }
+  }  
+
 
-      std::cout << s.GetUncompressedSize() << " " << s.GetInstancesCount() << std::endl;
+  static void CreateBatchMedia(RestApiPostCall& call)
+  {
+    ArchiveIndex archive(ResourceType_Patient);  // root
+
+    if (AddResourcesOfInterest(archive, call))
+    {
+      MediaWriterVisitor::Apply(call.GetOutput(),
+                                OrthancRestApi::GetContext(call),
+                                archive,
+                                "Archive.zip");
     }
   }  
 
 
+  static void CreateArchive(RestApiGetCall& call)
+  {
+    ServerIndex& index = OrthancRestApi::GetIndex(call);
+
+    std::string id = call.GetUriComponent("id", "");
+    ResourceIdentifiers resource(index, id);
+
+    ArchiveIndex archive(ResourceType_Patient);  // root
+    archive.Add(OrthancRestApi::GetIndex(call), resource);
+
+    ArchiveWriterVisitor::Apply(call.GetOutput(),
+                                OrthancRestApi::GetContext(call),
+                                archive,
+                                id + ".zip");
+  }
+
+
+  static void CreateMedia(RestApiGetCall& call)
+  {
+    ServerIndex& index = OrthancRestApi::GetIndex(call);
+
+    std::string id = call.GetUriComponent("id", "");
+    ResourceIdentifiers resource(index, id);
+
+    ArchiveIndex archive(ResourceType_Patient);  // root
+    archive.Add(OrthancRestApi::GetIndex(call), resource);
+
+    MediaWriterVisitor::Apply(call.GetOutput(),
+                              OrthancRestApi::GetContext(call),
+                              archive,
+                              id + ".zip");
+  }
+
 
   void OrthancRestApi::RegisterArchive()
   {
-    Register("/patients/{id}/archive", GetArchive<ResourceType_Patient>);
-    Register("/studies/{id}/archive", GetArchive<ResourceType_Study>);
-    Register("/series/{id}/archive", GetArchive<ResourceType_Series>);
+    Register("/patients/{id}/archive", CreateArchive);
+    Register("/studies/{id}/archive", CreateArchive);
+    Register("/series/{id}/archive", CreateArchive);
 
-    Register("/patients/{id}/media", GetMediaArchive);
-    Register("/studies/{id}/media", GetMediaArchive);
-    Register("/series/{id}/media", GetMediaArchive);
+    Register("/patients/{id}/media", CreateMedia);
+    Register("/studies/{id}/media", CreateMedia);
+    Register("/series/{id}/media", CreateMedia);
 
-    Register("/tools/archive", CreateBatchArchive);
+    Register("/tools/create-archive", CreateBatchArchive);
+    Register("/tools/create-media", CreateBatchMedia);
   }
 }
--- a/OrthancServer/ServerContext.cpp	Fri Nov 13 11:51:19 2015 +0100
+++ b/OrthancServer/ServerContext.cpp	Fri Nov 13 13:12:21 2015 +0100
@@ -407,6 +407,14 @@
   }
 
 
+  void ServerContext::ReadFile(std::string& result,
+                               const FileInfo& file)
+  {
+    StorageAccessor accessor(area_);
+    accessor.Read(result, file);
+  }
+
+
   IDynamicObject* ServerContext::DicomCacheProvider::Provide(const std::string& instancePublicId)
   {
     std::string content;
--- a/OrthancServer/ServerContext.h	Fri Nov 13 11:51:19 2015 +0100
+++ b/OrthancServer/ServerContext.h	Fri Nov 13 13:12:21 2015 +0100
@@ -201,6 +201,9 @@
                   FileContentType content,
                   bool uncompressIfNeeded = true);
 
+    void ReadFile(std::string& result,
+                  const FileInfo& file);
+
     void SetStoreMD5ForAttachments(bool storeMD5);
 
     bool IsStoreMD5ForAttachments() const