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

Julien Lamy lamy-guest at moszumanska.debian.org
Tue Aug 16 13:46:17 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 4a4dba294fbe6704732cd547a2f6544e76e9a6b6
Author: Julien Lamy <lamy at unistra.fr>
Date:   Tue Aug 16 13:20:55 2016 +0200

    Imported Upstream version 0.7.2
---
 .travis.yml                                   |   9 +-
 CMakeLists.txt                                |   2 +-
 src/odil/Association.cpp                      |  18 ++-
 src/odil/AssociationParameters.cpp            |  73 +++++----
 src/odil/AssociationParameters.h              |  13 ++
 src/odil/FindSCP.cpp                          |   1 +
 src/odil/GetSCP.cpp                           |   3 +-
 src/odil/MoveSCP.cpp                          |  19 ++-
 src/odil/MoveSCP.h                            |   7 +-
 src/odil/Reader.cpp                           | 143 +++++++++--------
 src/odil/Reader.h                             |  11 +-
 src/odil/VR.cpp                               |   2 +-
 src/odil/Writer.cpp                           |   4 +-
 src/odil/dul/Transport.cpp                    |  23 ++-
 src/odil/dul/Transport.h                      |   2 +
 src/odil/json_converter.cpp                   | 144 +++++++++++------
 src/odil/json_converter.h                     |   6 +-
 src/odil/logging.cpp                          |   2 +-
 src/odil/logging.h                            |   3 +-
 src/odil/unicode.cpp                          | 139 ++++++++++++++++
 src/odil/unicode.h                            |   5 +
 tests/code/AssociationParameters.cpp          |  10 ++
 tests/code/Reader.cpp                         | 222 +++++++++++++++++---------
 tests/code/json_converter.cpp                 |  51 ++++++
 tests/code/unicode.cpp                        | 211 ++++++++++++++++++++++--
 tests/wrappers/test_association_parameters.py |   7 +-
 tests/wrappers/test_value.py                  |   6 +
 wrappers/AssocationParameters.cpp             |  10 +-
 wrappers/Value.cpp                            |  14 ++
 29 files changed, 891 insertions(+), 269 deletions(-)

diff --git a/.travis.yml b/.travis.yml
index 4f694f8..1c6a9ab 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -22,7 +22,6 @@ addons:
     - libboost-test-dev
     - liblog4cpp5-dev
     - dcmtk
-    - ninja-build
     - cmake
     - pkg-config
 before_install:
@@ -31,7 +30,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 log4cpp ninja; fi
+  - if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew install dcmtk icu4c jsoncpp log4cpp; fi
   - pip install --user cpp-coveralls nose
   - export PATH=$(python -c 'import site; print(site.getuserbase())')/bin:${PATH}
 before_script:
@@ -42,9 +41,9 @@ before_script:
   - 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/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 ../
+  - cmake -D CMAKE_CXX_FLAGS:STRING="${CMAKE_CXX_FLAGS}" -D CMAKE_BUILD_TYPE:STRING=Debug ../
 script:
-  - ninja
-  - ../tests/run
+  - make
+  - ../tests/run --no-network
 after_success:
   - if [ "${CC}" = "gcc" ]; then coveralls --exclude examples --exclude tests --exclude-pattern '.*CMake[^/]+\.c(?:pp)?' --exclude-pattern "/usr/.*" --root=${SRC_DIR} --build-root ${BIN_DIR} > /dev/null; fi
diff --git a/CMakeLists.txt b/CMakeLists.txt
index c3d7bf3..9c34df5 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 2.8)
 project("odil")
 set(odil_MAJOR_VERSION 0)
 set(odil_MINOR_VERSION 7)
-set(odil_PATCH_VERSION 1)
+set(odil_PATCH_VERSION 2)
 set(odil_VERSION
     ${odil_MAJOR_VERSION}.${odil_MINOR_VERSION}.${odil_PATCH_VERSION})
 
diff --git a/src/odil/Association.cpp b/src/odil/Association.cpp
index cbc8bf3..001ab87 100644
--- a/src/odil/Association.cpp
+++ b/src/odil/Association.cpp
@@ -276,7 +276,18 @@ Association
     if(data.pdu == NULL)
     {
         // We have rejected the request
-        //return false;
+        if(!data.reject)
+        {
+            throw (*data.reject);
+        }
+        else
+        {
+            throw AssociationRejected(
+                Association::RejectedTransient,
+                Association::ULServiceProvderPresentationRelatedFunction,
+                Association::NoReasonGiven,
+                "No reject information");
+        }
     }
     else
     {
@@ -446,6 +457,11 @@ Association
 ::send_message(
     message::Message const & message, std::string const & abstract_syntax)
 {
+    if(!this->is_associated())
+    {
+        throw Exception("Not associated");
+    }
+    
     auto const transfer_syntax_it =
         this->_transfer_syntaxes_by_abstract_syntax.find(abstract_syntax);
     if(transfer_syntax_it == this->_transfer_syntaxes_by_abstract_syntax.end())
diff --git a/src/odil/AssociationParameters.cpp b/src/odil/AssociationParameters.cpp
index 7017b5b..e152cee 100644
--- a/src/odil/AssociationParameters.cpp
+++ b/src/odil/AssociationParameters.cpp
@@ -27,6 +27,19 @@
 namespace odil
 {
 
+AssociationParameters::PresentationContext
+::PresentationContext(
+    uint8_t id,
+    std::string const & abstract_syntax,
+    std::vector<std::string> const & transfer_syntaxes,
+    bool scu_role_support, bool scp_role_support, Result result)
+: id(id), abstract_syntax(abstract_syntax), transfer_syntaxes(transfer_syntaxes),
+  scu_role_support(scu_role_support), scp_role_support(scp_role_support),
+  result(result)
+{
+    // Nothing else.
+}
+
 bool 
 AssociationParameters::PresentationContext
 ::operator==(PresentationContext const & other) const
@@ -41,6 +54,22 @@ AssociationParameters::PresentationContext
     );
 }
 
+AssociationParameters::UserIdentity
+::UserIdentity()
+: type(UserIdentity::Type::None), primary_field(), secondary_field()
+{
+    // Nothing else.
+}
+
+AssociationParameters::UserIdentity
+::UserIdentity(
+    Type type, std::string const & primary_field,
+    std::string const & secondary_field)
+: type(type), primary_field(primary_field), secondary_field(secondary_field)
+{
+    // Nothing else.
+}
+
 bool 
 AssociationParameters::UserIdentity
 ::operator==(UserIdentity const & other) const
@@ -73,13 +102,15 @@ AssociationParameters::UserIdentity
 AssociationParameters
 ::AssociationParameters()
 : _called_ae_title(""), _calling_ae_title(""), _presentation_contexts(),
-  _user_identity({UserIdentity::Type::None, "", ""}), _maximum_length(16384)
+  _user_identity(), _maximum_length(16384)
 {
     // Nothing else.
 }
 
 AssociationParameters
 ::AssociationParameters(pdu::AAssociateRQ const & pdu)
+: _called_ae_title(""), _calling_ae_title(""), _presentation_contexts(),
+  _user_identity(), _maximum_length(16384)
 {
     this->set_called_ae_title(pdu.get_called_ae_title());
     this->set_calling_ae_title(pdu.get_calling_ae_title());
@@ -103,19 +134,12 @@ AssociationParameters
     pcs_parameters.reserve(pcs_pdu.size());
     for(auto const & pc_pdu: pcs_pdu)
     {
-        AssociationParameters::PresentationContext pc_parameters;
-
-        pc_parameters.id = pc_pdu.get_id();
-        pc_parameters.abstract_syntax = pc_pdu.get_abstract_syntax();
-        pc_parameters.transfer_syntaxes = pc_pdu.get_transfer_syntaxes();
-
         auto const it = roles_map.find(pc_pdu.get_abstract_syntax());
-        pc_parameters.scu_role_support =
-            (it!=roles_map.end())?it->second.first:true;
-        pc_parameters.scp_role_support =
-            (it!=roles_map.end())?it->second.second:false;
-
-        pcs_parameters.push_back(pc_parameters);
+        pcs_parameters.emplace_back(
+            pc_pdu.get_id(),
+            pc_pdu.get_abstract_syntax(), pc_pdu.get_transfer_syntaxes(),
+            (it!=roles_map.end())?it->second.first:true,
+            (it!=roles_map.end())?it->second.second:false);
     }
     this->set_presentation_contexts(pcs_parameters);
 
@@ -171,7 +195,7 @@ AssociationParameters
     std::map<uint8_t, PresentationContext> pcs_request_map;
     for(auto const & pc: pcs_request)
     {
-        pcs_request_map[pc.id] = pc;
+        pcs_request_map.insert({pc.id, pc});
     }
 
     auto const & pcs_pdu = pdu.get_presentation_contexts();
@@ -190,23 +214,16 @@ AssociationParameters
     pcs_parameters.reserve(pcs_pdu.size());
     for(auto const & pc_pdu: pcs_pdu)
     {
-        AssociationParameters::PresentationContext pc_parameters;
         auto const & pc_request = pcs_request_map.at(pc_pdu.get_id());
-
-        pc_parameters.id = pc_pdu.get_id();
-        pc_parameters.abstract_syntax = pc_request.abstract_syntax;
-        pc_parameters.transfer_syntaxes = { pc_pdu.get_transfer_syntax() };
-
         auto const it = roles_map.find(pc_request.abstract_syntax);
-        pc_parameters.scu_role_support =
-            (it!=roles_map.end())?it->second.first:pc_request.scu_role_support;
-        pc_parameters.scp_role_support =
-            (it!=roles_map.end())?it->second.second:pc_request.scp_role_support;
-
-        pc_parameters.result =
-            static_cast<PresentationContext::Result>(pc_pdu.get_result_reason());
 
-        pcs_parameters.push_back(pc_parameters);
+        pcs_parameters.emplace_back(
+            pc_pdu.get_id(),
+            pc_request.abstract_syntax,
+            std::vector<std::string>{ pc_pdu.get_transfer_syntax() },
+            (it!=roles_map.end())?it->second.first:pc_request.scu_role_support,
+            (it!=roles_map.end())?it->second.second:pc_request.scp_role_support,
+            static_cast<PresentationContext::Result>(pc_pdu.get_result_reason()));
     }
     this->set_presentation_contexts(pcs_parameters);
 
diff --git a/src/odil/AssociationParameters.h b/src/odil/AssociationParameters.h
index 185868a..19e88e7 100644
--- a/src/odil/AssociationParameters.h
+++ b/src/odil/AssociationParameters.h
@@ -39,6 +39,13 @@ public:
             TransferSyntaxesNotSupported = 4,
         };
 
+        PresentationContext(
+            uint8_t id,
+            std::string const & abstract_syntax,
+            std::vector<std::string> const & transfer_syntaxes,
+            bool scu_role_support, bool scp_role_support,
+            Result result=Result::NoReason);
+
         /// @brief Identifier of the presentation context, must be odd.
         uint8_t id;
         
@@ -74,6 +81,12 @@ public:
             SAML = 4
         };
 
+        UserIdentity();
+
+        UserIdentity(
+            Type type, std::string const & primary_field,
+            std::string const & secondary_field);
+
         /// @brief Identity type.
         Type type;
         
diff --git a/src/odil/FindSCP.cpp b/src/odil/FindSCP.cpp
index 97be4c3..b45e2b8 100644
--- a/src/odil/FindSCP.cpp
+++ b/src/odil/FindSCP.cpp
@@ -86,6 +86,7 @@ FindSCP
     }
     catch(odil::Exception const & e)
     {
+        status_fields.add(registry::ErrorComment, {e.what()});
         final_status = message::CFindResponse::UnableToProcess;
     }
 
diff --git a/src/odil/GetSCP.cpp b/src/odil/GetSCP.cpp
index 75354a4..38508b9 100644
--- a/src/odil/GetSCP.cpp
+++ b/src/odil/GetSCP.cpp
@@ -114,8 +114,9 @@ GetSCP
         final_status = e.status;
         status_fields = e.status_fields;
     }
-    catch(odil::Exception const &)
+    catch(odil::Exception const & e)
     {
+        status_fields.add(registry::ErrorComment, {e.what()});
         final_status = message::CGetResponse::UnableToProcess;
     }
 
diff --git a/src/odil/MoveSCP.cpp b/src/odil/MoveSCP.cpp
index ee26243..785492a 100644
--- a/src/odil/MoveSCP.cpp
+++ b/src/odil/MoveSCP.cpp
@@ -66,7 +66,21 @@ MoveSCP
 {
     message::CMoveRequest const request(message);
 
-    auto move_association = this->_generator->get_association(request);
+    Association move_association;
+    try
+    {
+        move_association = this->_generator->get_association(request);
+    }
+    catch(odil::Exception const &)
+    {
+        message::CMoveResponse response(
+            request.get_message_id(),
+            message::CMoveResponse::RefusedMoveDestinationUnknown);
+        this->_association.send_message(
+            response, request.get_affected_sop_class_uid());
+        return;
+    }
+
     move_association.associate();
     StoreSCU store_scu(move_association);
 
@@ -123,8 +137,9 @@ MoveSCP
         final_status = e.status;
         status_fields = e.status_fields;
     }
-    catch(odil::Exception const &)
+    catch(odil::Exception const & e)
     {
+        status_fields.add(registry::ErrorComment, {e.what()});
         final_status = message::CMoveResponse::UnableToProcess;
     }
 
diff --git a/src/odil/MoveSCP.h b/src/odil/MoveSCP.h
index f47d6af..63004fd 100644
--- a/src/odil/MoveSCP.h
+++ b/src/odil/MoveSCP.h
@@ -34,7 +34,12 @@ public:
         /// @brief Return the number of responses.
         virtual unsigned int count() const =0;
         
-        /// @brief Return the sub-association to send responses on.
+        /**
+         * @brief Return the sub-association to send responses on.
+         *
+         * If the move destination is unknown, an odil::Exception must be
+         * thrown.
+         */
         virtual Association get_association(message::CMoveRequest const &) const =0;
     };
 
diff --git a/src/odil/Reader.cpp b/src/odil/Reader.cpp
index 988acd9..6c06033 100644
--- a/src/odil/Reader.cpp
+++ b/src/odil/Reader.cpp
@@ -162,6 +162,39 @@ Reader
     return Tag(group, element);
 }
 
+uint32_t
+Reader
+::read_length(VR vr) const
+{
+    uint32_t length;
+    if(this->explicit_vr)
+    {
+        // PS 3.5, 7.1.2
+        if(is_binary(vr)
+            || vr == VR::SQ || vr == VR::UC || vr == VR::UR || vr == VR::UT)
+        {
+            Reader::ignore(this->stream, 2);
+            auto const vl = Reader::read_binary<uint32_t>(
+                this->stream, this->byte_ordering);
+            length = vl;
+        }
+        else
+        {
+            auto const vl = Reader::read_binary<uint16_t>(
+                this->stream, this->byte_ordering);
+            length = vl;
+        }
+    }
+    else
+    {
+        auto const vl = Reader::read_binary<uint32_t>(
+            this->stream, this->byte_ordering);
+        length = vl;
+    }
+
+    return length;
+}
+
 Element
 Reader
 ::read_element(Tag const & tag, DataSet const & data_set) const
@@ -179,7 +212,12 @@ Reader
 
     Value value;
 
-    if(vr == VR::IS || vr == VR::SL || vr == VR::SS ||
+    auto const vl = this->read_length(vr);
+    if(vl == 0)
+    {
+        value = Value();
+    }
+    else if(vr == VR::IS || vr == VR::SL || vr == VR::SS ||
         vr == VR::UL || vr == VR::US)
     {
         value = Value(Value::Integers());
@@ -209,10 +247,17 @@ Reader
         throw Exception("Cannot create value for VR " + as_string(vr));
     }
 
-    Visitor visitor(
-        this->stream, vr, this->transfer_syntax, this->byte_ordering,
-        this->explicit_vr, this->keep_group_length);
-    apply_visitor(visitor, value);
+    if(!value.empty())
+    {
+        Visitor visitor(
+            this->stream, vr, vl, this->transfer_syntax, this->byte_ordering,
+            this->explicit_vr, this->keep_group_length);
+        apply_visitor(visitor, value);
+        if(vr == VR::SQ && value.as_data_sets().empty())
+        {
+            value = Value();
+        }
+    }
 
     return Element(value, vr);
 }
@@ -266,9 +311,10 @@ Reader
 
 Reader::Visitor
 ::Visitor(
-    std::istream & stream, VR vr, std::string const & transfer_syntax,
-    ByteOrdering byte_ordering, bool explicit_vr, bool keep_group_length)
-: stream(stream), vr(vr), transfer_syntax(transfer_syntax),
+    std::istream & stream, VR vr, uint32_t vl,
+    std::string const & transfer_syntax, ByteOrdering byte_ordering,
+    bool explicit_vr, bool keep_group_length)
+: stream(stream), vr(vr), vl(vl), transfer_syntax(transfer_syntax),
     byte_ordering(byte_ordering), explicit_vr(explicit_vr),
     keep_group_length(keep_group_length)
 {
@@ -279,11 +325,9 @@ Reader::Visitor::result_type
 Reader::Visitor
 ::operator()(Value::Integers & value) const
 {
-    auto const vl = this->read_length();
-
     if(this->vr == VR::IS)
     {
-        auto const string = read_string(this->stream, vl);
+        auto const string = read_string(this->stream, this->vl);
         if(!string.empty())
         {
             auto const strings = this->split_strings(string);
@@ -299,11 +343,11 @@ Reader::Visitor
         uint32_t items = 0;
         if(this->vr == VR::SL || this->vr == VR::UL)
         {
-            items = vl/4;
+            items = this->vl/4;
         }
         else if(this->vr == VR::AT || this->vr == VR::SS || this->vr == VR::US)
         {
-            items = vl/2;
+            items = this->vl/2;
         }
         else
         {
@@ -345,11 +389,9 @@ Reader::Visitor::result_type
 Reader::Visitor
 ::operator()(Value::Reals & value) const
 {
-    auto const vl = this->read_length();
-
     if(this->vr == VR::DS)
     {
-        auto const string = read_string(this->stream, vl);
+        auto const string = read_string(this->stream, this->vl);
         if(!string.empty())
         {
             auto const strings = this->split_strings(string);
@@ -368,11 +410,11 @@ Reader::Visitor
         uint32_t items = 0;
         if(this->vr == VR::FD)
         {
-            items = vl/8;
+            items = this->vl/8;
         }
         else if(this->vr == VR::FL)
         {
-            items = vl/4;
+            items = this->vl/4;
         }
         else
         {
@@ -419,8 +461,7 @@ Reader::Visitor
     }
     else
     {
-        auto const vl = this->read_length();
-        auto const string = read_string(this->stream, vl);
+        auto const string = read_string(this->stream, this->vl);
         if(this->vr == VR::LT || this->vr == VR::ST || this->vr == VR::UT)
         {
             value = { string };
@@ -448,12 +489,10 @@ Reader::Visitor::result_type
 Reader::Visitor
 ::operator()(Value::DataSets & value) const
 {
-    auto const vl = this->read_length();
-
-    if(vl != 0xffffffff)
+    if(this->vl != 0xffffffff)
     {
         // Explicit length sequence
-        std::string const data = read_string(this->stream, vl);
+        std::string const data = read_string(this->stream, this->vl);
         std::istringstream sequence_stream(data);
         Reader const sequence_reader(
             sequence_stream, this->transfer_syntax, this->keep_group_length);
@@ -505,12 +544,11 @@ Reader::Visitor::result_type
 Reader::Visitor
 ::operator()(Value::Binary & value) const
 {
-    auto const vl = this->read_length();
-    if(vl == 0)
+    if(this->vl == 0)
     {
         return;
     }
-    else if(vl == 0xffffffff)
+    else if(this->vl == 0xffffffff)
     {
         value = Reader::read_encapsulated_pixel_data(
             this->stream, this->byte_ordering, this->transfer_syntax,
@@ -521,18 +559,18 @@ Reader::Visitor
         value.resize(1);
         if(this->vr == VR::OB || this->vr == VR::UN)
         {
-            value[0].resize(vl);
+            value[0].resize(this->vl);
             this->stream.read(
                 reinterpret_cast<char*>(&value[0][0]), value[0].size());
         }
         else if(this->vr == VR::OD)
         {
-            if(vl%8 != 0)
+            if(this->vl%8 != 0)
             {
                 throw Exception("Cannot read OD for odd-sized array");
             }
 
-            value[0].resize(vl);
+            value[0].resize(this->vl);
             for(unsigned int i=0; i<value[0].size(); i+=8)
             {
                 auto const item = Reader::read_binary<double>(
@@ -542,12 +580,12 @@ Reader::Visitor
         }
         else if(this->vr == VR::OF)
         {
-            if(vl%4 != 0)
+            if(this->vl%4 != 0)
             {
                 throw Exception("Cannot read OF for odd-sized array");
             }
 
-            value[0].resize(vl);
+            value[0].resize(this->vl);
             for(unsigned int i=0; i<value[0].size(); i+=4)
             {
                 auto const item = Reader::read_binary<float>(
@@ -557,12 +595,12 @@ Reader::Visitor
         }
         else if(this->vr == VR::OL)
         {
-            if(vl%4 != 0)
+            if(this->vl%4 != 0)
             {
                 throw Exception("Cannot read OL for odd-sized array");
             }
 
-            value[0].resize(vl);
+            value[0].resize(this->vl);
             for(unsigned int i=0; i<value[0].size(); i+=4)
             {
                 auto const item = Reader::read_binary<uint32_t>(
@@ -572,12 +610,12 @@ Reader::Visitor
         }
         else if(this->vr == VR::OW)
         {
-            if(vl%2 != 0)
+            if(this->vl%2 != 0)
             {
                 throw Exception("Cannot read OW for odd-sized array");
             }
 
-            value[0].resize(vl);
+            value[0].resize(this->vl);
             for(unsigned int i=0; i<value[0].size(); i+=2)
             {
                 auto const item = Reader::read_binary<uint16_t>(
@@ -592,39 +630,6 @@ Reader::Visitor
     }
 }
 
-uint32_t
-Reader::Visitor
-::read_length() const
-{
-    uint32_t length;
-    if(this->explicit_vr)
-    {
-        if(vr == VR::OB || vr == VR::OD || vr == VR::OF || vr == VR::OL ||
-           vr == VR::OW || vr == VR::OF || vr == VR::SQ || vr == VR::UC ||
-           vr == VR::UR || vr == VR::UT || vr == VR::UN)
-        {
-            Reader::ignore(this->stream, 2);
-            auto const vl = Reader::read_binary<uint32_t>(
-                this->stream, this->byte_ordering);
-            length = vl;
-        }
-        else
-        {
-            auto const vl = Reader::read_binary<uint16_t>(
-                this->stream, this->byte_ordering);
-            length = vl;
-        }
-    }
-    else
-    {
-        auto const vl = Reader::read_binary<uint32_t>(
-            this->stream, this->byte_ordering);
-        length = vl;
-    }
-
-    return length;
-}
-
 Value::Strings
 Reader::Visitor
 ::split_strings(std::string const & string) const
diff --git a/src/odil/Reader.h b/src/odil/Reader.h
index 5ae85a5..e4193ce 100644
--- a/src/odil/Reader.h
+++ b/src/odil/Reader.h
@@ -73,6 +73,9 @@ public:
     /// @brief Read a tag.
     Tag read_tag() const;
 
+    /// @brief Read the length of an element.
+    uint32_t read_length(VR vr) const;
+
     /**
      * @brief Read an element (VR and value), try to guess the VR from the tag,
      * partially read data set, and transfer syntax for implicit VR transfer
@@ -95,6 +98,7 @@ private:
 
         std::istream & stream;
         VR vr;
+        uint32_t vl;
 
         std::string transfer_syntax;
         ByteOrdering byte_ordering;
@@ -102,8 +106,9 @@ private:
         bool keep_group_length;
 
         Visitor(
-            std::istream & stream, VR vr, std::string const & transfer_syntax,
-            ByteOrdering byte_ordering, bool explicit_vr, bool keep_group_length);
+            std::istream & stream, VR vr, uint32_t vl,
+            std::string const & transfer_syntax, ByteOrdering byte_ordering,
+            bool explicit_vr, bool keep_group_length);
 
         result_type operator()(Value::Integers & value) const;
         result_type operator()(Value::Reals & value) const;
@@ -111,7 +116,7 @@ private:
         result_type operator()(Value::DataSets & value) const;
         result_type operator()(Value::Binary & value) const;
 
-        uint32_t read_length() const;
+        // uint32_t read_length() const;
 
         Value::Strings split_strings(std::string const & string) const;
         DataSet read_item(std::istream & specific_stream) const;
diff --git a/src/odil/VR.cpp b/src/odil/VR.cpp
index eb76674..f127bd9 100644
--- a/src/odil/VR.cpp
+++ b/src/odil/VR.cpp
@@ -89,7 +89,7 @@ std::string as_string(VR vr)
     }
     catch(std::out_of_range const &)
     {
-        throw Exception("Unknown VR");
+        throw Exception("Unknown VR: "+std::to_string(static_cast<int>(vr)));
     }
 }
 
diff --git a/src/odil/Writer.cpp b/src/odil/Writer.cpp
index 55186b2..4d00dd8 100644
--- a/src/odil/Writer.cpp
+++ b/src/odil/Writer.cpp
@@ -175,8 +175,8 @@ Writer
     if(this->explicit_vr)
     {
         if(vr == VR::OB || vr == VR::OD || vr == VR::OF || vr == VR::OL ||
-           vr == VR::OW || vr == VR::OF || vr == VR::SQ || vr == VR::UC ||
-           vr == VR::UR || vr == VR::UT || vr == VR::UN)
+           vr == VR::OW || vr == VR::SQ || vr == VR::UC || vr == VR::UR || 
+           vr == VR::UT || vr == VR::UN)
         {
             this->write_binary(uint16_t(0), this->stream, this->byte_ordering);
 
diff --git a/src/odil/dul/Transport.cpp b/src/odil/dul/Transport.cpp
index bd813c7..6b56f15 100644
--- a/src/odil/dul/Transport.cpp
+++ b/src/odil/dul/Transport.cpp
@@ -128,8 +128,11 @@ Transport
     this->_start_deadline(source, error);
 
     this->_socket = std::make_shared<Socket>(this->_service);
-    boost::asio::ip::tcp::acceptor acceptor(this->_service, endpoint);
-    acceptor.async_accept(
+    this->_acceptor = std::make_shared<boost::asio::ip::tcp::acceptor>(
+        this->_service, endpoint);
+    boost::asio::socket_base::reuse_address option(true);
+    this->_acceptor->set_option(option);
+    this->_acceptor->async_accept(
         *this->_socket,
         [&source,&error](boost::system::error_code const & e)
         {
@@ -139,19 +142,25 @@ Transport
     );
 
     this->_run(source, error);
+
+    this->_acceptor = nullptr;
 }
 
 void
 Transport
 ::close()
 {
-    if(!this->is_open())
+    if(this->_acceptor && this->_acceptor->is_open())
     {
-        throw Exception("Not connected");
+        this->_acceptor->close();
+        this->_acceptor = nullptr;
+    }
+    if(this->is_open())
+    {
+        this->_socket->shutdown(boost::asio::ip::tcp::socket::shutdown_both);
+        this->_socket->close();
+        this->_socket = nullptr;
     }
-
-    this->_socket->close();
-    this->_socket = nullptr;
 }
 
 std::string
diff --git a/src/odil/dul/Transport.h b/src/odil/dul/Transport.h
index 726ef87..8b9d046 100644
--- a/src/odil/dul/Transport.h
+++ b/src/odil/dul/Transport.h
@@ -87,6 +87,8 @@ private:
     duration_type _timeout;
     boost::asio::deadline_timer _deadline;
 
+    std::shared_ptr<boost::asio::ip::tcp::acceptor> _acceptor;
+
     enum class Source
     {
         NONE,
diff --git a/src/odil/json_converter.cpp b/src/odil/json_converter.cpp
index 4ecd7d6..c9a970f 100644
--- a/src/odil/json_converter.cpp
+++ b/src/odil/json_converter.cpp
@@ -15,6 +15,8 @@
 #include "odil/base64.h"
 #include "odil/DataSet.h"
 #include "odil/Exception.h"
+#include "odil/registry.h"
+#include "odil/unicode.h"
 #include "odil/Value.h"
 #include "odil/VR.h"
 
@@ -22,10 +24,17 @@ namespace odil
 {
 
 /// @brief Element visitor converting to JSON.
-struct ToJSONVisitor
+class ToJSONVisitor
 {
+public:
     typedef Json::Value result_type;
 
+    ToJSONVisitor(odil::Value::Strings const & specific_char_set)
+    : _specific_character_set(specific_char_set)
+    {
+        // Nothing else.
+    }
+
     result_type operator()(VR const vr) const
     {
         result_type result;
@@ -71,53 +80,16 @@ struct ToJSONVisitor
 
         if(vr == VR::PN)
         {
-            auto const fields = { "Alphabetic", "Ideographic", "Phonetic" };
-
             for(auto const item: value)
             {
-                auto fields_it = fields.begin();
-
-                Json::Value json_item;
-
-                std::string::size_type begin=0;
-                while(begin != std::string::npos)
-                {
-                    std::string::size_type const end = item.find("=", begin);
-
-                    std::string::size_type size = 0;
-                    if(end != std::string::npos)
-                    {
-                        size = end-begin;
-                    }
-                    else
-                    {
-                        size = std::string::npos;
-                    }
-
-                    json_item[*fields_it] = item.substr(begin, size);
-
-                    if(end != std::string::npos)
-                    {
-                        begin = end+1;
-                        ++fields_it;
-                        if(fields_it == fields.end())
-                        {
-                            throw Exception("Invalid Person Name");
-                        }
-                    }
-                    else
-                    {
-                        begin = end;
-                    }
-                }
-                result["Value"].append(json_item);
+                result["Value"].append(this->_convert_pn(item));
             }
         }
         else
         {
             for(auto const & item: value)
             {
-                result["Value"].append(item);
+                result["Value"].append(this->_convert_string(vr, item));
             }
         }
         return result;
@@ -158,20 +130,98 @@ struct ToJSONVisitor
 
         return result;
     }
+private:
+    odil::Value::Strings _specific_character_set;
+
+    std::string _convert_string(VR const vr, Value::String const & value) const
+    {
+        if(
+            vr != VR::LO && vr != VR::LT &&
+            vr != VR::PN && vr != odil::VR::SH &&
+            vr != VR::ST && vr != VR::UT)
+        {
+            // Nothing to do
+            return value;
+        }
+
+        return as_utf8(value, this->_specific_character_set, vr==VR::PN);
+    }
+
+    Json::Value _convert_pn(odil::Value::String const & value) const
+    {
+        static auto const fields = { "Alphabetic", "Ideographic", "Phonetic" };
+
+        Json::Value json;
 
+        auto fields_it = fields.begin();
+
+        std::string::size_type begin=0;
+        while(begin != std::string::npos)
+        {
+            std::string::size_type const end = value.find("=", begin);
+
+            std::string::size_type size = 0;
+            if(end != std::string::npos)
+            {
+                size = end-begin;
+            }
+            else
+            {
+                size = std::string::npos;
+            }
+
+            auto const component = value.substr(begin, size);
+            json[*fields_it] = this->_convert_string(odil::VR::PN, component);
+
+            if(end != std::string::npos)
+            {
+                begin = end+1;
+                ++fields_it;
+                if(fields_it == fields.end())
+                {
+                    throw Exception("Invalid Person Name");
+                }
+            }
+            else
+            {
+                begin = end;
+            }
+        }
+
+        return json;
+    }
 };
 
-Json::Value as_json(DataSet const & data_set)
+Json::Value as_json(
+    DataSet const & data_set,
+    odil::Value::Strings const & specific_character_set)
 {
+    auto current_specific_char_set = specific_character_set;
+
     Json::Value json;
 
     for(auto const & it: data_set)
     {
         auto const & tag = it.first;
+        if(tag.element == 0)
+        {
+            // Skip group length tags
+            continue;
+        }
+
         auto const & element = it.second;
 
+        // Specific character set
+        if(tag == registry::SpecificCharacterSet)
+        {
+            current_specific_char_set = element.as_string();
+        }
+
         std::string const key(tag);
-        auto const value = apply_visitor(ToJSONVisitor(), element);
+
+        ToJSONVisitor const visitor(current_specific_char_set);
+        auto const value = apply_visitor(visitor, element);
+
         json[key] = value;
     }
 
@@ -191,10 +241,7 @@ DataSet as_dataset(Json::Value const & json)
 
         Element element;
 
-        if(vr == VR::AE || vr == VR::AS || vr == VR::AT || vr == VR::CS ||
-           vr == VR::DA || vr == VR::DT || vr == VR::LO || vr == VR::LT ||
-           vr == VR::SH || vr == VR::ST || vr == VR::TM || vr == VR::UI ||
-           vr == VR::UT)
+        if(odil::is_string(vr) && vr != odil::VR::PN)
         {
             element = Element(Value::Strings(), vr);
 
@@ -229,7 +276,7 @@ DataSet as_dataset(Json::Value const & json)
                 element.as_string().push_back(dicom_item);
             }
         }
-        else if(vr == VR::DS || vr == VR::FD || vr == VR::FL)
+        else if(is_real(vr))
         {
             element = Element(Value::Reals(), vr);
 
@@ -239,8 +286,7 @@ DataSet as_dataset(Json::Value const & json)
                 element.as_real().push_back(json_item.asDouble());
             }
         }
-        else if(vr == VR::IS || vr == VR::SL || vr == VR::SS ||
-                vr == VR::UL || vr == VR::US)
+        else if(is_int(vr))
         {
             element = Element(Value::Integers(), vr);
 
diff --git a/src/odil/json_converter.h b/src/odil/json_converter.h
index f6df7f2..f4bcab7 100644
--- a/src/odil/json_converter.h
+++ b/src/odil/json_converter.h
@@ -10,13 +10,17 @@
 #define _6f5dc463_a89a_4f77_a0ed_36dca74b9e59
 
 #include <json/json.h>
+
 #include "odil/DataSet.h"
+#include "odil/Value.h"
 
 namespace odil
 {
 
 /// @brief Convert a data set to its JSON representation.
-Json::Value as_json(DataSet const & data_set);
+Json::Value as_json(
+    DataSet const & data_set,
+    odil::Value::Strings const & specific_character_set=odil::Value::Strings());
 
 /// @brief Create a data set from its JSON representation.
 DataSet as_dataset(Json::Value const & json);
diff --git a/src/odil/logging.cpp b/src/odil/logging.cpp
index 52e9626..674bd0d 100644
--- a/src/odil/logging.cpp
+++ b/src/odil/logging.cpp
@@ -23,7 +23,7 @@ bool configure()
     auto * appender = new log4cpp::OstreamAppender("console", &std::cout);
     appender->setLayout(new log4cpp::BasicLayout());
 
-    auto & root = log4cpp::Category::getRoot();
+    auto & root = log4cpp::Category::getInstance("odil");
     root.setPriority(log4cpp::Priority::WARN);
     root.addAppender(appender);
 
diff --git a/src/odil/logging.h b/src/odil/logging.h
index 21b2677..a9b9712 100644
--- a/src/odil/logging.h
+++ b/src/odil/logging.h
@@ -12,6 +12,7 @@
 #include <log4cpp/Category.hh>
 #include <log4cpp/Priority.hh>
 
-#define ODIL_LOG(level) log4cpp::Category::getRoot() << log4cpp::Priority::level
+#define ODIL_LOG(level) \
+    log4cpp::Category::getInstance("odil") << log4cpp::Priority::level
 
 #endif // _5382f5e0_e993_4966_9447_542844edb635
diff --git a/src/odil/unicode.cpp b/src/odil/unicode.cpp
index b971423..498b5dc 100644
--- a/src/odil/unicode.cpp
+++ b/src/odil/unicode.cpp
@@ -16,6 +16,7 @@
 #include <string>
 
 #include <unicode/errorcode.h>
+#include <unicode/ucnv.h>
 #include <unicode/unistr.h>
 
 #include "odil/Exception.h"
@@ -189,6 +190,47 @@ std::string as_utf8(
     return encoded;
 }
 
+std::string as_specific_character_set(
+    std::string::const_iterator const begin, std::string::const_iterator const end,
+    std::string const & encoding)
+{
+    UErrorCode error_code=U_ZERO_ERROR;
+
+    UConverter * converter = ucnv_open(
+        encoding.empty()?"ascii":encoding.c_str(), &error_code);
+    if(U_FAILURE(error_code))
+    {
+        throw Exception(
+            "Could not open converter '"+encoding+"': "+u_errorName(error_code));
+    }
+
+    auto const source_size = end-begin;
+
+    auto const target_capacity = 4*source_size;
+    char * target = new char[target_capacity];
+
+    auto const target_size = ucnv_fromAlgorithmic(
+        converter, UCNV_UTF8,
+        target, target_capacity,
+        &(*begin), source_size,
+        &error_code
+    );
+    if(U_FAILURE(error_code))
+    {
+        delete[] target;
+        throw Exception(
+            "Could not convert '"+encoding+"': "+u_errorName(error_code));
+    }
+
+    ucnv_close(converter);
+
+    std::string const result(target, target_size);
+
+    delete[] target;
+
+    return result;
+}
+
 enum class Group
 {
     Alphabetic,
@@ -330,4 +372,101 @@ std::string as_utf8(
     return result;
 }
 
+std::string as_specific_character_set(
+    std::string const & input, Value::Strings const & specific_character_set,
+    bool is_pn)
+{
+    // Control characters: line feed, carriage return, form feed and tabulation
+    // For Person Name, add the group splitters
+    std::string splitters = "\n\r\f\t";
+    if(is_pn)
+    {
+        splitters.append("^=");
+    }
+
+    std::string result;
+
+    Group group=Group::Alphabetic;
+
+    auto begin = input.begin();
+    while(begin != input.end())
+    {
+        // Active character set resets to default before any of the splitters
+        // cf. PS 3.5, 6.1.2.5.3
+        auto const end = std::find_first_of(
+            begin, input.end(), splitters.begin(), splitters.end());
+
+        std::string encoded;
+
+        if(specific_character_set.empty())
+        {
+            encoded = std::string(begin, end);
+        }
+        else if(is_pn && group != Group::Alphabetic && specific_character_set.size() > 1)
+        {
+            // Encode using specific_character_set[1], using escape sequences
+            auto const encoder = find_encoder(specific_character_set[1]);
+            auto const it = escape_sequences.find(specific_character_set[1]);
+            auto const specific_escape_sequences =
+                (it!=escape_sequences.end())?it->second:std::vector<std::string>();
+
+            // The ISO-2022-JP encoder of ICU already includes the escape sequences
+            if(encoder != "ISO-2022-JP" && specific_escape_sequences.size() > 0)
+            {
+                encoded += specific_escape_sequences[0];
+            }
+            encoded += as_specific_character_set(begin, end, encoder);
+            if(encoder != "ISO-2022-JP" && specific_escape_sequences.size() > 1)
+            {
+                encoded += specific_escape_sequences[1];
+            }
+
+            if(
+                specific_character_set[0] == "ISO 2022 IR 13"
+                && encoder == "ISO-2022-JP")
+            {
+                // ICU switches back to ASCII (1B 28 42), while the examples
+                // from D. Clunie switch back to JIS X 0201-1976 (Romaji)
+                // (1B 28 4A)
+                encoded[encoded.size()-1] = 0x4a;
+            }
+        }
+        else
+        {
+            auto const encoder = find_encoder(specific_character_set[0]);
+            encoded = as_specific_character_set(begin, end, encoder);
+        }
+
+        result.append(encoded);
+
+        // If present, add the splitter to the UTF-8 string.
+        if(end != input.end())
+        {
+            result.push_back(*end);
+            begin = end+1;
+            if(is_pn && *end == '=')
+            {
+                if(group == Group::Alphabetic)
+                {
+                    group = Group::Ideographic;
+                }
+                else if(group == Group::Ideographic)
+                {
+                    group = Group::Phonetic;
+                }
+                else
+                {
+                    throw Exception("Too many groups");
+                }
+            }
+        }
+        else
+        {
+            begin = end;
+        }
+    }
+
+    return result;
+}
+
 }
diff --git a/src/odil/unicode.h b/src/odil/unicode.h
index 2628fc3..03a6f94 100644
--- a/src/odil/unicode.h
+++ b/src/odil/unicode.h
@@ -20,6 +20,11 @@ std::string as_utf8(
     std::string const & input, Value::Strings const & specific_character_set,
     bool is_pn=false);
 
+/// @brief Convert an UTF-8 string to a specific representation
+std::string as_specific_character_set(
+    std::string const & input, Value::Strings const & specific_character_set,
+    bool is_pn=false);
+
 }
 
 #endif // _4a178325_e3d6_4f6f_9a18_ba6a983ee396
diff --git a/tests/code/AssociationParameters.cpp b/tests/code/AssociationParameters.cpp
index e76540e..d2ffd2b 100644
--- a/tests/code/AssociationParameters.cpp
+++ b/tests/code/AssociationParameters.cpp
@@ -86,6 +86,16 @@ BOOST_AUTO_TEST_CASE(PresentationContexts)
     BOOST_REQUIRE(parameters.get_presentation_contexts()[1].scp_role_support);
 }
 
+BOOST_AUTO_TEST_CASE(UserIdentityDefault)
+{
+    odil::AssociationParameters parameters;
+    BOOST_REQUIRE(
+        parameters.get_user_identity().type ==
+            odil::AssociationParameters::UserIdentity::Type::None);
+    BOOST_REQUIRE(parameters.get_user_identity().primary_field.empty());
+    BOOST_REQUIRE(parameters.get_user_identity().secondary_field.empty());
+}
+
 BOOST_AUTO_TEST_CASE(UserIdentityNone)
 {
     odil::AssociationParameters parameters;
diff --git a/tests/code/Reader.cpp b/tests/code/Reader.cpp
index 13a0106..78395bd 100644
--- a/tests/code/Reader.cpp
+++ b/tests/code/Reader.cpp
@@ -15,6 +15,8 @@
 #include "odil/VR.h"
 #include "odil/dcmtk/conversion.h"
 
+#include "odil/json_converter.h"
+
 BOOST_AUTO_TEST_CASE(Constructor)
 {
     std::istringstream stream;
@@ -77,100 +79,153 @@ void do_test(odil::DataSet const & odil_data_set)
     }
 }
 
-BOOST_AUTO_TEST_CASE(AT)
+template<typename T>
+void do_test(odil::Tag const & tag, odil::VR vr, std::initializer_list<T> const & value)
 {
-    odil::Element odil_element({"12345678", "9abcdef0"}, odil::VR::AT);
-    odil::DataSet odil_data_set;
-    odil_data_set.add(odil::registry::SelectorATValue, odil_element);
+    // Empty element
+    {
+        odil::Element element(odil::Value(), vr);
+        odil::DataSet data_set;
+        data_set.add(tag, element);
+        do_test(data_set);
+    }
+    // Single value
+    {
+        odil::Element element({ *value.begin() }, vr);
+        odil::DataSet data_set;
+        data_set.add(tag, element);
+        do_test(data_set);
+    }
+    // Multiple values
+    {
+        odil::Element element(value, vr);
+        odil::DataSet data_set;
+        data_set.add(tag, element);
+        do_test(data_set);
+    }
+}
+
+BOOST_AUTO_TEST_CASE(AE)
+{
+    do_test(
+        odil::registry::SelectorAEValue, odil::VR::AE,
+        {std::string("LOCAL"), std::string("REMOTE")});
+}
 
-    do_test(odil_data_set);
+BOOST_AUTO_TEST_CASE(AS)
+{
+    do_test(
+        odil::registry::SelectorASValue, odil::VR::AS,
+        {std::string("035Y"), std::string("022W")});
+}
+
+BOOST_AUTO_TEST_CASE(AT)
+{
+    do_test(
+        odil::registry::SelectorATValue, odil::VR::AT,
+        {std::string("12345678"), std::string("9abcdef0")});
 }
 
 BOOST_AUTO_TEST_CASE(CS)
 {
-    odil::Element odil_element({"ABC", "DEF"}, odil::VR::CS);
-    odil::DataSet odil_data_set;
-    odil_data_set.add(odil::registry::SelectorCSValue, odil_element);
+    do_test(
+        odil::registry::SelectorCSValue, odil::VR::CS,
+        {std::string("ABC"), std::string("DEF")});
+}
 
-    do_test(odil_data_set);
+BOOST_AUTO_TEST_CASE(DA)
+{
+    do_test(
+        odil::registry::SelectorDAValue, odil::VR::DA,
+        {std::string("20160103"), std::string("19700131")});
 }
 
 BOOST_AUTO_TEST_CASE(DS)
 {
-    odil::Element odil_element({1.23, -4.56}, odil::VR::DS);
-    odil::DataSet odil_data_set;
-    odil_data_set.add(odil::registry::SelectorDSValue, odil_element);
+    do_test(odil::registry::SelectorDSValue, odil::VR::DS, {1.23, -4.56});
+}
 
-    do_test(odil_data_set);
+BOOST_AUTO_TEST_CASE(DT)
+{
+    do_test(
+        odil::registry::SelectorDTValue, odil::VR::DT,
+        {std::string("20160103112233"), std::string("19700131001122.123456")});
 }
 
 BOOST_AUTO_TEST_CASE(FD)
 {
-    odil::Element odil_element({1.23, -4.56}, odil::VR::FD);
-    odil::DataSet odil_data_set;
-    odil_data_set.add(odil::registry::SelectorFDValue, odil_element);
-
-    do_test(odil_data_set);
+    do_test(odil::registry::SelectorFDValue, odil::VR::FD, {1.23, -4.56});
 }
 
 BOOST_AUTO_TEST_CASE(FL)
 {
-    odil::Element odil_element({0.5, -0.125}, odil::VR::FL);
-    odil::DataSet odil_data_set;
-    odil_data_set.add(odil::registry::SelectorFLValue, odil_element);
-
-    do_test(odil_data_set);
+    do_test(odil::registry::SelectorFLValue, odil::VR::FL, {0.5, -0.125});
 }
 
 BOOST_AUTO_TEST_CASE(IS)
 {
-    odil::Element odil_element({123, -456}, odil::VR::IS);
-    odil::DataSet odil_data_set;
-    odil_data_set.add(odil::registry::SelectorISValue, odil_element);
+    do_test(odil::registry::SelectorISValue, odil::VR::IS, {123, -456});
+}
 
-    do_test(odil_data_set);
+BOOST_AUTO_TEST_CASE(LO)
+{
+    do_test(
+        odil::registry::SelectorLOValue, odil::VR::LO,
+        {std::string("Foo"), std::string("Bar")});
 }
 
-BOOST_AUTO_TEST_CASE(OB)
+BOOST_AUTO_TEST_CASE(LT)
 {
-    odil::Element odil_element(
-        odil::Value::Binary({{0x01, 0x02, 0x03, 0x04}}),
-        odil::VR::OB);
-    odil::DataSet odil_data_set;
-    odil_data_set.add(odil::registry::EncapsulatedDocument, odil_element);
+    do_test(
+        odil::registry::SelectorLTValue, odil::VR::LT,
+        {std::string("Foo\\Bar")});
+}
 
-    do_test(odil_data_set);
+BOOST_AUTO_TEST_CASE(OB)
+{
+    do_test(
+        odil::registry::EncapsulatedDocument, odil::VR::OB, {
+            odil::Value::Binary::value_type{0x01, 0x02, 0x03, 0x04} });
 }
 
+// OD is not in current DCMTK
+
 BOOST_AUTO_TEST_CASE(OF)
 {
-    odil::Element odil_element(
-        odil::Value::Binary({{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}}),
-        odil::VR::OF);
-    odil::DataSet odil_data_set;
-    odil_data_set.add(odil::registry::VectorGridData, odil_element);
-
-    do_test(odil_data_set);
+    do_test(
+        odil::registry::VectorGridData, odil::VR::OF, {
+            odil::Value::Binary::value_type{
+                0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} });
 }
 
+// OL is not in current DCMTK
+
 BOOST_AUTO_TEST_CASE(OW)
 {
-    odil::Element odil_element(
-        odil::Value::Binary({{0x01, 0x02, 0x03, 0x04}}),
-        odil::VR::OW);
-    odil::DataSet odil_data_set;
-    odil_data_set.add(odil::registry::RedPaletteColorLookupTableData, odil_element);
+    do_test(
+        odil::registry::RedPaletteColorLookupTableData, odil::VR::OW, {
+            odil::Value::Binary::value_type{0x01, 0x02, 0x03, 0x04} });
+}
 
-    do_test(odil_data_set);
+BOOST_AUTO_TEST_CASE(PN)
+{
+    do_test(
+        odil::registry::SelectorPNValue, odil::VR::PN, {
+            std::string("Adams^John Robert Quincy^^Rev.^B.A. M.Div."),
+            std::string("Morrison-Jones^Susan^^^Ph.D., Chief Executive Officer")
+        });
 }
 
-BOOST_AUTO_TEST_CASE(SL)
+BOOST_AUTO_TEST_CASE(SH)
 {
-    odil::Element odil_element({12345678, -8765432}, odil::VR::SL);
-    odil::DataSet odil_data_set;
-    odil_data_set.add(odil::registry::SelectorSLValue, odil_element);
+    do_test(
+        odil::registry::SelectorSHValue, odil::VR::SH,
+        {std::string("Foo"), std::string("Bar")});
+}
 
-    do_test(odil_data_set);
+BOOST_AUTO_TEST_CASE(SL)
+{
+    do_test(odil::registry::SelectorSLValue, odil::VR::SL, {12345678, -8765432});
 }
 
 BOOST_AUTO_TEST_CASE(SQ)
@@ -184,48 +239,65 @@ BOOST_AUTO_TEST_CASE(SQ)
         odil::registry::SelectorFDValue,
         odil::Element({1.23, -4.56}, odil::VR::FD));
 
-    odil::DataSet odil_data_set;
-    odil_data_set.add(odil::registry::FrameExtractionSequence,
-        odil::Element({item1, item2}, odil::VR::SQ));
-
-    do_test(odil_data_set);
+    do_test(odil::registry::FrameExtractionSequence, odil::VR::SQ, {item1, item2});
 }
 
 BOOST_AUTO_TEST_CASE(SS)
 {
-    odil::Element odil_element({1234, -5678}, odil::VR::SS);
-    odil::DataSet odil_data_set;
-    odil_data_set.add(odil::registry::SelectorSSValue, odil_element);
+    do_test(odil::registry::SelectorSSValue, odil::VR::SS, {1234, -5678});
+}
+
+BOOST_AUTO_TEST_CASE(ST)
+{
+    do_test(
+        odil::registry::SelectorSTValue, odil::VR::ST,
+        {std::string("Foo\\Bar")});
+}
 
-    do_test(odil_data_set);
+BOOST_AUTO_TEST_CASE(TM)
+{
+    do_test(
+        odil::registry::SelectorTMValue, odil::VR::TM,
+        {std::string("112233"), std::string("001122.123456")});
 }
 
+// UC is not in current DCMTK
+//{
+//    do_test(
+//        odil::registry::SelectorUCValue, odil::VR::UC,
+//        {std::string("Foo"), std::string("Bar")});
+//}
+
 BOOST_AUTO_TEST_CASE(UI)
 {
-    odil::Element odil_element({"1.2", "3.4"}, odil::VR::UI);
-    odil::DataSet odil_data_set;
     // SelectorUIValue is not in current DCMTK
-    odil_data_set.add(odil::registry::SOPInstanceUID, odil_element);
-
-    do_test(odil_data_set);
+    do_test(
+        odil::registry::SOPInstanceUID, odil::VR::UI,
+        {std::string("1.2"), std::string("3.4")});
 }
 
 BOOST_AUTO_TEST_CASE(UL)
 {
-    odil::Element odil_element({12345678, 8765432}, odil::VR::UL);
-    odil::DataSet odil_data_set;
-    odil_data_set.add(odil::registry::SelectorULValue, odil_element);
-
-    do_test(odil_data_set);
+    do_test(odil::registry::SelectorULValue, odil::VR::UL, {12345678, 8765432});
 }
 
+// UR is not in current DCMTK
+//{
+//    do_test(
+//        odil::registry::SelectorURValue, odil::VR::UR,
+//        {std::string("https://example.com"), std::string("mailto:me at example.com")});
+//}
+
 BOOST_AUTO_TEST_CASE(US)
 {
-    odil::Element odil_element({1234, 5678}, odil::VR::US);
-    odil::DataSet odil_data_set;
-    odil_data_set.add(odil::registry::SelectorUSValue, odil_element);
+    do_test(odil::registry::SelectorUSValue, odil::VR::US, {1234, 5678});
+}
 
-    do_test(odil_data_set);
+BOOST_AUTO_TEST_CASE(UT)
+{
+    do_test(
+        odil::registry::SelectorUTValue, odil::VR::UT,
+        {std::string("Foo\\Bar")});
 }
 
 void do_file_test(
diff --git a/tests/code/json_converter.cpp b/tests/code/json_converter.cpp
index 9cf7d3b..419bc9b 100644
--- a/tests/code/json_converter.cpp
+++ b/tests/code/json_converter.cpp
@@ -10,6 +10,7 @@
 #include "odil/DataSet.h"
 #include "odil/Element.h"
 #include "odil/json_converter.h"
+#include "odil/registry.h"
 #include "odil/Value.h"
 #include "odil/VR.h"
 
@@ -100,6 +101,25 @@ BOOST_AUTO_TEST_CASE(AsJSONStrings)
         &Json::Value::isString, &Json::Value::asString, data_set.as_string(0xdeadbeef));
 }
 
+BOOST_AUTO_TEST_CASE(AsJSONStringsUnicode)
+{
+    odil::DataSet data_set;
+    data_set.add(
+        odil::registry::SpecificCharacterSet,
+        odil::Value::Strings({"ISO_IR 100"}), odil::VR::LO);
+    data_set.add(
+        0xdeadbeef, odil::Value::Strings({"J\xe9r\xf4me"}), odil::VR::LO);
+    auto const json = odil::as_json(data_set);
+
+    check_json_object(
+        json, {std::string(odil::registry::SpecificCharacterSet), "deadbeef"});
+    check_json_object(json["deadbeef"], {"vr", "Value"});
+    check_json_string(json["deadbeef"]["vr"], "LO");
+    check_json_array(json["deadbeef"]["Value"],
+        &Json::Value::isString, &Json::Value::asString,
+        odil::Value::Strings{std::string("J\xc3\xa9r\xc3\xb4me")});
+}
+
 BOOST_AUTO_TEST_CASE(AsJSONPersonName)
 {
     odil::DataSet data_set;
@@ -121,6 +141,28 @@ BOOST_AUTO_TEST_CASE(AsJSONPersonName)
     check_json_string(json["deadbeef"]["Value"][0]["Phonetic"], {"Pho^Netic"});
 }
 
+BOOST_AUTO_TEST_CASE(AsJSONPersonNameUnicode)
+{
+    odil::DataSet data_set;
+    data_set.add(
+        odil::registry::SpecificCharacterSet,
+        odil::Value::Strings({"ISO_IR 100"}), odil::VR::LO);
+    data_set.add(
+        0xdeadbeef, odil::Value::Strings({"Buc^J\xe9r\xf4me"}), odil::VR::PN);
+    auto const json = odil::as_json(data_set);
+    check_json_object(
+        json, {std::string(odil::registry::SpecificCharacterSet), "deadbeef"});
+    check_json_object(json["deadbeef"], {"vr", "Value"});
+    check_json_string(json["deadbeef"]["vr"], "PN");
+
+    BOOST_REQUIRE(json["deadbeef"]["Value"].isArray());
+    BOOST_REQUIRE_EQUAL(json["deadbeef"]["Value"].size(), 1);
+    check_json_object(json["deadbeef"]["Value"][0], {"Alphabetic"});
+    check_json_string(
+        json["deadbeef"]["Value"][0]["Alphabetic"],
+        {"Buc^J\xc3\xa9r\xc3\xb4me"});
+}
+
 BOOST_AUTO_TEST_CASE(AsJSONDataSets)
 {
     odil::DataSet item;
@@ -164,6 +206,15 @@ BOOST_AUTO_TEST_CASE(AsJSONBinary)
     check_json_string(json["deadbeef"]["InlineBinary"], "AQIDBAU=");
 }
 
+BOOST_AUTO_TEST_CASE(AsJSONGroupLength)
+{
+    odil::DataSet data_set;
+    data_set.add(0x00100000, {1234}, odil::VR::UL);
+    data_set.add(odil::registry::PatientID, {"DJ0001"});
+    auto const json = odil::as_json(data_set);
+    check_json_object(json, {"00100020"});
+}
+
 BOOST_AUTO_TEST_CASE(AsDataSetEmpty)
 {
     std::stringstream data;
diff --git a/tests/code/unicode.cpp b/tests/code/unicode.cpp
index 8f7d16c..9d5d73f 100644
--- a/tests/code/unicode.cpp
+++ b/tests/code/unicode.cpp
@@ -4,7 +4,7 @@
 #include "odil/DataSet.h"
 #include "odil/unicode.h"
 
-BOOST_AUTO_TEST_CASE(SCSARAB)
+BOOST_AUTO_TEST_CASE(SCSARAB_AsUTF8)
 {
     odil::Value::Strings const specific_character_set = { "ISO_IR 127" };
     std::string const source =
@@ -19,7 +19,22 @@ BOOST_AUTO_TEST_CASE(SCSARAB)
     BOOST_REQUIRE_EQUAL(utf8, expected);
 }
 
-BOOST_AUTO_TEST_CASE(SCSFREN)
+BOOST_AUTO_TEST_CASE(SCSARAB_AsSCS)
+{
+    odil::Value::Strings const specific_character_set = { "ISO_IR 127" };
+    std::string const source =
+        "\xd9\x82\xd8\xa8\xd8\xa7\xd9\x86\xd9\x8a"
+        "^"
+        "\xd9\x84\xd9\x86\xd8\xb2\xd8\xa7\xd8\xb1";
+    std::string const expected =
+        "\xe2\xc8\xc7\xe6\xea" "^" "\xe4\xe6\xd2\xc7\xd1";
+
+    std::string const scs = odil::as_specific_character_set(
+        source, specific_character_set, true);
+    BOOST_REQUIRE_EQUAL(scs, expected);
+}
+
+BOOST_AUTO_TEST_CASE(SCSFREN_AsUTF8)
 {
     odil::Value::Strings const specific_character_set = { "ISO_IR 100" };
     std::string const source = "Buc" "^" "J\xe9r\xf4me";
@@ -31,7 +46,18 @@ BOOST_AUTO_TEST_CASE(SCSFREN)
     BOOST_REQUIRE_EQUAL(utf8, expected);
 }
 
-BOOST_AUTO_TEST_CASE(SCSGERM)
+BOOST_AUTO_TEST_CASE(SCSFREN_AsSCS)
+{
+    odil::Value::Strings const specific_character_set = { "ISO_IR 100" };
+    std::string const source = "Buc" "^" "J\xc3\xa9r\xc3\xb4me";
+    std::string const expected = "Buc" "^" "J\xe9r\xf4me";
+
+    std::string const scs = odil::as_specific_character_set(
+        source, specific_character_set, true);
+    BOOST_REQUIRE_EQUAL(scs, expected);
+}
+
+BOOST_AUTO_TEST_CASE(SCSGERM_AsUTF8)
 {
     odil::Value::Strings const specific_character_set = { "ISO_IR 100" };
     std::string const source =
@@ -44,7 +70,18 @@ BOOST_AUTO_TEST_CASE(SCSGERM)
     BOOST_REQUIRE_EQUAL(utf8, expected);
 }
 
-BOOST_AUTO_TEST_CASE(SCSGREEK)
+BOOST_AUTO_TEST_CASE(SCSGERM_AsSCS)
+{
+    odil::Value::Strings const specific_character_set = { "ISO_IR 100" };
+    std::string const source = "\xc3\x84neas" "^" "R\xc3\xbc" "diger";
+    std::string const expected = "\xc4neas" "^" "R\xfc" "diger";
+
+    std::string const scs = odil::as_specific_character_set(
+        source, specific_character_set, true);
+    BOOST_REQUIRE_EQUAL(scs, expected);
+}
+
+BOOST_AUTO_TEST_CASE(SCSGREEK_AsUTF8)
 {
     odil::Value::Strings const specific_character_set = { "ISO_IR 126" };
     std::string const source = "\xc4\xe9\xef\xed\xf5\xf3\xe9\xef\xf2";
@@ -56,7 +93,19 @@ BOOST_AUTO_TEST_CASE(SCSGREEK)
     BOOST_REQUIRE_EQUAL(utf8, expected);
 }
 
-BOOST_AUTO_TEST_CASE(SCSH31)
+BOOST_AUTO_TEST_CASE(SCSGREEK_AsSCS)
+{
+    odil::Value::Strings const specific_character_set = { "ISO_IR 126" };
+    std::string const source =
+        "\xce\x94\xce\xb9\xce\xbf\xce\xbd\xcf\x85\xcf\x83\xce\xb9\xce\xbf\xcf\x82";
+    std::string const expected = "\xc4\xe9\xef\xed\xf5\xf3\xe9\xef\xf2";
+
+    std::string const scs = odil::as_specific_character_set(
+        source, specific_character_set, true);
+    BOOST_REQUIRE_EQUAL(scs, expected);
+}
+
+BOOST_AUTO_TEST_CASE(SCSH31_AsUTF8)
 {
     odil::Value::Strings const specific_character_set =
         { "", "ISO 2022 IR 87" };
@@ -81,7 +130,32 @@ BOOST_AUTO_TEST_CASE(SCSH31)
     BOOST_REQUIRE_EQUAL(utf8, expected);
 }
 
-BOOST_AUTO_TEST_CASE(SCSH32)
+BOOST_AUTO_TEST_CASE(SCSH31_AsSCS)
+{
+    odil::Value::Strings const specific_character_set =
+        { "", "ISO 2022 IR 87" };
+    std::string const source =
+        "Yamada" "^" "Tarou"
+        "="
+        "\xe5\xb1\xb1\xe7\x94\xb0" "^" "\xe5\xa4\xaa\xe9\x83\x8e"
+        "="
+        "\xe3\x82\x84\xe3\x81\xbe\xe3\x81\xa0"
+            "^" "\xe3\x81\x9f\xe3\x82\x8d\xe3\x81\x86";
+    std::string const expected =
+        "Yamada" "^" "Tarou"
+        "="
+        "\x1b\x24\x42\x3b\x33\x45\x44\x1b\x28\x42"
+            "^" "\x1b\x24\x42\x42\x40\x4f\x3a\x1b\x28\x42"
+        "="
+        "\x1b\x24\x42\x24\x64\x24\x5e\x24\x40\x1b\x28\x42"
+            "^" "\x1b\x24\x42\x24\x3f\x24\x6d\x24\x26\x1b\x28\x42";
+
+    std::string const scs = odil::as_specific_character_set(
+        source, specific_character_set, true);
+    BOOST_REQUIRE_EQUAL(scs, expected);
+}
+
+BOOST_AUTO_TEST_CASE(SCSH32_AsUTF8)
 {
     odil::Value::Strings const specific_character_set =
         { "ISO 2022 IR 13", "ISO 2022 IR 87" };
@@ -108,7 +182,34 @@ BOOST_AUTO_TEST_CASE(SCSH32)
     BOOST_REQUIRE_EQUAL(utf8, expected);
 }
 
-BOOST_AUTO_TEST_CASE(SCSHBRW)
+BOOST_AUTO_TEST_CASE(SCSH32_AsSCS)
+{
+    odil::Value::Strings const specific_character_set =
+        { "ISO 2022 IR 13", "ISO 2022 IR 87" };
+    std::string const source =
+        "\xef\xbe\x94\xef\xbe\x8f\xef\xbe\x80\xef\xbe\x9e"
+            "^" "\xef\xbe\x80\xef\xbe\x9b\xef\xbd\xb3"
+        "="
+        "\xe5\xb1\xb1\xe7\x94\xb0"
+            "^" "\xe5\xa4\xaa\xe9\x83\x8e"
+        "="
+        "\xe3\x82\x84\xe3\x81\xbe\xe3\x81\xa0"
+            "^" "\xe3\x81\x9f\xe3\x82\x8d\xe3\x81\x86";
+    std::string const expected =
+        "\xd4\xcf\xc0\xde" "^" "\xc0\xdb\xb3"
+        "="
+        "\x1b\x24\x42\x3b\x33\x45\x44\x1b\x28\x4a"
+            "^" "\x1b\x24\x42\x42\x40\x4f\x3a\x1b\x28\x4a"
+        "="
+        "\x1b\x24\x42\x24\x64\x24\x5e\x24\x40\x1b\x28\x4a"
+            "^" "\x1b\x24\x42\x24\x3f\x24\x6d\x24\x26\x1b\x28\x4a";
+
+    std::string const scs = odil::as_specific_character_set(
+        source, specific_character_set, true);
+    BOOST_REQUIRE_EQUAL(scs, expected);
+}
+
+BOOST_AUTO_TEST_CASE(SCSHBRW_AsUTF8)
 {
     odil::Value::Strings const specific_character_set = { "ISO_IR 138" };
     std::string const source = "\xf9\xf8\xe5\xef" "^" "\xe3\xe1\xe5\xf8\xe4";
@@ -121,7 +222,20 @@ BOOST_AUTO_TEST_CASE(SCSHBRW)
     BOOST_REQUIRE_EQUAL(utf8, expected);
 }
 
-BOOST_AUTO_TEST_CASE(SCSI2)
+BOOST_AUTO_TEST_CASE(SCSHBRW_AsSCS)
+{
+    odil::Value::Strings const specific_character_set = { "ISO_IR 138" };
+    std::string const source =
+        "\xd7\xa9\xd7\xa8\xd7\x95\xd7\x9f"
+            "^" "\xd7\x93\xd7\x91\xd7\x95\xd7\xa8\xd7\x94";
+    std::string const expected = "\xf9\xf8\xe5\xef" "^" "\xe3\xe1\xe5\xf8\xe4";
+
+    std::string const scs = odil::as_specific_character_set(
+        source, specific_character_set, true);
+    BOOST_REQUIRE_EQUAL(scs, expected);
+}
+
+BOOST_AUTO_TEST_CASE(SCSI2_AsUTF8)
 {
     odil::Value::Strings const specific_character_set =
         { "", "ISO 2022 IR 149" };
@@ -144,7 +258,29 @@ BOOST_AUTO_TEST_CASE(SCSI2)
     BOOST_REQUIRE_EQUAL(utf8, expected);
 }
 
-BOOST_AUTO_TEST_CASE(SCSRUSS)
+BOOST_AUTO_TEST_CASE(SCSI2_AsSCS)
+{
+    odil::Value::Strings const specific_character_set =
+        { "", "ISO 2022 IR 149" };
+    std::string const source =
+        "Hong" "^" "Gildong"
+        "="
+        "\xe6\xb4\xaa" "^" "\xe5\x90\x89\xe6\xb4\x9e"
+        "="
+        "\xed\x99\x8d" "^" "\xea\xb8\xb8\xeb\x8f\x99";
+    std::string const expected =
+        "Hong" "^" "Gildong"
+        "="
+        "\x1b\x24\x29\x43\xfb\xf3" "^" "\x1b\x24\x29\x43\xd1\xce\xd4\xd7"
+        "="
+        "\x1b\x24\x29\x43\xc8\xab" "^" "\x1b\x24\x29\x43\xb1\xe6\xb5\xbf";
+
+    std::string const scs = odil::as_specific_character_set(
+        source, specific_character_set, true);
+    BOOST_REQUIRE_EQUAL(scs, expected);
+}
+
+BOOST_AUTO_TEST_CASE(SCSRUSS_AsUTF8)
 {
     odil::Value::Strings const specific_character_set = { "ISO_IR 144" };
     std::string const source = "\xbb\xee\xda\x63\x65\xdc\xd1\x79\x70\xd3";
@@ -156,7 +292,19 @@ BOOST_AUTO_TEST_CASE(SCSRUSS)
     BOOST_REQUIRE_EQUAL(utf8, expected);
 }
 
-BOOST_AUTO_TEST_CASE(SCSX1)
+BOOST_AUTO_TEST_CASE(SCSRUSS_AsSCS)
+{
+    odil::Value::Strings const specific_character_set = { "ISO_IR 144" };
+    std::string const source =
+        "\xd0\x9b\xd1\x8e\xd0\xba\x63\x65\xd0\xbc\xd0\xb1\x79\x70\xd0\xb3";
+    std::string const expected = "\xbb\xee\xda\x63\x65\xdc\xd1\x79\x70\xd3";
+
+    std::string const scs = odil::as_specific_character_set(
+        source, specific_character_set, true);
+    BOOST_REQUIRE_EQUAL(scs, expected);
+}
+
+BOOST_AUTO_TEST_CASE(SCSX1_AsUTF8)
 {
     odil::Value::Strings const specific_character_set = { "ISO_IR 192" };
     std::string const source =
@@ -166,7 +314,8 @@ BOOST_AUTO_TEST_CASE(SCSX1)
         "=";
     std::string const expected =
         "Wang" "^" "XiaoDong"
-        "\x3d\xe7\x8e\x8b" "^" "\xe5\xb0\x8f\xe6\x9d\xb1"
+        "="
+        "\xe7\x8e\x8b" "^" "\xe5\xb0\x8f\xe6\x9d\xb1"
         "=";
 
     std::string const utf8 = odil::as_utf8(
@@ -174,7 +323,26 @@ BOOST_AUTO_TEST_CASE(SCSX1)
     BOOST_REQUIRE_EQUAL(utf8, expected);
 }
 
-BOOST_AUTO_TEST_CASE(SCSX2)
+BOOST_AUTO_TEST_CASE(SCSX1_AsSCS)
+{
+    odil::Value::Strings const specific_character_set = { "ISO_IR 192" };
+    std::string const source =
+        "Wang" "^" "XiaoDong"
+        "="
+        "\xe7\x8e\x8b" "^" "\xe5\xb0\x8f\xe6\x9d\xb1"
+        "=";
+    std::string const expected =
+        "Wang" "^" "XiaoDong"
+        "="
+        "\xe7\x8e\x8b" "^" "\xe5\xb0\x8f\xe6\x9d\xb1"
+        "=";
+
+    std::string const scs = odil::as_specific_character_set(
+        source, specific_character_set, true);
+    BOOST_REQUIRE_EQUAL(scs, expected);
+}
+
+BOOST_AUTO_TEST_CASE(SCSX2_AsUTF8)
 {
     odil::Value::Strings const specific_character_set = { "GB18030" };
     std::string const source =
@@ -192,3 +360,22 @@ BOOST_AUTO_TEST_CASE(SCSX2)
         source, specific_character_set, true);
     BOOST_REQUIRE_EQUAL(utf8, expected);
 }
+
+BOOST_AUTO_TEST_CASE(SCSX2_AsSCS)
+{
+    odil::Value::Strings const specific_character_set = { "GB18030" };
+    std::string const source =
+        "Wang" "^" "XiaoDong"
+        "="
+        "\xe7\x8e\x8b" "^" "\xe5\xb0\x8f\xe4\xb8\x9c"
+        "=";
+    std::string const expected =
+        "Wang" "^" "XiaoDong"
+        "=" ""
+        "\xcd\xf5" "^" "\xd0\xa1\xb6\xab"
+        "=";
+
+    std::string const scs = odil::as_specific_character_set(
+        source, specific_character_set, true);
+    BOOST_REQUIRE_EQUAL(scs, expected);
+}
diff --git a/tests/wrappers/test_association_parameters.py b/tests/wrappers/test_association_parameters.py
index fb4b8d0..721f77b 100644
--- a/tests/wrappers/test_association_parameters.py
+++ b/tests/wrappers/test_association_parameters.py
@@ -27,11 +27,8 @@ class TestAssociationParameters(unittest.TestCase):
         self.assertEqual(parameters.get_calling_ae_title(), "foo")
 
     def test_presentation_contexts(self):
-        presentation_context = odil.AssociationParameters.PresentationContext()
-        presentation_context.id = 1
-        presentation_context.abstract_syntax = "foo"
-        presentation_context.transfer_syntaxes.append("bar")
-
+        presentation_context = odil.AssociationParameters.PresentationContext(
+            1, "foo", ["bar"], True, False)
         parameters = odil.AssociationParameters()
         parameters.set_presentation_contexts([presentation_context])
 
diff --git a/tests/wrappers/test_value.py b/tests/wrappers/test_value.py
index f4b2f79..b461690 100644
--- a/tests/wrappers/test_value.py
+++ b/tests/wrappers/test_value.py
@@ -90,5 +90,11 @@ class TestValueBinary(unittest.TestCase):
         data = odil.Value.Binary(items)
         self.assertEqual([x for x in data[0]], [x for x in items[0]])
 
+    def test_buffer(self):
+        item = odil.Value.BinaryItem("\x01\x02\x03")
+        memory_view = item.get_memory_view()
+        self.assertTrue(isinstance(memory_view, memoryview))
+
+
 if __name__ == "__main__":
     unittest.main()
diff --git a/wrappers/AssocationParameters.cpp b/wrappers/AssocationParameters.cpp
index f545c5e..e799795 100644
--- a/wrappers/AssocationParameters.cpp
+++ b/wrappers/AssocationParameters.cpp
@@ -56,13 +56,14 @@ set_presentation_contexts(
     boost::python::list const & presentation_contexts)
 {
     std::vector<odil::AssociationParameters::PresentationContext> 
-        presentation_contexts_cpp(boost::python::len(presentation_contexts));
+        presentation_contexts_cpp;
+    presentation_contexts_cpp.reserve(boost::python::len(presentation_contexts));
     for(int i = 0; i<boost::python::len(presentation_contexts); ++i)
     {
-        presentation_contexts_cpp[i] = 
+        presentation_contexts_cpp.push_back(
             boost::python::extract<
                 odil::AssociationParameters::PresentationContext
-            >(presentation_contexts[i]);
+            >(presentation_contexts[i]));
     }
     parameters.set_presentation_contexts(presentation_contexts_cpp);
     
@@ -150,7 +151,8 @@ void wrap_AssociationParameters()
 
     {
         scope presentation_context_scope =
-            class_<AssociationParameters::PresentationContext>("PresentationContext")
+            class_<AssociationParameters::PresentationContext>(
+                "PresentationContext", no_init)
             .def(
                 "__init__", 
                 make_constructor(&presentation_context_constructor))
diff --git a/wrappers/Value.cpp b/wrappers/Value.cpp
index 92a7751..829b13e 100644
--- a/wrappers/Value.cpp
+++ b/wrappers/Value.cpp
@@ -35,6 +35,19 @@ boost::shared_ptr<T> create_value(boost::python::object const & sequence)
     return boost::shared_ptr<T>(new T(values));
 }
 
+boost::python::object
+as_memory_view(odil::Value::Binary::value_type const & binary_item)
+{
+    Py_buffer buffer;
+    PyBuffer_FillInfo(
+        &buffer, nullptr,
+        const_cast<odil::Value::Binary::value_type::value_type*>(&binary_item[0]),
+        binary_item.size(), 1, PyBUF_SIMPLE);
+    PyObject * memory_view = PyMemoryView_FromBuffer(&buffer);
+
+    return boost::python::object(boost::python::handle<>(memory_view));
+}
+
 }
 
 void wrap_Value()
@@ -111,6 +124,7 @@ void wrap_Value()
             "__init__",
             make_constructor(create_value<Value::Binary::value_type, char>))
         .def(vector_indexing_suite<Value::Binary::value_type>())
+        .def("get_memory_view", as_memory_view)
     ;
 
     class_<Value::Binary>("Binary")

-- 
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