diff Core/DicomNetworking/DicomStoreUserConnection.cpp @ 3920:82e88ff003d7 c-get

merge default -> c-get
author Alain Mazy <alain@mazy.be>
date Tue, 12 May 2020 14:58:24 +0200
parents 1555feda39e2
children 0b3256c3ee14
line wrap: on
line diff
--- a/Core/DicomNetworking/DicomStoreUserConnection.cpp	Tue May 12 07:50:38 2020 +0200
+++ b/Core/DicomNetworking/DicomStoreUserConnection.cpp	Tue May 12 14:58:24 2020 +0200
@@ -48,8 +48,28 @@
   bool DicomStoreUserConnection::ProposeStorageClass(const std::string& sopClassUid,
                                                      const std::set<DicomTransferSyntax>& syntaxes)
   {
+    // 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 (proposeUncompressedSyntaxes_)
+    if (addLittleEndianImplicit)
+    {
+      requiredCount += 1;
+    }
+      
+    if (addLittleEndianExplicit ||
+        addBigEndianExplicit)
     {
       requiredCount += 1;
     }
@@ -58,40 +78,49 @@
     {
       return false;  // Not enough room
     }
-      
-    for (std::set<DicomTransferSyntax>::const_iterator
-           it = syntaxes.begin(); it != syntaxes.end(); ++it)
+    else
     {
-      association_->ProposePresentationContext(sopClassUid, *it);
-    }
-
-    if (proposeUncompressedSyntaxes_)
-    {
-      std::set<DicomTransferSyntax> uncompressed;
-        
-      if (syntaxes.find(DicomTransferSyntax_LittleEndianImplicit) == syntaxes.end())
+      for (std::set<DicomTransferSyntax>::const_iterator
+             it = syntaxes.begin(); it != syntaxes.end(); ++it)
       {
-        uncompressed.insert(DicomTransferSyntax_LittleEndianImplicit);
+        association_->ProposePresentationContext(sopClassUid, *it);
+        proposedOriginalClasses_.insert(std::make_pair(sopClassUid, *it));
       }
-        
-      if (syntaxes.find(DicomTransferSyntax_LittleEndianExplicit) == syntaxes.end())
+
+      if (addLittleEndianImplicit)
       {
-        uncompressed.insert(DicomTransferSyntax_LittleEndianExplicit);
-      }
-        
-      if (proposeRetiredBigEndian_ &&
-          syntaxes.find(DicomTransferSyntax_BigEndianExplicit) == syntaxes.end())
-      {
-        uncompressed.insert(DicomTransferSyntax_BigEndianExplicit);
+        association_->ProposePresentationContext(sopClassUid, DicomTransferSyntax_LittleEndianImplicit);
+        proposedOriginalClasses_.insert(std::make_pair(sopClassUid, DicomTransferSyntax_LittleEndianImplicit));
       }
 
-      if (!uncompressed.empty())
+      if (addLittleEndianExplicit ||
+          addBigEndianExplicit)
       {
+        std::set<DicomTransferSyntax> uncompressed;
+
+        if (addLittleEndianExplicit)
+        {
+          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()));
+        }
       }
-    }      
 
-    return true;
+      return true;
+    }
   }
 
 
@@ -116,8 +145,8 @@
 
     return false;
   }
-    
-        
+
+
   DicomStoreUserConnection::DicomStoreUserConnection(
     const DicomAssociationParameters& params) :
     parameters_(params),
@@ -129,16 +158,16 @@
   }
     
 
-  void DicomStoreUserConnection::PrepareStorageClass(const std::string& sopClassUid,
-                                                     DicomTransferSyntax syntax)
+  void DicomStoreUserConnection::RegisterStorageClass(const std::string& sopClassUid,
+                                                      DicomTransferSyntax syntax)
   {
-    StorageClasses::iterator found = storageClasses_.find(sopClassUid);
+    RegisteredClasses::iterator found = registeredClasses_.find(sopClassUid);
 
-    if (found == storageClasses_.end())
+    if (found == registeredClasses_.end())
     {
       std::set<DicomTransferSyntax> ts;
       ts.insert(syntax);
-      storageClasses_[sopClassUid] = ts;
+      registeredClasses_[sopClassUid] = ts;
     }
     else
     {
@@ -147,6 +176,36 @@
   }
 
 
+  void DicomStoreUserConnection::LookupParameters(std::string& sopClassUid,
+                                                  std::string& sopInstanceUid,
+                                                  DicomTransferSyntax& transferSyntax,
+                                                  DcmFileFormat& dicom)
+  {
+    if (dicom.getDataset() == NULL)
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+    
+    OFString a, b;
+    if (!dicom.getDataset()->findAndGetOFString(DCM_SOPClassUID, a).good() ||
+        !dicom.getDataset()->findAndGetOFString(DCM_SOPInstanceUID, b).good())
+    {
+      throw OrthancException(ErrorCode_NoSopClassOrInstance,
+                             "Unable to determine the SOP class/instance for C-STORE with AET " +
+                             parameters_.GetRemoteModality().GetApplicationEntityTitle());
+    }
+
+    sopClassUid.assign(a.c_str());
+    sopInstanceUid.assign(b.c_str());
+
+    if (!FromDcmtkBridge::LookupOrthancTransferSyntax(transferSyntax, dicom))
+    {
+      throw OrthancException(ErrorCode_InternalError,
+                             "Unknown transfer syntax from DCMTK");
+    }
+  }
+  
+
   bool DicomStoreUserConnection::NegotiatePresentationContext(
     uint8_t& presentationContextId,
     const std::string& sopClassUid,
@@ -154,7 +213,7 @@
   {
     /**
      * Step 1: Check whether this presentation context is already
-     * available in the previously negociated assocation.
+     * available in the previously negotiated assocation.
      **/
 
     if (LookupPresentationContext(presentationContextId, sopClassUid, transferSyntax))
@@ -163,22 +222,37 @@
     }
 
     // The association must be re-negotiated
-    LOG(INFO) << "Re-negociating DICOM association with "
-              << parameters_.GetRemoteApplicationEntityTitle();
+    if (association_->IsOpen())
+    {
+      LOG(INFO) << "Re-negotiating DICOM association with "
+                << parameters_.GetRemoteModality().GetApplicationEntityTitle();
+
+      if (proposedOriginalClasses_.find(std::make_pair(sopClassUid, transferSyntax)) !=
+          proposedOriginalClasses_.end())
+      {
+        LOG(INFO) << "The remote modality has already rejected SOP class UID \""
+                  << sopClassUid << "\" with transfer syntax \""
+                  << GetTransferSyntaxUid(transferSyntax) << "\", don't renegotiate";
+        return false;
+      }
+    }
+
     association_->ClearPresentationContexts();
-    PrepareStorageClass(sopClassUid, transferSyntax);
+    proposedOriginalClasses_.clear();
+    RegisterStorageClass(sopClassUid, transferSyntax);  // (*)
 
-      
+    
     /**
      * Step 2: Propose at least the mandatory SOP class.
      **/
 
     {
-      StorageClasses::const_iterator mandatory = storageClasses_.find(sopClassUid);
+      RegisteredClasses::const_iterator mandatory = registeredClasses_.find(sopClassUid);
 
-      if (mandatory == storageClasses_.end() ||
+      if (mandatory == registeredClasses_.end() ||
           mandatory->second.find(transferSyntax) == mandatory->second.end())
       {
+        // Should never fail because of (*)
         throw OrthancException(ErrorCode_InternalError);
       }
 
@@ -194,11 +268,11 @@
       
     /**
      * Step 3: Propose all the previously spotted SOP classes, as
-     * registered through the "PrepareStorageClass()" method.
+     * registered through the "RegisterStorageClass()" method.
      **/
       
-    for (StorageClasses::const_iterator it = storageClasses_.begin();
-         it != storageClasses_.end(); ++it)
+    for (RegisteredClasses::const_iterator it = registeredClasses_.begin();
+         it != registeredClasses_.end(); ++it)
     {
       if (it->first != sopClassUid)
       {
@@ -217,15 +291,16 @@
 
     if (proposeCommonClasses_)
     {
+      // The method "ProposeStorageClass()" will automatically add
+      // "LittleEndianImplicit"
       std::set<DicomTransferSyntax> ts;
-      ts.insert(DicomTransferSyntax_LittleEndianImplicit);
         
       for (int i = 0; i < numberOfDcmShortSCUStorageSOPClassUIDs; i++)
       {
         std::string c(dcmShortSCUStorageSOPClassUIDs[i]);
           
         if (c != sopClassUid &&
-            storageClasses_.find(c) == storageClasses_.end())
+            registeredClasses_.find(c) == registeredClasses_.end())
         {
           ProposeStorageClass(c, ts);
         }
@@ -245,36 +320,23 @@
 
   void DicomStoreUserConnection::Store(std::string& sopClassUid,
                                        std::string& sopInstanceUid,
-                                       DcmDataset& dataset,
+                                       DcmFileFormat& dicom,
+                                       bool hasMoveOriginator,
                                        const std::string& moveOriginatorAET,
                                        uint16_t moveOriginatorID)
   {
-    OFString a, b;
-    if (!dataset.findAndGetOFString(DCM_SOPClassUID, a).good() ||
-        !dataset.findAndGetOFString(DCM_SOPInstanceUID, b).good())
-    {
-      throw OrthancException(ErrorCode_NoSopClassOrInstance,
-                             "Unable to determine the SOP class/instance for C-STORE with AET " +
-                             parameters_.GetRemoteApplicationEntityTitle());
-    }
-
-    sopClassUid.assign(a.c_str());
-    sopInstanceUid.assign(b.c_str());
+    DicomTransferSyntax transferSyntax;
+    LookupParameters(sopClassUid, sopInstanceUid, transferSyntax, dicom);
 
-    DicomTransferSyntax transferSyntax;
-    if (!FromDcmtkBridge::LookupOrthancTransferSyntax(
-          transferSyntax, dataset.getOriginalXfer()))
+    uint8_t presID;
+    if (!NegotiatePresentationContext(presID, sopClassUid, transferSyntax))
     {
-      throw OrthancException(ErrorCode_InternalError,
-                             "Unknown transfer syntax from DCMTK");
-    }
-
-    // Figure out which accepted presentation context should be used
-    uint8_t presID;
-    if (!NegotiatePresentationContext(presID, sopClassUid.c_str(), transferSyntax))
-    {
-      throw OrthancException(ErrorCode_InternalError,
-                             "No valid presentation context was negotiated upfront");
+      throw OrthancException(ErrorCode_NetworkProtocol,
+                             "No valid presentation context was negotiated for "
+                             "SOP class UID [" + sopClassUid + "] and transfer "
+                             "syntax [" + GetTransferSyntaxUid(transferSyntax) + "] "
+                             "while sending to modality [" +
+                             parameters_.GetRemoteModality().GetApplicationEntityTitle() + "]");
     }
     
     // Prepare the transmission of data
@@ -286,8 +348,8 @@
     request.DataSetType = DIMSE_DATASET_PRESENT;
     strncpy(request.AffectedSOPInstanceUID, sopInstanceUid.c_str(), DIC_UI_LEN);
 
-    if (!moveOriginatorAET.empty())
-    {
+    if (hasMoveOriginator)
+    {    
       strncpy(request.MoveOriginatorApplicationEntityTitle, 
               moveOriginatorAET.c_str(), DIC_AE_LEN);
       request.opts = O_STORE_MOVEORIGINATORAETITLE;
@@ -296,12 +358,17 @@
       request.opts |= O_STORE_MOVEORIGINATORID;
     }
 
+    if (dicom.getDataset() == NULL)
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
     // Finally conduct transmission of data
     T_DIMSE_C_StoreRSP response;
     DcmDataset* statusDetail = NULL;
     DicomAssociation::CheckCondition(
       DIMSE_storeUser(&association_->GetDcmtkAssociation(), presID, &request,
-                      NULL, &dataset, /*progressCallback*/ NULL, NULL,
+                      NULL, dicom.getDataset(), /*progressCallback*/ NULL, NULL,
                       /*opt_blockMode*/ (GetParameters().HasTimeout() ? DIMSE_NONBLOCKING : DIMSE_BLOCKING),
                       /*opt_dimse_timeout*/ GetParameters().GetTimeout(),
                       &response, &statusDetail, NULL),
@@ -326,7 +393,7 @@
       sprintf(buf, "%04X", response.DimseStatus);
       throw OrthancException(ErrorCode_NetworkProtocol,
                              "C-STORE SCU to AET \"" +
-                             GetParameters().GetRemoteApplicationEntityTitle() +
+                             GetParameters().GetRemoteModality().GetApplicationEntityTitle() +
                              "\" has failed with DIMSE status 0x" + buf);
     }
   }
@@ -334,26 +401,132 @@
 
   void DicomStoreUserConnection::Store(std::string& sopClassUid,
                                        std::string& sopInstanceUid,
-                                       ParsedDicomFile& parsed,
-                                       const std::string& moveOriginatorAET,
-                                       uint16_t moveOriginatorID)
-  {
-    Store(sopClassUid, sopInstanceUid, *parsed.GetDcmtkObject().getDataset(),
-          moveOriginatorAET, moveOriginatorID);
-  }
-
-
-  void DicomStoreUserConnection::Store(std::string& sopClassUid,
-                                       std::string& sopInstanceUid,
                                        const void* buffer,
                                        size_t size,
+                                       bool hasMoveOriginator,
                                        const std::string& moveOriginatorAET,
                                        uint16_t moveOriginatorID)
   {
     std::unique_ptr<DcmFileFormat> dicom(
       FromDcmtkBridge::LoadFromMemoryBuffer(buffer, size));
 
-    Store(sopClassUid, sopInstanceUid, *dicom->getDataset(),
-          moveOriginatorAET, moveOriginatorID);
+    if (dicom.get() == NULL)
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+    
+    Store(sopClassUid, sopInstanceUid, *dicom, hasMoveOriginator, moveOriginatorAET, moveOriginatorID);
+  }
+
+
+  void DicomStoreUserConnection::LookupTranscoding(std::set<DicomTransferSyntax>& acceptedSyntaxes,
+                                                   const std::string& sopClassUid,
+                                                   DicomTransferSyntax sourceSyntax)
+  {
+    acceptedSyntaxes.clear();
+
+    // Make sure a negotiation has already occurred for this transfer
+    // 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);
+
+    std::map<DicomTransferSyntax, uint8_t> contexts;
+    if (association_->LookupAcceptedPresentationContext(contexts, sopClassUid))
+    {
+      for (std::map<DicomTransferSyntax, uint8_t>::const_iterator
+             it = contexts.begin(); it != contexts.end(); ++it)
+      {
+        acceptedSyntaxes.insert(it->first);
+      }
+    }
+  }
+
+
+  void DicomStoreUserConnection::Transcode(std::string& sopClassUid /* out */,
+                                           std::string& sopInstanceUid /* out */,
+                                           IDicomTranscoder& transcoder,
+                                           const void* buffer,
+                                           size_t size,
+                                           bool hasMoveOriginator,
+                                           const std::string& moveOriginatorAET,
+                                           uint16_t moveOriginatorID)
+  {
+    std::unique_ptr<DcmFileFormat> dicom(FromDcmtkBridge::LoadFromMemoryBuffer(buffer, size));
+    if (dicom.get() == NULL ||
+        dicom->getDataset() == NULL)
+    {
+      throw OrthancException(ErrorCode_NullPointer);
+    }
+
+    DicomTransferSyntax inputSyntax;
+    LookupParameters(sopClassUid, sopInstanceUid, inputSyntax, *dicom);
+
+    std::set<DicomTransferSyntax> accepted;
+    LookupTranscoding(accepted, sopClassUid, inputSyntax);
+
+    if (accepted.find(inputSyntax) != accepted.end())
+    {
+      // No need for transcoding
+      Store(sopClassUid, sopInstanceUid, *dicom,
+            hasMoveOriginator, moveOriginatorAET, moveOriginatorID);
+    }
+    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);
+      }
+
+      std::unique_ptr<IDicomTranscoder::TranscodedDicom> transcoded(
+        transcoder.TranscodeToParsed(*dicom, buffer, size, uncompressedSyntaxes, false));
+
+      // WARNING: Below this point, "transcoded->GetDicom()" is possibly
+      // a reference to "*dicom", if the DCMTK transcoder was used
+      
+      if (transcoded.get() == NULL ||
+          transcoded->GetDicom().getDataset() == NULL)
+      {
+        throw OrthancException(
+          ErrorCode_NotImplemented,
+          "Cannot transcode from \"" + std::string(GetTransferSyntaxUid(inputSyntax)) +
+          "\" to an uncompressed syntax for modality: " +
+          GetParameters().GetRemoteModality().GetApplicationEntityTitle());
+      }
+      else if (transcoded->HasSopInstanceUidChanged())
+      {
+        throw OrthancException(ErrorCode_Plugin, "The transcoder has changed the SOP "
+                               "instance UID while transcoding to an uncompressed transfer syntax");
+      }
+      else
+      {
+        DicomTransferSyntax transcodedSyntax;
+
+        // Sanity check
+        if (!FromDcmtkBridge::LookupOrthancTransferSyntax(transcodedSyntax, transcoded->GetDicom()) ||
+            accepted.find(transcodedSyntax) == accepted.end())
+        {
+          throw OrthancException(ErrorCode_InternalError);
+        }
+        else
+        {
+          Store(sopClassUid, sopInstanceUid, transcoded->GetDicom(),
+                hasMoveOriginator, moveOriginatorAET, moveOriginatorID);
+        }
+      }
+    }
   }
 }