This is an automated email from the git hooks/post-receive script. lamy-guest pushed a commit to branch master in repository odil.
commit ff5fc36d0ba8c992e08d2bc9583dd2790bf5bc28 Author: Julien Lamy <[email protected]> Date: Fri Jul 8 17:50:06 2016 +0200 Imported Upstream version 0.7.1 --- .gitlab-ci.yml | 12 ++ CMakeLists.txt | 4 +- applications/get.py | 125 ++++++++++++++++++++- src/odil/EchoSCP.cpp | 14 ++- src/odil/EchoSCP.h | 5 +- src/odil/Exception.cpp | 4 +- src/odil/Exception.h | 4 +- src/odil/FindSCP.cpp | 20 ++-- src/odil/GetSCP.cpp | 39 +++---- src/odil/MoveSCP.cpp | 39 +++---- src/odil/Reader.cpp | 242 +++++++++++++++++++++------------------- src/odil/Reader.h | 17 +++ src/odil/Reader.txx | 59 ++++++++++ src/odil/SCP.cpp | 15 +++ src/odil/SCP.h | 26 ++++- src/odil/StoreSCP.cpp | 14 ++- src/odil/StoreSCP.h | 5 +- src/odil/VR.cpp | 4 +- src/odil/Writer.cpp | 152 +++++++++++-------------- src/odil/Writer.h | 17 ++- src/odil/Writer.txx | 56 ++++++++++ src/odil/dcmtk/Exception.cpp | 2 +- src/odil/json_converter.cpp | 2 +- src/odil/message/Response.cpp | 17 +++ src/odil/message/Response.h | 14 +++ src/odil/xml_converter.cpp | 2 +- tests/code/message/Response.cpp | 14 +++ 27 files changed, 640 insertions(+), 284 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..2d0abf9 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,12 @@ +before_script: + - apt-get update -qq && apt-get install -y -qq build-essential pkg-config cmake ninja-build libdcmtk2-dev libwrap0-dev libjsoncpp-dev libicu-dev zlib1g-dev libboost-dev libboost-filesystem-dev libboost-python-dev libboost-regex-dev libboost-test-dev liblog4cpp5-dev dcmtk python-minimal python-nose + - mkdir build && cd build + - cmake -G Ninja -D CMAKE_CXX_FLAGS=-std=c++11 ../ + +trusty: + image: ubuntu:trusty + script: ninja && ../tests/run --no-network + +xenial: + image: ubuntu:xenial + script: ninja && ../tests/run --no-network diff --git a/CMakeLists.txt b/CMakeLists.txt index c5a1276..c3d7bf3 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 0) +set(odil_PATCH_VERSION 1) set(odil_VERSION ${odil_MAJOR_VERSION}.${odil_MINOR_VERSION}.${odil_PATCH_VERSION}) @@ -58,7 +58,7 @@ endif() add_custom_target( CIIntegration ${CMAKE_COMMAND} -E echo "CI Integration" - SOURCES appveyor.yml .travis.yml) + SOURCES appveyor.yml .gitlab-ci.yml .travis.yml) set_target_properties(CIIntegration PROPERTIES FOLDER "Utils") add_custom_target( diff --git a/applications/get.py b/applications/get.py index 30fb046..813c96b 100644 --- a/applications/get.py +++ b/applications/get.py @@ -2,10 +2,12 @@ from __future__ import print_function import argparse import logging import os +import re import odil from print_ import find_max_name_length, print_data_set +from dicomdir import create_dicomdir def add_subparser(subparsers): parser = subparsers.add_parser( @@ -24,10 +26,40 @@ def add_subparser(subparsers): parser.add_argument( "--directory", "-d", default=os.getcwd(), help="Directory where the output files will be stored") + parser.add_argument( + "--iso-9660", "-I", action="store_true", + help="Save file names using ISO-9660 compatible file names") + parser.add_argument( + "--layout", "-l", choices=["flat", "tree"], default="flat", + help="Save files in the same directory (flat) or in a " + "patient/study/series tree (hierarchical)") + parser.add_argument( + "--dicomdir", "-D", action="store_true", + help="Create a DICOMDIR from the retrieved files") + parser.add_argument( + "--patient-key", "-p", default=[], action="append", + help="User-defined keys for PATIENT-level records, " + "expressed as KEYWORD[:TYPE]. TYPE defaults to 3.") + parser.add_argument( + "--study-key", "-S", default=[], action="append", + help="User-defined keys for STUDY-level records") + parser.add_argument( + "--series-key", "-s", default=[], action="append", + help="User-defined keys for SERIES-level records") + parser.add_argument( + "--image-key", "-i", default=[], action="append", + help="User-defined keys for IMAGE-level records") parser.set_defaults(function=get) return parser -def get(host, port, calling_ae_title, called_ae_title, level, keys, directory): +def get( + host, port, calling_ae_title, called_ae_title, level, keys, directory, + iso_9660, layout, + dicomdir, patient_key, study_key, series_key, image_key): + + if dicomdir and not iso_9660: + raise Exception("Cannot create a DICOMDIR without ISO-9660 filenames") + query = odil.DataSet() for key in keys: key, value = key.split("=", 1) @@ -88,10 +120,84 @@ def get(host, port, calling_ae_title, called_ae_title, level, keys, directory): self.remaining = 0 self.failed = 0 self.warning = 0 + self.stored = {} + self.files = [] + self._study_ids = {} + self._series_ids = {} def store(self, data_set): - uid = data_set.as_string("SOPInstanceUID")[0] - odil.write(data_set, os.path.join(self.directory, uid)) + if layout == "flat": + directory = "" + elif layout == "tree": + # Patient directory: <PatientName> or <PatientID>. + patient_directory = None + if "PatientName" in data_set and data_set.as_string("PatientName"): + patient_directory = data_set.as_string("PatientName")[0] + else: + + patient_directory = data_set.as_string("PatientID")[0] + + # Study directory: <StudyID>_<StudyDescription>, both parts are + # optional. If both tags are missing or empty, default to a + # numeric index based on StudyInstanceUID. + study_directory = [] + if "StudyID" in data_set and data_set.as_string("StudyID"): + study_directory.append(data_set.as_string("StudyID")[0]) + if ("StudyDescription" in data_set and + data_set.as_string("StudyDescription")): + study_directory.append( + data_set.as_string("StudyDescription")[0]) + + if not study_directory: + study_instance_uid = data_set.as_string("StudyInstanceUID")[0] + study_directory.append( + self._study_ids.setdefault( + study_instance_uid, 1+len(self._study_ids))) + + study_directory = "_".join(study_directory) + + # Study directory: <SeriesNumber>_<SeriesDescription>, both + # parts are optional. If both tags are missing or empty, default + # to a numeric index based on SeriesInstanceUID. + series_directory = [] + if "SeriesNumber" in data_set and data_set.as_int("SeriesNumber"): + series_directory.append(str(data_set.as_int("SeriesNumber")[0])) + if ("SeriesDescription" in data_set and + data_set.as_string("SeriesDescription")): + series_directory.append( + data_set.as_string("SeriesDescription")[0]) + + if not series_directory: + series_instance_uid = data_set.as_string("series_instance_uid")[0] + series_directory.append( + self._series_ids.setdefault( + series_instance_uid, 1+len(self._series_ids))) + + series_directory = "_".join(series_directory) + + if iso_9660: + patient_directory = to_iso_9660(patient_directory) + study_directory = to_iso_9660(study_directory) + series_directory = to_iso_9660(series_directory) + directory = os.path.join( + patient_directory, study_directory, series_directory) + if not os.path.isdir(os.path.join(self.directory, directory)): + os.makedirs(os.path.join(self.directory, directory)) + else: + raise NotImplementedError() + + self.stored.setdefault(directory, 0) + + if iso_9660: + filename = "IM{:06d}".format(1+self.stored[directory]) + else: + filename = data_set.as_string("SOPInstanceUID")[0] + + odil.write( + data_set, os.path.join(self.directory, directory, filename)) + + self.stored[directory] += 1 + self.files.append(os.path.join(directory, filename)) def get(self, message): for type_ in ["completed", "remaining", "failed", "warning"]: @@ -105,6 +211,8 @@ def get(host, port, calling_ae_title, called_ae_title, level, keys, directory): if not os.path.isdir(directory): os.makedirs(directory) + if len(os.listdir(directory)): + logging.warning("{} is not empty".format(directory)) callback = Callback(directory) get.get(query, callback.store, callback.get) @@ -115,3 +223,14 @@ def get(host, port, calling_ae_title, called_ae_title, level, keys, directory): association.release() logging.info("Association released") + + if dicomdir: + logging.info("Creating DICOMDIR") + create_dicomdir( + [os.path.join(directory, x) for x in callback.files], + directory, patient_key, study_key, series_key, image_key) + +def to_iso_9660(value): + value = value[:8].upper() + value = re.sub(r"[^A-Z0-9_]", "_", value) + return value diff --git a/src/odil/EchoSCP.cpp b/src/odil/EchoSCP.cpp index f663c77..e80e24b 100644 --- a/src/odil/EchoSCP.cpp +++ b/src/odil/EchoSCP.cpp @@ -60,21 +60,25 @@ EchoSCP message::CEchoRequest const request(message); Value::Integer status=message::CEchoResponse::Success; + DataSet status_fields; try { status = this->_callback(request); } - catch(Exception const &) + catch(SCP::Exception const & e) + { + status = e.status; + status_fields = e.status_fields; + } + catch(odil::Exception const &) { status = message::CEchoResponse::ProcessingFailure; - // Error Comment - // Error ID - // Affected SOP Class UID } - message::CEchoResponse const response( + message::CEchoResponse response( request.get_message_id(), status, request.get_affected_sop_class_uid()); + response.set_status_fields(status_fields); this->_association.send_message( response, request.get_affected_sop_class_uid()); } diff --git a/src/odil/EchoSCP.h b/src/odil/EchoSCP.h index ef9680b..5fa0433 100644 --- a/src/odil/EchoSCP.h +++ b/src/odil/EchoSCP.h @@ -24,7 +24,10 @@ namespace odil class EchoSCP: public SCP { public: - /// @brief Callback called when a request is received. + /** + * @brief Callback called when a request is received, shall throw an + * SCP::Exception on error. + */ typedef std::function<Value::Integer(message::CEchoRequest const &)> Callback; /// @brief Constructor. diff --git a/src/odil/Exception.cpp b/src/odil/Exception.cpp index ce8ae19..334a995 100644 --- a/src/odil/Exception.cpp +++ b/src/odil/Exception.cpp @@ -22,14 +22,14 @@ Exception } Exception -::~Exception() throw() +::~Exception() noexcept { // Nothing to do. } char const * Exception -::what() const throw() +::what() const noexcept { return this->_message.c_str(); } diff --git a/src/odil/Exception.h b/src/odil/Exception.h index 2e01b2c..89583d6 100644 --- a/src/odil/Exception.h +++ b/src/odil/Exception.h @@ -23,10 +23,10 @@ public: Exception(std::string const & message=""); /// @brief Destructor. - virtual ~Exception() throw(); + virtual ~Exception() noexcept; /// @brief Return the reason for the exception. - virtual const char* what() const throw(); + virtual const char* what() const noexcept; protected: /// @brief Message of the exception. diff --git a/src/odil/FindSCP.cpp b/src/odil/FindSCP.cpp index bc7badb..97be4c3 100644 --- a/src/odil/FindSCP.cpp +++ b/src/odil/FindSCP.cpp @@ -62,6 +62,9 @@ FindSCP { message::CFindRequest const request(message); + Value::Integer final_status = message::CFindResponse::Success; + DataSet status_fields; + try { this->_generator->initialize(request); @@ -76,17 +79,18 @@ FindSCP this->_generator->next(); } } - catch(Exception const & e) + catch(SCP::Exception const & e) + { + final_status = e.status; + status_fields = e.status_fields; + } + catch(odil::Exception const & e) { - message::CFindResponse response( - request.get_message_id(), message::CFindResponse::UnableToProcess); - this->_association.send_message( - response, request.get_affected_sop_class_uid()); - return; + final_status = message::CFindResponse::UnableToProcess; } - message::CFindResponse response( - request.get_message_id(), message::CFindResponse::Success); + message::CFindResponse response(request.get_message_id(), final_status); + response.set_status_fields(status_fields); this->_association.send_message( response, request.get_affected_sop_class_uid()); } diff --git a/src/odil/GetSCP.cpp b/src/odil/GetSCP.cpp index a72262c..75354a4 100644 --- a/src/odil/GetSCP.cpp +++ b/src/odil/GetSCP.cpp @@ -65,6 +65,8 @@ GetSCP StoreSCU store_scu(this->_association); + Value::Integer final_status = message::CGetResponse::Success; + DataSet status_fields; unsigned int remaining_sub_operations = 0; unsigned int completed_sub_operations=0; unsigned int failed_sub_operations=0; @@ -107,33 +109,22 @@ GetSCP this->_generator->next(); } } - catch(Exception const &) + catch(SCP::Exception const & e) { - message::CGetResponse response( - request.get_message_id(), message::CGetResponse::UnableToProcess); - response.set_number_of_remaining_sub_operations( - remaining_sub_operations); - response.set_number_of_completed_sub_operations( - completed_sub_operations); - response.set_number_of_failed_sub_operations( - failed_sub_operations); - response.set_number_of_warning_sub_operations( - warning_sub_operations); - this->_association.send_message( - response, request.get_affected_sop_class_uid()); - return; + final_status = e.status; + status_fields = e.status_fields; + } + catch(odil::Exception const &) + { + final_status = message::CGetResponse::UnableToProcess; } - message::CGetResponse response( - request.get_message_id(), message::CGetResponse::Success); - response.set_number_of_remaining_sub_operations( - remaining_sub_operations); - response.set_number_of_completed_sub_operations( - completed_sub_operations); - response.set_number_of_failed_sub_operations( - failed_sub_operations); - response.set_number_of_warning_sub_operations( - warning_sub_operations); + message::CGetResponse response(request.get_message_id(), final_status); + response.set_status_fields(status_fields); + response.set_number_of_remaining_sub_operations(remaining_sub_operations); + response.set_number_of_completed_sub_operations(completed_sub_operations); + response.set_number_of_failed_sub_operations(failed_sub_operations); + response.set_number_of_warning_sub_operations(warning_sub_operations); this->_association.send_message( response, request.get_affected_sop_class_uid()); } diff --git a/src/odil/MoveSCP.cpp b/src/odil/MoveSCP.cpp index 739925a..ee26243 100644 --- a/src/odil/MoveSCP.cpp +++ b/src/odil/MoveSCP.cpp @@ -70,6 +70,8 @@ MoveSCP move_association.associate(); StoreSCU store_scu(move_association); + Value::Integer final_status = message::CMoveResponse::Success; + DataSet status_fields; unsigned int remaining_sub_operations = 0; unsigned int completed_sub_operations=0; unsigned int failed_sub_operations=0; @@ -116,33 +118,22 @@ MoveSCP this->_generator->next(); } } - catch(Exception const &) + catch(SCP::Exception const & e) { - message::CMoveResponse response( - request.get_message_id(), message::CMoveResponse::UnableToProcess); - response.set_number_of_remaining_sub_operations( - remaining_sub_operations); - response.set_number_of_completed_sub_operations( - completed_sub_operations); - response.set_number_of_failed_sub_operations( - failed_sub_operations); - response.set_number_of_warning_sub_operations( - warning_sub_operations); - this->_association.send_message( - response, request.get_affected_sop_class_uid()); - return; + final_status = e.status; + status_fields = e.status_fields; + } + catch(odil::Exception const &) + { + final_status = message::CMoveResponse::UnableToProcess; } - message::CMoveResponse response( - request.get_message_id(), message::CMoveResponse::Success); - response.set_number_of_remaining_sub_operations( - remaining_sub_operations); - response.set_number_of_completed_sub_operations( - completed_sub_operations); - response.set_number_of_failed_sub_operations( - failed_sub_operations); - response.set_number_of_warning_sub_operations( - warning_sub_operations); + message::CMoveResponse response(request.get_message_id(), final_status); + response.set_status_fields(status_fields); + response.set_number_of_remaining_sub_operations(remaining_sub_operations); + response.set_number_of_completed_sub_operations(completed_sub_operations); + response.set_number_of_failed_sub_operations(failed_sub_operations); + response.set_number_of_warning_sub_operations(warning_sub_operations); this->_association.send_message( response, request.get_affected_sop_class_uid()); } diff --git a/src/odil/Reader.cpp b/src/odil/Reader.cpp index dc3dfd4..988acd9 100644 --- a/src/odil/Reader.cpp +++ b/src/odil/Reader.cpp @@ -26,37 +26,6 @@ #include "odil/VR.h" #include "odil/VRFinder.h" -#define odil_read_binary(type, value, stream, byte_ordering, size) \ -type value; \ -{ \ - uint##size##_t raw; \ - stream.read(reinterpret_cast<char*>(&raw), sizeof(raw)); \ - if(!stream) \ - { \ - throw Exception("Could not read from stream"); \ - } \ - if(byte_ordering == ByteOrdering::LittleEndian) \ - { \ - raw = little_endian_to_host(raw); \ - } \ - else if(byte_ordering == ByteOrdering::BigEndian) \ - { \ - raw = big_endian_to_host(raw); \ - } \ - else \ - { \ - throw Exception("Unknown endianness"); \ - } \ - value = *reinterpret_cast<type*>(&raw);\ -} - -#define odil_ignore(stream, size) \ - stream.ignore(size); \ - if(!stream) \ - { \ - throw Exception("Could not read from stream"); \ - } - std::string read_string(std::istream & stream, unsigned int size) { if(size == 0) @@ -75,6 +44,66 @@ std::string read_string(std::istream & stream, unsigned int size) namespace odil { +Value::Binary +Reader +::read_encapsulated_pixel_data( + std::istream & stream, ByteOrdering byte_ordering, + std::string transfer_syntax, bool keep_group_length) +{ + Value::Binary value; + + // PS 3.5, A.4 + Reader const sequence_reader(stream, transfer_syntax, keep_group_length); + + bool done = false; + while(!done) + { + auto const tag = sequence_reader.read_tag(); + auto const item_length = Reader::read_binary<uint32_t>( + stream, byte_ordering); + + if(tag == registry::Item) + { + Value::Binary::value_type item_data(item_length); + + if(item_length > 0) + { + stream.read( + reinterpret_cast<char*>(&item_data[0]), item_length); + if(!stream) + { + throw Exception("Could not read from stream"); + } + } + + value.push_back(item_data); + } + else if(tag == registry::SequenceDelimitationItem) + { + // No value for Sequence Delimitation Item + done = true; + } + else + { + throw Exception( + "Expected SequenceDelimitationItem, got: "+std::string(tag)); + } + } + + return value; +} + +void +Reader +::ignore(std::istream & stream, std::streamsize size) +{ + stream.ignore(size); + if(!stream) + { + throw Exception("Could not read from stream"); + } +} + Reader ::Reader( std::istream & stream, std::string const & transfer_syntax, @@ -126,8 +155,10 @@ Tag Reader ::read_tag() const { - odil_read_binary(uint16_t, group, this->stream, this->byte_ordering, 16); - odil_read_binary(uint16_t, element, this->stream, this->byte_ordering, 16); + auto const group = this->read_binary<uint16_t>( + this->stream, this->byte_ordering); + auto const element = this->read_binary<uint16_t>( + this->stream, this->byte_ordering); return Tag(group, element); } @@ -169,7 +200,7 @@ Reader { value = Value(Value::DataSets()); } - else if(vr == VR::OB || vr == VR::OF || vr == VR::OW || vr == VR::UN) + else if(is_binary(vr)) { value = Value(Value::Binary()); } @@ -284,26 +315,26 @@ Reader::Visitor { if(this->vr == VR::SL) { - odil_read_binary( - int32_t, item, this->stream, this->byte_ordering, 32); + auto const item = Reader::read_binary<int32_t>( + this->stream, this->byte_ordering); value[i] = item; } else if(this->vr == VR::SS) { - odil_read_binary( - int16_t, item, this->stream, this->byte_ordering, 16); + auto const item = Reader::read_binary<int16_t>( + this->stream, this->byte_ordering); value[i] = item; } else if(this->vr == VR::UL) { - odil_read_binary( - uint32_t, item, this->stream, this->byte_ordering, 32); + auto const item = Reader::read_binary<uint32_t>( + this->stream, this->byte_ordering); value[i] = item; } else if(this->vr == VR::AT || this->vr == VR::US) { - odil_read_binary( - uint16_t, item, this->stream, this->byte_ordering, 16); + auto const item = Reader::read_binary<uint16_t>( + this->stream, this->byte_ordering); value[i] = item; } } @@ -353,14 +384,14 @@ Reader::Visitor { if(this->vr == VR::FD) { - odil_read_binary( - double, item, this->stream, this->byte_ordering, 64); + auto const item = Reader::read_binary<double>( + this->stream, this->byte_ordering); value[i] = item; } else if(this->vr == VR::FL) { - odil_read_binary( - float, item, this->stream, this->byte_ordering, 32); + auto const item = Reader::read_binary<float>( + this->stream, this->byte_ordering); value[i] = item; } } @@ -460,7 +491,7 @@ Reader::Visitor else if(tag == registry::SequenceDelimitationItem) { done = true; - odil_ignore(this->stream, 4); + Reader::ignore(this->stream, 4); } else { @@ -481,7 +512,9 @@ Reader::Visitor } else if(vl == 0xffffffff) { - value = this->read_encapsulated_pixel_data(this->stream); + value = Reader::read_encapsulated_pixel_data( + this->stream, this->byte_ordering, this->transfer_syntax, + this->keep_group_length); } else { @@ -492,6 +525,21 @@ Reader::Visitor this->stream.read( reinterpret_cast<char*>(&value[0][0]), value[0].size()); } + else if(this->vr == VR::OD) + { + if(vl%8 != 0) + { + throw Exception("Cannot read OD for odd-sized array"); + } + + value[0].resize(vl); + for(unsigned int i=0; i<value[0].size(); i+=8) + { + auto const item = Reader::read_binary<double>( + this->stream, this->byte_ordering); + *reinterpret_cast<double*>(&value[0][i]) = item; + } + } else if(this->vr == VR::OF) { if(vl%4 != 0) @@ -502,11 +550,26 @@ Reader::Visitor value[0].resize(vl); for(unsigned int i=0; i<value[0].size(); i+=4) { - odil_read_binary( - float, item, this->stream, this->byte_ordering, 32); + auto const item = Reader::read_binary<float>( + this->stream, this->byte_ordering); *reinterpret_cast<float*>(&value[0][i]) = item; } } + else if(this->vr == VR::OL) + { + if(vl%4 != 0) + { + throw Exception("Cannot read OL for odd-sized array"); + } + + value[0].resize(vl); + for(unsigned int i=0; i<value[0].size(); i+=4) + { + auto const item = Reader::read_binary<uint32_t>( + this->stream, this->byte_ordering); + *reinterpret_cast<uint32_t*>(&value[0][i]) = item; + } + } else if(this->vr == VR::OW) { if(vl%2 != 0) @@ -517,8 +580,8 @@ Reader::Visitor value[0].resize(vl); for(unsigned int i=0; i<value[0].size(); i+=2) { - odil_read_binary( - uint16_t, item, this->stream, this->byte_ordering, 16); + auto const item = Reader::read_binary<uint16_t>( + this->stream, this->byte_ordering); *reinterpret_cast<uint16_t*>(&value[0][i]) = item; } } @@ -536,25 +599,26 @@ Reader::Visitor uint32_t length; if(this->explicit_vr) { - if(vr == VR::OB || vr == VR::OW || vr == VR::OF || vr == VR::SQ || - vr == VR::UC || vr == VR::UR || vr == VR::UT || vr == VR::UN) + 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) { - odil_ignore(this->stream, 2); - odil_read_binary( - uint32_t, vl, this->stream, this->byte_ordering, 32); + Reader::ignore(this->stream, 2); + auto const vl = Reader::read_binary<uint32_t>( + this->stream, this->byte_ordering); length = vl; } else { - odil_read_binary( - uint16_t, vl, this->stream, this->byte_ordering, 16); + auto const vl = Reader::read_binary<uint16_t>( + this->stream, this->byte_ordering); length = vl; } } else { - odil_read_binary( - uint32_t, vl, this->stream, this->byte_ordering, 32); + auto const vl = Reader::read_binary<uint32_t>( + this->stream, this->byte_ordering); length = vl; } @@ -590,8 +654,8 @@ DataSet Reader::Visitor ::read_item(std::istream & specific_stream) const { - odil_read_binary( - uint32_t, item_length, specific_stream, this->byte_ordering, 32); + auto const item_length = Reader::read_binary<uint32_t>( + specific_stream, this->byte_ordering); DataSet item; if(item_length != 0xffffffff) @@ -616,60 +680,10 @@ Reader::Visitor { throw Exception("Unexpected tag: "+std::string(tag)); } - odil_ignore(specific_stream, 4); + Reader::ignore(specific_stream, 4); } return item; } -Value::Binary -Reader::Visitor -::read_encapsulated_pixel_data(std::istream & specific_stream) const -{ - Value::Binary value; - - // PS 3.5, A.4 - Reader const sequence_reader( - specific_stream, this->transfer_syntax, this->keep_group_length); - bool done = false; - while(!done) - { - auto const tag = sequence_reader.read_tag(); - odil_read_binary( - uint32_t, item_length, specific_stream, this->byte_ordering, 32); - - if(tag == registry::Item) - { - Value::Binary::value_type item_data(item_length); - - if(item_length > 0) - { - specific_stream.read( - reinterpret_cast<char*>(&item_data[0]), item_length); - if(!stream) - { - throw Exception("Could not read from stream"); - } - } - - value.push_back(item_data); - } - else if(tag == registry::SequenceDelimitationItem) - { - // No value for Sequence Delimitation Item - done = true; - } - else - { - throw Exception( - "Expected SequenceDelimitationItem, got: "+std::string(tag)); - } - } - - return value; -} - } - -#undef odil_ignore -#undef odil_read_binary diff --git a/src/odil/Reader.h b/src/odil/Reader.h index 00e1fb5..5ae85a5 100644 --- a/src/odil/Reader.h +++ b/src/odil/Reader.h @@ -44,6 +44,21 @@ public: bool keep_group_length; /** + * @brief Read binary data from an stream encoded with the given endianness, + * ensure stream is still good. + */ + template<typename T> + static T read_binary(std::istream & stream, ByteOrdering byte_ordering); + + /// @brief Read pixel data in encapsulated form. + static Value::Binary read_encapsulated_pixel_data( + std::istream & stream, ByteOrdering byte_ordering, + std::string transfer_syntax, bool keep_group_length=false); + + /// @brief Ignore data from a stream, ensure stream is still good. + static void ignore(std::istream & stream, std::streamsize size); + + /** * @brief Build a reader, derive byte ordering and explicit-ness of VR * from transfer syntax. */ @@ -107,4 +122,6 @@ private: } +#include "odil/Reader.txx" + #endif // _aa2965aa_e891_4713_9c90_e8eacd2944ea diff --git a/src/odil/Reader.txx b/src/odil/Reader.txx new file mode 100644 index 0000000..d1f7df1 --- /dev/null +++ b/src/odil/Reader.txx @@ -0,0 +1,59 @@ +/************************************************************************* + * odil - Copyright (C) Universite de Strasbourg + * Distributed under the terms of the CeCILL-B license, as published by + * the CEA-CNRS-INRIA. Refer to the LICENSE file or to + * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html + * for details. + ************************************************************************/ + +#ifndef _b5ac563c_c5fd_4dcc_815c_66868a4b9614 +#define _b5ac563c_c5fd_4dcc_815c_66868a4b9614 + +#include "odil/Reader.h" + +#include <functional> +#include <istream> +#include <string> +#include <utility> + +#include "odil/DataSet.h" +#include "odil/Element.h" +#include "odil/Exception.h" +#include "odil/endian.h" +#include "odil/Tag.h" +#include "odil/Value.h" +#include "odil/VR.h" + +namespace odil +{ + +template<typename T> +T +Reader +::read_binary(std::istream & stream, ByteOrdering byte_ordering) +{ + T value; + stream.read(reinterpret_cast<char*>(&value), sizeof(value)); + if(!stream) + { + throw Exception("Could not read from stream"); + } + if(byte_ordering == ByteOrdering::LittleEndian) + { + value = little_endian_to_host(value); + } + else if(byte_ordering == ByteOrdering::BigEndian) + { + value = big_endian_to_host(value); + } + else + { + throw Exception("Unknown endianness"); + } + + return value; +} + +} + +#endif // _b5ac563c_c5fd_4dcc_815c_66868a4b9614 diff --git a/src/odil/SCP.cpp b/src/odil/SCP.cpp index 6132188..854c0b6 100644 --- a/src/odil/SCP.cpp +++ b/src/odil/SCP.cpp @@ -22,6 +22,21 @@ SCP::DataSetGenerator // Nothing to do. } +SCP::Exception +::Exception( + std::string const & message, + Value::Integer status, DataSet const & status_fields) +: ::odil::Exception(message), status(status), status_fields(status_fields) +{ + // Nothing else. +} + +SCP::Exception +::~Exception() noexcept +{ + // Nothing to do. +} + SCP ::SCP(Association & association) : _association(association) diff --git a/src/odil/SCP.h b/src/odil/SCP.h index fa0dc09..4f55077 100644 --- a/src/odil/SCP.h +++ b/src/odil/SCP.h @@ -11,8 +11,10 @@ #include "odil/Association.h" #include "odil/DataSet.h" +#include "odil/Exception.h" #include "odil/message/Message.h" #include "odil/message/Request.h" +#include "odil/Value.h" namespace odil { @@ -21,7 +23,11 @@ namespace odil class SCP { public: - /// @brief Abstract base class for SCP returning multiple data sets. + /** + * @brief Abstract base class for SCP returning multiple data sets. + * + * initialize, done, next and get shall throw an SCP::Exception on error. + */ class DataSetGenerator { public: @@ -41,6 +47,24 @@ public: virtual DataSet get() const =0; }; + class Exception: public odil::Exception + { + public: + /// @brief Status to be sent back to user. + Value::Integer status; + + /// @brief Status detail fields (e.g. offending element). + DataSet status_fields; + + /// @brief Constructor. + Exception( + std::string const & message, + Value::Integer status, DataSet const & status_fields=DataSet()); + + /// @brief Destructor. + virtual ~Exception() noexcept; + }; + /// @brief Create a Service Class Provider. SCP(Association & association); diff --git a/src/odil/StoreSCP.cpp b/src/odil/StoreSCP.cpp index 2dd4b79..ae1a3a9 100644 --- a/src/odil/StoreSCP.cpp +++ b/src/odil/StoreSCP.cpp @@ -63,20 +63,24 @@ StoreSCP message::CStoreRequest const request(message); Value::Integer status=message::CStoreResponse::Success; + DataSet status_fields; try { status = this->_callback(request); } - catch(Exception const & exception) + catch(SCP::Exception const & e) + { + status = e.status; + status_fields = e.status_fields; + } + catch(odil::Exception const &) { status = message::CStoreResponse::ProcessingFailure; - // Error Comment - // Error ID - // Affected SOP Class UID } - message::CStoreResponse const response(request.get_message_id(), status); + message::CStoreResponse response(request.get_message_id(), status); + response.set_status_fields(status_fields); this->_association.send_message( response, request.get_affected_sop_class_uid()); } diff --git a/src/odil/StoreSCP.h b/src/odil/StoreSCP.h index f92b71f..55ac977 100644 --- a/src/odil/StoreSCP.h +++ b/src/odil/StoreSCP.h @@ -24,7 +24,10 @@ namespace odil class StoreSCP: public SCP { public: - /// @brief Callback called when a request is received. + /** + * @brief Callback called when a request is received, shall throw an + * SCP::Exception on error. + */ typedef std::function<Value::Integer(message::CStoreRequest const &)> Callback; /// @brief Constructor. diff --git a/src/odil/VR.cpp b/src/odil/VR.cpp index 6d1e828..eb76674 100644 --- a/src/odil/VR.cpp +++ b/src/odil/VR.cpp @@ -141,7 +141,9 @@ bool is_string(VR vr) bool is_binary(VR vr) { - return (vr == VR::OB || vr == VR::OF || vr == VR::OW || vr == VR::UN); + return ( + vr == VR::OB || vr == VR::OD || vr == VR::OF || vr == VR::OL || + vr == VR::OW || vr == VR::UN); } diff --git a/src/odil/Writer.cpp b/src/odil/Writer.cpp index 9d98e2f..55186b2 100644 --- a/src/odil/Writer.cpp +++ b/src/odil/Writer.cpp @@ -26,31 +26,40 @@ #include "odil/VR.h" #include "odil/write_ds.h" -#define odil_write_binary(value, stream, byte_ordering, size) \ -{ \ - auto raw = value; \ - if(byte_ordering == ByteOrdering::LittleEndian) \ - { \ - raw = host_to_little_endian(raw); \ - } \ - else if(byte_ordering == ByteOrdering::BigEndian) \ - { \ - raw = host_to_big_endian(raw); \ - } \ - else \ - { \ - throw Exception("Unknown endianness"); \ - } \ - stream.write(reinterpret_cast<char const*>(&raw), sizeof(raw)); \ - if(!stream) \ - { \ - throw Exception("Could not write to stream"); \ - } \ -} - namespace odil { +void +Writer +::write_encapsulated_pixel_data( + Value::Binary const & value, std::ostream & stream, + ByteOrdering byte_ordering, bool explicit_vr) +{ + Writer writer(stream, byte_ordering, explicit_vr); + uint32_t length; + for(auto const & fragment: value) + { + writer.write_tag(registry::Item); + length = fragment.size(); + Writer::write_binary(length, stream, byte_ordering); + if(length > 0) + { + stream.write(reinterpret_cast<char const*>(&fragment[0]), length); + if(!stream) + { + throw Exception("Could not write to stream"); + } + } + } + writer.write_tag(registry::SequenceDelimitationItem); + length = 0; + Writer::write_binary(length, stream, byte_ordering); + if(!stream) + { + throw Exception("Could not write to stream"); + } +} + Writer ::Writer( std::ostream & stream, @@ -132,8 +141,8 @@ void Writer ::write_tag(Tag const & tag) const { - odil_write_binary(tag.group, this->stream, this->byte_ordering, 16); - odil_write_binary(tag.element, this->stream, this->byte_ordering, 16); + this->write_binary(tag.group, this->stream, this->byte_ordering); + this->write_binary(tag.element, this->stream, this->byte_ordering); } void @@ -165,10 +174,11 @@ Writer // Write VL if(this->explicit_vr) { - if(vr == VR::OB || vr == VR::OW || vr == VR::OF || vr == VR::SQ || - vr == VR::UC || vr == VR::UR || vr == VR::UT || vr == VR::UN) + 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) { - odil_write_binary(uint16_t(0), this->stream, this->byte_ordering, 16); + this->write_binary(uint16_t(0), this->stream, this->byte_ordering); uint32_t vl; if(vr == VR::SQ && @@ -176,7 +186,7 @@ Writer { vl = 0xffffffff; } - else if((vr == VR::OB || vr == VR::OW) && element.size() > 1) + else if(is_binary(vr) && element.size() > 1) { vl = 0xffffffff; } @@ -184,16 +194,18 @@ Writer { vl = value_stream.tellp(); } - odil_write_binary(vl, this->stream, this->byte_ordering, 32); + this->write_binary(vl, this->stream, this->byte_ordering); } else { - odil_write_binary(uint16_t(value_stream.tellp()), this->stream, this->byte_ordering, 16); + this->write_binary( + uint16_t(value_stream.tellp()), this->stream, this->byte_ordering); } } else { - odil_write_binary(uint32_t(value_stream.tellp()), this->stream, this->byte_ordering, 32); + this->write_binary( + uint32_t(value_stream.tellp()), this->stream, this->byte_ordering); } this->stream.write(value_stream.str().c_str(), value_stream.tellp()); @@ -314,32 +326,32 @@ Writer::Visitor { for(auto item: value) { - odil_write_binary( - int32_t(item), this->stream, this->byte_ordering, 32); + Writer::write_binary( + int32_t(item), this->stream, this->byte_ordering); } } else if(this->vr == VR::SS) { for(auto item: value) { - odil_write_binary( - int16_t(item), this->stream, this->byte_ordering, 16); + Writer::write_binary( + int16_t(item), this->stream, this->byte_ordering); } } else if(this->vr == VR::UL) { for(auto item: value) { - odil_write_binary( - uint32_t(item), this->stream, this->byte_ordering, 32); + Writer::write_binary( + uint32_t(item), this->stream, this->byte_ordering); } } else if(this->vr == VR::AT || this->vr == VR::US) { for(auto item: value) { - odil_write_binary( - uint16_t(item), this->stream, this->byte_ordering, 16); + Writer::write_binary( + uint16_t(item), this->stream, this->byte_ordering); } } else @@ -364,9 +376,10 @@ Writer::Visitor throw Exception("DS items must be finite"); } - // Each item in the DS is at most 16 bytes. - static char buffer[16]; - write_ds(item, buffer, 16); + // Each item in the DS is at most 16 bytes, account for NUL at end + static unsigned int const buffer_size=16+1; + static char buffer[buffer_size]; + write_ds(item, buffer, buffer_size); auto const length = strlen(buffer); this->stream.write(buffer, length); @@ -396,16 +409,16 @@ Writer::Visitor { for(auto const & item: value) { - odil_write_binary( - double(item), this->stream, this->byte_ordering, 64); + Writer::write_binary( + double(item), this->stream, this->byte_ordering); } } else if(this->vr == VR::FL) { for(auto const & item: value) { - odil_write_binary( - float(item), this->stream, this->byte_ordering, 32); + Writer::write_binary( + float(item), this->stream, this->byte_ordering); } } else @@ -467,7 +480,7 @@ Writer::Visitor { item_length = 0xffffffff; } - odil_write_binary(item_length, sequence_stream, this->byte_ordering, 32); + Writer::write_binary(item_length, sequence_stream, this->byte_ordering); // Data set sequence_stream.write(item_stream.str().c_str(), item_stream.tellp()); @@ -480,7 +493,7 @@ Writer::Visitor if(this->item_encoding == ItemEncoding::UndefinedLength) { sequence_writer.write_tag(registry::ItemDelimitationItem); - odil_write_binary(uint32_t(0), sequence_stream, this->byte_ordering, 32); + Writer::write_binary(uint32_t(0), sequence_stream, this->byte_ordering); } } @@ -488,7 +501,7 @@ Writer::Visitor if(this->item_encoding == ItemEncoding::UndefinedLength) { sequence_writer.write_tag(registry::SequenceDelimitationItem); - odil_write_binary(uint32_t(0), sequence_stream, this->byte_ordering, 32); + Writer::write_binary(uint32_t(0), sequence_stream, this->byte_ordering); } this->stream.write(sequence_stream.str().c_str(), sequence_stream.tellp()); @@ -508,7 +521,8 @@ Writer::Visitor } else if(value.size() > 1) { - this->write_encapsulated_pixel_data(value); + Writer::write_encapsulated_pixel_data( + value, this->stream, this->byte_ordering, this->explicit_vr); } else { @@ -526,7 +540,7 @@ Writer::Visitor for(int i=0; i<value[0].size(); i+=2) { uint16_t item = *reinterpret_cast<uint16_t const *>(&value[0][i]); - odil_write_binary(item, this->stream, this->byte_ordering, 16); + Writer::write_binary(item, this->stream, this->byte_ordering); } } else if(this->vr == VR::OF) @@ -538,7 +552,7 @@ Writer::Visitor for(int i=0; i<value[0].size(); i+=4) { uint32_t item = *reinterpret_cast<uint32_t const *>(&value[0][i]); - odil_write_binary(item, this->stream, this->byte_ordering, 32); + Writer::write_binary(item, this->stream, this->byte_ordering); } } else @@ -601,38 +615,4 @@ Writer::Visitor } } -void -Writer::Visitor -::write_encapsulated_pixel_data(Value::Binary const & value) const -{ - // FIXME: handle fragments - Writer writer(this->stream, this->byte_ordering, this->explicit_vr); - uint32_t length; - for(auto const & fragment: value) - { - writer.write_tag(registry::Item); - length = fragment.size(); - odil_write_binary( - length, this->stream, this->byte_ordering, 8*sizeof(length)); - if(length > 0) - { - this->stream.write(reinterpret_cast<char const*>(&fragment[0]), length); - if(!this->stream) - { - throw Exception("Could not write to stream"); - } - } - } - writer.write_tag(registry::SequenceDelimitationItem); - length = 0; - odil_write_binary( - length, this->stream, this->byte_ordering, 8*sizeof(length)); - if(!this->stream) - { - throw Exception("Could not write to stream"); - } } - -} - -#undef odil_write_binary diff --git a/src/odil/Writer.h b/src/odil/Writer.h index 4998307..b7eb63f 100644 --- a/src/odil/Writer.h +++ b/src/odil/Writer.h @@ -46,6 +46,19 @@ public: /// @brief Presence of group length elements. bool use_group_length; + /** + * @brief Write binary data to an stream encoded with the given endianness, + * ensure stream is still good. + */ + template<typename T> + static void write_binary( + T const & value, std::ostream & stream, ByteOrdering byte_ordering); + + /// @brief Write pixel data in encapsulated form. + static void write_encapsulated_pixel_data( + Value::Binary const & value, std::ostream & stream, + ByteOrdering byte_ordering, bool explicit_vr); + /// @brief Build a writer. Writer( std::ostream & stream, @@ -107,11 +120,11 @@ private: template<typename T> void write_strings(T const & sequence, char padding) const; - - void write_encapsulated_pixel_data(Value::Binary const & value) const; }; }; } +#include "odil/Writer.txx" + #endif // _ca5c06d2_04f9_4009_9e98_5607e1060379 diff --git a/src/odil/Writer.txx b/src/odil/Writer.txx new file mode 100644 index 0000000..bd5f0c5 --- /dev/null +++ b/src/odil/Writer.txx @@ -0,0 +1,56 @@ +/************************************************************************* + * odil - Copyright (C) Universite de Strasbourg + * Distributed under the terms of the CeCILL-B license, as published by + * the CEA-CNRS-INRIA. Refer to the LICENSE file or to + * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html + * for details. + ************************************************************************/ + +#ifndef _2e568ec2_62fc_43e5_8342_b4511db705e3 +#define _2e568ec2_62fc_43e5_8342_b4511db705e3 + +#include "odil/Writer.h" + +#include <ostream> +#include <string> + +#include "odil/DataSet.h" +#include "odil/Element.h" +#include "odil/endian.h" +#include "odil/registry.h" +#include "odil/Tag.h" +#include "odil/Value.h" +#include "odil/VR.h" + +namespace odil +{ + +template<typename T> +void +Writer +::write_binary( + T const & value, std::ostream & stream, ByteOrdering byte_ordering) +{ + auto raw = value; + if(byte_ordering == ByteOrdering::LittleEndian) + { + raw = host_to_little_endian(raw); + } + else if(byte_ordering == ByteOrdering::BigEndian) + { + raw = host_to_big_endian(raw); + } + else + { + throw Exception("Unknown endianness"); + } + stream.write(reinterpret_cast<char const*>(&raw), sizeof(raw)); + if(!stream) + { + throw Exception("Could not write to stream"); + } +} + +} + +#endif // _2e568ec2_62fc_43e5_8342_b4511db705e3 diff --git a/src/odil/dcmtk/Exception.cpp b/src/odil/dcmtk/Exception.cpp index d918f27..d7eb13d 100644 --- a/src/odil/dcmtk/Exception.cpp +++ b/src/odil/dcmtk/Exception.cpp @@ -43,7 +43,7 @@ Exception char const * Exception -::what() const throw() +::what() const noexcept { if(this->_source == Source::Message) { diff --git a/src/odil/json_converter.cpp b/src/odil/json_converter.cpp index dfd4f55..4ecd7d6 100644 --- a/src/odil/json_converter.cpp +++ b/src/odil/json_converter.cpp @@ -260,7 +260,7 @@ DataSet as_dataset(Json::Value const & json) element.as_data_set().push_back(dicom_item); } } - else if(vr == VR::OB || vr == VR::OF || vr == VR::OW || vr == VR::UN) + else if(is_binary(vr)) { element = Element(Value::Binary(), vr); diff --git a/src/odil/message/Response.cpp b/src/odil/message/Response.cpp index fdeb491..85c2aa4 100644 --- a/src/odil/message/Response.cpp +++ b/src/odil/message/Response.cpp @@ -92,6 +92,23 @@ Response return Response::is_failure(this->get_status()); } +void +Response +::set_status_fields(DataSet const & status_fields) +{ + for(auto const & tag_and_element: status_fields) + { + if(this->_command_set.has(tag_and_element.first)) + { + this->_command_set[tag_and_element.first] = tag_and_element.second; + } + else + { + this->_command_set.add(tag_and_element.first, tag_and_element.second); + } + } +} + } } diff --git a/src/odil/message/Response.h b/src/odil/message/Response.h index a1f44b6..fe1dd80 100644 --- a/src/odil/message/Response.h +++ b/src/odil/message/Response.h @@ -86,6 +86,17 @@ public: message_id_being_responded_to, registry::MessageIDBeingRespondedTo) ODIL_MESSAGE_MANDATORY_FIELD_INTEGER_MACRO(status, registry::Status) + ODIL_MESSAGE_OPTIONAL_FIELD_STRING_MACRO( + offending_element, registry::OffendingElement) + ODIL_MESSAGE_OPTIONAL_FIELD_STRING_MACRO( + error_comment, registry::ErrorComment) + ODIL_MESSAGE_OPTIONAL_FIELD_INTEGER_MACRO( + error_id, registry::ErrorID) + ODIL_MESSAGE_OPTIONAL_FIELD_STRING_MACRO( + affected_sop_instance_uid, odil::registry::AffectedSOPInstanceUID) + ODIL_MESSAGE_OPTIONAL_FIELD_STRING_MACRO( + attribute_identifier_list, odil::registry::AttributeIdentifierList) + /// @brief Test whether the status class is pending. bool is_pending() const; @@ -94,6 +105,9 @@ public: /// @brief Test whether the status class is failure. bool is_failure() const; + + /// @brief Set the status fields (cf. PS.37, C) + void set_status_fields(DataSet const & status_fields); }; } diff --git a/src/odil/xml_converter.cpp b/src/odil/xml_converter.cpp index a97ce16..94a7944 100644 --- a/src/odil/xml_converter.cpp +++ b/src/odil/xml_converter.cpp @@ -545,7 +545,7 @@ DataSet as_dataset(boost::property_tree::ptree const & xml) element.as_data_set().push_back(it->second); } } - else if(vr == VR::OB || vr == VR::OF || vr == VR::OW || vr == VR::UN) + else if(is_binary(vr)) { element = Element(Value::Binary(), vr); diff --git a/tests/code/message/Response.cpp b/tests/code/message/Response.cpp index 616772c..a8adab1 100644 --- a/tests/code/message/Response.cpp +++ b/tests/code/message/Response.cpp @@ -110,3 +110,17 @@ BOOST_AUTO_TEST_CASE(StatusFailure) BOOST_REQUIRE(response.is_failure()); } } + +BOOST_AUTO_TEST_CASE(StatusDetails) +{ + odil::message::Response response( + 1234, odil::message::Response::SOPClassNotSupported); + odil::DataSet status_details; + status_details.add( + odil::registry::ErrorComment, {"This is the error comment"}); + response.set_status_fields(status_details); + + BOOST_REQUIRE(response.has_error_comment()); + BOOST_REQUIRE_EQUAL( + response.get_error_comment(), "This is the error comment"); +} -- Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-med/odil.git _______________________________________________ debian-med-commit mailing list [email protected] http://lists.alioth.debian.org/cgi-bin/mailman/listinfo/debian-med-commit
