changeset 662:70161eb45b5c

orthanc can act as a C-Store SCU for JPEG transfer syntax
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 06 Nov 2013 16:19:25 +0100
parents d233b5090105
children 95b4c6d89c42
files NEWS OrthancServer/DicomProtocol/DicomUserConnection.cpp OrthancServer/DicomProtocol/DicomUserConnection.h THANKS
diffstat 4 files changed, 128 insertions(+), 43 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Tue Nov 05 17:41:25 2013 +0100
+++ b/NEWS	Wed Nov 06 16:19:25 2013 +0100
@@ -2,7 +2,7 @@
 ===============================
 
 
-* Accept more transfer syntaxes for C-Store SCP (including JPEG)
+* Accept more transfer syntaxes for C-Store SCP and SCU (notably JPEG)
 * Create the meta-header when receiving files through C-Store SCP
 * Fixes and improvements thanks to the static analyzer cppcheck
 
--- a/OrthancServer/DicomProtocol/DicomUserConnection.cpp	Tue Nov 05 17:41:25 2013 +0100
+++ b/OrthancServer/DicomProtocol/DicomUserConnection.cpp	Wed Nov 06 16:19:25 2013 +0100
@@ -39,9 +39,11 @@
 #include <dcmtk/dcmdata/dcistrmb.h>
 #include <dcmtk/dcmdata/dcistrmf.h>
 #include <dcmtk/dcmdata/dcfilefo.h>
+#include <dcmtk/dcmdata/dcmetinf.h>
 #include <dcmtk/dcmnet/diutil.h>
 
 #include <set>
+#include <glog/logging.h>
 
 
 
@@ -56,6 +58,8 @@
 #endif 
 
 
+static const char* DEFAULT_PREFERRED_TRANSFER_SYNTAX = UID_LittleEndianImplicitTransferSyntax;
+
 namespace Orthanc
 {
   struct DicomUserConnection::PImpl
@@ -72,7 +76,7 @@
 
     void CheckIsOpen() const;
 
-    void Store(DcmInputStream& is);
+    void Store(DcmInputStream& is, DicomUserConnection& connection);
   };
 
 
@@ -106,21 +110,18 @@
     distantAet_ = other.distantAet_;
     distantHost_ = other.distantHost_;
     distantPort_ = other.distantPort_;
+    manufacturer_ = other.manufacturer_;
+    preferredTransferSyntax_ = other.preferredTransferSyntax_;
   }
 
 
-  void DicomUserConnection::SetupPresentationContexts()
+  void DicomUserConnection::SetupPresentationContexts(const std::string& preferredTransferSyntax)
   {
-    // The preferred abstract syntax
-    std::string preferredSyntax = UID_LittleEndianImplicitTransferSyntax;
-
-    // Fallback abstract syntaxes
-    std::set<std::string> abstractSyntaxes;
-    abstractSyntaxes.insert(UID_LittleEndianExplicitTransferSyntax);
-    abstractSyntaxes.insert(UID_BigEndianExplicitTransferSyntax);
-    abstractSyntaxes.insert(UID_LittleEndianImplicitTransferSyntax);
-    abstractSyntaxes.erase(preferredSyntax);
-    assert(abstractSyntaxes.size() == 2);
+    // Fallback transfer syntaxes
+    std::set<std::string> fallbackSyntaxes;
+    fallbackSyntaxes.insert(UID_LittleEndianExplicitTransferSyntax);
+    fallbackSyntaxes.insert(UID_BigEndianExplicitTransferSyntax);
+    fallbackSyntaxes.insert(UID_LittleEndianImplicitTransferSyntax);
 
     // Transfer syntaxes for C-ECHO, C-FIND and C-MOVE
     std::vector<std::string> transferSyntaxes;
@@ -146,13 +147,18 @@
       }
     }
 
-    // Flatten the fallback abstract syntaxes array
-    const char* asPreferred[1] = { preferredSyntax.c_str() };
-    const char* asFallback[2];
-    std::set<std::string>::const_iterator it = abstractSyntaxes.begin();
-    asFallback[0] = it->c_str();
-    ++it;
-    asFallback[1] = it->c_str();
+    // Flatten the fallback transfer syntaxes array
+    const char* asPreferred[1] = { preferredTransferSyntax.c_str() };
+
+    fallbackSyntaxes.erase(preferredTransferSyntax);
+
+    std::vector<const char*> asFallback;
+    asFallback.reserve(fallbackSyntaxes.size());
+    for (std::set<std::string>::const_iterator 
+           it = fallbackSyntaxes.begin(); it != fallbackSyntaxes.end(); ++it)
+    {
+      asFallback.push_back(it->c_str());
+    }
 
     unsigned int presentationContextId = 1;
     for (size_t i = 0; i < transferSyntaxes.size(); i++)
@@ -161,20 +167,55 @@
                                        transferSyntaxes[i].c_str(), asPreferred, 1));
       presentationContextId += 2;
 
-      Check(ASC_addPresentationContext(pimpl_->params_, presentationContextId, 
-                                       transferSyntaxes[i].c_str(), asFallback, 2));
-      presentationContextId += 2;
+      if (asFallback.size() > 0)
+      {
+        Check(ASC_addPresentationContext(pimpl_->params_, presentationContextId, 
+                                         transferSyntaxes[i].c_str(), &asFallback[0], asFallback.size()));
+        presentationContextId += 2;
+      }
     }
   }
 
 
-  void DicomUserConnection::PImpl::Store(DcmInputStream& is)
+  static bool IsGenericTransferSyntax(const std::string& syntax)
+  {
+    return (syntax == UID_LittleEndianExplicitTransferSyntax ||
+            syntax == UID_BigEndianExplicitTransferSyntax ||
+            syntax == UID_LittleEndianImplicitTransferSyntax);
+  }
+
+
+  void DicomUserConnection::PImpl::Store(DcmInputStream& is, DicomUserConnection& connection)
   {
     CheckIsOpen();
 
     DcmFileFormat dcmff;
     Check(dcmff.read(is, EXS_Unknown, EGL_noChange, DCM_MaxReadLength));
 
+    // Determine whether a new presentation context must be
+    // negociated, depending on the transfer syntax of this instance
+    DcmXfer xfer(dcmff.getDataset()->getOriginalXfer());
+    const std::string syntax(xfer.getXferID());
+    bool isGeneric = IsGenericTransferSyntax(syntax);
+
+    if (isGeneric ^ IsGenericTransferSyntax(connection.GetPreferredTransferSyntax()))
+    {
+      // Making a generic-to-specific or specific-to-generic change of
+      // the transfer syntax. Renegociate the connection.
+      LOG(INFO) << "Renegociating a C-Store association due to a change in the transfer syntax";
+
+      if (isGeneric)
+      {
+        connection.ResetPreferredTransferSyntax();
+      }
+      else
+      {
+        connection.SetPreferredTransferSyntax(syntax);
+      }
+
+      connection.Open();
+    }
+
     // Figure out which SOP class and SOP instance is encapsulated in the file
     DIC_UI sopClass;
     DIC_UI sopInstance;
@@ -468,6 +509,7 @@
   {
     distantPort_ = 104;
     manufacturer_ = ModalityManufacturer_Generic;
+    preferredTransferSyntax_ = DEFAULT_PREFERRED_TRANSFER_SYNTAX;
 
     pimpl_->net_ = NULL;
     pimpl_->params_ = NULL;
@@ -481,43 +523,76 @@
 
   void DicomUserConnection::SetLocalApplicationEntityTitle(const std::string& aet)
   {
-    Close();
-    localAet_ = aet;
+    if (localAet_ != aet)
+    {
+      Close();
+      localAet_ = aet;
+    }
   }
 
   void DicomUserConnection::SetDistantApplicationEntityTitle(const std::string& aet)
   {
-    Close();
-    distantAet_ = aet;
+    if (distantAet_ != aet)
+    {
+      Close();
+      distantAet_ = aet;
+    }
   }
 
   void DicomUserConnection::SetDistantManufacturer(ModalityManufacturer manufacturer)
   {
-    Close();
-    manufacturer_ = manufacturer;
+    if (manufacturer_ != manufacturer)
+    {
+      Close();
+      manufacturer_ = manufacturer;
+    }
+  }
+
+  void DicomUserConnection::ResetPreferredTransferSyntax()
+  {
+    SetPreferredTransferSyntax(DEFAULT_PREFERRED_TRANSFER_SYNTAX);
+  }
+
+  void DicomUserConnection::SetPreferredTransferSyntax(const std::string& preferredTransferSyntax)
+  {
+    if (preferredTransferSyntax_ != preferredTransferSyntax)
+    {
+      Close();
+      preferredTransferSyntax_ = preferredTransferSyntax;
+    }
   }
 
 
   void DicomUserConnection::SetDistantHost(const std::string& host)
   {
-    if (host.size() > HOST_NAME_MAX - 10)
+    if (distantHost_ != host)
     {
-      throw OrthancException("Distant host name is too long");
+      if (host.size() > HOST_NAME_MAX - 10)
+      {
+        throw OrthancException("Distant host name is too long");
+      }
+
+      Close();
+      distantHost_ = host;
     }
-
-    Close();
-    distantHost_ = host;
   }
 
   void DicomUserConnection::SetDistantPort(uint16_t port)
   {
-    Close();
-    distantPort_ = port;
+    if (distantPort_ != port)
+    {
+      Close();
+      distantPort_ = port;
+    }
   }
 
   void DicomUserConnection::Open()
   {
-    Close();
+    if (IsOpen())
+    {
+      // Don't reopen the connection
+      return;
+    }
 
     Check(ASC_initializeNetwork(NET_REQUESTOR, 0, /*opt_acse_timeout*/ 30, &pimpl_->net_));
     Check(ASC_createAssociationParameters(&pimpl_->params_, /*opt_maxReceivePDULength*/ ASC_DEFAULTMAXPDU));
@@ -543,7 +618,7 @@
     // Set various options
     Check(ASC_setTransportLayerType(pimpl_->params_, /*opt_secureConnection*/ false));
 
-    SetupPresentationContexts();
+    SetupPresentationContexts(preferredTransferSyntax_);
 
     // Do the association
     Check(ASC_requestAssociation(pimpl_->net_, pimpl_->params_, &pimpl_->assoc_));
@@ -592,7 +667,7 @@
       is.setBuffer(buffer, size);
     is.setEos();
       
-    pimpl_->Store(is);
+    pimpl_->Store(is, *this);
   }
 
   void DicomUserConnection::Store(const std::string& buffer)
@@ -607,7 +682,7 @@
   {
     // Prepare an input stream for the file
     DcmInputFileStream is(path.c_str());
-    pimpl_->Store(is);
+    pimpl_->Store(is, *this);
   }
 
   bool DicomUserConnection::Echo()
--- a/OrthancServer/DicomProtocol/DicomUserConnection.h	Tue Nov 05 17:41:25 2013 +0100
+++ b/OrthancServer/DicomProtocol/DicomUserConnection.h	Wed Nov 06 16:19:25 2013 +0100
@@ -56,6 +56,7 @@
     boost::shared_ptr<PImpl> pimpl_;
 
     // Connection parameters
+    std::string preferredTransferSyntax_;
     std::string localAet_;
     std::string distantAet_;
     std::string distantHost_;
@@ -64,7 +65,7 @@
 
     void CheckIsOpen() const;
 
-    void SetupPresentationContexts();
+    void SetupPresentationContexts(const std::string& preferredTransferSyntax);
 
     void Find(DicomFindAnswers& result,
               FindRootModel model,
@@ -115,6 +116,15 @@
       return manufacturer_;
     }
 
+    void ResetPreferredTransferSyntax();
+
+    void SetPreferredTransferSyntax(const std::string& preferredTransferSyntax);
+
+    const std::string& GetPreferredTransferSyntax() const
+    {
+      return preferredTransferSyntax_;
+    }
+
     void Open();
 
     void Close();
--- a/THANKS	Tue Nov 05 17:41:25 2013 +0100
+++ b/THANKS	Wed Nov 06 16:19:25 2013 +0100
@@ -16,7 +16,7 @@
 * Will Ryder (will.ryder@sydney.edu.au), for improvements with the
   handling of series with temporal positions (fMRI and dynamic PET).
 * Ryan Walklin (ryanwalklin@gmail.com), for Mac OS X build.
-* Peter Somlo (peter.somlo@gmail.com), for ClearCanvas support.
+* Peter Somlo (peter.somlo@gmail.com), for ClearCanvas and JPEG support.
 * 12maksqwe@gmail.com, for fixing issue #8.
 * Julien Nabet, for various suggestions to improve the source code.