changeset 3839:5bba4d249422 transcoding

integration mainline->transcoding
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 15 Apr 2020 22:03:21 +0200
parents 4fde7933e504 (current diff) 95083d2f6819 (diff)
children 44bfcfdf42e8
files Core/DicomNetworking/DicomStoreUserConnection.h OrthancServer/ServerContext.cpp Resources/CMake/DcmtkConfiguration.cmake UnitTestsSources/FromDcmtkTests.cpp
diffstat 23 files changed, 473 insertions(+), 55 deletions(-) [+]
line wrap: on
line diff
--- a/Core/DicomNetworking/DicomAssociation.cpp	Fri Apr 10 16:36:02 2020 +0200
+++ b/Core/DicomNetworking/DicomAssociation.cpp	Wed Apr 15 22:03:21 2020 +0200
@@ -209,7 +209,7 @@
     {
       return;  // Already open
     }
-      
+
     // Timeout used during association negociation and ASC_releaseAssociation()
     uint32_t acseTimeout = parameters.GetTimeout();
     if (acseTimeout == 0)
--- a/Core/DicomNetworking/DicomControlUserConnection.cpp	Fri Apr 10 16:36:02 2020 +0200
+++ b/Core/DicomNetworking/DicomControlUserConnection.cpp	Wed Apr 15 22:03:21 2020 +0200
@@ -431,6 +431,15 @@
   }
     
 
+  DicomControlUserConnection::DicomControlUserConnection(const std::string& localAet,
+                                                         const RemoteModalityParameters& remote) :
+    parameters_(localAet, remote),
+    association_(new DicomAssociation)
+  {
+    SetupPresentationContexts();
+  }
+    
+
   bool DicomControlUserConnection::Echo()
   {
     association_->Open(parameters_);
--- a/Core/DicomNetworking/DicomControlUserConnection.h	Fri Apr 10 16:36:02 2020 +0200
+++ b/Core/DicomNetworking/DicomControlUserConnection.h	Wed Apr 15 22:03:21 2020 +0200
@@ -65,6 +65,9 @@
                       const DicomMap& fields);
     
   public:
+    DicomControlUserConnection(const std::string& localAet,
+                               const RemoteModalityParameters& remote);
+    
     DicomControlUserConnection(const DicomAssociationParameters& params);
     
     const DicomAssociationParameters& GetParameters() const
--- a/Core/DicomNetworking/DicomStoreUserConnection.cpp	Fri Apr 10 16:36:02 2020 +0200
+++ b/Core/DicomNetworking/DicomStoreUserConnection.cpp	Wed Apr 15 22:03:21 2020 +0200
@@ -34,10 +34,14 @@
 #include "../PrecompiledHeaders.h"
 #include "DicomStoreUserConnection.h"
 
+#include "../DicomParsing/FromDcmtkBridge.h"
+#include "../DicomParsing/ParsedDicomFile.h"
+#include "../Logging.h"
+#include "../OrthancException.h"
 #include "DicomAssociation.h"
 
-#include "../Logging.h"
-#include "../OrthancException.h"
+#include <dcmtk/dcmdata/dcdeftag.h>
+
 
 namespace Orthanc
 {
@@ -237,4 +241,119 @@
     association_->Open(parameters_);
     return LookupPresentationContext(presentationContextId, sopClassUid, transferSyntax);
   }
+
+
+  void DicomStoreUserConnection::Store(std::string& sopClassUid,
+                                       std::string& sopInstanceUid,
+                                       DcmDataset& dataset,
+                                       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;
+    if (!FromDcmtkBridge::LookupOrthancTransferSyntax(
+          transferSyntax, dataset.getOriginalXfer()))
+    {
+      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");
+    }
+    
+    // Prepare the transmission of data
+    T_DIMSE_C_StoreRQ request;
+    memset(&request, 0, sizeof(request));
+    request.MessageID = association_->GetDcmtkAssociation().nextMsgID++;
+    strncpy(request.AffectedSOPClassUID, sopClassUid.c_str(), DIC_UI_LEN);
+    request.Priority = DIMSE_PRIORITY_MEDIUM;
+    request.DataSetType = DIMSE_DATASET_PRESENT;
+    strncpy(request.AffectedSOPInstanceUID, sopInstanceUid.c_str(), DIC_UI_LEN);
+
+    if (!moveOriginatorAET.empty())
+    {
+      strncpy(request.MoveOriginatorApplicationEntityTitle, 
+              moveOriginatorAET.c_str(), DIC_AE_LEN);
+      request.opts = O_STORE_MOVEORIGINATORAETITLE;
+
+      request.MoveOriginatorID = moveOriginatorID;  // The type DIC_US is an alias for uint16_t
+      request.opts |= O_STORE_MOVEORIGINATORID;
+    }
+
+    // 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,
+                      /*opt_blockMode*/ (GetParameters().HasTimeout() ? DIMSE_NONBLOCKING : DIMSE_BLOCKING),
+                      /*opt_dimse_timeout*/ GetParameters().GetTimeout(),
+                      &response, &statusDetail, NULL),
+      GetParameters(), "C-STORE");
+
+    if (statusDetail != NULL) 
+    {
+      delete statusDetail;
+    }
+    
+    /**
+     * New in Orthanc 1.6.0: Deal with failures during C-STORE.
+     * http://dicom.nema.org/medical/dicom/current/output/chtml/part04/sect_B.2.3.html#table_B.2-1
+     **/
+    
+    if (response.DimseStatus != 0x0000 &&  // Success
+        response.DimseStatus != 0xB000 &&  // Warning - Coercion of Data Elements
+        response.DimseStatus != 0xB007 &&  // Warning - Data Set does not match SOP Class
+        response.DimseStatus != 0xB006)    // Warning - Elements Discarded
+    {
+      char buf[16];
+      sprintf(buf, "%04X", response.DimseStatus);
+      throw OrthancException(ErrorCode_NetworkProtocol,
+                             "C-STORE SCU to AET \"" +
+                             GetParameters().GetRemoteApplicationEntityTitle() +
+                             "\" has failed with DIMSE status 0x" + buf);
+    }
+  }
+
+
+  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,
+                                       const std::string& moveOriginatorAET,
+                                       uint16_t moveOriginatorID)
+  {
+    std::unique_ptr<DcmFileFormat> dicom(
+      FromDcmtkBridge::LoadFromMemoryBuffer(buffer, size));
+
+    Store(sopClassUid, sopInstanceUid, *dicom->getDataset(),
+          moveOriginatorAET, moveOriginatorID);
+  }
 }
--- a/Core/DicomNetworking/DicomStoreUserConnection.h	Fri Apr 10 16:36:02 2020 +0200
+++ b/Core/DicomNetworking/DicomStoreUserConnection.h	Wed Apr 15 22:03:21 2020 +0200
@@ -41,6 +41,8 @@
 #include <stdint.h>  // For uint8_t
 
 
+class DcmDataset;
+
 namespace Orthanc
 {
   /**
@@ -62,6 +64,7 @@
   **/
 
   class DicomAssociation;  // Forward declaration for PImpl design pattern
+  class ParsedDicomFile;
 
   class DicomStoreUserConnection : public boost::noncopyable
   {
@@ -78,7 +81,7 @@
     // Return "false" if there is not enough room remaining in the association
     bool ProposeStorageClass(const std::string& sopClassUid,
                              const std::set<DicomTransferSyntax>& syntaxes);
-    
+
   public:
     DicomStoreUserConnection(const DicomAssociationParameters& params);
     
@@ -120,12 +123,34 @@
     void PrepareStorageClass(const std::string& sopClassUid,
                              DicomTransferSyntax syntax);
 
+    // Should only be used if transcoding
+    // TODO => to private
     bool LookupPresentationContext(uint8_t& presentationContextId,
                                    const std::string& sopClassUid,
                                    DicomTransferSyntax transferSyntax);
         
+    // TODO => to private
     bool NegotiatePresentationContext(uint8_t& presentationContextId,
                                       const std::string& sopClassUid,
                                       DicomTransferSyntax transferSyntax);
+
+    void Store(std::string& sopClassUid,
+               std::string& sopInstanceUid,
+               DcmDataset& dataset,
+               const std::string& moveOriginatorAET,
+               uint16_t moveOriginatorID);
+
+    void Store(std::string& sopClassUid,
+               std::string& sopInstanceUid,
+               ParsedDicomFile& parsed,
+               const std::string& moveOriginatorAET,
+               uint16_t moveOriginatorID);
+
+    void Store(std::string& sopClassUid,
+               std::string& sopInstanceUid,
+               const void* buffer,
+               size_t size,
+               const std::string& moveOriginatorAET,
+               uint16_t moveOriginatorID);
   };
 }
--- a/Core/HttpServer/HttpServer.cpp	Fri Apr 10 16:36:02 2020 +0200
+++ b/Core/HttpServer/HttpServer.cpp	Wed Apr 15 22:03:21 2020 +0200
@@ -170,7 +170,7 @@
 
 
 
-  class ChunkStore
+  class ChunkStore : public boost::noncopyable
   {
   private:
     typedef std::list<ChunkedFile*>  Content;
@@ -308,7 +308,7 @@
                                                   struct mg_connection *connection,
                                                   const std::string& contentLength)
   {
-    int length;      
+    int length;
     try
     {
       length = boost::lexical_cast<int>(contentLength);
@@ -904,7 +904,6 @@
       }
     }
 
-
     if (!found && 
         server.HasHandler())
     {
--- a/Core/Images/PamReader.cpp	Fri Apr 10 16:36:02 2020 +0200
+++ b/Core/Images/PamReader.cpp	Wed Apr 15 22:03:21 2020 +0200
@@ -42,6 +42,7 @@
 #  include "../SystemToolbox.h"
 #endif
 
+#include <stdlib.h>  // For malloc/free
 #include <boost/algorithm/string/find.hpp>
 #include <boost/lexical_cast.hpp>
 
@@ -200,7 +201,30 @@
     }
 
     size_t offset = content_.size() - pitch * height;
-    AssignWritable(format, width, height, pitch, &content_[offset]);
+
+    {
+      intptr_t bufferAddr = reinterpret_cast<intptr_t>(&content_[offset]);
+      if((bufferAddr % 8) == 0)
+        LOG(TRACE) << "PamReader::ParseContent() image address = " << bufferAddr;
+      else
+        LOG(TRACE) << "PamReader::ParseContent() image address = " << bufferAddr << " (not a multiple of 8!)";
+    }
+    
+    // if we want to enforce alignment, we need to use a freshly allocated
+    // buffer, since we have no alignment guarantees on the original one
+    if (enforceAligned_)
+    {
+      if (alignedImageBuffer_ != NULL)
+        free(alignedImageBuffer_);
+      alignedImageBuffer_ = malloc(pitch * height);
+      memcpy(alignedImageBuffer_, &content_[offset], pitch* height);
+      content_ = "";
+      AssignWritable(format, width, height, pitch, alignedImageBuffer_);
+    }
+    else
+    {
+      AssignWritable(format, width, height, pitch, &content_[offset]);
+    }
 
     // Byte swapping if needed
     if (bytesPerChannel != 1 &&
@@ -231,7 +255,7 @@
             at SAFE_HEAP_LOAD_i32_2_2 (wasm-function[251132]:39)
             at __ZN7Orthanc9PamReader12ParseContentEv (wasm-function[11457]:8088)
 
-          Web Assenmbly IS LITTLE ENDIAN!
+          Web Assembly IS LITTLE ENDIAN!
 
           Perhaps in htobe16 ?
           */
@@ -274,4 +298,12 @@
     content_.assign(reinterpret_cast<const char*>(buffer), size);
     ParseContent();
   }
+
+  PamReader::~PamReader()
+  {
+    if (alignedImageBuffer_ != NULL)
+    {
+      free(alignedImageBuffer_);
+    }
+  }
 }
--- a/Core/Images/PamReader.h	Fri Apr 10 16:36:02 2020 +0200
+++ b/Core/Images/PamReader.h	Wed Apr 15 22:03:21 2020 +0200
@@ -46,9 +46,41 @@
   private:
     void ParseContent();
     
+    /**
+    Whether we want to use the default malloc alignment in the image buffer,
+    at the expense of an extra copy
+    */
+    bool enforceAligned_;
+
+    /**
+    This is actually a copy of wrappedContent_, but properly aligned.
+
+    It is only used if the enforceAligned parameter is set to true in the
+    constructor.
+    */
+    void* alignedImageBuffer_;
+    
+    /**
+    Points somewhere in the content_ buffer.      
+    */
+    ImageAccessor wrappedContent_;
+
+    /**
+    Raw content (file bytes or answer from the server, for instance). 
+    */
     std::string content_;
 
   public:
+    /**
+    See doc for field enforceAligned_
+    */
+    PamReader(bool enforceAligned = false) :
+      enforceAligned_(enforceAligned),
+      alignedImageBuffer_(NULL)
+    {
+    }
+
+    virtual ~PamReader();
 
 #if ORTHANC_SANDBOXED == 0
     void ReadFromFile(const std::string& filename);
--- a/NEWS	Fri Apr 10 16:36:02 2020 +0200
+++ b/NEWS	Wed Apr 15 22:03:21 2020 +0200
@@ -19,6 +19,7 @@
 * Fix lookup form in Orthanc Explorer (wildcards not allowed in StudyDate)
 * Fix signature of "OrthancPluginRegisterStorageCommitmentScpCallback()" in plugins SDK
 * Error reporting on failure while initializing SSL
+* Fix unit test ParsedDicomFile.ToJsonFlags2 on big-endian architectures
 * Upgraded dependencies for static builds (notably on Windows):
   - civetweb 1.12
   - openssl 1.1.1f
--- a/OrthancServer/DicomInstanceToStore.h	Fri Apr 10 16:36:02 2020 +0200
+++ b/OrthancServer/DicomInstanceToStore.h	Wed Apr 15 22:03:21 2020 +0200
@@ -44,7 +44,7 @@
 {
   class ParsedDicomFile;
 
-  class DicomInstanceToStore
+  class DicomInstanceToStore : public boost::noncopyable
   {
   public:
     typedef std::map<std::pair<ResourceType, MetadataType>, std::string>  MetadataMap;
--- a/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp	Fri Apr 10 16:36:02 2020 +0200
+++ b/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp	Wed Apr 15 22:03:21 2020 +0200
@@ -35,6 +35,8 @@
 #include "OrthancRestApi.h"
 
 #include "../../Core/Cache/SharedArchive.h"
+#include "../../Core/DicomNetworking/DicomAssociation.h"
+#include "../../Core/DicomNetworking/DicomControlUserConnection.h"
 #include "../../Core/DicomParsing/FromDcmtkBridge.h"
 #include "../../Core/Logging.h"
 #include "../../Core/SerializationToolbox.h"
@@ -80,8 +82,7 @@
 
     try
     {
-      DicomUserConnection connection(localAet, remote);
-      connection.Open();
+      DicomControlUserConnection connection(localAet, remote);
       
       if (connection.Echo())
       {
@@ -127,7 +128,7 @@
 
 
   static void FindPatient(DicomFindAnswers& result,
-                          DicomUserConnection& connection,
+                          DicomControlUserConnection& connection,
                           const DicomMap& fields)
   {
     // Only keep the filters from "fields" that are related to the patient
@@ -138,7 +139,7 @@
 
 
   static void FindStudy(DicomFindAnswers& result,
-                        DicomUserConnection& connection,
+                        DicomControlUserConnection& connection,
                         const DicomMap& fields)
   {
     // Only keep the filters from "fields" that are related to the study
@@ -153,7 +154,7 @@
   }
 
   static void FindSeries(DicomFindAnswers& result,
-                         DicomUserConnection& connection,
+                         DicomControlUserConnection& connection,
                          const DicomMap& fields)
   {
     // Only keep the filters from "fields" that are related to the series
@@ -168,7 +169,7 @@
   }
 
   static void FindInstance(DicomFindAnswers& result,
-                           DicomUserConnection& connection,
+                           DicomControlUserConnection& connection,
                            const DicomMap& fields)
   {
     // Only keep the filters from "fields" that are related to the instance
@@ -203,8 +204,7 @@
     DicomFindAnswers answers(false);
 
     {
-      DicomUserConnection connection(localAet, remote);
-      connection.Open();
+      DicomControlUserConnection connection(localAet, remote);
       FindPatient(answers, connection, fields);
     }
 
@@ -238,8 +238,7 @@
     DicomFindAnswers answers(false);
 
     {
-      DicomUserConnection connection(localAet, remote);
-      connection.Open();
+      DicomControlUserConnection connection(localAet, remote);
       FindStudy(answers, connection, fields);
     }
 
@@ -274,8 +273,7 @@
     DicomFindAnswers answers(false);
 
     {
-      DicomUserConnection connection(localAet, remote);
-      connection.Open();
+      DicomControlUserConnection connection(localAet, remote);
       FindSeries(answers, connection, fields);
     }
 
@@ -311,8 +309,7 @@
     DicomFindAnswers answers(false);
 
     {
-      DicomUserConnection connection(localAet, remote);
-      connection.Open();
+      DicomControlUserConnection connection(localAet, remote);
       FindInstance(answers, connection, fields);
     }
 
@@ -350,8 +347,7 @@
     RemoteModalityParameters remote =
       MyGetModalityUsingSymbolicName(call.GetUriComponent("id", ""));
 
-    DicomUserConnection connection(localAet, remote);
-    connection.Open();
+    DicomControlUserConnection connection(localAet, remote);
     
     DicomFindAnswers patients(false);
     FindPatient(patients, connection, m);
@@ -804,7 +800,7 @@
         DicomMap answer;
         parent.GetHandler().GetAnswer(answer, index);
 
-        // This switch-case mimics "DicomUserConnection::Move()"
+        // This switch-case mimics "DicomControlUserConnection::Move()"
         switch (parent.GetHandler().GetLevel())
         {
           case ResourceType_Patient:
@@ -1031,8 +1027,7 @@
     const RemoteModalityParameters source =
       MyGetModalityUsingSymbolicName(call.GetUriComponent("id", ""));
 
-    DicomUserConnection connection(localAet, source);
-    connection.Open();
+    DicomControlUserConnection connection(localAet, source);
     
     for (Json::Value::ArrayIndex i = 0; i < request[KEY_RESOURCES].size(); i++)
     {
@@ -1315,8 +1310,7 @@
       DicomFindAnswers answers(true);
 
       {
-        DicomUserConnection connection(localAet, remote);
-        connection.Open();
+        DicomControlUserConnection connection(localAet, remote);
         connection.FindWorklist(answers, *query);
       }
 
@@ -1486,11 +1480,11 @@
         context.GetStorageCommitmentReports().Store(
           transactionUid, new StorageCommitmentReports::Report(remoteAet));
 
-        DicomUserConnection scu(localAet, remote);
-
+        DicomAssociationParameters parameters(localAet, remote);
+        
         std::vector<std::string> a(sopClassUids.begin(), sopClassUids.end());
         std::vector<std::string> b(sopInstanceUids.begin(), sopInstanceUids.end());
-        scu.RequestStorageCommitment(transactionUid, a, b);
+        DicomAssociation::RequestStorageCommitment(parameters, transactionUid, a, b);
       }
 
       Json::Value result = Json::objectValue;
--- a/OrthancServer/ServerContext.cpp	Fri Apr 10 16:36:02 2020 +0200
+++ b/OrthancServer/ServerContext.cpp	Wed Apr 15 22:03:21 2020 +0200
@@ -691,7 +691,7 @@
   {
 #if ENABLE_DICOM_CACHE == 0
     static std::unique_ptr<IDynamicObject> p;
-    p.reset(provider_.Provide(instancePublicId));
+    p.reset(that_.provider_.Provide(instancePublicId));
     dicom_ = dynamic_cast<ParsedDicomFile*>(p.get());
 #else
     dicom_ = &dynamic_cast<ParsedDicomFile&>(that_.dicomCache_.Access(instancePublicId));
--- a/Resources/CMake/Compiler.cmake	Fri Apr 10 16:36:02 2020 +0200
+++ b/Resources/CMake/Compiler.cmake	Wed Apr 15 22:03:21 2020 +0200
@@ -1,6 +1,7 @@
 # This file sets all the compiler-related flags
 
-if (CMAKE_CROSSCOMPILING OR
+if ((CMAKE_CROSSCOMPILING AND NOT
+      "${CMAKE_SYSTEM_VERSION}" STREQUAL "CrossToolNg") OR    
     "${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase")
   # Cross-compilation necessarily implies standalone and static build
   SET(STATIC_BUILD ON)
--- a/Resources/CMake/DcmtkConfiguration.cmake	Fri Apr 10 16:36:02 2020 +0200
+++ b/Resources/CMake/DcmtkConfiguration.cmake	Wed Apr 15 22:03:21 2020 +0200
@@ -148,13 +148,34 @@
 
 
 else()
-  # The following line allows to manually add libraries at the
-  # command-line, which is necessary for Ubuntu/Debian packages
-  set(tmp "${DCMTK_LIBRARIES}")
-  include(FindDCMTK)
-  list(APPEND DCMTK_LIBRARIES "${tmp}")
+  if (CMAKE_CROSSCOMPILING AND
+      "${CMAKE_SYSTEM_VERSION}" STREQUAL "CrossToolNg")
+
+    CHECK_INCLUDE_FILE_CXX(dcmtk/dcmdata/dcfilefo.h HAVE_DCMTK_H)
+    if (NOT HAVE_DCMTK_H)
+      message(FATAL_ERROR "Please install the libdcmtk-dev package")
+    endif()
+
+    CHECK_LIBRARY_EXISTS(dcmdata "dcmDataDict" "" HAVE_DCMTK_LIB)
+    if (NOT HAVE_DCMTK_LIB)
+      message(FATAL_ERROR "Please install the libdcmtk package")
+    endif()  
 
-  include_directories(${DCMTK_INCLUDE_DIRS})
+    find_path(DCMTK_INCLUDE_DIRS dcmtk/config/osconfig.h
+      /usr/include
+      )
+
+    link_libraries(dcmdata dcmnet dcmjpeg oflog ofstd)
+
+  else()
+    # The following line allows to manually add libraries at the
+    # command-line, which is necessary for Ubuntu/Debian packages
+    set(tmp "${DCMTK_LIBRARIES}")
+    include(FindDCMTK)
+    list(APPEND DCMTK_LIBRARIES "${tmp}")
+
+    include_directories(${DCMTK_INCLUDE_DIRS})
+  endif()
 
   add_definitions(
     -DHAVE_CONFIG_H=1
@@ -225,6 +246,13 @@
       message(FATAL_ERROR "Cannot locate the DICOM dictionary on this system")
     endif()
 
+    if (CMAKE_CROSSCOMPILING AND
+        "${CMAKE_SYSTEM_VERSION}" STREQUAL "CrossToolNg")
+      # Remove the sysroot prefix
+      file(RELATIVE_PATH tmp ${CMAKE_FIND_ROOT_PATH} ${DCMTK_DICTIONARY_DIR_AUTO})
+      set(DCMTK_DICTIONARY_DIR_AUTO /${tmp} CACHE INTERNAL "")
+    endif()
+
     message("Autodetected path to the DICOM dictionaries: ${DCMTK_DICTIONARY_DIR_AUTO}")
     add_definitions(-DDCMTK_DICTIONARY_DIR="${DCMTK_DICTIONARY_DIR_AUTO}")
   else()
--- a/Resources/CMake/GoogleTestConfiguration.cmake	Fri Apr 10 16:36:02 2020 +0200
+++ b/Resources/CMake/GoogleTestConfiguration.cmake	Wed Apr 15 22:03:21 2020 +0200
@@ -2,15 +2,15 @@
   find_path(GOOGLE_TEST_DEBIAN_SOURCES_DIR
     NAMES src/gtest-all.cc
     PATHS
-    /usr/src/gtest
-    /usr/src/googletest/googletest
+    ${CROSSTOOL_NG_IMAGE}/usr/src/gtest
+    ${CROSSTOOL_NG_IMAGE}/usr/src/googletest/googletest
     PATH_SUFFIXES src
     )
 
   find_path(GOOGLE_TEST_DEBIAN_INCLUDE_DIR
     NAMES gtest.h
     PATHS
-    /usr/include/gtest
+    ${CROSSTOOL_NG_IMAGE}/usr/include/gtest
     )
 
   message("Path to the Debian Google Test sources: ${GOOGLE_TEST_DEBIAN_SOURCES_DIR}")
--- a/Resources/CMake/LibCurlConfiguration.cmake	Fri Apr 10 16:36:02 2020 +0200
+++ b/Resources/CMake/LibCurlConfiguration.cmake	Wed Apr 15 22:03:21 2020 +0200
@@ -262,7 +262,7 @@
 
     check_struct_has_member("struct sockaddr_un" sun_path "sys/un.h" USE_UNIX_SOCKETS)
 
-    set(CMAKE_REQUIRED_INCLUDES "${CURL_SOURCES_DIR}/include")
+    list(APPEND CMAKE_REQUIRED_INCLUDES "${CURL_SOURCES_DIR}/include")
     set(CMAKE_EXTRA_INCLUDE_FILES "curl/system.h")
     check_type_size("curl_off_t"  SIZEOF_CURL_OFF_T)
 
@@ -312,6 +312,22 @@
       ${CURL_SOURCES_DIR}/lib/curl_config.h
       )
   endif()
+
+elseif (CMAKE_CROSSCOMPILING AND
+    "${CMAKE_SYSTEM_VERSION}" STREQUAL "CrossToolNg")
+
+  CHECK_INCLUDE_FILE_CXX(curl/curl.h HAVE_CURL_H)
+  if (NOT HAVE_CURL_H)
+    message(FATAL_ERROR "Please install the libcurl-dev package")
+  endif()
+
+  CHECK_LIBRARY_EXISTS(curl "curl_easy_init" "" HAVE_CURL_LIB)
+  if (NOT HAVE_CURL_LIB)
+    message(FATAL_ERROR "Please install the libcurl package")
+  endif()  
+  
+  link_libraries(curl)
+
 else()
   include(FindCURL)
   include_directories(${CURL_INCLUDE_DIRS})
--- a/Resources/CMake/LuaConfiguration.cmake	Fri Apr 10 16:36:02 2020 +0200
+++ b/Resources/CMake/LuaConfiguration.cmake	Wed Apr 15 22:03:21 2020 +0200
@@ -100,6 +100,32 @@
 
   source_group(ThirdParty\\Lua REGULAR_EXPRESSION ${LUA_SOURCES_DIR}/.*)
 
+elseif (CMAKE_CROSSCOMPILING AND
+    "${CMAKE_SYSTEM_VERSION}" STREQUAL "CrossToolNg")
+
+  set(LUA_VERSIONS 5.3 5.2 5.1)
+
+  unset(LUA_VERSION)
+  foreach(version IN ITEMS ${LUA_VERSIONS})
+    CHECK_INCLUDE_FILE(lua${version}/lua.h HAVE_LUA${version}_H)
+    if (HAVE_LUA${version}_H)
+      set(LUA_VERSION ${version})
+      break()
+    endif()
+  endforeach()
+
+  if (NOT LUA_VERSION)
+    message(FATAL_ERROR "Please install the liblua-dev package")
+  endif()
+  
+  CHECK_LIBRARY_EXISTS(lua${LUA_VERSION} "lua_call" "${LUA_LIB_DIR}" HAVE_LUA_LIB)
+  if (NOT HAVE_LUA_LIB)
+    message(FATAL_ERROR "Please install the liblua package")
+  endif()  
+
+  include_directories(${CROSSTOOL_NG_IMAGE}/usr/include/lua${LUA_VERSION})
+  link_libraries(lua${LUA_VERSION})
+
 else()
   include(FindLua)
 
--- a/Resources/CMake/OpenSslConfiguration.cmake	Fri Apr 10 16:36:02 2020 +0200
+++ b/Resources/CMake/OpenSslConfiguration.cmake	Wed Apr 15 22:03:21 2020 +0200
@@ -9,6 +9,26 @@
 
   source_group(ThirdParty\\OpenSSL REGULAR_EXPRESSION ${OPENSSL_SOURCES_DIR}/.*)
 
+elseif (CMAKE_CROSSCOMPILING AND
+    "${CMAKE_SYSTEM_VERSION}" STREQUAL "CrossToolNg")
+
+  CHECK_INCLUDE_FILE_CXX(openssl/opensslv.h HAVE_OPENSSL_H)
+  if (NOT HAVE_OPENSSL_H)
+    message(FATAL_ERROR "Please install the libopenssl-dev package")
+  endif()
+
+  CHECK_LIBRARY_EXISTS(crypto "OPENSSL_init" "" HAVE_OPENSSL_CRYPTO_LIB)
+  if (NOT HAVE_OPENSSL_CRYPTO_LIB)
+    message(FATAL_ERROR "Please install the libopenssl package")
+  endif()  
+  
+  CHECK_LIBRARY_EXISTS(ssl "SSL_library_init" "" HAVE_OPENSSL_SSL_LIB)
+  if (NOT HAVE_OPENSSL_SSL_LIB)
+    message(FATAL_ERROR "Please install the libopenssl package")
+  endif()  
+  
+  link_libraries(crypto ssl)
+
 else()
   include(FindOpenSSL)
 
--- a/Resources/CMake/SQLiteConfiguration.cmake	Fri Apr 10 16:36:02 2020 +0200
+++ b/Resources/CMake/SQLiteConfiguration.cmake	Wed Apr 15 22:03:21 2020 +0200
@@ -41,12 +41,14 @@
   source_group(ThirdParty\\SQLite REGULAR_EXPRESSION ${SQLITE_SOURCES_DIR}/.*)
 
 else()
-  CHECK_INCLUDE_FILE_CXX(sqlite3.h HAVE_SQLITE_H)
+  CHECK_INCLUDE_FILE(sqlite3.h HAVE_SQLITE_H)
   if (NOT HAVE_SQLITE_H)
     message(FATAL_ERROR "Please install the libsqlite3-dev package")
   endif()
 
-  find_path(SQLITE_INCLUDE_DIR sqlite3.h
+  find_path(SQLITE_INCLUDE_DIR
+    NAMES sqlite3.h
+    PATHS
     /usr/include
     /usr/local/include
     )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/CrossToolchain.cmake	Wed Apr 15 22:03:21 2020 +0200
@@ -0,0 +1,56 @@
+#
+#  $ CROSSTOOL_NG_ARCH=mips CROSSTOOL_NG_BOARD=malta CROSSTOOL_NG_IMAGE=/tmp/mips cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TOOLCHAIN_FILE=../Resources/CrossToolchain.cmake -DBUILD_CONNECTIVITY_CHECKS=OFF -DUSE_SYSTEM_CIVETWEB=OFF -DUSE_GOOGLE_TEST_DEBIAN_PACKAGE=ON -DUSE_SYSTEM_JSONCPP=OFF -DUSE_SYSTEM_UUID=OFF -DENABLE_DCMTK_JPEG_LOSSLESS=OFF -G Ninja && ninja
+#
+
+INCLUDE(CMakeForceCompiler)
+
+SET(CROSSTOOL_NG_ROOT $ENV{CROSSTOOL_NG_ROOT} CACHE STRING "")
+SET(CROSSTOOL_NG_ARCH $ENV{CROSSTOOL_NG_ARCH} CACHE STRING "")
+SET(CROSSTOOL_NG_BOARD $ENV{CROSSTOOL_NG_BOARD} CACHE STRING "")
+SET(CROSSTOOL_NG_SUFFIX $ENV{CROSSTOOL_NG_SUFFIX} CACHE STRING "")
+SET(CROSSTOOL_NG_IMAGE $ENV{CROSSTOOL_NG_IMAGE} CACHE STRING "")
+
+IF ("${CROSSTOOL_NG_ROOT}" STREQUAL "")
+  SET(CROSSTOOL_NG_ROOT "/home/$ENV{USER}/x-tools")
+ENDIF()
+
+IF ("${CROSSTOOL_NG_SUFFIX}" STREQUAL "")
+  SET(CROSSTOOL_NG_SUFFIX "linux-gnu")
+ENDIF()
+
+SET(CROSSTOOL_NG_NAME ${CROSSTOOL_NG_ARCH}-${CROSSTOOL_NG_BOARD}-${CROSSTOOL_NG_SUFFIX})
+SET(CROSSTOOL_NG_BASE ${CROSSTOOL_NG_ROOT}/${CROSSTOOL_NG_NAME})
+
+# the name of the target operating system
+SET(CMAKE_SYSTEM_NAME Linux)
+SET(CMAKE_SYSTEM_VERSION CrossToolNg)
+SET(CMAKE_SYSTEM_PROCESSOR ${CROSSTOOL_NG_ARCH})
+
+# which compilers to use for C and C++
+SET(CMAKE_C_COMPILER ${CROSSTOOL_NG_BASE}/bin/${CROSSTOOL_NG_NAME}-gcc)
+
+if (${CMAKE_VERSION} VERSION_LESS "3.6.0") 
+  CMAKE_FORCE_CXX_COMPILER(${CROSSTOOL_NG_BASE}/bin/${CROSSTOOL_NG_NAME}-g++ GNU)
+else()
+  SET(CMAKE_CXX_COMPILER ${CROSSTOOL_NG_BASE}/bin/${CROSSTOOL_NG_NAME}-g++)
+endif()
+
+# here is the target environment located
+SET(CMAKE_FIND_ROOT_PATH ${CROSSTOOL_NG_IMAGE})
+#SET(CMAKE_FIND_ROOT_PATH ${CROSSTOOL_NG_BASE}/${CROSSTOOL_NG_NAME}/sysroot)
+
+# adjust the default behaviour of the FIND_XXX() commands:
+# search headers and libraries in the target environment, search 
+# programs in the host environment
+SET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
+SET(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
+SET(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
+SET(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
+
+SET(CMAKE_CROSSCOMPILING ON)
+#SET(CROSS_COMPILER_PREFIX ${CROSSTOOL_NG_ARCH}-${CROSSTOOL_NG_SUFFIX})
+
+SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -I${CROSSTOOL_NG_IMAGE}/usr/include -I${CROSSTOOL_NG_IMAGE}/usr/include/${CROSSTOOL_NG_ARCH}-${CROSSTOOL_NG_SUFFIX}" CACHE INTERNAL "" FORCE)
+SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -I${CROSSTOOL_NG_IMAGE}/usr/include -I${CROSSTOOL_NG_IMAGE}/usr/include/${CROSSTOOL_NG_ARCH}-${CROSSTOOL_NG_SUFFIX}" CACHE INTERNAL "" FORCE)
+SET(CMAKE_EXE_LINKER_FLAGS "-Wl,--unresolved-symbols=ignore-in-shared-libs -L${CROSSTOOL_NG_BASE}/${CROSSTOOL_NG_NAME}/sysroot/usr/lib -L${CROSSTOOL_NG_IMAGE}/usr/lib -L${CROSSTOOL_NG_IMAGE}/usr/lib/${CROSSTOOL_NG_ARCH}-${CROSSTOOL_NG_SUFFIX} -L${CROSSTOOL_NG_IMAGE}/lib -L${CROSSTOOL_NG_IMAGE}/lib/${CROSSTOOL_NG_ARCH}-${CROSSTOOL_NG_SUFFIX}" CACHE INTERNAL "" FORCE)
+SET(CMAKE_SHARED_LINKER_FLAGS "-Wl,--unresolved-symbols=ignore-in-shared-libs -L${CROSSTOOL_NG_BASE}/${CROSSTOOL_NG_NAME}/sysroot/usr/lib -L${CROSSTOOL_NG_IMAGE}/usr/lib -L${CROSSTOOL_NG_IMAGE}/usr/lib/${CROSSTOOL_NG_ARCH}-${CROSSTOOL_NG_SUFFIX} -L${CROSSTOOL_NG_IMAGE}/lib -L${CROSSTOOL_NG_IMAGE}/lib/${CROSSTOOL_NG_ARCH}-${CROSSTOOL_NG_SUFFIX}" CACHE INTERNAL "" FORCE)
--- a/Resources/LinuxStandardBaseToolchain.cmake	Fri Apr 10 16:36:02 2020 +0200
+++ b/Resources/LinuxStandardBaseToolchain.cmake	Wed Apr 15 22:03:21 2020 +0200
@@ -10,10 +10,10 @@
 
 INCLUDE(CMakeForceCompiler)
 
-SET(LSB_PATH $ENV{LSB_PATH})
-SET(LSB_CC $ENV{LSB_CC})
-SET(LSB_CXX $ENV{LSB_CXX})
-SET(LSB_TARGET_VERSION "4.0")
+SET(LSB_PATH $ENV{LSB_PATH} CACHE STRING "")
+SET(LSB_CC $ENV{LSB_CC} CACHE STRING "")
+SET(LSB_CXX $ENV{LSB_CXX} CACHE STRING "")
+SET(LSB_TARGET_VERSION "4.0" CACHE STRING "")
 
 IF ("${LSB_PATH}" STREQUAL "")
   SET(LSB_PATH "/opt/lsb")
--- a/UnitTestsSources/FromDcmtkTests.cpp	Fri Apr 10 16:36:02 2020 +0200
+++ b/UnitTestsSources/FromDcmtkTests.cpp	Wed Apr 15 22:03:21 2020 +0200
@@ -710,7 +710,14 @@
 TEST(ParsedDicomFile, ToJsonFlags2)
 {
   ParsedDicomFile f(true);
-  f.Insert(DICOM_TAG_PIXEL_DATA, "Pixels", false, "");
+
+  {
+    // "ParsedDicomFile" uses Little Endian => 'B' (least significant
+    // byte) will be stored first in the memory buffer and in the
+    // file, then 'A'. Hence the expected "BA" value below.
+    Uint16 v[] = { 'A' * 256 + 'B', 0 };
+    ASSERT_TRUE(f.GetDcmtkObject().getDataset()->putAndInsertUint16Array(DCM_PixelData, v, 2).good());
+  }
 
   Json::Value v;
   f.DatasetToJson(v, DicomToJsonFormat_Short, DicomToJsonFlags_None, 0);
@@ -729,7 +736,7 @@
   ASSERT_EQ(6u, v.getMemberNames().size());
   ASSERT_TRUE(v.isMember("7fe0,0010"));  
   ASSERT_EQ(Json::stringValue, v["7fe0,0010"].type());  
-  ASSERT_EQ("Pixels", v["7fe0,0010"].asString());  
+  ASSERT_EQ("BA", v["7fe0,0010"].asString().substr(0, 2));
 
   f.DatasetToJson(v, DicomToJsonFormat_Short, DicomToJsonFlags_IncludePixelData, 0);
   ASSERT_EQ(Json::objectValue, v.type());
@@ -739,7 +746,7 @@
   std::string mime, content;
   ASSERT_TRUE(Toolbox::DecodeDataUriScheme(mime, content, v["7fe0,0010"].asString()));
   ASSERT_EQ("application/octet-stream", mime);
-  ASSERT_EQ("Pixels", content);
+  ASSERT_EQ("BA", content.substr(0, 2));
 }
 
 
--- a/UnitTestsSources/ImageTests.cpp	Fri Apr 10 16:36:02 2020 +0200
+++ b/UnitTestsSources/ImageTests.cpp	Wed Apr 15 22:03:21 2020 +0200
@@ -410,6 +410,28 @@
   }
 
   {
+    // true means "enforce alignment by using a temporary buffer"
+    Orthanc::PamReader r(true);
+    r.ReadFromMemory(s);
+
+    ASSERT_EQ(r.GetFormat(), Orthanc::PixelFormat_Grayscale16);
+    ASSERT_EQ(r.GetWidth(), width);
+    ASSERT_EQ(r.GetHeight(), height);
+
+    v = 0;
+    for (unsigned int y = 0; y < height; y++)
+    {
+      const uint16_t* p = reinterpret_cast<const uint16_t*>
+        ((const uint8_t*)r.GetConstBuffer() + y * r.GetPitch());
+      ASSERT_EQ(p, r.GetConstRow(y));
+      for (unsigned int x = 0; x < width; x++, p++, v++)
+      {
+        ASSERT_EQ(v, *p);
+      }
+    }
+  }
+
+  {
     Orthanc::TemporaryFile tmp;
     tmp.Write(s);
 
@@ -432,4 +454,30 @@
       }
     }
   }
+
+  {
+    Orthanc::TemporaryFile tmp;
+    tmp.Write(s);
+
+    // true means "enforce alignment by using a temporary buffer"
+    Orthanc::PamReader r2(true);
+    r2.ReadFromFile(tmp.GetPath());
+
+    ASSERT_EQ(r2.GetFormat(), Orthanc::PixelFormat_Grayscale16);
+    ASSERT_EQ(r2.GetWidth(), width);
+    ASSERT_EQ(r2.GetHeight(), height);
+
+    v = 0;
+    for (unsigned int y = 0; y < height; y++)
+    {
+      const uint16_t* p = reinterpret_cast<const uint16_t*>
+        ((const uint8_t*)r2.GetConstBuffer() + y * r2.GetPitch());
+      ASSERT_EQ(p, r2.GetConstRow(y));
+      for (unsigned int x = 0; x < width; x++, p++, v++)
+      {
+        ASSERT_EQ(*p, v);
+      }
+    }
+  }
+
 }