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 <l...@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:m...@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 _______________________________________________ debian-med-commit mailing list debian-med-commit@lists.alioth.debian.org http://lists.alioth.debian.org/cgi-bin/mailman/listinfo/debian-med-commit