changeset 4465:fe774d8e904b

New configuration option: "DicomScuPreferredTransferSyntax" to control transcoding in C-STORE SCU
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 21 Jan 2021 17:08:32 +0100
parents e8c7be7a02a9
children 2243f1bb909b
files NEWS OrthancFramework/Sources/DicomNetworking/DicomStoreUserConnection.cpp OrthancFramework/Sources/DicomNetworking/DicomStoreUserConnection.h OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp OrthancServer/Resources/Configuration.json OrthancServer/Sources/ServerContext.cpp OrthancServer/Sources/ServerContext.h OrthancServer/Sources/ServerJobs/DicomModalityStoreJob.cpp
diffstat 8 files changed, 210 insertions(+), 111 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Wed Jan 20 17:43:15 2021 +0100
+++ b/NEWS	Thu Jan 21 17:08:32 2021 +0100
@@ -13,6 +13,7 @@
   - "UseDicomTls" in "DicomModalities" to enable DICOM TLS in outgoing SCU on a per-modality basis
   - "MaximumPduLength" to tune the maximum PDU length (Protocol Data Unit)
   - "LocalAet" in "DicomModalities" to overwrite global "DicomAet" for SCU on a per-modality basis
+* New configuration option: "DicomScuPreferredTransferSyntax" to control transcoding in C-STORE SCU
 * New command-line option: "--openapi" to write the OpenAPI documentation of the REST API to a file
 * New metadata automatically computed at the series level: "RemoteAET"
 
--- a/OrthancFramework/Sources/DicomNetworking/DicomStoreUserConnection.cpp	Wed Jan 20 17:43:15 2021 +0100
+++ b/OrthancFramework/Sources/DicomNetworking/DicomStoreUserConnection.cpp	Thu Jan 21 17:08:32 2021 +0100
@@ -31,6 +31,8 @@
 
 #include <dcmtk/dcmdata/dcdeftag.h>
 
+#include <list>
+
 
 namespace Orthanc
 {
@@ -49,76 +51,77 @@
 
 
   bool DicomStoreUserConnection::ProposeStorageClass(const std::string& sopClassUid,
-                                                     const std::set<DicomTransferSyntax>& syntaxes)
+                                                     const std::set<DicomTransferSyntax>& sourceSyntaxes,
+                                                     bool hasPreferred,
+                                                     DicomTransferSyntax preferred)
   {
-    // Default transfer syntax for DICOM
-    const bool addLittleEndianImplicit = (
-      proposeUncompressedSyntaxes_ &&
-      syntaxes.find(DicomTransferSyntax_LittleEndianImplicit) == syntaxes.end());
-    
-    const bool addLittleEndianExplicit = (
-      proposeUncompressedSyntaxes_ &&
-      syntaxes.find(DicomTransferSyntax_LittleEndianExplicit) == syntaxes.end());
-    
-    const bool addBigEndianExplicit = (
-      proposeUncompressedSyntaxes_ &&
-      proposeRetiredBigEndian_ &&
-      syntaxes.find(DicomTransferSyntax_BigEndianExplicit) == syntaxes.end());
-    
-    size_t requiredCount = syntaxes.size();
-    if (addLittleEndianImplicit)
+    typedef std::list< std::set<DicomTransferSyntax> >  GroupsOfSyntaxes;
+
+    GroupsOfSyntaxes  groups;
+
+    // Firstly, add one group for each individual transfer syntax
+    for (std::set<DicomTransferSyntax>::const_iterator
+           it = sourceSyntaxes.begin(); it != sourceSyntaxes.end(); ++it)
+    {
+      std::set<DicomTransferSyntax> group;
+      group.insert(*it);
+      groups.push_back(group);
+    }
+
+    // Secondly, add one group with the preferred transfer syntax
+    if (hasPreferred &&
+        sourceSyntaxes.find(preferred) == sourceSyntaxes.end())
+    {
+      std::set<DicomTransferSyntax> group;
+      group.insert(preferred);
+      groups.push_back(group);
+    }
+
+    // Thirdly, add all the uncompressed transfer syntaxes as one single group
+    if (proposeUncompressedSyntaxes_)
     {
-      requiredCount += 1;
+      static const size_t N = 3;
+      static const DicomTransferSyntax UNCOMPRESSED_SYNTAXES[N] = {
+        DicomTransferSyntax_LittleEndianImplicit,
+        DicomTransferSyntax_LittleEndianExplicit,
+        DicomTransferSyntax_BigEndianExplicit
+      };
+
+      std::set<DicomTransferSyntax> group;
+
+      for (size_t i = 0; i < N; i++)
+      {
+        DicomTransferSyntax syntax = UNCOMPRESSED_SYNTAXES[i];
+        if (sourceSyntaxes.find(syntax) == sourceSyntaxes.end() &&
+            (!hasPreferred || preferred != syntax))
+        {
+          group.insert(syntax);
+        }
+      }
+
+      if (!group.empty())
+      {
+        groups.push_back(group);
+      }
     }
-      
-    if (addLittleEndianExplicit ||
-        addBigEndianExplicit)
-    {
-      requiredCount += 1;
-    }
-      
-    if (association_->GetRemainingPropositions() <= requiredCount)
+
+    // Now, propose each of these groups of transfer syntaxes
+    if (association_->GetRemainingPropositions() <= groups.size())
     {
       return false;  // Not enough room
     }
     else
     {
-      for (std::set<DicomTransferSyntax>::const_iterator
-             it = syntaxes.begin(); it != syntaxes.end(); ++it)
+      for (GroupsOfSyntaxes::const_iterator it = groups.begin(); it != groups.end(); ++it)
       {
         association_->ProposePresentationContext(sopClassUid, *it);
-        proposedOriginalClasses_.insert(std::make_pair(sopClassUid, *it));
-      }
 
-      if (addLittleEndianImplicit)
-      {
-        association_->ProposePresentationContext(sopClassUid, DicomTransferSyntax_LittleEndianImplicit);
-        proposedOriginalClasses_.insert(std::make_pair(sopClassUid, DicomTransferSyntax_LittleEndianImplicit));
-      }
-
-      if (addLittleEndianExplicit ||
-          addBigEndianExplicit)
-      {
-        std::set<DicomTransferSyntax> uncompressed;
-
-        if (addLittleEndianExplicit)
+        // Remember the syntaxes that were individually proposed, in
+        // order to avoid renegociation if they are seen again (**)
+        if (it->size() == 1)
         {
-          uncompressed.insert(DicomTransferSyntax_LittleEndianExplicit);
-        }
-
-        if (addBigEndianExplicit)
-        {
-          uncompressed.insert(DicomTransferSyntax_BigEndianExplicit);
-        }
-
-        association_->ProposePresentationContext(sopClassUid, uncompressed);
-
-        assert(!uncompressed.empty());
-        if (addLittleEndianExplicit ^ addBigEndianExplicit)
-        {
-          // Only one transfer syntax was proposed for this presentation context
-          assert(uncompressed.size() == 1);
-          proposedOriginalClasses_.insert(std::make_pair(sopClassUid, *uncompressed.begin()));
+          DicomTransferSyntax syntax = *it->begin();
+          proposedOriginalClasses_.insert(std::make_pair(sopClassUid, syntax));
         }
       }
 
@@ -247,7 +250,9 @@
   bool DicomStoreUserConnection::NegotiatePresentationContext(
     uint8_t& presentationContextId,
     const std::string& sopClassUid,
-    DicomTransferSyntax transferSyntax)
+    DicomTransferSyntax transferSyntax,
+    bool hasPreferred,
+    DicomTransferSyntax preferred)
   {
     /**
      * Step 1: Check whether this presentation context is already
@@ -265,6 +270,8 @@
       CLOG(INFO, DICOM) << "Re-negotiating DICOM association with "
                         << parameters_.GetRemoteModality().GetApplicationEntityTitle();
 
+      // Don't renegociate if we know that the remote modality was
+      // already proposed this individual transfer syntax (**)
       if (proposedOriginalClasses_.find(std::make_pair(sopClassUid, transferSyntax)) !=
           proposedOriginalClasses_.end())
       {
@@ -294,7 +301,7 @@
         throw OrthancException(ErrorCode_InternalError);
       }
 
-      if (!ProposeStorageClass(sopClassUid, mandatory->second))
+      if (!ProposeStorageClass(sopClassUid, mandatory->second, hasPreferred, preferred))
       {
         // Should never happen in real life: There are no more than
         // 128 transfer syntaxes in DICOM!
@@ -314,7 +321,7 @@
     {
       if (it->first != sopClassUid)
       {
-        ProposeStorageClass(it->first, it->second);
+        ProposeStorageClass(it->first, it->second, hasPreferred, preferred);
       }
     }
       
@@ -340,7 +347,7 @@
         if (c != sopClassUid &&
             registeredClasses_.find(c) == registeredClasses_.end())
         {
-          ProposeStorageClass(c, ts);
+          ProposeStorageClass(c, ts, hasPreferred, preferred);
         }
       }
     }
@@ -367,7 +374,8 @@
     LookupParameters(sopClassUid, sopInstanceUid, transferSyntax, dicom);
 
     uint8_t presID;
-    if (!NegotiatePresentationContext(presID, sopClassUid, transferSyntax))
+    if (!NegotiatePresentationContext(presID, sopClassUid, transferSyntax, proposeUncompressedSyntaxes_,
+                                      DicomTransferSyntax_LittleEndianExplicit))
     {
       throw OrthancException(ErrorCode_NetworkProtocol,
                              "No valid presentation context was negotiated for "
@@ -465,7 +473,9 @@
 
   void DicomStoreUserConnection::LookupTranscoding(std::set<DicomTransferSyntax>& acceptedSyntaxes,
                                                    const std::string& sopClassUid,
-                                                   DicomTransferSyntax sourceSyntax)
+                                                   DicomTransferSyntax sourceSyntax,
+                                                   bool hasPreferred,
+                                                   DicomTransferSyntax preferred)
   {
     acceptedSyntaxes.clear();
 
@@ -473,7 +483,7 @@
     // syntax. We don't use the return code: Transcoding is possible
     // even if the "sourceSyntax" is not supported.
     uint8_t presID;
-    NegotiatePresentationContext(presID, sopClassUid, sourceSyntax);
+    NegotiatePresentationContext(presID, sopClassUid, sourceSyntax, hasPreferred, preferred);
 
     std::map<DicomTransferSyntax, uint8_t> contexts;
     if (association_->LookupAcceptedPresentationContext(contexts, sopClassUid))
@@ -492,6 +502,7 @@
                                            IDicomTranscoder& transcoder,
                                            const void* buffer,
                                            size_t size,
+                                           DicomTransferSyntax preferredTransferSyntax,
                                            bool hasMoveOriginator,
                                            const std::string& moveOriginatorAET,
                                            uint16_t moveOriginatorID)
@@ -503,13 +514,13 @@
       throw OrthancException(ErrorCode_NullPointer);
     }
 
-    DicomTransferSyntax inputSyntax;
-    LookupParameters(sopClassUid, sopInstanceUid, inputSyntax, *dicom);
+    DicomTransferSyntax sourceSyntax;
+    LookupParameters(sopClassUid, sopInstanceUid, sourceSyntax, *dicom);
 
     std::set<DicomTransferSyntax> accepted;
-    LookupTranscoding(accepted, sopClassUid, inputSyntax);
+    LookupTranscoding(accepted, sopClassUid, sourceSyntax, true, preferredTransferSyntax);
 
-    if (accepted.find(inputSyntax) != accepted.end())
+    if (accepted.find(sourceSyntax) != accepted.end())
     {
       // No need for transcoding
       Store(sopClassUid, sopInstanceUid, *dicom,
@@ -518,54 +529,106 @@
     else
     {
       // Transcoding is needed
-      std::set<DicomTransferSyntax> uncompressedSyntaxes;
-
-      if (accepted.find(DicomTransferSyntax_LittleEndianImplicit) != accepted.end())
-      {
-        uncompressedSyntaxes.insert(DicomTransferSyntax_LittleEndianImplicit);
-      }
-
-      if (accepted.find(DicomTransferSyntax_LittleEndianExplicit) != accepted.end())
-      {
-        uncompressedSyntaxes.insert(DicomTransferSyntax_LittleEndianExplicit);
-      }
-
-      if (accepted.find(DicomTransferSyntax_BigEndianExplicit) != accepted.end())
-      {
-        uncompressedSyntaxes.insert(DicomTransferSyntax_BigEndianExplicit);
-      }
-
       IDicomTranscoder::DicomImage source;
       source.AcquireParsed(dicom.release());
       source.SetExternalBuffer(buffer, size);
 
       const std::string sourceUid = IDicomTranscoder::GetSopInstanceUid(source.GetParsed());
-      
+        
       IDicomTranscoder::DicomImage transcoded;
-      if (transcoder.Transcode(transcoded, source, uncompressedSyntaxes, false))
+      bool success = false;
+      bool isDestructiveCompressionAllowed = false;
+      std::set<DicomTransferSyntax> attemptedSyntaxes;
+
+      if (accepted.find(preferredTransferSyntax) != accepted.end())
+      {
+        // New in Orthanc 1.9.0: The preferred transfer syntax is
+        // accepted by the remote modality => transcode to this syntax
+        std::set<DicomTransferSyntax> targetSyntaxes;
+        targetSyntaxes.insert(preferredTransferSyntax);
+        attemptedSyntaxes.insert(preferredTransferSyntax);
+
+        success = transcoder.Transcode(transcoded, source, targetSyntaxes, true);
+        isDestructiveCompressionAllowed = true;
+      }
+
+      if (!success)
       {
-        if (sourceUid != IDicomTranscoder::GetSopInstanceUid(transcoded.GetParsed()))
+        // Transcode to either one of the uncompressed transfer
+        // syntaxes that are accepted by the remote modality
+
+        std::set<DicomTransferSyntax> targetSyntaxes;
+
+        if (accepted.find(DicomTransferSyntax_LittleEndianImplicit) != accepted.end())
+        {
+          targetSyntaxes.insert(DicomTransferSyntax_LittleEndianImplicit);
+          attemptedSyntaxes.insert(DicomTransferSyntax_LittleEndianImplicit);
+        }
+
+        if (accepted.find(DicomTransferSyntax_LittleEndianExplicit) != accepted.end())
+        {
+          targetSyntaxes.insert(DicomTransferSyntax_LittleEndianExplicit);
+          attemptedSyntaxes.insert(DicomTransferSyntax_LittleEndianExplicit);
+        }
+
+        if (accepted.find(DicomTransferSyntax_BigEndianExplicit) != accepted.end())
+        {
+          targetSyntaxes.insert(DicomTransferSyntax_BigEndianExplicit);
+          attemptedSyntaxes.insert(DicomTransferSyntax_BigEndianExplicit);
+        }
+
+        if (!targetSyntaxes.empty())
         {
-          throw OrthancException(ErrorCode_Plugin, "The transcoder has changed the SOP "
-                                 "instance UID while transcoding to an uncompressed transfer syntax");
+          success = transcoder.Transcode(transcoded, source, targetSyntaxes, false);
+          isDestructiveCompressionAllowed = false;
+        }
+      }
+
+      if (success)
+      {
+        std::string targetUid = IDicomTranscoder::GetSopInstanceUid(transcoded.GetParsed());
+        if (sourceUid != targetUid)
+        {
+          if (isDestructiveCompressionAllowed)
+          {
+            LOG(WARNING) << "Because of the use of a preferred transfer syntax that corresponds to "
+                         << "a destructive compression, C-STORE SCU has hanged the SOP Instance UID "
+                         << "of a DICOM instance from \"" << sourceUid << "\" to \"" << targetUid << "\"";
+          }
+          else
+          {
+            throw OrthancException(ErrorCode_Plugin, "The transcoder has changed the SOP "
+                                   "Instance UID while transcoding to an uncompressed transfer syntax");
+          }
+        }
+
+        DicomTransferSyntax transcodedSyntax;
+          
+        // Sanity check
+        if (!FromDcmtkBridge::LookupOrthancTransferSyntax(transcodedSyntax, transcoded.GetParsed()) ||
+            accepted.find(transcodedSyntax) == accepted.end())
+        {
+          throw OrthancException(ErrorCode_InternalError);
         }
         else
         {
-          DicomTransferSyntax transcodedSyntax;
-          
-          // Sanity check
-          if (!FromDcmtkBridge::LookupOrthancTransferSyntax(transcodedSyntax, transcoded.GetParsed()) ||
-              accepted.find(transcodedSyntax) == accepted.end())
-          {
-            throw OrthancException(ErrorCode_InternalError);
-          }
-          else
-          {
-            Store(sopClassUid, sopInstanceUid, transcoded.GetParsed(),
-                  hasMoveOriginator, moveOriginatorAET, moveOriginatorID);
-          }
+          Store(sopClassUid, sopInstanceUid, transcoded.GetParsed(),
+                hasMoveOriginator, moveOriginatorAET, moveOriginatorID);
         }
       }
+      else
+      {
+        std::string s;
+        for (std::set<DicomTransferSyntax>::const_iterator
+               it = attemptedSyntaxes.begin(); it != attemptedSyntaxes.end(); ++it)
+        {
+          s += " " + std::string(GetTransferSyntaxUid(*it));
+        }
+        
+        throw OrthancException(ErrorCode_NotImplemented, "Cannot transcode from " +
+                               std::string(GetTransferSyntaxUid(sourceSyntax)) +
+                               " to one of [" + s + " ]");
+      }
     }
   }
 }
--- a/OrthancFramework/Sources/DicomNetworking/DicomStoreUserConnection.h	Wed Jan 20 17:43:15 2021 +0100
+++ b/OrthancFramework/Sources/DicomNetworking/DicomStoreUserConnection.h	Thu Jan 21 17:08:32 2021 +0100
@@ -70,7 +70,7 @@
     // "ProposedOriginalClasses" keeps track of the storage classes
     // that were proposed with a single transfer syntax
     typedef std::set< std::pair<std::string, DicomTransferSyntax> > ProposedOriginalClasses;
-    
+
     DicomAssociationParameters           parameters_;
     boost::shared_ptr<DicomAssociation>  association_;  // "shared_ptr" is for PImpl
     RegisteredClasses                    registeredClasses_;
@@ -81,7 +81,9 @@
 
     // Return "false" if there is not enough room remaining in the association
     bool ProposeStorageClass(const std::string& sopClassUid,
-                             const std::set<DicomTransferSyntax>& syntaxes);
+                             const std::set<DicomTransferSyntax>& sourceSyntaxes,
+                             bool hasPreferred,
+                             DicomTransferSyntax preferred);
 
     bool LookupPresentationContext(uint8_t& presentationContextId,
                                    const std::string& sopClassUid,
@@ -89,11 +91,15 @@
     
     bool NegotiatePresentationContext(uint8_t& presentationContextId,
                                       const std::string& sopClassUid,
-                                      DicomTransferSyntax transferSyntax);
+                                      DicomTransferSyntax transferSyntax,
+                                      bool hasPreferred,
+                                      DicomTransferSyntax preferred);
 
     void LookupTranscoding(std::set<DicomTransferSyntax>& acceptedSyntaxes,
                            const std::string& sopClassUid,
-                           DicomTransferSyntax sourceSyntax);
+                           DicomTransferSyntax sourceSyntax,
+                           bool hasPreferred,
+                           DicomTransferSyntax preferred);
 
   public:
     explicit DicomStoreUserConnection(const DicomAssociationParameters& params);
@@ -140,6 +146,7 @@
                    IDicomTranscoder& transcoder,
                    const void* buffer,
                    size_t size,
+                   DicomTransferSyntax preferredTransferSyntax,
                    bool hasMoveOriginator,
                    const std::string& moveOriginatorAET,
                    uint16_t moveOriginatorID);
--- a/OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp	Wed Jan 20 17:43:15 2021 +0100
+++ b/OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp	Thu Jan 21 17:08:32 2021 +0100
@@ -2195,7 +2195,8 @@
         std::string c, k;
         try
         {
-          scu.Transcode(c, k, transcoder, source.c_str(), source.size(), false, "", 0);
+          scu.Transcode(c, k, transcoder, source.c_str(), source.size(),
+                        DicomTransferSyntax_LittleEndianExplicit, false, "", 0);
         }
         catch (OrthancException& e)
         {
--- a/OrthancServer/Resources/Configuration.json	Wed Jan 20 17:43:15 2021 +0100
+++ b/OrthancServer/Resources/Configuration.json	Thu Jan 21 17:08:32 2021 +0100
@@ -367,6 +367,18 @@
   // DICOM SCP (server) does not answer.
   "DicomScuTimeout" : 10,
 
+  // During a C-STORE SCU request initiated by Orthanc, if the remote
+  // modality doesn't support the original transfer syntax of some
+  // DICOM instance, specify which transfer syntax should be preferred
+  // to transcode this instance (provided the remote modality accepts
+  // this syntax). In Orthanc between 1.7.0 and 1.8.2, this parameter
+  // was implicitly set to "Little Endian Implicit"
+  // (1.2.840.10008.1.2). In Orthanc <= 1.6.1 and in Orthanc >= 1.9.0,
+  // this parameter is by default set to "Little Endian Explicit"
+  // (1.2.840.10008.1.2.1). This parameter can possibly correspond to
+  // a compressed transfer syntax. (new in Orthanc 1.9.0)
+  "DicomScuPreferredTransferSyntax" : "1.2.840.10008.1.2.1",
+  
   // The list of the known Orthanc peers. This option is ignored if
   // "OrthancPeersInDatabase" is set to "true", in which case you must
   // use the REST API to define Orthanc peers.
--- a/OrthancServer/Sources/ServerContext.cpp	Wed Jan 20 17:43:15 2021 +0100
+++ b/OrthancServer/Sources/ServerContext.cpp	Thu Jan 21 17:08:32 2021 +0100
@@ -307,6 +307,7 @@
     isIngestTranscoding_(false),
     ingestTranscodingOfUncompressed_(true),
     ingestTranscodingOfCompressed_(true),
+    preferredTransferSyntax_(DicomTransferSyntax_LittleEndianExplicit),
     deidentifyLogs_(false)
   {
     try
@@ -347,6 +348,7 @@
             LOG(WARNING) << "Incoming DICOM instances will automatically be transcoded to "
                          << "transfer syntax: " << GetTransferSyntaxUid(ingestTransferSyntax_);
 
+            // New options in Orthanc 1.8.2
             ingestTranscodingOfUncompressed_ = lock.GetConfiguration().GetBooleanParameter("IngestTranscodingOfUncompressed", true);
             ingestTranscodingOfCompressed_ = lock.GetConfiguration().GetBooleanParameter("IngestTranscodingOfCompressed", true);
 
@@ -370,6 +372,7 @@
           LOG(INFO) << "Automated transcoding of incoming DICOM instances is disabled";
         }
 
+        // New options in Orthanc 1.8.2
         if (lock.GetConfiguration().GetBooleanParameter("DeidentifyLogs", true))
         {
           deidentifyLogs_ = true;
@@ -387,6 +390,17 @@
           deidentifyLogs_ = false;
           CLOG(INFO, DICOM) << "Deidentification of log contents (notably for DIMSE queries) is disabled";
         }
+
+        // New option in Orthanc 1.9.0
+        if (lock.GetConfiguration().LookupStringParameter(s, "DicomScuPreferredTransferSyntax") &&
+            !LookupTransferSyntax(preferredTransferSyntax_, s))
+        {
+          throw OrthancException(ErrorCode_ParameterOutOfRange,
+                                 "Unknown preferred transfer syntax: " + s);
+        }
+        
+        CLOG(INFO, DICOM) << "Preferred transfer syntax for Orthanc C-STORE SCU: "
+                          << GetTransferSyntaxUid(preferredTransferSyntax_);
       }
 
       jobsEngine_.SetThreadSleep(unitTesting ? 20 : 200);
@@ -1718,7 +1732,7 @@
     }
     else
     {
-      connection.Transcode(sopClassUid, sopInstanceUid, *this, data, dicom.size(),
+      connection.Transcode(sopClassUid, sopInstanceUid, *this, data, dicom.size(), preferredTransferSyntax_,
                            hasMoveOriginator, moveOriginatorAet, moveOriginatorId);
     }
   }
--- a/OrthancServer/Sources/ServerContext.h	Wed Jan 20 17:43:15 2021 +0100
+++ b/OrthancServer/Sources/ServerContext.h	Thu Jan 21 17:08:32 2021 +0100
@@ -225,6 +225,7 @@
     DicomTransferSyntax ingestTransferSyntax_;
     bool ingestTranscodingOfUncompressed_;
     bool ingestTranscodingOfCompressed_;
+    DicomTransferSyntax preferredTransferSyntax_;   // New in Orthanc 1.9.0
 
     StoreStatus StoreAfterTranscoding(std::string& resultPublicId,
                                       DicomInstanceToStore& dicom,
--- a/OrthancServer/Sources/ServerJobs/DicomModalityStoreJob.cpp	Wed Jan 20 17:43:15 2021 +0100
+++ b/OrthancServer/Sources/ServerJobs/DicomModalityStoreJob.cpp	Thu Jan 21 17:08:32 2021 +0100
@@ -72,7 +72,7 @@
       LOG(WARNING) << "An instance was removed after the job was issued: " << instance;
       return false;
     }
-    
+
     std::string sopClassUid, sopInstanceUid;
     context_.StoreWithTranscoding(sopClassUid, sopInstanceUid, *connection_, dicom,
                                   HasMoveOriginator(), moveOriginatorAet_, moveOriginatorId_);