[med-svn] [odil] 01/03: Imported Upstream version 0.7.0

Julien Lamy lamy-guest at moszumanska.debian.org
Tue Jun 14 14:28:00 UTC 2016


This is an automated email from the git hooks/post-receive script.

lamy-guest pushed a commit to branch master
in repository odil.

commit 63a87252558431a075cb1a44cc3d4c1a29939510
Author: Julien Lamy <lamy at unistra.fr>
Date:   Tue Jun 14 16:12:27 2016 +0200

    Imported Upstream version 0.7.0
---
 .travis.yml                        |   5 +-
 CMakeLists.txt                     |   5 +-
 FindLog4Cpp.cmake                  |  24 +++++
 applications/find.py               |   2 +-
 applications/print_.py             |   2 +-
 registry.h.tmpl                    |   7 +-
 src/CMakeLists.txt                 |  17 ++-
 src/odil/Association.cpp           |  58 ++++++++--
 src/odil/AssociationAcceptor.cpp   |   4 +-
 src/odil/DataSet.cpp               | 109 ++++++++++---------
 src/odil/DataSet.h                 |  55 ++++++----
 src/odil/MoveSCP.cpp               |   5 +-
 src/odil/Reader.cpp                |   8 +-
 src/odil/Reader.h                  |   3 +-
 src/odil/StoreSCU.cpp              |   9 +-
 src/odil/StoreSCU.h                |   7 +-
 src/odil/VRFinder.cpp              |  42 ++++----
 src/odil/Writer.cpp                |  41 +++++++-
 src/odil/dul/StateMachine.cpp      |  11 +-
 src/odil/logging.cpp               |  37 +++++++
 src/odil/logging.h                 |  17 +++
 src/odil/message/CStoreRequest.cpp |   9 +-
 src/odil/message/CStoreRequest.h   |   4 +-
 src/odil/pdu/Item.cpp              |   9 +-
 src/odil/pdu/UserInformation.cpp   |  38 ++++++-
 src/odil/registry.cpp              |  26 ++++-
 src/odil/registry.h                |   6 ++
 src/odil/uid.cpp                   |   4 +-
 src/odil/uid.h                     |   6 +-
 src/odil/write_ds.cpp              | 210 +++++++++++++++++++++++++++++++++++++
 src/odil/write_ds.h                |  25 +++++
 tests/CMakeLists.txt               |  18 +++-
 tests/code/DataSet.cpp             |  16 +++
 tests/code/VRFinder.cpp            |  35 +++----
 tests/code/Writer.cpp              |   2 +-
 tests/code/unicode.cpp             |   1 -
 tests/run                          |   2 +-
 tests/wrappers/test_data_set.py    |  14 +++
 wrappers/DataSet.cpp               |   8 +-
 wrappers/StoreSCU.cpp              |   7 +-
 wrappers/read.cpp                  |  29 +++--
 41 files changed, 762 insertions(+), 175 deletions(-)

diff --git a/.travis.yml b/.travis.yml
index 9b19003..4f694f8 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -20,6 +20,7 @@ addons:
     - libboost-python-dev
     - libboost-regex-dev
     - libboost-test-dev
+    - liblog4cpp5-dev
     - dcmtk
     - ninja-build
     - cmake
@@ -30,7 +31,7 @@ before_install:
   - if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew uninstall json-c; fi
   # Boost is already installed with another version
   - if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew unlink boost; brew install boost boost-python; fi
-  - if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew install dcmtk icu4c jsoncpp ninja; fi
+  - if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew install dcmtk icu4c jsoncpp log4cpp ninja; fi
   - pip install --user cpp-coveralls nose
   - export PATH=$(python -c 'import site; print(site.getuserbase())')/bin:${PATH}
 before_script:
@@ -40,7 +41,7 @@ before_script:
   - export BIN_DIR=$PWD
   - CMAKE_CXX_FLAGS="-std=c++11"
   - if [ "${CC}" = "gcc" ]; then CMAKE_CXX_FLAGS="${CMAKE_CXX_FLAGS} --coverage"; fi
-  - if [ "$TRAVIS_OS_NAME" = "osx" ]; then export PKG_CONFIG_PATH=/usr/local/opt/icu4c/lib/pkgconfig; fi
+  - if [ "$TRAVIS_OS_NAME" = "osx" ]; then export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig:/usr/local/opt/icu4c/lib/pkgconfig; fi
   - cmake -G Ninja -D CMAKE_CXX_FLAGS:STRING="${CMAKE_CXX_FLAGS}" -D CMAKE_BUILD_TYPE:STRING=Debug ../
 script:
   - ninja
diff --git a/CMakeLists.txt b/CMakeLists.txt
index a8f8a62..c5a1276 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -2,15 +2,16 @@ cmake_minimum_required(VERSION 2.8)
 
 project("odil")
 set(odil_MAJOR_VERSION 0)
-set(odil_MINOR_VERSION 5)
+set(odil_MINOR_VERSION 7)
 set(odil_PATCH_VERSION 0)
 set(odil_VERSION
     ${odil_MAJOR_VERSION}.${odil_MINOR_VERSION}.${odil_PATCH_VERSION})
 
+option(BUILD_SHARED_LIBS "Build Odil with shared libraries." ON)
 option(BUILD_EXAMPLES "Build the examples directory." ON)
 option(BUILD_WRAPPERS "Build the Python Wrappers." ON)
 
-option(BUILD_SHARED_LIBS "Build Odil with shared libraries." ON)
+option(WITH_DCMTK "Build the DCMTK converter" ON)
 
 set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}" ${CMAKE_MODULE_PATH})
 include(CTest)
diff --git a/FindLog4Cpp.cmake b/FindLog4Cpp.cmake
new file mode 100644
index 0000000..7f83da4
--- /dev/null
+++ b/FindLog4Cpp.cmake
@@ -0,0 +1,24 @@
+# - Try to find Log4Cpp
+# Once done this will define
+#  Log4Cpp_FOUND - System has Log4Cpp
+#  Log4Cpp_INCLUDE_DIRS - The Log4Cpp include directories
+#  Log4Cpp_LIBRARIES - The libraries needed to use Log4Cpp
+#  Log4Cpp_DEFINITIONS - Compiler switches required for using Log4Cpp
+
+find_package(PkgConfig)
+pkg_check_modules(PC_Log4Cpp QUIET log4cpp)
+set(Log4Cpp_DEFINITIONS ${PC_Log4Cpp_CFLAGS_OTHER})
+
+find_path(Log4Cpp_INCLUDE_DIR "log4cpp/Category.hh" HINTS ${PC_Log4Cpp_INCLUDE_DIRS})
+find_library(Log4Cpp_LIBRARY NAMES log4cpp HINTS ${PC_Log4Cpp_LIBRARY_DIRS} )
+
+set(Log4Cpp_LIBRARIES ${Log4Cpp_LIBRARY} )
+set(Log4Cpp_INCLUDE_DIRS ${Log4Cpp_INCLUDE_DIR} )
+
+include(FindPackageHandleStandardArgs)
+# handle the QUIETLY and REQUIRED arguments and set Log4Cpp_FOUND to TRUE
+# if all listed variables are TRUE
+find_package_handle_standard_args(
+    Log4Cpp DEFAULT_MSG Log4Cpp_LIBRARY Log4Cpp_INCLUDE_DIR)
+
+mark_as_advanced(Log4Cpp_INCLUDE_DIR Log4Cpp_LIBRARY)
diff --git a/applications/find.py b/applications/find.py
index e522386..f3e256d 100644
--- a/applications/find.py
+++ b/applications/find.py
@@ -80,7 +80,7 @@ def find(host, port, calling_ae_title, called_ae_title, level, keys, decode_uids
 
     for data_set in data_sets:
         print_data_set(data_set, decode_uids, "", max_length)
-        print
+        print()
 
     association.release()
     logging.info("Association released")
diff --git a/applications/print_.py b/applications/print_.py
index ce15da1..2a7be04 100644
--- a/applications/print_.py
+++ b/applications/print_.py
@@ -29,7 +29,7 @@ def print_(inputs, print_header, decode_uids):
 
         if print_header:
             print_data_set(header, decode_uids, "", max_length)
-            print
+            print()
         print_data_set(data_set, decode_uids, "", max_length)
 
 def print_data_set(data_set, decode_uids, padding, max_length):
diff --git a/registry.h.tmpl b/registry.h.tmpl
index dc82d5c..a9c8728 100644
--- a/registry.h.tmpl
+++ b/registry.h.tmpl
@@ -12,6 +12,7 @@
 #include <map>
 #include <string>
 
+#include "odil/odil.h"
 #include "odil/ElementsDictionary.h"
 #include "odil/Tag.h"
 #include "odil/UIDsDictionary.h"
@@ -33,9 +34,9 @@ Tag const {{ entry[2] }}({{ "0x%04x, 0x%04x"|format(*entry[0]) }});
 std::string const {{ entry[2] }}("{{ entry[0] }}");
 {% endfor %}
 
-extern ElementsDictionary public_dictionary;
-extern std::map<std::string, Tag> public_tags;
-extern UIDsDictionary uids_dictionary;
+extern ODIL_API ElementsDictionary public_dictionary;
+extern ODIL_API std::map<std::string, Tag> public_tags;
+extern ODIL_API UIDsDictionary uids_dictionary;
 }
 
 }
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 47bea79..e17e565 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -1,19 +1,29 @@
 find_package(Boost REQUIRED COMPONENTS filesystem system)
-find_package(DCMTK REQUIRED)
 find_package(ICU REQUIRED)
 find_package(JsonCpp REQUIRED)
+find_package(Log4Cpp REQUIRED)
+if(WITH_DCMTK)
+    find_package(DCMTK REQUIRED)
+endif()
 
 file(GLOB_RECURSE Header_Files "*.h")
 file(GLOB_RECURSE Source_Files "*.cpp")
 file(GLOB_RECURSE templates "*.txx")
 
+if(NOT WITH_DCMTK)
+    set(pattern "${CMAKE_CURRENT_SOURCE_DIR}/odil/dcmtk/[^;]+[;$]")
+    string(REGEX REPLACE ${pattern} "" Header_Files "${Header_Files}")
+    string(REGEX REPLACE ${pattern} "" Source_Files "${Source_Files}")
+    string(REGEX REPLACE ${pattern} "" templates "${templates}")
+endif()
+
 # Regroup files by folder
 GroupFiles(Header_Files)
 GroupFiles(Source_Files)
 
 include_directories(
     ${CMAKE_CURRENT_SOURCE_DIR} ${Boost_INCLUDE_DIRS} ${DCMTK_INCLUDE_DIRS}
-    ${ICU_INCLUDE_DIRS} ${JsonCpp_INCLUDE_DIRS})
+${ICU_INCLUDE_DIRS} ${Log4Cpp_INCLUDE_DIRS} ${JsonCpp_INCLUDE_DIRS})
 add_definitions(
     ${DCMTK_DEFINITIONS}
     -D BOOST_ASIO_SEPARATE_COMPILATION
@@ -25,7 +35,8 @@ add_library(libodil ${Source_Files} ${Header_Files} ${templates})
 set_target_properties(libodil PROPERTIES OUTPUT_NAME odil)
 
 target_link_libraries(libodil
-    ${Boost_LIBRARIES} ${DCMTK_LIBRARIES} ${ICU_LIBRARIES} ${JsonCpp_LIBRARIES})
+    ${Boost_LIBRARIES} ${DCMTK_LIBRARIES} ${ICU_LIBRARIES} ${JsonCpp_LIBRARIES}
+    ${Log4Cpp_LIBRARIES})
 
 if(WIN32)
     add_definitions(-DBUILDING_ODIL)
diff --git a/src/odil/Association.cpp b/src/odil/Association.cpp
index 2d0fb47..cbc8bf3 100644
--- a/src/odil/Association.cpp
+++ b/src/odil/Association.cpp
@@ -463,8 +463,8 @@ Association
         command_stream, registry::ImplicitVRLittleEndian, // implicit vr for command
         Writer::ItemEncoding::ExplicitLength, true); // true for Command
     command_writer.write_data_set(message.get_command_set());
-    pdv_items.push_back(
-        pdu::PDataTF::PresentationDataValueItem(id, 3, command_stream.str()));
+    auto const command_buffer = command_stream.str();
+    pdv_items.emplace_back(id, 3, command_buffer);
 
     if (message.has_data_set())
     {
@@ -473,17 +473,55 @@ Association
             data_stream, transfer_syntax,
             Writer::ItemEncoding::ExplicitLength, false);
         data_writer.write_data_set(message.get_data_set());
-        pdv_items.push_back(
-            pdu::PDataTF::PresentationDataValueItem(
-                transfer_syntax_it->second.first, 2, data_stream.str()));
-    }
+        auto const data_buffer = data_stream.str();
 
-    auto pdu = std::make_shared<pdu::PDataTF>(pdv_items);
+        auto const max_length = this->_negotiated_parameters.get_maximum_length();
+        auto current_length = command_buffer.size() + 12; // 12 is the size of all that is added on top of the fragment
+        if (!max_length 
+            || (current_length + data_buffer.size() + 6 < max_length))
+        {   // Can send all the buffer in one go
+            pdv_items.emplace_back(transfer_syntax_it->second.first, 2, data_buffer);
 
-    dul::EventData data;
-    data.pdu = pdu;
+            dul::EventData data;
+            data.pdu = std::make_shared<pdu::PDataTF>(pdv_items);
+            this->_state_machine.send_pdu(data);
+        }
+        else // We have to fragment into multiple PDUs
+        {
+            auto available = max_length - 6 - current_length; // Need at least 6 bytes for the headers
+            int64_t remaining = data_buffer.size();
+            std::size_t offset = 0;
 
-    this->_state_machine.send_pdu(data);
+            if (available > 0) // Send some data with the command set
+            {
+                remaining -= available;
+                pdv_items.emplace_back(transfer_syntax_it->second.first, (remaining > 0 ? 0 : 2), data_buffer.substr(0, available));
+                offset += available;
+            }
+
+            auto pdu = std::make_shared<pdu::PDataTF>(pdv_items);
+            dul::EventData data;
+            data.pdu = pdu;
+            this->_state_machine.send_pdu(data);
+
+            available = max_length - 6; // In case some software do not take into account the size of the header when allocating their buffer
+            while (remaining > 0)
+            {
+                remaining -= available;
+                pdv_items.clear();
+                pdv_items.emplace_back(transfer_syntax_it->second.first, (remaining > 0 ? 0 : 2), data_buffer.substr(offset, available));
+                offset += available;
+                pdu->set_pdv_items(pdv_items);
+                this->_state_machine.send_pdu(data);
+            }
+        }
+    }
+    else
+    {
+        dul::EventData data;
+        data.pdu = std::make_shared<pdu::PDataTF>(pdv_items);
+        this->_state_machine.send_pdu(data);
+    }
 }
 
 uint16_t
diff --git a/src/odil/AssociationAcceptor.cpp b/src/odil/AssociationAcceptor.cpp
index bcdd8b2..e5c625c 100644
--- a/src/odil/AssociationAcceptor.cpp
+++ b/src/odil/AssociationAcceptor.cpp
@@ -23,8 +23,8 @@ default_association_acceptor(AssociationParameters const & input)
 {
     AssociationParameters output;
 
-    output.set_called_ae_title(input.get_calling_ae_title());
-    output.set_calling_ae_title(input.get_called_ae_title());
+    output.set_called_ae_title(input.get_called_ae_title());
+    output.set_calling_ae_title(input.get_calling_ae_title());
 
     std::vector<AssociationParameters::PresentationContext>
         presentation_contexts = input.get_presentation_contexts();
diff --git a/src/odil/DataSet.cpp b/src/odil/DataSet.cpp
index cd60507..493c788 100644
--- a/src/odil/DataSet.cpp
+++ b/src/odil/DataSet.cpp
@@ -22,9 +22,10 @@ namespace odil
 {
 
 DataSet
-::DataSet()
+::DataSet(std::string const & transfer_syntax)
+: _transfer_syntax(transfer_syntax)
 {
-    // Nothing to do.
+    // Nothing else.
 }
 
 void
@@ -244,150 +245,150 @@ template<typename TContainer>
 typename TContainer::value_type const & at_pos(
     TContainer const & container, unsigned int position)
 {
-    if(container.size() <= position) 
-    { 
-        throw Exception("No such element"); 
-    } 
-    return container[position]; 
+    if(container.size() <= position)
+    {
+        throw Exception("No such element");
+    }
+    return container[position];
 }
 
 bool
 DataSet
-::is_int(Tag const & tag) const 
-{ 
+::is_int(Tag const & tag) const
+{
     return (*this)[tag].is_int();
 }
 
 Value::Integers const &
 DataSet
-::as_int(Tag const & tag) const 
-{ 
+::as_int(Tag const & tag) const
+{
     return (*this)[tag].as_int();
 }
 
 Value::Integers &
 DataSet
-::as_int(Tag const & tag) 
-{ 
+::as_int(Tag const & tag)
+{
     return (*this)[tag].as_int();
 }
 
 Value::Integer const &
 DataSet
-::as_int(Tag const & tag, unsigned int position) const 
-{ 
+::as_int(Tag const & tag, unsigned int position) const
+{
     return at_pos(as_int(tag), position);
 }
 
 bool
 DataSet
-::is_real(Tag const & tag) const 
-{ 
+::is_real(Tag const & tag) const
+{
     return (*this)[tag].is_real();
 }
 
 Value::Reals const &
 DataSet
-::as_real(Tag const & tag) const 
-{ 
+::as_real(Tag const & tag) const
+{
     return (*this)[tag].as_real();
 }
 
 Value::Reals &
 DataSet
-::as_real(Tag const & tag) 
-{ 
+::as_real(Tag const & tag)
+{
     return (*this)[tag].as_real();
 }
 
 Value::Real const &
 DataSet
-::as_real(Tag const & tag, unsigned int position) const 
-{ 
+::as_real(Tag const & tag, unsigned int position) const
+{
     return at_pos(as_real(tag), position);
 }
 
 bool
 DataSet
-::is_string(Tag const & tag) const 
-{ 
+::is_string(Tag const & tag) const
+{
     return (*this)[tag].is_string();
 }
 
 Value::Strings const &
 DataSet
-::as_string(Tag const & tag) const 
-{ 
+::as_string(Tag const & tag) const
+{
     return (*this)[tag].as_string();
 }
 
 Value::Strings &
 DataSet
-::as_string(Tag const & tag) 
-{ 
+::as_string(Tag const & tag)
+{
     return (*this)[tag].as_string();
 }
 
 Value::String const &
 DataSet
-::as_string(Tag const & tag, unsigned int position) const 
-{ 
+::as_string(Tag const & tag, unsigned int position) const
+{
     return at_pos(as_string(tag), position);
 }
 
 bool
 DataSet
-::is_data_set(Tag const & tag) const 
-{ 
+::is_data_set(Tag const & tag) const
+{
     return (*this)[tag].is_data_set();
 }
 
 Value::DataSets const &
 DataSet
-::as_data_set(Tag const & tag) const 
-{ 
+::as_data_set(Tag const & tag) const
+{
     return (*this)[tag].as_data_set();
 }
 
 Value::DataSets &
 DataSet
-::as_data_set(Tag const & tag) 
-{ 
+::as_data_set(Tag const & tag)
+{
     return (*this)[tag].as_data_set();
 }
 
 DataSet const &
 DataSet
-::as_data_set(Tag const & tag, unsigned int position) const 
-{ 
+::as_data_set(Tag const & tag, unsigned int position) const
+{
     return at_pos(as_data_set(tag), position);
 }
 
 bool
 DataSet
-::is_binary(Tag const & tag) const 
-{ 
+::is_binary(Tag const & tag) const
+{
     return (*this)[tag].is_binary();
 }
 
 Value::Binary const &
 DataSet
-::as_binary(Tag const & tag) const 
-{ 
+::as_binary(Tag const & tag) const
+{
     return (*this)[tag].as_binary();
 }
 
 Value::Binary &
 DataSet
-::as_binary(Tag const & tag) 
-{ 
+::as_binary(Tag const & tag)
+{
     return (*this)[tag].as_binary();
 }
 
 Value::Binary::value_type const &
 DataSet
-::as_binary(Tag const & tag, unsigned int position) const 
-{ 
+::as_binary(Tag const & tag, unsigned int position) const
+{
     return at_pos(as_binary(tag), position);
 }
 
@@ -451,4 +452,18 @@ DataSet
     return !(*this == other);
 }
 
+std::string const &
+DataSet
+::get_transfer_syntax() const
+{
+    return _transfer_syntax;
+}
+
+void
+DataSet
+::set_transfer_syntax(std::string const & transfer_syntax)
+{
+    this->_transfer_syntax = transfer_syntax;
+}
+
 }
diff --git a/src/odil/DataSet.h b/src/odil/DataSet.h
index e8bf7ed..ff59454 100644
--- a/src/odil/DataSet.h
+++ b/src/odil/DataSet.h
@@ -29,7 +29,7 @@ class DataSet
 {
 public:
     /// @brief Create an empty data set.
-    DataSet();
+    explicit DataSet(std::string const & transfer_syntax="");
 
     /// @brief Add an element to the dataset.
     void add(Tag const & tag, Element const & element);
@@ -135,71 +135,71 @@ public:
 
     /// @brief Test whether an existing element has integer type.
     bool is_int(Tag const & tag) const;
-    
+
     /// @brief Return the integers contained in an existing element (read-only).
     Value::Integers const & as_int(Tag const & tag) const;
-    
+
     /// @brief Return the integers contained in an existing element (read-write).
     Value::Integers & as_int(Tag const & tag);
-    
+
     /// @brief Return an integer contained in an existing element (read-only).
     Value::Integer const & as_int(Tag const & tag, unsigned int position) const;
-    
+
     /// @brief Test whether an existing element has real type.
     bool is_real(Tag const & tag) const;
-    
+
     /// @brief Return the reals contained in an existing element (read-only).
     Value::Reals const & as_real(Tag const & tag) const;
-    
+
     /// @brief Return the reals contained in an existing element (read-write).
     Value::Reals & as_real(Tag const & tag);
-    
+
     /// @brief Return an real contained in an existing element (read-only).
     Value::Real const & as_real(Tag const & tag, unsigned int position) const;
-    
+
     /// @brief Test whether an existing element has string type.
     bool is_string(Tag const & tag) const;
-    
+
     /// @brief Return the strings contained in an existing element (read-only).
     Value::Strings const & as_string(Tag const & tag) const;
-    
+
     /// @brief Return the strings contained in an existing element (read-write).
     Value::Strings & as_string(Tag const & tag);
-    
+
     /// @brief Return a string contained in an existing element (read-only).
     Value::String const & as_string(Tag const & tag, unsigned int position) const;
-    
+
     /// @brief Test whether an existing element has data set type.
     bool is_data_set(Tag const & tag) const;
-    
+
     /// @brief Return the data sets contained in an existing element (read-only).
     Value::DataSets const & as_data_set(Tag const & tag) const;
-    
+
     /// @brief Return the data sets contained in an existing element (read-write).
     Value::DataSets & as_data_set(Tag const & tag);
-    
+
     /// @brief Return a data set contained in an existing element (read-only).
     DataSet const & as_data_set(Tag const & tag, unsigned int position) const;
-    
+
     /// @brief Test whether an existing element has binary type.
     bool is_binary(Tag const & tag) const;
-    
+
     /// @brief Return the binary items contained in an existing element (read-only).
     Value::Binary const & as_binary(Tag const & tag) const;
-    
+
     /// @brief Return the binary items contained in an existing element (read-write).
     Value::Binary & as_binary(Tag const & tag);
-    
+
     /// @brief Return a binary item contained in an existing element (read-only).
-    Value::Binary::value_type const & 
+    Value::Binary::value_type const &
     as_binary(Tag const & tag, unsigned int position) const;
 
     /// @brief Iterator to the elements.
     typedef std::map<Tag, Element>::const_iterator const_iterator;
-    
+
     /// @brief Return an iterator to the start of the elements.
     const_iterator begin() const { return this->_elements.begin(); }
-    
+
     /// @brief Return an iterator to the end of the elements.
     const_iterator end() const { return this->_elements.end(); }
 
@@ -209,10 +209,19 @@ public:
     /// @brief Difference test.
     bool operator!=(DataSet const & other) const;
 
+    /// @brief Return the current transfer syntax.
+    std::string const & get_transfer_syntax() const;
+
+    /// @brief Set the current transfer syntax.
+    void set_transfer_syntax(std::string const & transfer_syntax);
+
 private:
     typedef std::map<Tag, Element> ElementMap;
 
     ElementMap _elements;
+
+    /// @brief Current transfer syntax.
+    std::string _transfer_syntax;
 };
 
 }
diff --git a/src/odil/MoveSCP.cpp b/src/odil/MoveSCP.cpp
index 9b9b7bf..739925a 100644
--- a/src/odil/MoveSCP.cpp
+++ b/src/odil/MoveSCP.cpp
@@ -75,6 +75,9 @@ MoveSCP
     unsigned int failed_sub_operations=0;
     unsigned int warning_sub_operations=0;
 
+    auto const& move_originator_aet = this->_association.get_negotiated_parameters().get_calling_ae_title();
+    auto move_originator_message_id = request.get_message_id();
+
     try
     {
         this->_generator->initialize(request);
@@ -99,7 +102,7 @@ MoveSCP
             store_scu.set_affected_sop_class(data_set);
             try
             {
-                store_scu.store(data_set);
+                store_scu.store(data_set, move_originator_aet, move_originator_message_id);
 
                 --remaining_sub_operations;
                 ++completed_sub_operations;
diff --git a/src/odil/Reader.cpp b/src/odil/Reader.cpp
index 668c9a9..dc3dfd4 100644
--- a/src/odil/Reader.cpp
+++ b/src/odil/Reader.cpp
@@ -93,7 +93,7 @@ DataSet
 Reader
 ::read_data_set(std::function<bool(Tag const &)> halt_condition) const
 {
-    DataSet data_set;
+    DataSet data_set(transfer_syntax);
 
     bool done = (this->stream.peek() == EOF);
     while(!done)
@@ -188,7 +188,9 @@ Reader
 
 std::pair<DataSet, DataSet>
 Reader
-::read_file(std::istream & stream, bool keep_group_length)
+::read_file(
+    std::istream & stream, bool keep_group_length,
+    std::function<bool(Tag const &)> halt_condition)
 {
     // File preamble
     stream.ignore(128);
@@ -226,7 +228,7 @@ Reader
     Reader data_set_reader(
         stream, meta_information.as_string(registry::TransferSyntaxUID)[0],
         keep_group_length);
-    auto const data_set = data_set_reader.read_data_set();
+    auto const data_set = data_set_reader.read_data_set(halt_condition);
 
     return std::pair<DataSet, DataSet>(meta_information, data_set);
 }
diff --git a/src/odil/Reader.h b/src/odil/Reader.h
index 4081a31..00e1fb5 100644
--- a/src/odil/Reader.h
+++ b/src/odil/Reader.h
@@ -70,7 +70,8 @@ public:
     /// @brief Return the meta-data header and data set stored in the stream.
     static std::pair<DataSet, DataSet> read_file(
         std::istream & stream,
-        bool keep_group_length=false);
+        bool keep_group_length=false,
+        std::function<bool(Tag const &)> halt_condition = [](Tag const &) { return false;});
 
 private:
     struct Visitor
diff --git a/src/odil/StoreSCU.cpp b/src/odil/StoreSCU.cpp
index 33fa4bd..f4c7ae7 100644
--- a/src/odil/StoreSCU.cpp
+++ b/src/odil/StoreSCU.cpp
@@ -64,14 +64,19 @@ StoreSCU
 
 void 
 StoreSCU
-::store(DataSet const & dataset) const
+::store(
+    DataSet const & dataset,
+    Value::String const & move_originator_ae_title,
+    Value::Integer move_originator_message_id) const
 {
     message::CStoreRequest const request(
         this->_association.next_message_id(),
         this->_affected_sop_class,
         dataset.as_string(registry::SOPInstanceUID, 0),
         message::Message::Priority::MEDIUM,
-        dataset);
+        dataset, move_originator_ae_title,
+        move_originator_message_id);
+
     this->_association.send_message(request, this->_affected_sop_class);
     
     message::CStoreResponse const response = this->_association.receive_message();
diff --git a/src/odil/StoreSCU.h b/src/odil/StoreSCU.h
index d1f71bf..7500fa1 100644
--- a/src/odil/StoreSCU.h
+++ b/src/odil/StoreSCU.h
@@ -28,9 +28,14 @@ public:
     
     /// @brief Set the affected SOP class based on the dataset.
     void set_affected_sop_class(DataSet const & dataset);
+
+	using SCU::set_affected_sop_class;
     
     /// @brief Perform the C-STORE.
-    void store(DataSet const & dataset) const;
+    void store(
+        DataSet const & dataset,
+        Value::String const & move_originator_ae_title = "",
+        Value::Integer move_originator_message_id = -1) const;
 };
 
 }
diff --git a/src/odil/VRFinder.cpp b/src/odil/VRFinder.cpp
index 0277f90..a85b88c 100644
--- a/src/odil/VRFinder.cpp
+++ b/src/odil/VRFinder.cpp
@@ -46,7 +46,10 @@ VRFinder::operator()(
         try
         {
             vr = finder(tag, data_set, transfer_syntax);
-            break;
+            if (vr != VR::UNKNOWN)
+            {
+                break;
+            }
         }
         catch(Exception &)
         {
@@ -61,7 +64,10 @@ VRFinder::operator()(
             try
             {
                 vr = finder(tag, data_set, transfer_syntax);
-                break;
+                if (vr != VR::UNKNOWN)
+                {
+                    break;
+                }
             }
             catch(Exception &)
             {
@@ -83,7 +89,7 @@ VRFinder
 ::public_dictionary(
     Tag const & tag, DataSet const &, std::string const &)
 {
-    VR vr = VR::INVALID;
+    VR vr = VR::UNKNOWN;
     
     auto const iterator = find(registry::public_dictionary, tag);
     if(iterator != registry::public_dictionary.end())
@@ -91,12 +97,6 @@ VRFinder
         vr = as_vr(iterator->second.vr);
     }
 
-    if(vr == VR::INVALID)
-    {
-        throw Exception(
-            "Element " + std::string(tag) + " is not in the public dictionary");
-    }
-
     return vr;
 }
 
@@ -109,10 +109,8 @@ VRFinder
     {
         return VR::UL;
     }
-    else
-    {
-        throw Exception("Not a group length tag");
-    }
+  
+    return VR::UNKNOWN; // Not a group length tag
 }
 
 VR
@@ -124,10 +122,8 @@ VRFinder
     {
         return VR::UN;
     }
-    else
-    {
-        throw Exception("Not a private tag");
-    }
+
+    return VR::UNKNOWN; // Not a private tag
 }
 
 VR
@@ -194,12 +190,14 @@ VRFinder
         }
         else
         {
-            throw Exception("Unknown tag");
+            // Unknown tag
+            return VR::UNKNOWN;
         }
     }
     else
     {
-        throw Exception("Unknown transfer syntax");
+        // Unknown transfer syntax
+        return VR::UNKNOWN;
     }
 }
 
@@ -273,12 +271,14 @@ VRFinder
         }
         else
         {
-            throw Exception("Unknown tag");
+            // Unknown tag
+            return VR::UNKNOWN;
         }
     }
     else
     {
-        throw Exception("Unknown transfer syntax");
+        // Unknown transfer syntax
+        return VR::UNKNOWN;
     }
 }
 
diff --git a/src/odil/Writer.cpp b/src/odil/Writer.cpp
index 96b8efd..9d98e2f 100644
--- a/src/odil/Writer.cpp
+++ b/src/odil/Writer.cpp
@@ -8,7 +8,9 @@
 
 #include "odil/Writer.h"
 
+#include <cmath>
 #include <cstdint>
+#include <cstring>
 #include <map>
 #include <ostream>
 #include <sstream>
@@ -22,6 +24,7 @@
 #include "odil/Tag.h"
 #include "odil/uid.h"
 #include "odil/VR.h"
+#include "odil/write_ds.h"
 
 #define odil_write_binary(value, stream, byte_ordering, size) \
 { \
@@ -351,7 +354,43 @@ Writer::Visitor
 {
     if(this->vr == VR::DS)
     {
-        this->write_strings(value, ' ');
+        int written = 0;
+        for(int i=0; i<value.size(); ++i)
+        {
+            auto const & item = value[i];
+            
+            if(!std::isfinite(item))
+            {
+                throw Exception("DS items must be finite");
+            }
+            
+            // Each item in the DS is at most 16 bytes.
+            static char buffer[16];
+            write_ds(item, buffer, 16);
+            auto const length = strlen(buffer);
+            
+            this->stream.write(buffer, length);
+            written += length;
+            if(!this->stream.good())
+            {
+                throw Exception("Could not write DS");
+            }
+            
+            if(i<value.size()-1)
+            {
+                this->stream.put('\\');
+                written += 1;
+                if(!this->stream.good())
+                {
+                    throw Exception("Could not write DS");
+                }
+            }
+        };
+        if(written % 2 == 1)
+        {
+            this->stream.put(' ');
+        }
+        //this->write_strings(value, ' ');
     }
     else if(this->vr == VR::FD)
     {
diff --git a/src/odil/dul/StateMachine.cpp b/src/odil/dul/StateMachine.cpp
index 6f6dc5a..13d451b 100644
--- a/src/odil/dul/StateMachine.cpp
+++ b/src/odil/dul/StateMachine.cpp
@@ -697,8 +697,15 @@ void
 StateMachine
 ::AA_1(EventData & data)
 {
-    data.pdu = std::make_shared<pdu::AAbort>(1, 2);
-    this->send_pdu(data);
+    if(std::dynamic_pointer_cast<pdu::AAbort>(data.pdu))
+    {
+        this->_send_pdu(data, 0x07);
+    }
+    else
+    {
+        data.pdu = std::make_shared<pdu::AAbort>(1, 2);
+        this->send_pdu(data);
+    }
 
     this->start_timer(data);
 }
diff --git a/src/odil/logging.cpp b/src/odil/logging.cpp
new file mode 100644
index 0000000..52e9626
--- /dev/null
+++ b/src/odil/logging.cpp
@@ -0,0 +1,37 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#include <iostream>
+
+#include <log4cpp/Category.hh>
+#include <log4cpp/OstreamAppender.hh>
+#include <log4cpp/Priority.hh>
+
+namespace odil
+{
+
+namespace logging
+{
+
+bool configure()
+{
+    auto * appender = new log4cpp::OstreamAppender("console", &std::cout);
+    appender->setLayout(new log4cpp::BasicLayout());
+
+    auto & root = log4cpp::Category::getRoot();
+    root.setPriority(log4cpp::Priority::WARN);
+    root.addAppender(appender);
+
+    return true;
+}
+
+static bool const configured = configure();
+
+}
+
+}
diff --git a/src/odil/logging.h b/src/odil/logging.h
new file mode 100644
index 0000000..21b2677
--- /dev/null
+++ b/src/odil/logging.h
@@ -0,0 +1,17 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#ifndef _5382f5e0_e993_4966_9447_542844edb635
+#define _5382f5e0_e993_4966_9447_542844edb635
+
+#include <log4cpp/Category.hh>
+#include <log4cpp/Priority.hh>
+
+#define ODIL_LOG(level) log4cpp::Category::getRoot() << log4cpp::Priority::level
+
+#endif // _5382f5e0_e993_4966_9447_542844edb635
diff --git a/src/odil/message/CStoreRequest.cpp b/src/odil/message/CStoreRequest.cpp
index e6b1b56..5cd1f31 100644
--- a/src/odil/message/CStoreRequest.cpp
+++ b/src/odil/message/CStoreRequest.cpp
@@ -23,7 +23,9 @@ CStoreRequest
 ::CStoreRequest(
     Value::Integer message_id, Value::String const & affected_sop_class_uid,
     Value::String const & affected_sop_instance_uid,
-    Value::Integer priority, DataSet const & dataset)
+    Value::Integer priority, DataSet const & dataset,
+    Value::String const & move_originator_ae_title,
+    Value::Integer move_originator_message_id)
 : Request(message_id)
 {
     this->set_command_field(Command::C_STORE_RQ);
@@ -31,6 +33,11 @@ CStoreRequest
     this->set_affected_sop_instance_uid(affected_sop_instance_uid);
     this->set_priority(priority);
 
+    if(!move_originator_ae_title.empty())
+        this->set_move_originator_ae_title(move_originator_ae_title);
+    if(move_originator_message_id >= 0)
+        this->set_move_originator_message_id(move_originator_message_id);
+
     if(dataset.empty())
     {
         throw Exception("Data set is required");
diff --git a/src/odil/message/CStoreRequest.h b/src/odil/message/CStoreRequest.h
index 2f20ac8..0c08e26 100644
--- a/src/odil/message/CStoreRequest.h
+++ b/src/odil/message/CStoreRequest.h
@@ -33,7 +33,9 @@ public:
     CStoreRequest(
         Value::Integer message_id, Value::String const & affected_sop_class_uid,
         Value::String const & affected_sop_instance_uid,
-        Value::Integer priority, DataSet const & dataset);
+        Value::Integer priority, DataSet const & dataset,
+        Value::String const & move_originator_ae_title = "",
+        Value::Integer move_originator_message_id = -1);
 
     /**
      * @brief Create a C-STORE-RQ from a generic Message.
diff --git a/src/odil/pdu/Item.cpp b/src/odil/pdu/Item.cpp
index 368a50a..dc78a50 100644
--- a/src/odil/pdu/Item.cpp
+++ b/src/odil/pdu/Item.cpp
@@ -391,10 +391,13 @@ Item
     else if(type == Field::Type::string)
     {
         std::string value(size, '\0');
-        stream.read(reinterpret_cast<char*>(&value[0]), value.size());
-        if(!stream.good())
+        if(value.size() > 0)
         {
-            throw Exception("Could not read string field");
+            stream.read(reinterpret_cast<char*>(&value[0]), value.size());
+            if(!stream.good())
+            {
+                throw Exception("Could not read string field");
+            }
         }
 
         this->add(name, Field(value));
diff --git a/src/odil/pdu/UserInformation.cpp b/src/odil/pdu/UserInformation.cpp
index 32b4c52..e788519 100644
--- a/src/odil/pdu/UserInformation.cpp
+++ b/src/odil/pdu/UserInformation.cpp
@@ -12,7 +12,9 @@
 #include <istream>
 #include <vector>
 
+#include "odil/endian.h"
 #include "odil/Exception.h"
+#include "odil/logging.h"
 #include "odil/pdu/ImplementationClassUID.h"
 #include "odil/pdu/ImplementationVersionName.h"
 #include "odil/pdu/MaximumLength.h"
@@ -96,7 +98,41 @@ UserInformation
         }
         else
         {
-            throw Exception("Invalid sub-item type");
+            stream.ignore(2*sizeof(uint8_t)); // item type, reserved
+            if(!stream.good())
+            {
+                throw Exception("Could not skip sub-item header");
+            }
+
+            uint16_t sub_item_length;
+            stream.read(
+                reinterpret_cast<char*>(&sub_item_length),
+                sizeof(sub_item_length));
+            if(!stream.good())
+            {
+                throw Exception("Could not read length");
+            }
+            sub_item_length = big_endian_to_host(sub_item_length);
+
+            ODIL_LOG(WARN)
+                << "Skipping unknown item with type "
+                << std::hex << (unsigned int)type << std::dec << " "
+                << "(" << sub_item_length << " byte"
+                << (sub_item_length>1?"s":"") << ")";
+
+            if(sub_item_length > 0)
+            {
+                // CAUTION: using ignore could cause eofbit to be positioned and
+                // change semantics of later calls. Read the sub-item instead; this
+                // is sub-optimal but does not crash.
+                std::string sub_item(sub_item_length, '\0');
+                stream.read(reinterpret_cast<char*>(&sub_item[0]), sub_item.size());
+
+                if(!stream.good())
+                {
+                    throw Exception("Could not skip sub-item");
+                }
+            }
         }
     }
 
diff --git a/src/odil/registry.cpp b/src/odil/registry.cpp
index 2121eeb..e8b39d7 100644
--- a/src/odil/registry.cpp
+++ b/src/odil/registry.cpp
@@ -296,6 +296,12 @@ ElementsDictionary create_public_dictionary()
           "URN Code Value", "URNCodeValue",  "UR", "1" },
         { Tag(0x0008, 0x0121),
           "Equivalent Code Sequence", "EquivalentCodeSequence",  "SQ", "1" },
+        { Tag(0x0008, 0x0122),
+          "Mapping Resource Name", "MappingResourceName",  "LO", "1" },
+        { Tag(0x0008, 0x0123),
+          "Context Group Identification Sequence", "ContextGroupIdentificationSequence",  "SQ", "1" },
+        { Tag(0x0008, 0x0124),
+          "Mapping Resource Identification Sequence", "MappingResourceIdentificationSequence",  "SQ", "1" },
         { Tag(0x0008, 0x0201),
           "Timezone Offset From UTC", "TimezoneOffsetFromUTC",  "SH", "1" },
         { Tag(0x0008, 0x0300),
@@ -7719,10 +7725,16 @@ ElementsDictionary create_public_dictionary()
           "Isocenter to Range Modulator Distance", "IsocenterToRangeModulatorDistance",  "FL", "1" },
         { Tag(0x300a, 0x0390),
           "Scan Spot Tune ID", "ScanSpotTuneID",  "SH", "1" },
+        { Tag(0x300a, 0x0391),
+          "Scan Spot Prescribed Indices", "ScanSpotPrescribedIndices",  "IS", "1-n" },
         { Tag(0x300a, 0x0392),
           "Number of Scan Spot Positions", "NumberOfScanSpotPositions",  "IS", "1" },
+        { Tag(0x300a, 0x0393),
+          "Scan Spot Reordered", "ScanSpotReordered",  "CS", "1" },
         { Tag(0x300a, 0x0394),
           "Scan Spot Position Map", "ScanSpotPositionMap",  "FL", "1-n" },
+        { Tag(0x300a, 0x0395),
+          "Scan Spot Reordering Allowed", "ScanSpotReorderingAllowed",  "CS", "1" },
         { Tag(0x300a, 0x0396),
           "Scan Spot Meterset Weights", "ScanSpotMetersetWeights",  "FL", "1-n" },
         { Tag(0x300a, 0x0398),
@@ -8534,7 +8546,10 @@ std::map<std::string, odil::Tag> create_public_tags()
           "Mapping Resource UID", "MappingResourceUID",  "UI", "1" },    { Tag(0x0008, 0x0119),
           "Long Code Value", "LongCodeValue",  "UC", "1" },    { Tag(0x0008, 0x0120),
           "URN Code Value", "URNCodeValue",  "UR", "1" },    { Tag(0x0008, 0x0121),
-          "Equivalent Code Sequence", "EquivalentCodeSequence",  "SQ", "1" },    { Tag(0x0008, 0x0201),
+          "Equivalent Code Sequence", "EquivalentCodeSequence",  "SQ", "1" },    { Tag(0x0008, 0x0122),
+          "Mapping Resource Name", "MappingResourceName",  "LO", "1" },    { Tag(0x0008, 0x0123),
+          "Context Group Identification Sequence", "ContextGroupIdentificationSequence",  "SQ", "1" },    { Tag(0x0008, 0x0124),
+          "Mapping Resource Identification Sequence", "MappingResourceIdentificationSequence",  "SQ", "1" },    { Tag(0x0008, 0x0201),
           "Timezone Offset From UTC", "TimezoneOffsetFromUTC",  "SH", "1" },    { Tag(0x0008, 0x0300),
           "Private Data Element Characteristics Sequence", "PrivateDataElementCharacteristicsSequence",  "SQ", "1" },    { Tag(0x0008, 0x0301),
           "Private Group Reference", "PrivateGroupReference",  "US", "1" },    { Tag(0x0008, 0x0302),
@@ -12220,9 +12235,12 @@ std::map<std::string, odil::Tag> create_public_tags()
           "Range Modulator Gating Start Water Equivalent Thickness", "RangeModulatorGatingStartWaterEquivalentThickness",  "FL", "1" },    { Tag(0x300a, 0x0388),
           "Range Modulator Gating Stop Water Equivalent Thickness", "RangeModulatorGatingStopWaterEquivalentThickness",  "FL", "1" },    { Tag(0x300a, 0x038a),
           "Isocenter to Range Modulator Distance", "IsocenterToRangeModulatorDistance",  "FL", "1" },    { Tag(0x300a, 0x0390),
-          "Scan Spot Tune ID", "ScanSpotTuneID",  "SH", "1" },    { Tag(0x300a, 0x0392),
-          "Number of Scan Spot Positions", "NumberOfScanSpotPositions",  "IS", "1" },    { Tag(0x300a, 0x0394),
-          "Scan Spot Position Map", "ScanSpotPositionMap",  "FL", "1-n" },    { Tag(0x300a, 0x0396),
+          "Scan Spot Tune ID", "ScanSpotTuneID",  "SH", "1" },    { Tag(0x300a, 0x0391),
+          "Scan Spot Prescribed Indices", "ScanSpotPrescribedIndices",  "IS", "1-n" },    { Tag(0x300a, 0x0392),
+          "Number of Scan Spot Positions", "NumberOfScanSpotPositions",  "IS", "1" },    { Tag(0x300a, 0x0393),
+          "Scan Spot Reordered", "ScanSpotReordered",  "CS", "1" },    { Tag(0x300a, 0x0394),
+          "Scan Spot Position Map", "ScanSpotPositionMap",  "FL", "1-n" },    { Tag(0x300a, 0x0395),
+          "Scan Spot Reordering Allowed", "ScanSpotReorderingAllowed",  "CS", "1" },    { Tag(0x300a, 0x0396),
           "Scan Spot Meterset Weights", "ScanSpotMetersetWeights",  "FL", "1-n" },    { Tag(0x300a, 0x0398),
           "Scanning Spot Size", "ScanningSpotSize",  "FL", "2" },    { Tag(0x300a, 0x039a),
           "Number of Paintings", "NumberOfPaintings",  "IS", "1" },    { Tag(0x300a, 0x03a0),
diff --git a/src/odil/registry.h b/src/odil/registry.h
index 56ba225..f19064a 100644
--- a/src/odil/registry.h
+++ b/src/odil/registry.h
@@ -152,6 +152,9 @@ Tag const MappingResourceUID(0x0008, 0x0118);
 Tag const LongCodeValue(0x0008, 0x0119);
 Tag const URNCodeValue(0x0008, 0x0120);
 Tag const EquivalentCodeSequence(0x0008, 0x0121);
+Tag const MappingResourceName(0x0008, 0x0122);
+Tag const ContextGroupIdentificationSequence(0x0008, 0x0123);
+Tag const MappingResourceIdentificationSequence(0x0008, 0x0124);
 Tag const TimezoneOffsetFromUTC(0x0008, 0x0201);
 Tag const PrivateDataElementCharacteristicsSequence(0x0008, 0x0300);
 Tag const PrivateGroupReference(0x0008, 0x0301);
@@ -3855,8 +3858,11 @@ Tag const RangeModulatorGatingStartWaterEquivalentThickness(0x300a, 0x0386);
 Tag const RangeModulatorGatingStopWaterEquivalentThickness(0x300a, 0x0388);
 Tag const IsocenterToRangeModulatorDistance(0x300a, 0x038a);
 Tag const ScanSpotTuneID(0x300a, 0x0390);
+Tag const ScanSpotPrescribedIndices(0x300a, 0x0391);
 Tag const NumberOfScanSpotPositions(0x300a, 0x0392);
+Tag const ScanSpotReordered(0x300a, 0x0393);
 Tag const ScanSpotPositionMap(0x300a, 0x0394);
+Tag const ScanSpotReorderingAllowed(0x300a, 0x0395);
 Tag const ScanSpotMetersetWeights(0x300a, 0x0396);
 Tag const ScanningSpotSize(0x300a, 0x0398);
 Tag const NumberOfPaintings(0x300a, 0x039a);
diff --git a/src/odil/uid.cpp b/src/odil/uid.cpp
index 8a8be2b..0db0d2c 100644
--- a/src/odil/uid.cpp
+++ b/src/odil/uid.cpp
@@ -20,8 +20,8 @@ namespace odil
 {
 
 #ifdef ODIL_MAJOR_VERSION
-std::string const implementation_class_uid=uid_prefix+"0." ODIL_STRINGIFY(ODIL_MAJOR_VERSION);
-std::string const implementation_version_name="Odil " ODIL_STRINGIFY(ODIL_MAJOR_VERSION);
+std::string implementation_class_uid=uid_prefix+"0." ODIL_STRINGIFY(ODIL_MAJOR_VERSION);
+std::string implementation_version_name="Odil " ODIL_STRINGIFY(ODIL_MAJOR_VERSION);
 #else
 #error ODIL_MAJOR_VERSION must be defined
 #endif
diff --git a/src/odil/uid.h b/src/odil/uid.h
index b1fd3c2..e60d25c 100644
--- a/src/odil/uid.h
+++ b/src/odil/uid.h
@@ -11,6 +11,8 @@
 
 #include <string>
 
+#include "odil/odil.h"
+
 namespace odil
 {
 
@@ -18,10 +20,10 @@ namespace odil
 std::string const uid_prefix="1.2.826.0.1.3680043.9.5560";
 
 /// @brief Implementation class UID of Odil.
-extern std::string const implementation_class_uid;
+extern ODIL_API std::string implementation_class_uid;
 
 /// @brief Implementation version name of Odil.
-extern std::string const implementation_version_name;
+extern ODIL_API std::string implementation_version_name;
 
 /// @brief Generate a UID under the UID prefix.
 std::string generate_uid();
diff --git a/src/odil/write_ds.cpp b/src/odil/write_ds.cpp
new file mode 100644
index 0000000..46d9049
--- /dev/null
+++ b/src/odil/write_ds.cpp
@@ -0,0 +1,210 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#include "odil/write_ds.h"
+
+#include <cmath>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+
+// Helper functions
+namespace 
+{
+
+/// @brief Remove the trailing zeros of the mantissa.
+void clean(char * mantissa)
+{
+    char * it = mantissa + strlen(mantissa) - 1;
+    while(*it == '0' && it > mantissa)
+    {
+        *it = '\0';
+        --it;
+    }
+    
+    if(*it == '.')
+    {
+        *it = '\0';
+    }
+}
+
+/**
+ * @brief Add 1 to the integer contained in (buffer[0], buffer[n]) (end included).
+ * @return 1 if a carry was used, 0 otherwise.
+ */
+int add1(char * buffer, int n)
+{
+    if(n < 0)
+    {
+        return 1;
+    }
+    
+    if(buffer[n] == '9')
+    {
+        buffer[n] = '0';
+        return add1(buffer, n-1);
+    }
+    else
+    {
+        buffer[n] += 1;
+    }
+    return 0;
+}
+
+/**
+ * @brief Round the integer contained in (buffer[0], buffer[n]) (end included)
+ *        to the nearest factor of 10.
+ * @return 1 if rounding was carried to next digit, 0 otherwise.
+ */
+int doround(char * buffer, unsigned int n)
+{
+    if(n >= strlen(buffer))
+    {
+        return 0;
+    }
+    
+    char const c = buffer[n];
+    buffer[n] = 0;
+    if(c >= '5' && c <= '9')
+    {
+        return add1(buffer, n-1);
+    }
+    
+    return 0;
+}
+
+int roundat(char * buffer, unsigned int i, int exponent) 
+{
+    if(doround(buffer, i) != 0) 
+    {
+        exponent += 1;
+        switch(exponent) 
+        {
+            case -2:
+                strcpy(buffer, ".01");
+                break;
+            case -1:
+                strcpy(buffer, ".1");
+                break;
+            case 0:
+                strcpy(buffer, "1.");
+                break;
+            case 1:
+                strcpy(buffer, "10");
+                break;
+            case 2:
+                strcpy(buffer, "100");
+                break;
+            default:
+                sprintf(buffer, "1e%d", exponent);
+        }
+        return 1;
+    }
+    return 0;
+}
+
+}
+
+namespace odil
+{
+
+void write_ds(double f, char * buffer, int size)
+{
+    // Negative number: add initial '-' to buffer and process as positive number
+    if(f < 0)
+    {
+        f = -f;
+        size -= 1;
+        *buffer = '-';
+        ++buffer;
+    }
+    
+    
+    char line[40];
+    sprintf(line, "%1.16e", f);
+    
+    if(line[0] == '-')
+    {
+        // Happens in the case of -0, other negative numbers have been already
+        // handled
+        f = -f;
+        size -= 1;
+        *buffer = '-';
+        ++buffer;
+        sprintf(line, "%1.16e", f);
+    }
+    
+    char * mantissa = line+1;
+    *mantissa = line[0];
+    
+    auto const end_of_mantissa = strcspn(mantissa, "eE");
+    mantissa[end_of_mantissa] = '\0';
+    int const exponent = strtol(mantissa + end_of_mantissa + 1, NULL, 10);
+    
+    if((exponent >= size) || (exponent < -3))
+    {
+        char exponent_buffer[6];
+        auto const exponent_length = sprintf(exponent_buffer, "e%d", exponent);
+        
+        auto const i = roundat(mantissa, size - 1 - exponent_length, exponent);
+        if(i == 1)
+        {
+            strcpy(buffer, mantissa);
+            return;
+        }
+        buffer[0] = mantissa[0];
+        buffer[1] = '.';
+        strncpy(buffer + i + 2, mantissa + 1, size - 2 - exponent_length);
+        buffer[size-exponent_length] = 0;
+        clean(buffer);
+        strcat(buffer, exponent_buffer);
+    }
+    else if(exponent >= size - 2)
+    {
+        roundat(mantissa, exponent + 1, exponent);
+        strcpy(buffer, mantissa);
+    }
+    else if(exponent >= 0)
+    {
+        auto const i = roundat(mantissa, size - 1, exponent);
+        if(i == 1)
+        {
+            strcpy(buffer, mantissa);
+            return;
+        }
+        strncpy(buffer, mantissa, exponent + 1);
+        buffer[exponent + 1] = '.';
+        strncpy(buffer + exponent + 2, mantissa + exponent + 1, size - exponent - 1);
+        buffer[size] = 0;
+        clean(buffer);
+    }
+    else
+    {
+        int const i = roundat(mantissa, size + 1 + exponent, exponent);
+        if (i == 1)
+        {
+            strcpy(buffer, mantissa);
+            return;
+        }
+        buffer[0] = '.';
+        for(int j=0; j< -1 - exponent; j++)
+        {
+            buffer[j+1] = '0';
+        }
+        if((i == 1) && (exponent != -1))
+        {
+            buffer[-exponent] = '1';
+            ++buffer;
+        }
+        strncpy(buffer - exponent, mantissa, size + 1 + exponent);
+        buffer[size] = 0;
+        clean(buffer);
+    }
+}
+
+}
diff --git a/src/odil/write_ds.h b/src/odil/write_ds.h
new file mode 100644
index 0000000..87b5cac
--- /dev/null
+++ b/src/odil/write_ds.h
@@ -0,0 +1,25 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#ifndef _1fe89041_9f3b_4536_a55a_81f045984a62
+#define _1fe89041_9f3b_4536_a55a_81f045984a62
+
+#include <cmath>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+
+namespace odil
+{
+
+/// @brief Write a double as a DS to the buffer.
+void write_ds(double f, char * buffer, int size=16);
+
+}
+
+#endif // _1fe89041_9f3b_4536_a55a_81f045984a62
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 64b23aa..2afa0eb 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -6,6 +6,19 @@ add_subdirectory(tools)
 
 file(GLOB headers *.h)
 file(GLOB_RECURSE tests code/*.cpp)
+if(NOT WITH_DCMTK)
+    list(
+        REMOVE_ITEM tests
+        ${CMAKE_CURRENT_SOURCE_DIR}/code/conversion.cpp
+        ${CMAKE_CURRENT_SOURCE_DIR}/code/DcmtkException.cpp
+        ${CMAKE_CURRENT_SOURCE_DIR}/code/ElementAccessor.cpp)
+    set(
+        extra_files
+        ${CMAKE_SOURCE_DIR}/src/odil/dcmtk/conversion.cpp
+        ${CMAKE_SOURCE_DIR}/src/odil/dcmtk/ElementAccessor.cpp
+        ${CMAKE_SOURCE_DIR}/src/odil/dcmtk/ElementTraits.cpp
+        ${CMAKE_SOURCE_DIR}/src/odil/dcmtk/Exception.cpp)
+endif()
 
 include_directories(
     ${CMAKE_SOURCE_DIR}/src ${Boost_INCLUDE_DIRS} ${DCMTK_INCLUDE_DIRS}
@@ -22,8 +35,9 @@ link_directories(${Boost_LIBRARY_DIRS} ${DCMTK_LIBRARY_DIRS})
 foreach(test_file ${tests})
     get_filename_component(test ${test_file} NAME_WE)
 
-    add_executable(test_${test} ${test_file} ${headers})
-    target_link_libraries(test_${test} libodil ${Boost_LIBRARIES})
+    add_executable(test_${test} ${test_file} ${headers} ${extra_files})
+    target_link_libraries(
+        test_${test} libodil ${Boost_LIBRARIES} ${DCMTK_LIBRARIES})
     set_target_properties(test_${test} PROPERTIES OUTPUT_NAME ${test} FOLDER "Tests")
 
     file(READ ${test_file} content)
diff --git a/tests/code/DataSet.cpp b/tests/code/DataSet.cpp
index 608537b..891071d 100644
--- a/tests/code/DataSet.cpp
+++ b/tests/code/DataSet.cpp
@@ -14,6 +14,22 @@ BOOST_AUTO_TEST_CASE(Empty)
     BOOST_CHECK(dataset.empty());
     BOOST_CHECK_EQUAL(dataset.size(), 0);
     BOOST_CHECK(!dataset.has(odil::Tag("PatientName")));
+    BOOST_CHECK(dataset.get_transfer_syntax().empty());
+}
+
+BOOST_AUTO_TEST_CASE(TransferSyntaxConstructor)
+{
+    odil::DataSet dataset(odil::registry::ExplicitVRLittleEndian);
+    BOOST_CHECK_EQUAL(
+        dataset.get_transfer_syntax(), odil::registry::ExplicitVRLittleEndian);
+}
+
+BOOST_AUTO_TEST_CASE(TransferSyntax)
+{
+    odil::DataSet dataset;
+    dataset.set_transfer_syntax(odil::registry::ExplicitVRLittleEndian);
+    BOOST_CHECK_EQUAL(
+        dataset.get_transfer_syntax(), odil::registry::ExplicitVRLittleEndian);
 }
 
 BOOST_AUTO_TEST_CASE(AddExplicitVR)
diff --git a/tests/code/VRFinder.cpp b/tests/code/VRFinder.cpp
index ddc26be..8dfa958 100644
--- a/tests/code/VRFinder.cpp
+++ b/tests/code/VRFinder.cpp
@@ -50,11 +50,10 @@ BOOST_AUTO_TEST_CASE(PublicDictionaryRepeatingGroup)
 
 BOOST_AUTO_TEST_CASE(PublicDictionaryNotApplicable)
 {
-    BOOST_REQUIRE_THROW(
-        odil::VRFinder::public_dictionary(
+    auto const vr = odil::VRFinder::public_dictionary(
             odil::Tag(0x0011, 0x0011), odil::DataSet(),
-            odil::registry::ImplicitVRLittleEndian),
-        odil::Exception);
+            odil::registry::ImplicitVRLittleEndian);
+    BOOST_REQUIRE(vr == odil::VR::UNKNOWN);
 }
 
 BOOST_AUTO_TEST_CASE(GroupLength)
@@ -67,11 +66,10 @@ BOOST_AUTO_TEST_CASE(GroupLength)
 
 BOOST_AUTO_TEST_CASE(GroupLengthNotApplicable)
 {
-    BOOST_REQUIRE_THROW(
-        odil::VRFinder::group_length(
+    auto const vr = odil::VRFinder::group_length(
             odil::Tag(0x0010, 0x0010), odil::DataSet(),
-            odil::registry::ImplicitVRLittleEndian),
-        odil::Exception);
+            odil::registry::ImplicitVRLittleEndian);
+    BOOST_REQUIRE(vr == odil::VR::UNKNOWN);
 }
 
 BOOST_AUTO_TEST_CASE(PrivateTag)
@@ -84,11 +82,10 @@ BOOST_AUTO_TEST_CASE(PrivateTag)
 
 BOOST_AUTO_TEST_CASE(PrivateTagNotApplicable)
 {
-    BOOST_REQUIRE_THROW(
-        odil::VRFinder::private_tag(
+    auto const vr = odil::VRFinder::private_tag(
             odil::Tag(0x0010, 0x0010), odil::DataSet(),
-            odil::registry::ImplicitVRLittleEndian),
-        odil::Exception);
+            odil::registry::ImplicitVRLittleEndian);
+    BOOST_REQUIRE(vr == odil::VR::UNKNOWN);
 }
 
 BOOST_AUTO_TEST_CASE(ImplictiVRLittleEndian)
@@ -101,20 +98,18 @@ BOOST_AUTO_TEST_CASE(ImplictiVRLittleEndian)
 
 BOOST_AUTO_TEST_CASE(ImplictiVRLittleEndianNotApplicableTag)
 {
-    BOOST_REQUIRE_THROW(
-        odil::VRFinder::implicit_vr_little_endian(
+    auto const vr = odil::VRFinder::implicit_vr_little_endian(
             odil::Tag(0x0010, 0x0010), odil::DataSet(),
-            odil::registry::ImplicitVRLittleEndian),
-        odil::Exception);
+            odil::registry::ImplicitVRLittleEndian);
+    BOOST_REQUIRE(vr == odil::VR::UNKNOWN);
 }
 
 BOOST_AUTO_TEST_CASE(ImplictiVRLittleEndianNotApplicableVR)
 {
-    BOOST_REQUIRE_THROW(
-        odil::VRFinder::implicit_vr_little_endian(
+    auto const vr = odil::VRFinder::implicit_vr_little_endian(
             odil::Tag(0x7fe0, 0x0010), odil::DataSet(),
-            odil::registry::ExplicitVRLittleEndian),
-        odil::Exception);
+            odil::registry::ExplicitVRLittleEndian);
+    BOOST_REQUIRE(vr == odil::VR::UNKNOWN);
 }
 
 BOOST_AUTO_TEST_CASE(PublicElement)
diff --git a/tests/code/Writer.cpp b/tests/code/Writer.cpp
index ee13d3e..15ec884 100644
--- a/tests/code/Writer.cpp
+++ b/tests/code/Writer.cpp
@@ -107,7 +107,7 @@ BOOST_AUTO_TEST_CASE(CS)
 
 BOOST_AUTO_TEST_CASE(DS)
 {
-    odil::Element odil_element({1.23, -4.56}, odil::VR::DS);
+    odil::Element odil_element({24.5282145946261, -4.56}, odil::VR::DS);
     odil::DataSet odil_data_set;
     odil_data_set.add(odil::registry::SelectorDSValue, odil_element);
 
diff --git a/tests/code/unicode.cpp b/tests/code/unicode.cpp
index 5b540e2..8f7d16c 100644
--- a/tests/code/unicode.cpp
+++ b/tests/code/unicode.cpp
@@ -3,7 +3,6 @@
 
 #include "odil/DataSet.h"
 #include "odil/unicode.h"
-#include "odil/dcmtk/conversion.h"
 
 BOOST_AUTO_TEST_CASE(SCSARAB)
 {
diff --git a/tests/run b/tests/run
index c41e6d4..e426ca4 100755
--- a/tests/run
+++ b/tests/run
@@ -23,7 +23,7 @@ def main():
     if arguments.no_network:
         excluded_cpp = [
             "Association", "Network", "ServiceRole", "SCP", "SCU", "Transport"]
-        excluded_python = ["scu"]
+        excluded_python = ["scu", "scp"]
 
         arguments.exclude_regex = "{}{}{}".format(
             arguments.exclude_regex or "",
diff --git a/tests/wrappers/test_data_set.py b/tests/wrappers/test_data_set.py
index d0ca2ac..8e4a20e 100644
--- a/tests/wrappers/test_data_set.py
+++ b/tests/wrappers/test_data_set.py
@@ -8,6 +8,20 @@ class TestDataSet(unittest.TestCase):
         self.assertTrue(data_set.empty())
         self.assertEqual(data_set.size(), 0)
         self.assertEqual(len(data_set), 0)
+        self.assertEqual(len(data_set.get_transfer_syntax()), 0)
+
+    def test_transfer_syntax_constructor(self):
+        data_set = odil.DataSet(odil.registry.ExplicitVRLittleEndian)
+        self.assertEqual(
+            data_set.get_transfer_syntax(),
+            odil.registry.ExplicitVRLittleEndian)
+
+    def test_transfer_syntax(self):
+        data_set = odil.DataSet()
+        data_set.set_transfer_syntax(odil.registry.ExplicitVRLittleEndian)
+        self.assertEqual(
+            data_set.get_transfer_syntax(),
+            odil.registry.ExplicitVRLittleEndian)
 
     def test_empty_element_tag(self):
         tag = odil.registry.PatientName
diff --git a/wrappers/DataSet.cpp b/wrappers/DataSet.cpp
index d84cdb3..e9cd25e 100644
--- a/wrappers/DataSet.cpp
+++ b/wrappers/DataSet.cpp
@@ -177,7 +177,9 @@ void wrap_DataSet()
     using namespace odil;
 
 
-    class_<DataSet>("DataSet", init<>())
+    class_<DataSet>("DataSet")
+        .def(init<>())
+        .def(init<std::string>())
         .def(
             "add",
             static_cast<void (DataSet::*)(Tag const &, VR)>(&DataSet::add),
@@ -231,6 +233,10 @@ void wrap_DataSet()
             static_cast<Value::Binary & (DataSet::*)(Tag const &)>(
                 &DataSet::as_binary),
             return_value_policy<reference_existing_object>())
+        .def(
+            "get_transfer_syntax", &DataSet::get_transfer_syntax,
+            return_value_policy<copy_const_reference>())
+        .def("set_transfer_syntax", &DataSet::set_transfer_syntax)
         .def("set", &set)
         .def("keys", &keys)
         .def("__iter__", range(&begin, &end))
diff --git a/wrappers/StoreSCU.cpp b/wrappers/StoreSCU.cpp
index 266d731..1481201 100644
--- a/wrappers/StoreSCU.cpp
+++ b/wrappers/StoreSCU.cpp
@@ -10,6 +10,8 @@
 
 #include "odil/StoreSCU.h"
 
+BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(storeMethod, odil::StoreSCU::store, 1, 3)
+
 void wrap_StoreSCU()
 {
     using namespace boost::python;
@@ -23,11 +25,12 @@ void wrap_StoreSCU()
         )
         .def(
             "set_affected_sop_class",
-            &StoreSCU::set_affected_sop_class
+            static_cast<void(StoreSCU::*)(DataSet const &)>(&StoreSCU::set_affected_sop_class)
         )
         .def(
             "store", 
-            &StoreSCU::store
+            &StoreSCU::store,
+            storeMethod()
         )
     ;
 }
diff --git a/wrappers/read.cpp b/wrappers/read.cpp
index ba76e85..98d11f3 100644
--- a/wrappers/read.cpp
+++ b/wrappers/read.cpp
@@ -13,12 +13,15 @@
 
 #include "odil/Exception.h"
 #include "odil/Reader.h"
+#include "odil/Tag.h"
 
 namespace
 {
 
 boost::python::tuple 
-read(std::string const & path, bool keep_group_length=false)
+read(
+    std::string const & path, bool keep_group_length, 
+    boost::python::object const & halt_condition)
 {
     std::ifstream stream(path);
     if(!stream)
@@ -26,8 +29,19 @@ read(std::string const & path, bool keep_group_length=false)
         throw odil::Exception("Could not open "+path);
     }
 
-    auto const header_and_data_set = 
-        odil::Reader::read_file(stream, keep_group_length);
+    std::function<bool(odil::Tag const &)> halt_condition_cpp = 
+        [](odil::Tag const &) { return false;};
+    if(halt_condition)
+    {
+        halt_condition_cpp = 
+            [halt_condition](odil::Tag const & tag) 
+            { 
+                return boost::python::call<bool>(halt_condition.ptr(), tag); 
+            };
+    }
+
+    auto const header_and_data_set = odil::Reader::read_file(
+            stream, keep_group_length, halt_condition_cpp);
 
     return boost::python::make_tuple(
         header_and_data_set.first, header_and_data_set.second);
@@ -35,14 +49,15 @@ read(std::string const & path, bool keep_group_length=false)
 
 }
 
-BOOST_PYTHON_FUNCTION_OVERLOADS(read_overloads, read, 1, 2)
-
 void wrap_read()
 {
     using namespace boost::python;
 
     def(
         "read", 
-        static_cast<boost::python::tuple (*)(std::string const &, bool)>(read),
-        read_overloads());
+        static_cast<boost::python::tuple (*)(std::string const &, bool, object const &)>(read),
+        (
+            arg("path"), arg("keep_group_length")=false, 
+            arg("halt_condition")=object())
+        );
 }

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-med/odil.git



More information about the debian-med-commit mailing list