[med-svn] [odil] 01/04: Imported Upstream version 0.7.1

Julien Lamy lamy-guest at moszumanska.debian.org
Fri Jul 8 16:22:05 UTC 2016


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

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

commit ff5fc36d0ba8c992e08d2bc9583dd2790bf5bc28
Author: Julien Lamy <lamy at unistra.fr>
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



More information about the debian-med-commit mailing list