changeset 3943:b26d25d3c1c7 transcoding

"/{patients|studies|series}/.../modify": New option "Transcode"
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 18 May 2020 21:37:31 +0200
parents 5b882ad2ffd0
children aae045f802f4
files NEWS OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp OrthancServer/ServerJobs/ResourceModificationJob.cpp OrthancServer/ServerJobs/ResourceModificationJob.h UnitTestsSources/MultiThreadingTests.cpp
diffstat 5 files changed, 159 insertions(+), 8 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Mon May 18 19:09:06 2020 +0200
+++ b/NEWS	Mon May 18 21:37:31 2020 +0200
@@ -23,7 +23,7 @@
     (defaults to the local AET)
 * Changes:
   - "/{patients|studies|series}/.../modify": New option "KeepSource"
-  - "/instances/.../modify": New option "Transcode"
+  - "/{patients|studies|series|instances}/.../modify": New option "Transcode"
   - ".../archive", ".../media", "/tools/create-media" and
     "/tools/create-archive": New option "Transcode"
   - "/ordered-slices": reverted the change introduced in 1.5.8 and go-back 
--- a/OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp	Mon May 18 19:09:06 2020 +0200
+++ b/OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp	Mon May 18 21:37:31 2020 +0200
@@ -177,9 +177,10 @@
       modification.SetLevel(ResourceType_Instance);
     }
 
-    if (request.isMember("Transcode"))
+    static const char* TRANSCODE = "Transcode";
+    if (request.isMember(TRANSCODE))
     {
-      std::string s = SerializationToolbox::ReadString(request, "Transcode");
+      std::string s = SerializationToolbox::ReadString(request, TRANSCODE);
       
       DicomTransferSyntax syntax;
       if (LookupTransferSyntax(syntax, s))
@@ -236,6 +237,12 @@
     job->SetModification(modification.release(), level, isAnonymization);
     job->SetOrigin(call);
     SetKeepSource(*job, body);
+
+    static const char* TRANSCODE = "Transcode";
+    if (body.isMember(TRANSCODE))
+    {
+      job->SetTranscode(SerializationToolbox::ReadString(body, TRANSCODE));
+    }
     
     context.AddChildInstances(*job, call.GetUriComponent("id", ""));
     job->AddTrailingStep();
--- a/OrthancServer/ServerJobs/ResourceModificationJob.cpp	Mon May 18 19:09:06 2020 +0200
+++ b/OrthancServer/ServerJobs/ResourceModificationJob.cpp	Mon May 18 21:37:31 2020 +0200
@@ -38,6 +38,8 @@
 #include "../../Core/SerializationToolbox.h"
 #include "../ServerContext.h"
 
+#include <dcmtk/dcmdata/dcfilefo.h>
+
 namespace Orthanc
 {
   class ResourceModificationJob::Output : public boost::noncopyable
@@ -171,6 +173,26 @@
 
     modification_->Apply(*modified);
 
+    if (transcode_)
+    {
+      std::set<DicomTransferSyntax> syntaxes;
+      syntaxes.insert(transferSyntax_);
+      
+      std::string s;
+      modified->SaveToMemoryBuffer(s);  // TODO - AVOID THIS SERIALIZATION IF NO PLUGIN
+
+      std::unique_ptr<IDicomTranscoder::TranscodedDicom> transcoded;
+      transcoded.reset(GetContext().TranscodeToParsed(modified->GetDcmtkObject(), s.empty() ? NULL : s.c_str(), s.size(), syntaxes, true));
+      if (transcoded.get() == NULL)
+      {
+        LOG(WARNING) << "Cannot transcode instance, keeping original transfer syntax: " << instance;
+      }
+      else
+      {
+        modified.reset(ParsedDicomFile::AcquireDcmtkObject(transcoded->ReleaseDicom()));
+      }
+    }
+
     DicomInstanceToStore toStore;
     toStore.SetOrigin(origin_);
     toStore.SetParsedDicomFile(*modified);
@@ -231,6 +253,15 @@
   }
 
 
+  ResourceModificationJob::ResourceModificationJob(ServerContext& context) :
+    CleaningInstancesJob(context, true /* by default, keep source */),
+    modification_(new DicomModification),
+    isAnonymization_(false),
+    transcode_(false)
+  {
+  }
+
+
   void ResourceModificationJob::SetModification(DicomModification* modification,
                                                 ResourceType level,
                                                 bool isAnonymization)
@@ -284,6 +315,61 @@
   }
 
 
+  DicomTransferSyntax ResourceModificationJob::GetTransferSyntax() const
+  {
+    if (transcode_)
+    {
+      return transferSyntax_;
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+  }
+  
+
+  void ResourceModificationJob::SetTranscode(DicomTransferSyntax syntax)
+  {
+    if (IsStarted())
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      transcode_ = true;
+      transferSyntax_ = syntax;
+    }    
+  }
+
+
+  void ResourceModificationJob::SetTranscode(const std::string& transferSyntaxUid)
+  {
+    DicomTransferSyntax s;
+    if (LookupTransferSyntax(s, transferSyntaxUid))
+    {
+      SetTranscode(s);
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_BadFileFormat,
+                             "Unknown transfer syntax UID: " + transferSyntaxUid);
+    }
+  }
+
+
+  void ResourceModificationJob::ClearTranscode()
+  {
+    if (IsStarted())
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      transcode_ = false;
+    }
+  }
+
+
   void ResourceModificationJob::GetPublicContent(Json::Value& value)
   {
     CleaningInstancesJob::GetPublicContent(value);
@@ -294,21 +380,38 @@
     {
       output_->Format(value);
     }
+
+    if (transcode_)
+    {
+      value["Transcode"] = GetTransferSyntaxUid(transferSyntax_);
+    }
   }
 
 
   static const char* MODIFICATION = "Modification";
   static const char* ORIGIN = "Origin";
   static const char* IS_ANONYMIZATION = "IsAnonymization";
+  static const char* TRANSCODE = "Transcode";
   
 
   ResourceModificationJob::ResourceModificationJob(ServerContext& context,
                                                    const Json::Value& serialized) :
     CleaningInstancesJob(context, serialized, true /* by default, keep source */)
   {
+    assert(serialized.type() == Json::objectValue);
+
     isAnonymization_ = SerializationToolbox::ReadBoolean(serialized, IS_ANONYMIZATION);
     origin_ = DicomInstanceOrigin(serialized[ORIGIN]);
     modification_.reset(new DicomModification(serialized[MODIFICATION]));
+
+    if (serialized.isMember(TRANSCODE))
+    {
+      SetTranscode(SerializationToolbox::ReadString(serialized, TRANSCODE));
+    }
+    else
+    {
+      transcode_ = false;
+    }
   }
   
   bool ResourceModificationJob::Serialize(Json::Value& value)
@@ -319,7 +422,15 @@
     }
     else
     {
+      assert(value.type() == Json::objectValue);
+      
       value[IS_ANONYMIZATION] = isAnonymization_;
+
+      if (transcode_)
+      {
+        value[TRANSCODE] = GetTransferSyntaxUid(transferSyntax_);
+      }
+      
       origin_.Serialize(value[ORIGIN]);
       
       Json::Value tmp;
--- a/OrthancServer/ServerJobs/ResourceModificationJob.h	Mon May 18 19:09:06 2020 +0200
+++ b/OrthancServer/ServerJobs/ResourceModificationJob.h	Mon May 18 21:37:31 2020 +0200
@@ -50,16 +50,14 @@
     boost::shared_ptr<Output>           output_;
     bool                                isAnonymization_;
     DicomInstanceOrigin                 origin_;
+    bool                                transcode_;
+    DicomTransferSyntax                 transferSyntax_;
 
   protected:
     virtual bool HandleInstance(const std::string& instance);
     
   public:
-    ResourceModificationJob(ServerContext& context) :
-      CleaningInstancesJob(context, true /* by default, keep source */),
-      isAnonymization_(false)
-    {
-    }
+    ResourceModificationJob(ServerContext& context);
 
     ResourceModificationJob(ServerContext& context,
                             const Json::Value& serialized);
@@ -84,6 +82,19 @@
       return origin_;
     }
 
+    bool IsTranscode() const
+    {
+      return transcode_;
+    }
+
+    DicomTransferSyntax GetTransferSyntax() const;
+
+    void SetTranscode(DicomTransferSyntax syntax);
+
+    void SetTranscode(const std::string& transferSyntaxUid);
+
+    void ClearTranscode();
+
     virtual void Stop(JobStopReason reason)
     {
     }
--- a/UnitTestsSources/MultiThreadingTests.cpp	Mon May 18 19:09:06 2020 +0200
+++ b/UnitTestsSources/MultiThreadingTests.cpp	Mon May 18 21:37:31 2020 +0200
@@ -1572,10 +1572,32 @@
 
     ResourceModificationJob& tmp = dynamic_cast<ResourceModificationJob&>(*job);
     ASSERT_TRUE(tmp.IsAnonymization());
+    ASSERT_FALSE(tmp.IsTranscode());
+    ASSERT_THROW(tmp.GetTransferSyntax(), OrthancException);
     ASSERT_EQ(RequestOrigin_Lua, tmp.GetOrigin().GetRequestOrigin());
     ASSERT_TRUE(tmp.GetModification().IsRemoved(DICOM_TAG_STUDY_DESCRIPTION));
   }
 
+  {
+    ResourceModificationJob job(GetContext());
+    job.SetTranscode(DicomTransferSyntax_JPEGProcess1);
+
+    job.AddTrailingStep();  // Necessary since 1.7.0
+    ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job));
+    ASSERT_TRUE(job.Serialize(s));
+  }
+
+  {
+    std::unique_ptr<IJob> job;
+    job.reset(unserializer.UnserializeJob(s));
+
+    ResourceModificationJob& tmp = dynamic_cast<ResourceModificationJob&>(*job);
+    ASSERT_FALSE(tmp.IsAnonymization());
+    ASSERT_TRUE(tmp.IsTranscode());
+    ASSERT_EQ(DicomTransferSyntax_JPEGProcess1, tmp.GetTransferSyntax());
+    ASSERT_EQ(RequestOrigin_Unknown, tmp.GetOrigin().GetRequestOrigin());
+  }
+
   // SplitStudyJob
 
   std::string instance;