changeset 3939:c205f670098e transcoding

new configuration options: BuiltinDecoderTranscoderOrder and IngestTranscoding
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 18 May 2020 17:15:16 +0200
parents 54dbebbcc032
children 3661e2a72482
files Core/DicomParsing/DcmtkTranscoder.cpp NEWS OrthancServer/OrthancConfiguration.cpp OrthancServer/OrthancConfiguration.h OrthancServer/ServerContext.cpp OrthancServer/ServerContext.h Resources/Configuration.json
diffstat 7 files changed, 115 insertions(+), 40 deletions(-) [+]
line wrap: on
line diff
--- a/Core/DicomParsing/DcmtkTranscoder.cpp	Mon May 18 15:59:50 2020 +0200
+++ b/Core/DicomParsing/DcmtkTranscoder.cpp	Mon May 18 17:15:16 2020 +0200
@@ -120,10 +120,14 @@
     if (quality <= 0 ||
         quality > 100)
     {
-      throw OrthancException(ErrorCode_ParameterOutOfRange);
+      throw OrthancException(
+        ErrorCode_ParameterOutOfRange,
+        "The quality for lossy transcoding must be an integer between 1 and 100, received: " +
+        boost::lexical_cast<std::string>(quality));
     }
     else
     {
+      LOG(INFO) << "Quality for lossy transcoding using DCMTK is set to: " << quality;
       lossyQuality_ = quality;
     }
   }
--- a/NEWS	Mon May 18 15:59:50 2020 +0200
+++ b/NEWS	Mon May 18 17:15:16 2020 +0200
@@ -7,8 +7,9 @@
 * DICOM transcoding over the REST API
 * Transcoding from compressed to uncompressed transfer syntaxes over DICOM
   C-STORE SCU (if the remote modality doesn't support compressed syntaxes)
-* New configuration options: "TranscodeDicomProtocol" and
-  "BuiltinDecoderTranscoderOrder"
+* New configuration options related to transcoding:
+  "TranscodeDicomProtocol", "BuiltinDecoderTranscoderOrder",
+  "IngestTranscoding" and "DicomLossyTranscodingQuality"
 
 REST API
 --------
--- a/OrthancServer/OrthancConfiguration.cpp	Mon May 18 15:59:50 2020 +0200
+++ b/OrthancServer/OrthancConfiguration.cpp	Mon May 18 17:15:16 2020 +0200
@@ -422,8 +422,8 @@
   }
 
 
-  std::string OrthancConfiguration::GetStringParameter(const std::string& parameter,
-                                                       const std::string& defaultValue) const
+  bool OrthancConfiguration::LookupStringParameter(std::string& target,
+                                                   const std::string& parameter) const
   {
     if (json_.isMember(parameter))
     {
@@ -434,11 +434,27 @@
       }
       else
       {
-        return json_[parameter].asString();
+        target = json_[parameter].asString();
+        return true;
       }
     }
     else
     {
+      return false;
+    }
+  }
+
+
+  std::string OrthancConfiguration::GetStringParameter(const std::string& parameter,
+                                                       const std::string& defaultValue) const
+  {
+    std::string value;
+    if (LookupStringParameter(value, parameter))
+    {
+      return value;
+    }
+    else
+    {
       return defaultValue;
     }
   }
--- a/OrthancServer/OrthancConfiguration.h	Mon May 18 15:59:50 2020 +0200
+++ b/OrthancServer/OrthancConfiguration.h	Mon May 18 17:15:16 2020 +0200
@@ -163,6 +163,9 @@
       fontRegistry_.AddFromResource(resource);
     }
 
+    bool LookupStringParameter(std::string& target,
+                               const std::string& parameter) const;
+
     std::string GetStringParameter(const std::string& parameter,
                                    const std::string& defaultValue) const;
     
--- a/OrthancServer/ServerContext.cpp	Mon May 18 15:59:50 2020 +0200
+++ b/OrthancServer/ServerContext.cpp	Mon May 18 17:15:16 2020 +0200
@@ -246,37 +246,72 @@
     isHttpServerSecure_(true),
     isExecuteLuaEnabled_(false),
     overwriteInstances_(false),
-    dcmtkTranscoder_(new DcmtkTranscoder)
+    dcmtkTranscoder_(new DcmtkTranscoder),
+    isIngestTranscoding_(false)
   {
+    try
     {
-      OrthancConfiguration::ReaderLock lock;
+      unsigned int lossyQuality;
+
+      {
+        OrthancConfiguration::ReaderLock lock;
 
-      queryRetrieveArchive_.reset(
-        new SharedArchive(lock.GetConfiguration().GetUnsignedIntegerParameter("QueryRetrieveSize", 100)));
-      mediaArchive_.reset(
-        new SharedArchive(lock.GetConfiguration().GetUnsignedIntegerParameter("MediaArchiveSize", 1)));
-      defaultLocalAet_ = lock.GetConfiguration().GetStringParameter("DicomAet", "ORTHANC");
-      jobsEngine_.SetWorkersCount(lock.GetConfiguration().GetUnsignedIntegerParameter("ConcurrentJobs", 2));
-      saveJobs_ = lock.GetConfiguration().GetBooleanParameter("SaveJobs", true);
-      metricsRegistry_->SetEnabled(lock.GetConfiguration().GetBooleanParameter("MetricsEnabled", true));
+        queryRetrieveArchive_.reset(
+          new SharedArchive(lock.GetConfiguration().GetUnsignedIntegerParameter("QueryRetrieveSize", 100)));
+        mediaArchive_.reset(
+          new SharedArchive(lock.GetConfiguration().GetUnsignedIntegerParameter("MediaArchiveSize", 1)));
+        defaultLocalAet_ = lock.GetConfiguration().GetStringParameter("DicomAet", "ORTHANC");
+        jobsEngine_.SetWorkersCount(lock.GetConfiguration().GetUnsignedIntegerParameter("ConcurrentJobs", 2));
+        saveJobs_ = lock.GetConfiguration().GetBooleanParameter("SaveJobs", true);
+        metricsRegistry_->SetEnabled(lock.GetConfiguration().GetBooleanParameter("MetricsEnabled", true));
+
+        // New configuration options in Orthanc 1.5.1
+        findStorageAccessMode_ = StringToFindStorageAccessMode(lock.GetConfiguration().GetStringParameter("StorageAccessOnFind", "Always"));
+        limitFindInstances_ = lock.GetConfiguration().GetUnsignedIntegerParameter("LimitFindInstances", 0);
+        limitFindResults_ = lock.GetConfiguration().GetUnsignedIntegerParameter("LimitFindResults", 0);
+
+        // New configuration option in Orthanc 1.6.0
+        storageCommitmentReports_.reset(new StorageCommitmentReports(lock.GetConfiguration().GetUnsignedIntegerParameter("StorageCommitmentReportsSize", 100)));
+
+        // New options in Orthanc 1.7.0
+        transcodeDicomProtocol_ = lock.GetConfiguration().GetBooleanParameter("TranscodeDicomProtocol", true);
+        builtinDecoderTranscoderOrder_ = StringToBuiltinDecoderTranscoderOrder(lock.GetConfiguration().GetStringParameter("BuiltinDecoderTranscoderOrder", "After"));
+        lossyQuality = lock.GetConfiguration().GetUnsignedIntegerParameter("DicomLossyTranscodingQuality", 90);
 
-      // New configuration options in Orthanc 1.5.1
-      findStorageAccessMode_ = StringToFindStorageAccessMode(lock.GetConfiguration().GetStringParameter("StorageAccessOnFind", "Always"));
-      limitFindInstances_ = lock.GetConfiguration().GetUnsignedIntegerParameter("LimitFindInstances", 0);
-      limitFindResults_ = lock.GetConfiguration().GetUnsignedIntegerParameter("LimitFindResults", 0);
-
-      // New configuration option in Orthanc 1.6.0
-      storageCommitmentReports_.reset(new StorageCommitmentReports(lock.GetConfiguration().GetUnsignedIntegerParameter("StorageCommitmentReportsSize", 100)));
+        std::string s;
+        if (lock.GetConfiguration().LookupStringParameter(s, "IngestTranscoding"))
+        {
+          if (LookupTransferSyntax(ingestTransferSyntax_, s))
+          {
+            isIngestTranscoding_ = true;
+            LOG(WARNING) << "Incoming DICOM instances will automatically be transcoded to "
+                         << "transfer syntax: " << GetTransferSyntaxUid(ingestTransferSyntax_);
+          }
+          else
+          {
+            throw OrthancException(ErrorCode_ParameterOutOfRange,
+                                   "Unknown transfer syntax for ingest transcoding: " + s);
+          }
+        }
+        else
+        {
+          isIngestTranscoding_ = true;
+          LOG(INFO) << "Automated transcoding of incoming DICOM instances is disabled";
+        }
+      }
 
-      // New options in Orthanc 1.7.0
-      transcodeDicomProtocol_ = lock.GetConfiguration().GetBooleanParameter("TranscodeDicomProtocol", true);
-      builtinDecoderTranscoderOrder_ = StringToBuiltinDecoderTranscoderOrder(lock.GetConfiguration().GetStringParameter("BuiltinDecoderTranscoderOrder", "After"));
-    }
+      jobsEngine_.SetThreadSleep(unitTesting ? 20 : 200);
 
-    jobsEngine_.SetThreadSleep(unitTesting ? 20 : 200);
-
-    listeners_.push_back(ServerListener(luaListener_, "Lua"));
-    changeThread_ = boost::thread(ChangeThread, this, (unitTesting ? 20 : 100));
+      listeners_.push_back(ServerListener(luaListener_, "Lua"));
+      changeThread_ = boost::thread(ChangeThread, this, (unitTesting ? 20 : 100));
+    
+      dynamic_cast<DcmtkTranscoder&>(*dcmtkTranscoder_).SetLossyQuality(lossyQuality);
+    }
+    catch (OrthancException&)
+    {
+      Stop();
+      throw;
+    }
   }
 
 
@@ -508,22 +543,19 @@
                                    DicomInstanceToStore& dicom,
                                    StoreInstanceMode mode)
   {
-    const DicomTransferSyntax option = DicomTransferSyntax_JPEGProcess1;
-    //const DicomTransferSyntax option = DicomTransferSyntax_JPEGProcess14SV1;
-    //const DicomTransferSyntax option = DicomTransferSyntax_LittleEndianExplicit;
-
-    if (1)
+    if (!isIngestTranscoding_)
     {
+      // No automated transcoding
       return StoreAfterTranscoding(resultPublicId, dicom, mode);
     }
     else
     {
-      // TODO => Automated transcoding of incoming DICOM files
+      // Automated transcoding of incoming DICOM files
       
       DicomTransferSyntax sourceSyntax;
       if (!FromDcmtkBridge::LookupOrthancTransferSyntax(
             sourceSyntax, dicom.GetParsedDicomFile().GetDcmtkObject()) ||
-          sourceSyntax == option)
+          sourceSyntax == ingestTransferSyntax_)
       {
         // No transcoding
         return StoreAfterTranscoding(resultPublicId, dicom, mode);
@@ -531,7 +563,7 @@
       else
       {      
         std::set<DicomTransferSyntax> syntaxes;
-        syntaxes.insert(option);
+        syntaxes.insert(ingestTransferSyntax_);
 
         std::unique_ptr<IDicomTranscoder::TranscodedDicom> transcoded(
           TranscodeToParsed(dicom.GetParsedDicomFile().GetDcmtkObject(),
--- a/OrthancServer/ServerContext.h	Mon May 18 15:59:50 2020 +0200
+++ b/OrthancServer/ServerContext.h	Mon May 18 17:15:16 2020 +0200
@@ -230,6 +230,8 @@
     bool transcodeDicomProtocol_;
     std::unique_ptr<IDicomTranscoder>  dcmtkTranscoder_;
     BuiltinDecoderTranscoderOrder builtinDecoderTranscoderOrder_;
+    bool isIngestTranscoding_;
+    DicomTransferSyntax ingestTransferSyntax_;
 
     StoreStatus StoreAfterTranscoding(std::string& resultPublicId,
                                       DicomInstanceToStore& dicom,
--- a/Resources/Configuration.json	Mon May 18 15:59:50 2020 +0200
+++ b/Resources/Configuration.json	Mon May 18 17:15:16 2020 +0200
@@ -554,5 +554,22 @@
   // or is not applied at all (new in Orthanc 1.7.0). The allowed
   // values for this option are "After" (default value, corresponding
   // to the behavior of Orthanc <= 1.6.1), "Before", or "Disabled".
-  "BuiltinDecoderTranscoderOrder" : "After"
+  "BuiltinDecoderTranscoderOrder" : "After",
+
+  // If this option is set, Orthanc will transparently transcode any
+  // incoming DICOM instance to the given transfer syntax before
+  // storing it into its database. Beware that this might result in
+  // high CPU usage (if transcoding to some compressed transfer
+  // syntax), or in higher disk consumption (if transcoding to an
+  // uncompressed syntax). Also, beware that transcoding to a transfer
+  // syntax with lossy compression (notably JPEG) will change the
+  // "SOPInstanceUID" DICOM tag, and thus the Orthanc identifier at
+  // the instance level, which might break external workflow.
+  /**
+     "IngestTranscoding" : "1.2.840.10008.1.2",
+  **/
+
+  // The compression level that is used when transcoding to one of the
+  // lossy/JPEG transfer syntaxes (integer between 1 and 100).
+  "DicomLossyTranscodingQuality" : 90
 }