[med-svn] [odil] 01/06: Imported Upstream version 0.5.0
Julien Lamy
lamy-guest at moszumanska.debian.org
Wed Apr 13 16:12:54 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 a4169f13b1c79d24a3e68c3ac4e3f7b5e0b31452
Author: Julien Lamy <lamy at unistra.fr>
Date: Fri Apr 8 18:25:30 2016 +0200
Imported Upstream version 0.5.0
---
.travis.yml | 23 ++-
CMakeLists.txt | 12 +-
applications/CMakeLists.txt | 4 +
applications/echo.py | 38 ++++
applications/find.py | 85 +++++++++
applications/odil | 41 +++++
applications/print_.py | 92 ++++++++++
applications/transcode.py | 59 +++++++
appveyor.yml | 4 +-
examples/CMakeLists.txt | 14 +-
examples/convert.py | 48 ++++++
examples/dump.py | 69 ++++++++
examples/find.cpp | 6 +-
examples/find.py | 40 +++++
examples/store.cpp | 6 +-
src/CMakeLists.txt | 9 +-
src/odil/AssociationParameters.cpp | 56 ++++++
src/odil/AssociationParameters.h | 9 +
src/odil/BasicDirectoryCreator.cpp | 2 +-
src/odil/{SCU.cpp => EchoSCU.cpp} | 32 +---
src/odil/EchoSCU.h | 35 ++++
src/odil/ElementsDictionary.cpp | 24 +++
src/odil/ElementsDictionary.h | 7 +-
src/odil/Reader.cpp | 122 +++++++++----
src/odil/Reader.h | 2 +
src/odil/SCU.cpp | 22 ---
src/odil/SCU.h | 3 -
src/odil/UIDsDictionary.h | 4 +-
src/odil/VR.cpp | 2 +-
src/odil/Value.h | 2 +-
src/odil/Writer.cpp | 99 +++++++----
src/odil/Writer.h | 2 +
src/odil/dcmtk/conversion.cpp | 24 ++-
src/odil/json_converter.cpp | 18 +-
src/odil/xml_converter.cpp | 33 ++--
tests/CMakeLists.txt | 17 +-
tests/code/Association.cpp | 38 ----
tests/code/DataSet.cpp | 5 +-
tests/code/EchoSCU.cpp | 28 +++
tests/code/Reader.cpp | 6 +-
tests/code/SCU.cpp | 5 -
tests/code/Writer.cpp | 6 +-
tests/code/json_converter.cpp | 4 +-
tests/code/xml_converter.cpp | 15 +-
tests/peer_fixture_base.py | 18 ++
tests/run | 139 +++++++++++++++
tests/run.sh | 57 ------
tests/tools/CMakeLists.txt | 5 +-
tests/wrappers/test_association.py | 48 ++++++
tests/wrappers/test_association_parameters.py | 116 +++++++++++++
tests/wrappers/test_data_set.py | 166 ++++++++++++++++++
tests/wrappers/test_echo_scu.py | 26 +++
tests/wrappers/test_element.py | 55 ++++++
tests/wrappers/test_elements_dictionary.py | 48 ++++++
tests/wrappers/test_find_scu.py | 51 ++++++
tests/wrappers/test_get_scu.py | 54 ++++++
tests/wrappers/test_move_scu.py | 65 +++++++
tests/wrappers/test_registry.py | 29 ++++
tests/wrappers/test_store_scu.py | 52 ++++++
tests/wrappers/test_tag.py | 87 ++++++++++
tests/wrappers/test_uid.py | 14 ++
tests/wrappers/test_uids_dictionary.py | 21 +++
tests/wrappers/test_value.py | 88 ++++++++++
tests/wrappers/test_vr.py | 12 ++
wrappers/Assocation.cpp | 62 +++++++
wrappers/AssocationParameters.cpp | 240 ++++++++++++++++++++++++++
wrappers/CMakeLists.txt | 19 ++
wrappers/DataSet.cpp | 225 ++++++++++++++++++++++++
wrappers/EchoSCU.cpp | 33 ++++
wrappers/Element.cpp | 63 +++++++
wrappers/ElementsDictionary.cpp | 96 +++++++++++
wrappers/Exception.cpp | 25 +++
wrappers/FindSCU.cpp | 50 ++++++
wrappers/GetSCU.cpp | 50 ++++++
wrappers/MoveSCU.cpp | 59 +++++++
wrappers/StoreSCU.cpp | 33 ++++
wrappers/Tag.cpp | 33 ++++
wrappers/UIDsDictionary.cpp | 32 ++++
wrappers/VR.cpp | 52 ++++++
wrappers/Value.cpp | 110 ++++++++++++
wrappers/json_converter.cpp | 44 +++++
wrappers/odil.cpp | 58 +++++++
wrappers/read.cpp | 48 ++++++
wrappers/registry.cpp | 44 +++++
wrappers/uid.cpp | 22 +++
wrappers/write.cpp | 72 ++++++++
wrappers/xml_converter.cpp | 45 +++++
87 files changed, 3543 insertions(+), 295 deletions(-)
diff --git a/.travis.yml b/.travis.yml
index ea77fb1..c11ff2a 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -17,16 +17,22 @@ addons:
- zlib1g-dev
- libboost-dev
- libboost-filesystem-dev
+ - libboost-python-dev
+ - libboost-regex-dev
- libboost-test-dev
- dcmtk
+ - ninja-build
+ - cmake
+ - pkg-config
before_install:
- if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew update; fi
# JSONCpp conflicts with json-c
- if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew uninstall json-c; fi
# Boost is already installed with another version
- - if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew unlink boost; brew install boost; fi
- - if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew install dcmtk icu4c jsoncpp; fi
- - pip install --user cpp-coveralls
+ - if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew unlink boost; brew install boost boost-python; fi
+ - if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew install dcmtk icu4c jsoncpp ninja; fi
+ - pip install --user cpp-coveralls nose
+ - export PATH=$(python -c 'import site; print(site.getuserbase())')/bin:${PATH}
before_script:
- export SRC_DIR=$PWD
- mkdir build
@@ -34,11 +40,10 @@ before_script:
- export BIN_DIR=$PWD
- CMAKE_CXX_FLAGS="-std=c++11"
- if [ "${CC}" = "gcc" ]; then CMAKE_CXX_FLAGS="${CMAKE_CXX_FLAGS} --coverage"; fi
- - if [ "$TRAVIS_OS_NAME" = "osx" ]; then CMAKE_ARGUMENTS="-D ICU_INCLUDE_DIR=/usr/local/opt/icu4c/include/ -D ICU_LIBRARY=/usr/local/opt/icu4c/lib/libicuuc.dylib"; fi
- - cmake -D CMAKE_CXX_FLAGS:STRING="${CMAKE_CXX_FLAGS}" -D CMAKE_BUILD_TYPE:STRING=Debug ${CMAKE_ARGUMENTS} ../
+ - if [ "$TRAVIS_OS_NAME" = "osx" ]; then export PKG_CONFIG_PATH=/usr/local/opt/icu4c/lib/pkgconfig; fi
+ - cmake -G Ninja -D CMAKE_CXX_FLAGS:STRING="${CMAKE_CXX_FLAGS}" -D CMAKE_BUILD_TYPE:STRING=Debug ../
script:
- - make -j $(nproc)
- - export PATH=$PWD/tests/tools:$PATH
- - ../tests/run.sh
+ - ninja
+ - ../tests/run --no-network
after_success:
- - if [ "${CC}" = "gcc" ]; then ${HOME}/.local/bin/coveralls --exclude examples --exclude tests --exclude-pattern '.*CMake[^/]+\.c(?:pp)?' --exclude-pattern "/usr/.*" --root=${SRC_DIR} --build-root ${BIN_DIR} | grep -vP "^File '.*'$" | grep -vP ":creating '.*'$" | grep -vP "^Lines executed:.*" | sed '/^$/d'; fi
+ - if [ "${CC}" = "gcc" ]; then coveralls --exclude examples --exclude tests --exclude-pattern '.*CMake[^/]+\.c(?:pp)?' --exclude-pattern "/usr/.*" --root=${SRC_DIR} --build-root ${BIN_DIR} > /dev/null; fi
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 1b5dfdc..05d609f 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -2,17 +2,19 @@ cmake_minimum_required(VERSION 2.8)
project("odil")
set(odil_MAJOR_VERSION 0)
-set(odil_MINOR_VERSION 4)
-set(odil_PATCH_VERSION 2)
-set(odil_VERSION
+set(odil_MINOR_VERSION 5)
+set(odil_PATCH_VERSION 0)
+set(odil_VERSION
${odil_MAJOR_VERSION}.${odil_MINOR_VERSION}.${odil_PATCH_VERSION})
option(BUILD_EXAMPLES "Build the examples directory." ON)
+option(BUILD_WRAPPERS "Build the Python Wrappers." ON)
set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}" ${CMAKE_MODULE_PATH})
include(CTest)
add_subdirectory("src")
+add_subdirectory("applications")
if(BUILD_EXAMPLES)
add_subdirectory("examples")
@@ -22,6 +24,10 @@ if(BUILD_TESTING)
add_subdirectory("tests")
endif()
+if(BUILD_WRAPPERS)
+ add_subdirectory("wrappers")
+endif()
+
add_custom_target(
CIIntegration ${CMAKE_COMMAND} -E echo "CI Integration"
SOURCES appveyor.yml .travis.yml)
diff --git a/applications/CMakeLists.txt b/applications/CMakeLists.txt
new file mode 100644
index 0000000..2bf5a75
--- /dev/null
+++ b/applications/CMakeLists.txt
@@ -0,0 +1,4 @@
+file(GLOB_RECURSE python_files *.py)
+set(python_files ${python_files} odil)
+add_custom_target(
+ PythonFiles ${CMAKE_COMMAND} -E echo "Python files" SOURCES ${python_files})
diff --git a/applications/echo.py b/applications/echo.py
new file mode 100644
index 0000000..0ed6190
--- /dev/null
+++ b/applications/echo.py
@@ -0,0 +1,38 @@
+import logging
+
+import odil
+
+def add_subparser(subparsers):
+ parser = subparsers.add_parser(
+ "echo", help="Ping a remote DICOM server (C-ECHO)")
+ parser.add_argument("host", help="Remote host address")
+ parser.add_argument("port", type=int, help="Remote host port")
+ parser.add_argument(
+ "calling_ae_title", help="AE title of the calling application")
+ parser.add_argument(
+ "called_ae_title", help="AE title of the called application")
+ parser.set_defaults(function=echo)
+ return parser
+
+def echo(host, port, calling_ae_title, called_ae_title):
+ association = odil.Association()
+ association.set_peer_host(host)
+ association.set_peer_port(port)
+ association.update_parameters()\
+ .set_calling_ae_title(calling_ae_title)\
+ .set_called_ae_title(called_ae_title) \
+ .set_presentation_contexts([
+ odil.AssociationParameters.PresentationContext(
+ 3, odil.registry.VerificationSOPClass,
+ [ odil.registry.ImplicitVRLittleEndian ], True, False
+ )
+ ])
+ association.associate()
+ logging.info("Association established")
+
+ echo = odil.EchoSCU(association)
+ echo.echo()
+ logging.info("C-ECHO successful")
+
+ association.release()
+ logging.info("Association released")
diff --git a/applications/find.py b/applications/find.py
new file mode 100644
index 0000000..48710f5
--- /dev/null
+++ b/applications/find.py
@@ -0,0 +1,85 @@
+import logging
+
+import odil
+
+from print_ import find_max_name_length, print_data_set
+
+def add_subparser(subparsers):
+ parser = subparsers.add_parser(
+ "find", help="DICOM query (C-FIND)")
+ parser.add_argument("host", help="Remote host address")
+ parser.add_argument("port", type=int, help="Remote host port")
+ parser.add_argument(
+ "calling_ae_title", help="AE title of the calling application")
+ parser.add_argument(
+ "called_ae_title", help="AE title of the called application")
+ parser.add_argument(
+ "level", choices=["patient", "study"], help="Root object of the query")
+ parser.add_argument("keys", nargs="+", help="Query keys")
+ parser.add_argument(
+ "--decode-uids", "-u", action="store_true",
+ help="Print human-friendly name of known UIDs")
+ parser.set_defaults(function=find)
+ return parser
+
+def find(host, port, calling_ae_title, called_ae_title, level, keys, decode_uids):
+ query = odil.DataSet()
+ for key in keys:
+ if "=" in key:
+ key, value = key.split("=", 1)
+ value = value.split("\\")
+ else:
+ value = None
+
+ tag = getattr(odil.registry, key)
+
+ if value is not None:
+ vr = odil.registry.public_dictionary[tag].vr
+ if vr in ["DS", "FL", "FD"]:
+ value = odil.Value.Reals([float(x) for x in value])
+ elif vr in ["IS", "SL", "SS", "UL", "US"]:
+ value = odil.Value.Integers([int(x) for x in value])
+ else:
+ value = odil.Value.Strings(value)
+
+ query.add(tag, value)
+ else:
+ query.add(tag)
+
+ sop_class = getattr(
+ odil.registry,
+ "{}RootQueryRetrieveInformationModelFIND".format(level.capitalize()))
+
+ find_pc = odil.AssociationParameters.PresentationContext(
+ 1, sop_class,
+ [
+ odil.registry.ImplicitVRLittleEndian,
+ odil.registry.ExplicitVRLittleEndian
+ ], True, False
+ )
+
+ association = odil.Association()
+ association.set_peer_host(host)
+ association.set_peer_port(port)
+ association.update_parameters()\
+ .set_calling_ae_title(calling_ae_title)\
+ .set_called_ae_title(called_ae_title) \
+ .set_presentation_contexts([find_pc])
+ association.associate()
+ logging.info("Association established")
+
+ find = odil.FindSCU(association)
+ find.set_affected_sop_class(sop_class)
+ data_sets = find.find(query)
+ print "{} answer{}".format(len(data_sets), "s" if len(data_sets)>1 else "")
+
+ max_length = 0
+ for data_set in data_sets:
+ max_length = max(max_length, find_max_name_length(data_set))
+
+ for data_set in data_sets:
+ print_data_set(data_set, decode_uids, "", max_length)
+ print
+
+ association.release()
+ logging.info("Association released")
diff --git a/applications/odil b/applications/odil
new file mode 100755
index 0000000..a4ad30d
--- /dev/null
+++ b/applications/odil
@@ -0,0 +1,41 @@
+#!/usr/bin/env python
+
+import argparse
+import logging
+import sys
+
+import echo
+import find
+import print_
+import transcode
+
+def main():
+ parser = argparse.ArgumentParser()
+
+ subparsers = parser.add_subparsers(help="Sub-commands help")
+
+ modules = [
+ print_, transcode, echo, find
+ ]
+ for module in modules:
+ sub_parser = module.add_subparser(subparsers)
+ sub_parser.add_argument(
+ "--verbosity", "-v",
+ choices=["warning", "info", "debug"], default="warning")
+
+ arguments = parser.parse_args()
+
+ verbosity = arguments.__dict__.pop("verbosity")
+ logging.getLogger().setLevel(verbosity.upper())
+
+ function = arguments.__dict__.pop("function")
+ try:
+ function(**arguments.__dict__)
+ except Exception as e:
+ if verbosity == "debug":
+ raise
+ else:
+ parser.error(e)
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/applications/print_.py b/applications/print_.py
new file mode 100644
index 0000000..d612cbc
--- /dev/null
+++ b/applications/print_.py
@@ -0,0 +1,92 @@
+import logging
+
+import odil
+
+def add_subparser(subparsers):
+ parser = subparsers.add_parser(
+ "print", help="Print the contents of data sets")
+ parser.add_argument("inputs", nargs="+", metavar="FILE", help="Input files")
+ parser.add_argument(
+ "--print-header", "-H", action="store_true",
+ help="Print the header as well as the data set")
+ parser.add_argument(
+ "--decode-uids", "-u", action="store_true",
+ help="Print human-friendly name of known UIDs")
+ parser.set_defaults(function=print_)
+ return parser
+
+def print_(inputs, print_header, decode_uids):
+ for input in inputs:
+ logging.info("Printing {}".format(input))
+ header, data_set = odil.read(input)
+
+ max_length = find_max_name_length(data_set)
+ if print_header:
+ max_length = max(max_length, find_max_name_length(header))
+
+ if print_header:
+ print_data_set(header, decode_uids, "", max_length)
+ print
+ print_data_set(data_set, decode_uids, "", max_length)
+
+def print_data_set(data_set, decode_uids, padding, max_length):
+ for tag, element in data_set.items():
+ name = "{:04x},{:04x}".format(tag.group, tag.element)
+ if tag in odil.registry.public_dictionary:
+ entry = odil.registry.public_dictionary[tag]
+ name = entry.name
+
+ if element.is_data_set():
+ value = ""
+ elif element.is_binary():
+ lengths = [len(x) for x in element.as_binary()]
+ value = "(binary, {} item{}, {} byte{})".format(
+ len(element), "s" if len(element)>1 else "",
+ "+".join(str(x) for x in lengths),
+ "s" if sum(lengths)>1 else "")
+ else:
+ getter = None
+ if element.is_int():
+ getter = element.as_int
+ elif element.is_real():
+ getter = element.as_real
+ elif element.is_string():
+ getter = element.as_string
+ value = [x for x in getter()]
+
+ if decode_uids and element.vr == odil.VR.UI:
+ value = [
+ odil.registry.uids_dictionary[uid].name
+ if uid in odil.registry.uids_dictionary else uid
+ for uid in value
+ ]
+
+ print "{}{}{} {:04x},{:04x} {} {}".format(
+ padding,
+ name, (max_length-len(name)-len(padding))*" ",
+ tag.group, tag.element, element.vr,
+ value)
+
+ if element.is_data_set():
+ sequence = element.as_data_set()
+ if sequence:
+ for item in sequence[:-1]:
+ print_data_set(item, decode_uids, padding+" ", max_length)
+ print
+ print_data_set(sequence[-1], decode_uids, padding+" ", max_length)
+
+def find_max_name_length(data_set, max_length=0, padding_length=0):
+ for tag, element in data_set.items():
+ if tag in odil.registry.public_dictionary:
+ entry = odil.registry.public_dictionary[tag]
+ length = len(entry.name)
+ else:
+ length = 9 # xxxx,yyyy
+ max_length = max(max_length, padding_length+length)
+ if element.is_data_set():
+ sequence = element.as_data_set()
+ for item in sequence:
+ max_length = max(
+ max_length,
+ find_max_name_length(item, max_length, 2+padding_length))
+ return max_length
diff --git a/applications/transcode.py b/applications/transcode.py
new file mode 100644
index 0000000..572459e
--- /dev/null
+++ b/applications/transcode.py
@@ -0,0 +1,59 @@
+import logging
+
+import odil
+
+def add_subparser(subparsers):
+ parser = subparsers.add_parser(
+ "transcode",
+ help="Change the transfer syntax or the format")
+ parser.add_argument("input", help="Input file name")
+ parser.add_argument("output", help="Output file name")
+ parser.add_argument(
+ "--format", "-f",
+ choices=["binary", "json", "xml"], default="binary",
+ help="Output file format")
+ parser.add_argument(
+ "--transfer-syntax", "-t",
+ help="Transfer syntax of output file (binary only)")
+ parser.add_argument(
+ "--pretty-print", "-p", action="store_true",
+ help="Pretty-print in output file (JSON and XML only)")
+ parser.set_defaults(function=transcode)
+ return parser
+
+def transcode(input, output, format, transfer_syntax, pretty_print):
+
+ kwargs = {}
+
+ if format == "binary":
+ if transfer_syntax:
+ transfer_syntax = getattr(odil.registry, transfer_syntax)
+ ts_info = odil.registry.uids_dictionary[transfer_syntax]
+ if ts_info.type != "Transfer Syntax":
+ raise Exception(
+ "{} is not a transfer syntax".format(ts_info.name))
+ kwargs["transfer_syntax"] = transfer_syntax
+ else:
+ kwargs["transfer_syntax"] = None
+ elif format == "json":
+ kwargs["pretty_print"] = pretty_print
+ elif format == "xml":
+ kwargs["pretty_print"] = pretty_print
+
+ globals()["as_{}".format(format)](input, output, **kwargs)
+
+def as_binary(input, output, transfer_syntax):
+ _, data_set = odil.read(input)
+ odil.write(data_set, output, transfer_syntax=transfer_syntax)
+
+def as_json(input, output, pretty_print):
+ _, data_set = odil.read(input)
+ with open(output, "w") as fd:
+ json = odil.as_json(data_set, pretty_print)
+ fd.write(json)
+
+def as_xml(input, output, pretty_print):
+ _, data_set = odil.read(input)
+ with open(output, "w") as fd:
+ xml = odil.as_xml(data_set, pretty_print)
+ fd.write(xml)
diff --git a/appveyor.yml b/appveyor.yml
index f791cea..edbf936 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -25,8 +25,8 @@ install:
- ps: Start-FileDownload https://github.com/lamyj/jsoncpp/releases/download/0.10.5/jsoncpp_0_10_5_Win64_msvc14.zip
- 7z x -bd -oC:\Libraries jsoncpp_0_10_5_Win64_msvc14.zip
# DCMTK
-- ps: Start-FileDownload https://github.com/lamyj/dcmtk/releases/download/DCMTK-3.6.1_20150924/dcmtk-3.6.1_20150924_Win64_msvc14.zip
-- 7z x -bd -oC:\Libraries dcmtk-3.6.1_20150924_Win64_msvc14.zip
+- ps: Start-FileDownload https://github.com/lamyj/dcmtk/releases/download/DCMTK-3.6.1_20150924/dcmtk-3.6.1_20150924_Win64_msvc14_dynamic.zip
+- 7z x -bd -oC:\Libraries dcmtk-3.6.1_20150924_Win64_msvc14_dynamic.zip
before_build:
- cd c:\projects\odil
diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt
index 0c5827e..91d181a 100644
--- a/examples/CMakeLists.txt
+++ b/examples/CMakeLists.txt
@@ -1,17 +1,27 @@
+find_package(Boost REQUIRED)
find_package(DCMTK REQUIRED)
find_package(JsonCpp REQUIRED)
-include_directories(${CMAKE_SOURCE_DIR}/src ${JsonCpp_INCLUDE_DIRS})
+include_directories(
+ ${CMAKE_SOURCE_DIR}/src ${Boost_INCLUDE_DIRS} ${DCMTK_INCLUDE_DIRS}
+ ${JsonCpp_INCLUDE_DIRS})
add_definitions(
${DCMTK_DEFINITIONS}
-D BOOST_ASIO_DYN_LINK
-D ODIL_MAJOR_VERSION=${odil_MAJOR_VERSION}
)
+link_directories(${Boost_LIBRARY_DIRS} ${DCMTK_LIBRARY_DIRS})
file(GLOB_RECURSE examples *.cpp)
foreach(example_file ${examples})
get_filename_component(example ${example_file} NAME_WE)
add_executable(${example} ${example_file})
- target_link_libraries(${example} odil)
+ target_link_libraries(${example} libodil)
endforeach()
+
+file(GLOB_RECURSE python_examples *.py)
+add_custom_target(
+ PythonExamples
+ ${CMAKE_COMMAND} -E echo "Python examples"
+ SOURCES ${python_examples})
diff --git a/examples/convert.py b/examples/convert.py
new file mode 100644
index 0000000..e392f75
--- /dev/null
+++ b/examples/convert.py
@@ -0,0 +1,48 @@
+import argparse
+import sys
+
+import odil
+
+def main():
+ parser = argparse.ArgumentParser(description="Convert a DICOM file")
+ parser.add_argument("input")
+ parser.add_argument("output")
+ parser.add_argument("--transfer-syntax", "-t", help="Transfer syntax")
+ parser.add_argument(
+ "--item-length", "-i", choices=["Explicit", "Undefined"],
+ default="Explicit", help="Item length encoding")
+ parser.add_argument(
+ "--use-group-length", "-g", action="store_true",
+ help="Include group length tags")
+ arguments = parser.parse_args()
+
+ if arguments.transfer_syntax not in dir(odil.registry):
+ parser.error("Unknown transfer syntax")
+ uid = getattr(odil.registry, arguments.transfer_syntax)
+ if uid not in odil.registry.uids_dictionary:
+ parser.error("Unknown transfer syntax")
+ if odil.registry.uids_dictionary[uid].type != "Transfer Syntax":
+ parser.error("Not a transfer syntax")
+ arguments.transfer_syntax = uid
+
+ arguments.item_length = getattr(
+ odil.Writer.ItemEncoding, "{}Length".format(arguments.item_length))
+
+ convert(**arguments.__dict__)
+
+def convert(input, output, transfer_syntax, item_length, use_group_length):
+ header, data_set = odil.read(input)
+
+ to_remove = [
+ "FileMetaInformationVersion",
+ "MediaStorageSOPClassUID", "MediaStorageSOPInstanceUID",
+ "TransferSyntaxUID",
+ "ImplementationClassUID", "ImplementationVersionName"
+ ]
+ for name in to_remove:
+ header.remove(getattr(odil.registry, name))
+
+ odil.write(data_set, output, header, transfer_syntax, item_length, use_group_length)
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/examples/dump.py b/examples/dump.py
new file mode 100644
index 0000000..211e656
--- /dev/null
+++ b/examples/dump.py
@@ -0,0 +1,69 @@
+import argparse
+import sys
+
+import odil
+
+def main():
+ parser = argparse.ArgumentParser(description="Print content of DICOM file")
+ parser.add_argument("file")
+ parser.add_argument(
+ "--header", "-H", action="store_true", help="Print header")
+ parser.add_argument(
+ "--decode-uids", "-D", action="store_true", help="Display UID names")
+ arguments = parser.parse_args()
+
+ header, data_set = odil.read(arguments.file)
+ if arguments.header:
+ print_data_set(header, arguments.decode_uids)
+ print
+ print_data_set(data_set, arguments.decode_uids)
+
+def print_data_set(data_set, decode_uids=False, padding=""):
+ max_length = 0
+ for tag in data_set.keys():
+ if tag in odil.registry.public_dictionary:
+ entry = odil.registry.public_dictionary[tag]
+ max_length = max(max_length, len(entry.name))
+
+ for tag, element in data_set.items():
+ name = "{:04x},{:04x}".format(tag.group, tag.element)
+ if tag in odil.registry.public_dictionary:
+ entry = odil.registry.public_dictionary[tag]
+ name = entry.name
+
+ if element.is_data_set():
+ value = ""
+ elif element.is_binary():
+ length = len(element.as_binary())
+ value = "(binary, {} byte{})".format(length, "s" if length>1 else "")
+ else:
+ getter = None
+ if element.is_int():
+ getter = element.as_int
+ elif element.is_real():
+ getter = element.as_real
+ elif element.is_string():
+ getter = element.as_string
+ value = [x for x in getter()]
+
+ if decode_uids and element.vr == odil.VR.UI:
+ value = [
+ odil.registry.uids_dictionary[uid].name
+ if uid in odil.registry.uids_dictionary else uid
+ for uid in value
+ ]
+
+ print "{}{}{} {:04x},{:04x} {} {}".format(
+ padding,
+ name, (max_length-len(name))*" ",
+ tag.group, tag.element, element.vr,
+ value)
+
+ if element.is_data_set():
+ for item in element.as_data_set()[:-1]:
+ print_data_set(item, decode_uids, padding+" ")
+ print
+ print_data_set(element.as_data_set()[-1], padding+" ")
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/examples/find.cpp b/examples/find.cpp
index 4c5b1e0..7540efc 100644
--- a/examples/find.cpp
+++ b/examples/find.cpp
@@ -2,6 +2,7 @@
#include "odil/Association.h"
#include "odil/DataSet.h"
+#include "odil/EchoSCU.h"
#include "odil/FindSCU.h"
#include "odil/registry.h"
@@ -37,11 +38,12 @@ int main()
});
association.associate();
+
+ odil::EchoSCU echo_scu(association);
+ echo_scu.echo();
odil::FindSCU scu(association);
- scu.echo();
-
odil::DataSet query;
query.add("PatientName", { "*" });
query.add("QueryRetrieveLevel", { "STUDY" });
diff --git a/examples/find.py b/examples/find.py
new file mode 100644
index 0000000..56e3023
--- /dev/null
+++ b/examples/find.py
@@ -0,0 +1,40 @@
+import odil
+
+association = odil.Association()
+association.set_peer_host("184.73.255.26")
+association.set_peer_port(11112)
+
+presentation_contexts = [
+ odil.AssociationParameters.PresentationContext(
+ 1, odil.registry.StudyRootQueryRetrieveInformationModelFIND,
+ [ odil.registry.ExplicitVRLittleEndian ], True, False
+ ),
+ odil.AssociationParameters.PresentationContext(
+ 3, odil.registry.VerificationSOPClass,
+ [ odil.registry.ExplicitVRLittleEndian ], True, False
+ )
+]
+
+association.update_parameters()\
+ .set_calling_ae_title("myself")\
+ .set_called_ae_title("AWSPIXELMEDPUB") \
+ .set_presentation_contexts(presentation_contexts)
+association.associate()
+
+query = odil.DataSet()
+query.add(odil.registry.PatientName, odil.Value.Strings(["*"]))
+query.add(odil.registry.QueryRetrieveLevel, odil.Value.Strings(["STUDY"]))
+query.add(odil.registry.StudyDescription)
+query.add(odil.registry.StudyDate)
+
+find = odil.FindSCU(association)
+find.set_affected_sop_class(odil.registry.StudyRootQueryRetrieveInformationModelFIND)
+
+def callback(data_set):
+ print data_set.as_string(odil.registry.PatientName)[0]
+find.find(query, callback)
+
+data_sets = find.find(query)
+print len(data_sets), "found"
+for data_set in data_sets:
+ print data_set.as_string(odil.registry.PatientName)[0]
diff --git a/examples/store.cpp b/examples/store.cpp
index 4e3d170..9704993 100644
--- a/examples/store.cpp
+++ b/examples/store.cpp
@@ -1,5 +1,6 @@
#include "odil/Association.h"
#include "odil/DataSet.h"
+#include "odil/EchoSCU.h"
#include "odil/Reader.h"
#include "odil/StoreSCU.h"
@@ -23,11 +24,12 @@ int main(int argc, char** argv)
});
association.associate();
+
+ odil::EchoSCU echo_scu(association);
+ echo_scu.echo();
odil::StoreSCU scu(association);
- scu.echo();
-
for(int i=1; i<argc; ++i)
{
std::ifstream stream(argv[i], std::ios::binary);
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 0d582eb..5218b46 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -17,11 +17,12 @@ add_definitions(
)
link_directories(${Boost_LIBRARY_DIRS} ${DCMTK_LIBRARY_DIRS})
-add_library(odil SHARED ${files} ${headers} ${templates})
+add_library(libodil SHARED ${files} ${headers} ${templates})
+set_target_properties(libodil PROPERTIES OUTPUT_NAME odil)
-target_link_libraries(odil
+target_link_libraries(libodil
${Boost_LIBRARIES} ${DCMTK_LIBRARIES} ${ICU_LIBRARIES} ${JsonCpp_LIBRARIES})
-set_target_properties(odil PROPERTIES
+set_target_properties(libodil PROPERTIES
VERSION ${odil_VERSION}
SOVERSION ${odil_MAJOR_VERSION})
@@ -29,7 +30,7 @@ install(
DIRECTORY odil/ DESTINATION include/odil
FILES_MATCHING PATTERN "*.h" PATTERN "*.txx")
install(
- TARGETS odil
+ TARGETS libodil
ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib
RUNTIME DESTINATION bin)
diff --git a/src/odil/AssociationParameters.cpp b/src/odil/AssociationParameters.cpp
index ccae6ed..7017b5b 100644
--- a/src/odil/AssociationParameters.cpp
+++ b/src/odil/AssociationParameters.cpp
@@ -27,6 +27,49 @@
namespace odil
{
+bool
+AssociationParameters::PresentationContext
+::operator==(PresentationContext const & other) const
+{
+ return (
+ this->id == other.id &&
+ this->abstract_syntax == other.abstract_syntax &&
+ this->transfer_syntaxes == other.transfer_syntaxes &&
+ this->scu_role_support == other.scu_role_support &&
+ this->scp_role_support == other.scp_role_support &&
+ this->result == other.result
+ );
+}
+
+bool
+AssociationParameters::UserIdentity
+::operator==(UserIdentity const & other) const
+{
+ return (
+ this->type == other.type &&
+ (
+ this->type == Type::None ||
+ (
+ this->type == Type::Username &&
+ this->primary_field == other.primary_field
+ ) ||
+ (
+ this->type == Type::UsernameAndPassword &&
+ this->primary_field == other.primary_field &&
+ this->secondary_field == other.secondary_field
+ ) ||
+ (
+ this->type == Type::Kerberos &&
+ this->primary_field == other.primary_field
+ ) ||
+ (
+ this->type == Type::SAML &&
+ this->primary_field == other.primary_field
+ )
+ )
+ );
+}
+
AssociationParameters
::AssociationParameters()
: _called_ae_title(""), _calling_ae_title(""), _presentation_contexts(),
@@ -446,6 +489,19 @@ AssociationParameters
return pdu;
}
+bool
+AssociationParameters
+::operator==(AssociationParameters const & other) const
+{
+ return (
+ this->get_called_ae_title() == other.get_called_ae_title() &&
+ this->get_calling_ae_title() == other.get_calling_ae_title() &&
+ this->get_presentation_contexts() == other.get_presentation_contexts() &&
+ this->get_user_identity() == other.get_user_identity() &&
+ this->get_maximum_length() == other.get_maximum_length()
+ );
+}
+
AssociationParameters &
AssociationParameters
::_set_user_identity(UserIdentity const & value)
diff --git a/src/odil/AssociationParameters.h b/src/odil/AssociationParameters.h
index c251834..aa9c37b 100644
--- a/src/odil/AssociationParameters.h
+++ b/src/odil/AssociationParameters.h
@@ -44,6 +44,9 @@ public:
bool scu_role_support;
bool scp_role_support;
Result result;
+
+ /// @brief Member-wise equality.
+ bool operator==(PresentationContext const & other) const;
};
/// @brief User Identity, cf. PS3.8 D.3.3.7
@@ -61,6 +64,9 @@ public:
Type type;
std::string primary_field;
std::string secondary_field;
+
+ /// @brief Member-wise equality.
+ bool operator==(UserIdentity const & other) const;
};
/// @brief Constructor.
@@ -140,6 +146,9 @@ public:
/// @brief Create an A-ASSOCIATE-AC PDU.
pdu::AAssociateAC as_a_associate_ac() const;
+ /// @brief Member-wise equality.
+ bool operator==(AssociationParameters const & other) const;
+
private:
std::string _called_ae_title;
std::string _calling_ae_title;
diff --git a/src/odil/BasicDirectoryCreator.cpp b/src/odil/BasicDirectoryCreator.cpp
index b3c5aa8..d73aa89 100644
--- a/src/odil/BasicDirectoryCreator.cpp
+++ b/src/odil/BasicDirectoryCreator.cpp
@@ -357,7 +357,7 @@ BasicDirectoryCreator
{
DataSet meta_information;
meta_information.add(
- registry::FileMetaInformationVersion, Value::Binary({0x00, 0x01}));
+ registry::FileMetaInformationVersion, Value::Binary({{0x00, 0x01}}));
meta_information.add(
registry::MediaStorageSOPClassUID,
{ registry::MediaStorageDirectoryStorage });
diff --git a/src/odil/SCU.cpp b/src/odil/EchoSCU.cpp
similarity index 77%
copy from src/odil/SCU.cpp
copy to src/odil/EchoSCU.cpp
index 0952b9d..d323d42 100644
--- a/src/odil/SCU.cpp
+++ b/src/odil/EchoSCU.cpp
@@ -6,49 +6,32 @@
* for details.
************************************************************************/
-#include "odil/SCU.h"
-
-#include <string>
+#include "odil/EchoSCU.h"
#include "odil/Association.h"
#include "odil/Exception.h"
#include "odil/message/CEchoRequest.h"
#include "odil/message/CEchoResponse.h"
-#include "odil/message/Message.h"
#include "odil/registry.h"
namespace odil
{
-SCU
-::SCU(Association & association)
-: _association(association), _affected_sop_class("")
+EchoSCU
+::EchoSCU(Association & association)
+: SCU(association)
{
// Nothing else.
}
-SCU
-::~SCU()
+EchoSCU
+::~EchoSCU()
{
// Nothing to do.
}
-std::string const &
-SCU
-::get_affected_sop_class() const
-{
- return this->_affected_sop_class;
-}
-
-void
-SCU
-::set_affected_sop_class(std::string const & sop_class)
-{
- this->_affected_sop_class = sop_class;
-}
-
void
-SCU
+EchoSCU
::echo() const
{
auto const message_id = this->_association.next_message_id();
@@ -70,3 +53,4 @@ SCU
}
}
+
diff --git a/src/odil/EchoSCU.h b/src/odil/EchoSCU.h
new file mode 100644
index 0000000..5ad96d8
--- /dev/null
+++ b/src/odil/EchoSCU.h
@@ -0,0 +1,35 @@
+/*************************************************************************
+ * 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 _94f3b347_1f95_49ab_83f6_8710f5a3ad67
+#define _94f3b347_1f95_49ab_83f6_8710f5a3ad67
+
+#include "odil/Association.h"
+#include "odil/SCU.h"
+
+namespace odil
+{
+
+/// @brief SCU for the C-ECHO services.
+class EchoSCU: public SCU
+{
+public:
+ /// @brief Constructor
+ EchoSCU(Association & association);
+
+ /// @brief Destructor.
+ virtual ~EchoSCU();
+
+ /// @brief Perform DICOM ping
+ void echo() const;
+};
+
+}
+
+#endif // _94f3b347_1f95_49ab_83f6_8710f5a3ad67
+
diff --git a/src/odil/ElementsDictionary.cpp b/src/odil/ElementsDictionary.cpp
index 1612bf1..5fc6363 100644
--- a/src/odil/ElementsDictionary.cpp
+++ b/src/odil/ElementsDictionary.cpp
@@ -103,6 +103,30 @@ ElementsDictionaryKey
return static_cast<int>(this->_type) < static_cast<int>(other._type);
}
}
+bool
+ElementsDictionaryKey
+::operator==(ElementsDictionaryKey const & other) const
+{
+ if(this->_type == other._type)
+ {
+ if(this->_type == Type::Tag)
+ {
+ return (this->_tag == other._tag);
+ }
+ else if(this->_type == Type::String)
+ {
+ return (this->_string == other._string);
+ }
+ else
+ {
+ throw Exception("Invalid type");
+ }
+ }
+ else
+ {
+ return false;
+ }
+}
ElementsDictionaryEntry
::ElementsDictionaryEntry(
diff --git a/src/odil/ElementsDictionary.h b/src/odil/ElementsDictionary.h
index 7985da6..2443c5e 100644
--- a/src/odil/ElementsDictionary.h
+++ b/src/odil/ElementsDictionary.h
@@ -56,6 +56,9 @@ namespace odil
/// @brief Comparator.
bool operator<(ElementsDictionaryKey const & other) const;
+ /// @brief Comparator.
+ bool operator==(ElementsDictionaryKey const & other) const;
+
private:
Type _type;
Tag _tag;
@@ -73,8 +76,8 @@ struct ElementsDictionaryEntry
std::string vm;
ElementsDictionaryEntry(
- std::string const & name, std::string const & keyword,
- std::string const & vr, std::string const & vm);
+ std::string const & name="", std::string const & keyword="",
+ std::string const & vr="", std::string const & vm="");
};
typedef
diff --git a/src/odil/Reader.cpp b/src/odil/Reader.cpp
index 9e69061..5964c41 100644
--- a/src/odil/Reader.cpp
+++ b/src/odil/Reader.cpp
@@ -319,7 +319,11 @@ Reader::Visitor
value.resize(strings.size());
std::transform(
strings.begin(), strings.end(), value.begin(),
- [](std::string const & s) { return std::stod(s); });
+ [](std::string const & s)
+ {
+ return static_cast<Value::Real>(std::stold(s));
+ }
+ );
}
}
else
@@ -465,51 +469,54 @@ Reader::Visitor
::operator()(Value::Binary & value) const
{
auto const vl = this->read_length();
-
- if(this->vr == VR::OB)
+ if(vl == 0xffffffff)
{
- value.resize(vl);
- this->stream.read(reinterpret_cast<char*>(&value[0]), value.size());
+ value = this->read_encapsulated_pixel_data(this->stream);
}
- else if(this->vr == VR::OF)
+ else
{
- if(vl%4 != 0)
+ value.resize(1);
+ if(this->vr == VR::OB || this->vr == VR::UN)
{
- throw Exception("Cannot read OF for odd-sized array");
+ value[0].resize(vl);
+ this->stream.read(
+ reinterpret_cast<char*>(&value[0][0]), value[0].size());
}
-
- value.resize(vl);
- for(unsigned int i=0; i<value.size(); i+=4)
+ else if(this->vr == VR::OF)
{
- odil_read_binary(
- float, item, this->stream, this->byte_ordering, 32);
- *reinterpret_cast<float*>(&value[i]) = item;
+ if(vl%4 != 0)
+ {
+ throw Exception("Cannot read OF for odd-sized array");
+ }
+
+ 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);
+ *reinterpret_cast<float*>(&value[0][i]) = item;
+ }
}
- }
- else if(this->vr == VR::OW)
- {
- if(vl%2 != 0)
+ else if(this->vr == VR::OW)
{
- throw Exception("Cannot read OW for odd-sized array");
- }
+ if(vl%2 != 0)
+ {
+ throw Exception("Cannot read OW for odd-sized array");
+ }
- value.resize(vl);
- for(unsigned int i=0; i<value.size(); i+=2)
+ 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);
+ *reinterpret_cast<uint16_t*>(&value[0][i]) = item;
+ }
+ }
+ else
{
- odil_read_binary(
- uint16_t, item, this->stream, this->byte_ordering, 16);
- *reinterpret_cast<uint16_t*>(&value[i]) = item;
+ throw Exception("Cannot read "+as_string(this->vr)+" as binary");
}
}
- else if(this->vr == VR::UN)
- {
- value.resize(vl);
- this->stream.read(reinterpret_cast<char*>(&value[0]), value.size());
- }
- else
- {
- throw Exception("Cannot read "+as_string(this->vr)+" as binary");
- }
}
uint32_t
@@ -605,6 +612,53 @@ Reader::Visitor
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
diff --git a/src/odil/Reader.h b/src/odil/Reader.h
index aa3528b..14588a5 100644
--- a/src/odil/Reader.h
+++ b/src/odil/Reader.h
@@ -98,6 +98,8 @@ private:
Value::Strings split_strings(std::string const & string) const;
DataSet read_item(std::istream & specific_stream) const;
+ Value::Binary read_encapsulated_pixel_data(
+ std::istream & specific_stream) const;
};
};
diff --git a/src/odil/SCU.cpp b/src/odil/SCU.cpp
index 0952b9d..5f90d07 100644
--- a/src/odil/SCU.cpp
+++ b/src/odil/SCU.cpp
@@ -47,26 +47,4 @@ SCU
this->_affected_sop_class = sop_class;
}
-void
-SCU
-::echo() const
-{
- auto const message_id = this->_association.next_message_id();
-
- message::CEchoRequest const request(
- message_id, registry::VerificationSOPClass);
- this->_association.send_message(
- request, request.get_affected_sop_class_uid());
-
- message::CEchoResponse const response = this->_association.receive_message();
- if(response.get_message_id_being_responded_to() != message_id)
- {
- std::ostringstream message;
- message << "DIMSE: Unexpected Response MsgId: "
- << response.get_message_id_being_responded_to()
- << "(expected: " << message_id << ")";
- throw Exception(message.str());
- }
-}
-
}
diff --git a/src/odil/SCU.h b/src/odil/SCU.h
index 1c51249..d6696b8 100644
--- a/src/odil/SCU.h
+++ b/src/odil/SCU.h
@@ -31,9 +31,6 @@ public:
/// @brief Set the affected SOP class
void set_affected_sop_class(std::string const & sop_class);
- /// @brief Perform DICOM ping
- void echo() const;
-
protected:
Association & _association;
/// @brief Affected SOP class.
diff --git a/src/odil/UIDsDictionary.h b/src/odil/UIDsDictionary.h
index a82d131..fa36a14 100644
--- a/src/odil/UIDsDictionary.h
+++ b/src/odil/UIDsDictionary.h
@@ -25,8 +25,8 @@ struct UIDsDictionaryEntry
std::string type;
UIDsDictionaryEntry(
- std::string const & name, std::string const & keyword,
- std::string const & type);
+ std::string const & name="", std::string const & keyword="",
+ std::string const & type="");
};
typedef std::map<std::string, UIDsDictionaryEntry> UIDsDictionary;
diff --git a/src/odil/VR.cpp b/src/odil/VR.cpp
index 73c5fef..4590417 100644
--- a/src/odil/VR.cpp
+++ b/src/odil/VR.cpp
@@ -131,7 +131,7 @@ bool is_real(VR vr)
bool is_string(VR vr)
{
return (
- vr == VR::AE || vr == VR::AS || vr == VR::CS || vr == VR::DA ||
+ vr == VR::AE || vr == VR::AS || vr == VR::AT || vr == VR::CS || vr == VR::DA ||
vr == VR::DT || vr == VR::LO || vr == VR::LT || vr == VR::PN ||
vr == VR::SH || vr == VR::ST || vr == VR::TM || vr == VR::UC ||
vr == VR::UI || vr == VR::UR || vr == VR::UT);
diff --git a/src/odil/Value.h b/src/odil/Value.h
index 231ad74..522a577 100644
--- a/src/odil/Value.h
+++ b/src/odil/Value.h
@@ -55,7 +55,7 @@ public:
typedef std::vector<DataSet> DataSets;
/// @brief Binary data container.
- typedef std::vector<uint8_t> Binary;
+ typedef std::vector<std::vector<uint8_t>> Binary;
/// @brief Build an empty value.
Value();
diff --git a/src/odil/Writer.cpp b/src/odil/Writer.cpp
index 4998a20..c229ed1 100644
--- a/src/odil/Writer.cpp
+++ b/src/odil/Writer.cpp
@@ -173,6 +173,10 @@ Writer
{
vl = 0xffffffff;
}
+ else if((vr == VR::OB || vr == VR::OW) && element.size() > 1)
+ {
+ vl = 0xffffffff;
+ }
else
{
vl = value_stream.tellp();
@@ -206,7 +210,7 @@ Writer
// Build File Meta Information, PS3.10, 7.1
DataSet meta_info = meta_information;
meta_info.add(
- registry::FileMetaInformationVersion, Value::Binary({0x00, 0x01}));
+ registry::FileMetaInformationVersion, Value::Binary({{0x00, 0x01}}));
if(!data_set.has(registry::SOPClassUID))
{
@@ -459,47 +463,55 @@ Writer::Visitor::result_type
Writer::Visitor
::operator()(Value::Binary const & value) const
{
- if(this->vr == VR::OB || this->vr == VR::UN)
+ if(value.size() > 1)
{
- this->stream.write(reinterpret_cast<char const*>(&value[0]), value.size());
+ this->write_encapsulated_pixel_data(value);
}
- else if(this->vr == VR::OW)
+ else
{
- if(value.size()%2 != 0)
+ if(this->vr == VR::OB || this->vr == VR::UN)
{
- throw Exception("Value cannot be written as OW");
+ this->stream.write(
+ reinterpret_cast<char const*>(&value[0][0]), value[0].size());
}
- for(int i=0; i<value.size(); i+=2)
+ else if(this->vr == VR::OW)
{
- uint16_t item = *reinterpret_cast<uint16_t const *>(&value[i]);
- odil_write_binary(item, this->stream, this->byte_ordering, 16);
+ if(value[0].size()%2 != 0)
+ {
+ throw Exception("Value cannot be written as OW");
+ }
+ 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);
+ }
}
- }
- else if(this->vr == VR::OF)
- {
- if(value.size()%4 != 0)
+ else if(this->vr == VR::OF)
{
- throw Exception("Value cannot be written as OF");
+ if(value[0].size()%4 != 0)
+ {
+ throw Exception("Value cannot be written as OF");
+ }
+ 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);
+ }
}
- for(int i=0; i<value.size(); i+=4)
+ else
{
- uint32_t item = *reinterpret_cast<uint32_t const *>(&value[i]);
- odil_write_binary(item, this->stream, this->byte_ordering, 32);
+ throw Exception("Cannot write "+as_string(this->vr)+" as binary");
}
- }
- else
- {
- throw Exception("Cannot write "+as_string(this->vr)+" as binary");
- }
- if(!this->stream)
- {
- throw Exception("Could not write to stream");
- }
+ if(!this->stream)
+ {
+ throw Exception("Could not write to stream");
+ }
- if(value.size()%2 == 1)
- {
- this->stream.put('\0');
+ if(value[0].size()%2 == 1)
+ {
+ this->stream.put('\0');
+ }
}
}
@@ -541,6 +553,35 @@ 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));
+ 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 81a3c1e..4998307 100644
--- a/src/odil/Writer.h
+++ b/src/odil/Writer.h
@@ -107,6 +107,8 @@ private:
template<typename T>
void write_strings(T const & sequence, char padding) const;
+
+ void write_encapsulated_pixel_data(Value::Binary const & value) const;
};
};
diff --git a/src/odil/dcmtk/conversion.cpp b/src/odil/dcmtk/conversion.cpp
index ebb57a5..d4ce23c 100644
--- a/src/odil/dcmtk/conversion.cpp
+++ b/src/odil/dcmtk/conversion.cpp
@@ -332,8 +332,10 @@ void convert<std::vector<uint8_t>, Value::Binary>(
DcmElement * source, Element & destination,
Value::Binary & (Element::*getter)())
{
+ // FIXME: does DCMTK only handle a single fragment?
auto & destination_values = (destination.*getter)();
- destination_values =
+ destination_values.resize(1);
+ destination_values[0] =
ElementAccessor<std::vector<uint8_t>>::element_get(*source, 0);
}
@@ -435,17 +437,21 @@ Element convert(DcmElement * source)
void convert(Element const & source, DcmOtherByteOtherWord * destination)
{
auto const & value = source.as_binary();
+ if(value.size() > 1)
+ {
+ throw Exception("Cannot convert multiple valued binary element");
+ }
Uint8 * output;
OFCondition condition;
if(destination->getTag().getVR().getValidEVR() == EVR_OB)
{
- condition = destination->createUint8Array(source.size(), output);
+ condition = destination->createUint8Array(value[0].size(), output);
}
else
{
Uint16* temp;
- condition = destination->createUint16Array(source.size()/2, temp);
+ condition = destination->createUint16Array(value[0].size()/2, temp);
output = reinterpret_cast<Uint8*>(temp);
}
@@ -454,21 +460,25 @@ void convert(Element const & source, DcmOtherByteOtherWord * destination)
throw Exception(condition);
}
- std::copy(value.begin(), value.end(), output);
+ std::copy(value[0].begin(), value[0].end(), output);
}
void convert(Element const & source, DcmOtherFloat * destination)
{
auto const & value = source.as_binary();
+ if(value.size() > 1)
+ {
+ throw Exception("Cannot convert multiple valued binary element");
+ }
- if(value.size()%4 != 0)
+ if(value[0].size()%4 != 0)
{
throw Exception("Cannot convert OF from odd-sized array");
}
- for(unsigned int i=0; i<value.size()/4; ++i)
+ for(unsigned int i=0; i<value[0].size()/4; ++i)
{
- float const f = *reinterpret_cast<float const *>(&value[i*4]);
+ float const f = *reinterpret_cast<float const *>(&value[0][i*4]);
destination->putFloat32(f, i);
}
}
diff --git a/src/odil/json_converter.cpp b/src/odil/json_converter.cpp
index 3b5e5ef..70ef96a 100644
--- a/src/odil/json_converter.cpp
+++ b/src/odil/json_converter.cpp
@@ -137,13 +137,22 @@ struct ToJSONVisitor
result_type operator()(VR const vr, Value::Binary const & value) const
{
+ if(value.size() > 1)
+ {
+ // PS3.18 2016b, F.2.7: There is a single InlineBinary value
+ // representing the entire Value Field.
+ // PS3.18 2016b, Figure 6.5-1: Pixel data is not encoded in
+ // JSON/XML, but transfered using a different content type
+ throw Exception("Binary element is multiple-valued");
+ }
+
result_type result;
result["vr"] = as_string(vr);
std::string encoded;
- encoded.reserve(value.size()*4/3);
- base64::encode(value.begin(), value.end(), std::back_inserter(encoded));
+ encoded.reserve(value[0].size()*4/3);
+ base64::encode(value[0].begin(), value[0].end(), std::back_inserter(encoded));
result["InlineBinary"] = encoded;
return result;
@@ -254,11 +263,14 @@ DataSet as_dataset(Json::Value const & json)
{
element = Element(Value::Binary(), vr);
+ // cf. ToJSONVisitor::operator()(VR, Value::Binary): InlineBinary is
+ // single-valued
auto const & encoded = json_element["InlineBinary"].asString();
auto & decoded = element.as_binary();
+ decoded.resize(1);
decoded.reserve(encoded.size()*3/4);
base64::decode(
- encoded.begin(), encoded.end(), std::back_inserter(decoded));
+ encoded.begin(), encoded.end(), std::back_inserter(decoded[0]));
}
else
{
diff --git a/src/odil/xml_converter.cpp b/src/odil/xml_converter.cpp
index 227f2f5..187497d 100644
--- a/src/odil/xml_converter.cpp
+++ b/src/odil/xml_converter.cpp
@@ -208,6 +208,15 @@ struct ToXMLVisitor
result_type operator()(VR const vr, Value::Binary const & value) const
{
+ if(value.size() > 1)
+ {
+ // PS3.18 2016b, F.2.7: There is a single InlineBinary value
+ // representing the entire Value Field.
+ // PS3.18 2016b, Figure 6.5-1: Pixel data is not encoded in
+ // JSON/XML, but transfered using a different content type
+ throw Exception("Binary element is multiple-valued");
+ }
+
result_type result;
result.put("<xmlattr>.vr", as_string(vr)); // Mandatory
@@ -215,8 +224,8 @@ struct ToXMLVisitor
boost::property_tree::ptree tag_value;
std::string encoded;
- encoded.reserve(value.size()*4/3);
- base64::encode(value.begin(), value.end(), std::back_inserter(encoded));
+ encoded.reserve(value[0].size()*4/3);
+ base64::encode(value[0].begin(), value[0].end(), std::back_inserter(encoded));
tag_value.put_value(encoded);
result.add_child("InlineBinary", tag_value);
@@ -328,16 +337,15 @@ boost::property_tree::ptree as_xml(DataSet const & data_set)
boost::property_tree::ptree dicomattribute =
apply_visitor(ToXMLVisitor(), element);
- auto const dictionary_it = registry::public_dictionary.find(tag);
- if(dictionary_it == registry::public_dictionary.end())
- {
- throw Exception("No such element: " + std::string(tag));
- }
-
// Add Mandatory attribute Tag
dicomattribute.put("<xmlattr>.tag", std::string(tag));
// Add Optional attribute Keyword
- dicomattribute.put("<xmlattr>.keyword", dictionary_it->second.keyword);
+ auto const dictionary_it = registry::public_dictionary.find(tag);
+ if(dictionary_it != registry::public_dictionary.end())
+ {
+ dicomattribute.put(
+ "<xmlattr>.keyword", dictionary_it->second.keyword);
+ }
// Add Optional attribute PrivateCreator
//dicomattribute.put("<xmlattr>.privateCreator", todo);
@@ -566,11 +574,14 @@ DataSet as_dataset(boost::property_tree::ptree const & xml)
}
auto const & encoded = it_value->second.get_value<std::string>();
+ // cf. ToXMLVisitor::operator()(VR, Value::Binary): InlineBinary
+ // is single-valued
auto & decoded = element.as_binary();
- decoded.reserve(encoded.size()*3/4);
+ decoded.resize(1);
+ decoded[0].reserve(encoded.size()*3/4);
base64::decode(
encoded.begin(), encoded.end(),
- std::back_inserter(decoded));
+ std::back_inserter(decoded[0]));
find_inline_binary = true;
}
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index d112b72..2541e6e 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -7,21 +7,24 @@ add_subdirectory(tools)
file(GLOB headers *.h)
file(GLOB_RECURSE tests code/*.cpp)
-include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../src ${JsonCpp_INCLUDE_DIRS})
+include_directories(
+ ${CMAKE_SOURCE_DIR}/src ${Boost_INCLUDE_DIRS} ${DCMTK_INCLUDE_DIRS}
+ ${JsonCpp_INCLUDE_DIRS})
add_definitions(
${DCMTK_DEFINITIONS}
-D BOOST_ASIO_DYN_LINK
-D ODIL_MAJOR_VERSION=${odil_MAJOR_VERSION}
-DBOOST_TEST_DYN_LINK
)
-link_directories(${Boost_LIBRARY_DIRS})
+link_directories(${Boost_LIBRARY_DIRS} ${DCMTK_LIBRARY_DIRS})
foreach(test_file ${tests})
get_filename_component(test ${test_file} NAME_WE)
- add_executable(${test} ${test_file} ${headers})
- target_link_libraries(${test} odil ${Boost_LIBRARIES})
+ add_executable(test_${test} ${test_file} ${headers})
+ target_link_libraries(test_${test} libodil ${Boost_LIBRARIES})
+ set_target_properties(test_${test} PROPERTIES OUTPUT_NAME ${test})
file(READ ${test_file} content)
@@ -52,4 +55,8 @@ foreach(test_file ${tests})
endforeach()
-add_custom_target(Runner ${CMAKE_COMMAND} -E echo "Test runner" SOURCES run.sh)
+file(GLOB_RECURSE python_tests *.py)
+
+add_custom_target(Runner ${CMAKE_COMMAND} -E echo "Test runner" SOURCES run)
+add_custom_target(
+ PythonTests ${CMAKE_COMMAND} -E echo "Python tests" SOURCES ${python_tests})
diff --git a/tests/code/Association.cpp b/tests/code/Association.cpp
index e452cd1..5266ce8 100644
--- a/tests/code/Association.cpp
+++ b/tests/code/Association.cpp
@@ -9,44 +9,6 @@
#include "../PeerFixtureBase.h"
-namespace odil
-{
-
-bool
-operator==(
- AssociationParameters::PresentationContext const & left,
- AssociationParameters::PresentationContext const & right)
-{
- return (
- left.abstract_syntax == right.abstract_syntax &&
- left.transfer_syntaxes == right.transfer_syntaxes &&
- left.scu_role_support == right.scu_role_support &&
- left.scp_role_support == right.scp_role_support
- );
-}
-
-bool
-operator==(
- AssociationParameters const & left, AssociationParameters const & right)
-{
- return (
- left.get_calling_ae_title() == right.get_calling_ae_title() &&
- left.get_called_ae_title() == right.get_called_ae_title() &&
-
- left.get_presentation_contexts() == right.get_presentation_contexts() &&
-
-
- left.get_user_identity().type == right.get_user_identity().type &&
- left.get_user_identity().primary_field == right.get_user_identity().primary_field &&
- left.get_user_identity().secondary_field == right.get_user_identity().secondary_field &&
-
- left.get_maximum_length() == right.get_maximum_length()
- );
-}
-
-}
-
-
BOOST_AUTO_TEST_CASE(DefaultConstructor)
{
odil::Association association;
diff --git a/tests/code/DataSet.cpp b/tests/code/DataSet.cpp
index 414bb78..608537b 100644
--- a/tests/code/DataSet.cpp
+++ b/tests/code/DataSet.cpp
@@ -271,12 +271,11 @@ BOOST_AUTO_TEST_CASE(AddBinary)
odil::Tag const tag("BadPixelImage");
odil::DataSet dataset;
- dataset.add(tag, odil::Value::Binary({0x01, 0x02}));
+ dataset.add(tag, odil::Value::Binary({{0x01, 0x02}}));
BOOST_CHECK(dataset.is_binary(tag));
- BOOST_REQUIRE_EQUAL(dataset.size(tag), 2);
BOOST_REQUIRE(
- dataset.as_binary(tag) == odil::Value::Binary({ 0x01, 0x02 }));
+ dataset.as_binary(tag) == odil::Value::Binary({{ 0x01, 0x02 }}));
}
BOOST_AUTO_TEST_CASE(ModifyInt)
diff --git a/tests/code/EchoSCU.cpp b/tests/code/EchoSCU.cpp
new file mode 100644
index 0000000..9a2eb0c
--- /dev/null
+++ b/tests/code/EchoSCU.cpp
@@ -0,0 +1,28 @@
+#define BOOST_TEST_MODULE EchoSCU
+#include <boost/test/unit_test.hpp>
+
+#include "odil/EchoSCU.h"
+#include "odil/registry.h"
+
+#include "../PeerFixtureBase.h"
+
+struct Fixture: public PeerFixtureBase
+{
+ Fixture()
+ : PeerFixtureBase({
+ {
+ 1, odil::registry::VerificationSOPClass,
+ {odil::registry::ImplicitVRLittleEndian}, true, false
+ }
+ })
+ {
+ // Nothing else
+ }
+};
+
+BOOST_FIXTURE_TEST_CASE(Echo, Fixture)
+{
+ odil::EchoSCU scu(this->association);
+ scu.echo();
+}
+
diff --git a/tests/code/Reader.cpp b/tests/code/Reader.cpp
index 2999031..13a0106 100644
--- a/tests/code/Reader.cpp
+++ b/tests/code/Reader.cpp
@@ -134,7 +134,7 @@ BOOST_AUTO_TEST_CASE(IS)
BOOST_AUTO_TEST_CASE(OB)
{
odil::Element odil_element(
- std::vector<uint8_t>({0x01, 0x02, 0x03, 0x04}),
+ odil::Value::Binary({{0x01, 0x02, 0x03, 0x04}}),
odil::VR::OB);
odil::DataSet odil_data_set;
odil_data_set.add(odil::registry::EncapsulatedDocument, odil_element);
@@ -145,7 +145,7 @@ BOOST_AUTO_TEST_CASE(OB)
BOOST_AUTO_TEST_CASE(OF)
{
odil::Element odil_element(
- std::vector<uint8_t>({0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}),
+ odil::Value::Binary({{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}}),
odil::VR::OF);
odil::DataSet odil_data_set;
odil_data_set.add(odil::registry::VectorGridData, odil_element);
@@ -156,7 +156,7 @@ BOOST_AUTO_TEST_CASE(OF)
BOOST_AUTO_TEST_CASE(OW)
{
odil::Element odil_element(
- std::vector<uint8_t>({0x01, 0x02, 0x03, 0x04}),
+ odil::Value::Binary({{0x01, 0x02, 0x03, 0x04}}),
odil::VR::OW);
odil::DataSet odil_data_set;
odil_data_set.add(odil::registry::RedPaletteColorLookupTableData, odil_element);
diff --git a/tests/code/SCU.cpp b/tests/code/SCU.cpp
index 090d0ed..ba644a1 100644
--- a/tests/code/SCU.cpp
+++ b/tests/code/SCU.cpp
@@ -33,8 +33,3 @@ BOOST_FIXTURE_TEST_CASE(AffectedSOPClassUID, Fixture)
BOOST_CHECK_EQUAL(scu.get_affected_sop_class(), "1.2.3");
}
-BOOST_FIXTURE_TEST_CASE(Echo, Fixture)
-{
- odil::SCU scu(this->association);
- scu.echo();
-}
diff --git a/tests/code/Writer.cpp b/tests/code/Writer.cpp
index dc6a28b..ee13d3e 100644
--- a/tests/code/Writer.cpp
+++ b/tests/code/Writer.cpp
@@ -144,7 +144,7 @@ BOOST_AUTO_TEST_CASE(IS)
BOOST_AUTO_TEST_CASE(OB)
{
odil::Element odil_element(
- std::vector<uint8_t>({0x01, 0x02, 0x03, 0x04}),
+ odil::Value::Binary({{0x01, 0x02, 0x03, 0x04}}),
odil::VR::OB);
odil::DataSet odil_data_set;
odil_data_set.add(odil::registry::EncapsulatedDocument, odil_element);
@@ -155,7 +155,7 @@ BOOST_AUTO_TEST_CASE(OB)
BOOST_AUTO_TEST_CASE(OF)
{
odil::Element odil_element(
- std::vector<uint8_t>({0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}),
+ odil::Value::Binary({{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}}),
odil::VR::OF);
odil::DataSet odil_data_set;
odil_data_set.add(odil::registry::VectorGridData, odil_element);
@@ -166,7 +166,7 @@ BOOST_AUTO_TEST_CASE(OF)
BOOST_AUTO_TEST_CASE(OW)
{
odil::Element odil_element(
- std::vector<uint8_t>({0x01, 0x02, 0x03, 0x04}),
+ odil::Value::Binary({{0x01, 0x02, 0x03, 0x04}}),
odil::VR::OW);
odil::DataSet odil_data_set;
odil_data_set.add(odil::registry::RedPaletteColorLookupTableData, odil_element);
diff --git a/tests/code/json_converter.cpp b/tests/code/json_converter.cpp
index c4f9607..9cf7d3b 100644
--- a/tests/code/json_converter.cpp
+++ b/tests/code/json_converter.cpp
@@ -153,7 +153,7 @@ BOOST_AUTO_TEST_CASE(AsJSONBinary)
odil::DataSet data_set;
data_set.add(0xdeadbeef,
odil::Element(
- odil::Value::Binary({0x1, 0x2, 0x3, 0x4, 0x5}),
+ odil::Value::Binary({{0x1, 0x2, 0x3, 0x4, 0x5}}),
odil::VR::OB));
auto const json = odil::as_json(data_set);
@@ -275,5 +275,5 @@ BOOST_AUTO_TEST_CASE(AsDataSetBinary)
BOOST_REQUIRE(data_set.get_vr("deadbeef") == odil::VR::OB);
BOOST_REQUIRE(data_set.is_binary("deadbeef"));
BOOST_REQUIRE(data_set.as_binary("deadbeef") == odil::Value::Binary(
- {0x1, 0x2, 0x3, 0x4, 0x5}));
+ {{0x1, 0x2, 0x3, 0x4, 0x5}}));
}
diff --git a/tests/code/xml_converter.cpp b/tests/code/xml_converter.cpp
index 840a7a7..bb40b70 100644
--- a/tests/code/xml_converter.cpp
+++ b/tests/code/xml_converter.cpp
@@ -515,7 +515,7 @@ BOOST_AUTO_TEST_CASE(AsXMLBinary)
odil::DataSet data_set;
data_set.add(0x00660023,
odil::Element(
- odil::Value::Binary({0x1, 0x2, 0x3, 0x4, 0x5}),
+ odil::Value::Binary({{0x1, 0x2, 0x3, 0x4, 0x5}}),
odil::VR::OW));
auto const xml = odil::as_xml(data_set);
@@ -613,17 +613,6 @@ BOOST_AUTO_TEST_CASE(AsXMLInvalidPersonName)
}
/******************************* TEST Error ************************************/
-BOOST_AUTO_TEST_CASE(AsXMLInvalidDICOMTag)
-{
- odil::DataSet data_set;
- data_set.add(0xbad00bad,
- odil::Element(
- odil::Value::Strings({"value"}),
- odil::VR::CS));
- BOOST_REQUIRE_THROW(odil::as_xml(data_set), odil::Exception);
-}
-
-/******************************* TEST Error ************************************/
BOOST_AUTO_TEST_CASE(AsXMLBadVR)
{
odil::DataSet data_set;
@@ -929,7 +918,7 @@ BOOST_AUTO_TEST_CASE(AsDataSetBinary)
BOOST_REQUIRE(data_set.get_vr("00660023") == odil::VR::OW);
BOOST_REQUIRE(data_set.is_binary("00660023"));
BOOST_REQUIRE(data_set.as_binary("00660023") == odil::Value::Binary(
- {0x1, 0x2, 0x3, 0x4, 0x5}));
+ {{0x1, 0x2, 0x3, 0x4, 0x5}}));
}
/******************************* TEST Error ************************************/
diff --git a/tests/peer_fixture_base.py b/tests/peer_fixture_base.py
new file mode 100644
index 0000000..03a1690
--- /dev/null
+++ b/tests/peer_fixture_base.py
@@ -0,0 +1,18 @@
+import os
+import unittest
+
+import odil
+
+class PeerFixtureBase(unittest.TestCase):
+ def setUp(self, contexts):
+ self.association = odil.Association()
+ self.association.set_peer_host(os.environ["ODIL_PEER_HOST_NAME"])
+ self.association.set_peer_port(int(os.environ["ODIL_PEER_PORT"]))
+ self.association.update_parameters()\
+ .set_calling_ae_title(os.environ["ODIL_OWN_AET"])\
+ .set_called_ae_title(os.environ["ODIL_PEER_AET"]) \
+ .set_presentation_contexts(contexts)
+ self.association.associate()
+
+ def tearDown(self):
+ self.association.release()
diff --git a/tests/run b/tests/run
new file mode 100755
index 0000000..a2737e9
--- /dev/null
+++ b/tests/run
@@ -0,0 +1,139 @@
+#!/usr/bin/env python
+
+import argparse
+import os
+import re
+import shutil
+import subprocess
+import sys
+import time
+import tempfile
+
+def main():
+ parser = argparse.ArgumentParser(
+ description="Run all tests (C++ and Python). "
+ "This program must be run from the build directory.")
+ parser.add_argument("tests", nargs="*", help="Python only")
+ parser.add_argument("--no-network", action="store_true")
+ parser.add_argument("--test-regex", "-R", help="C++ only")
+ parser.add_argument("--exclude-regex", "-E", help="C++ only")
+ parser.add_argument("--exclude", "-e", help="Python only")
+ arguments = parser.parse_args()
+
+ if arguments.no_network:
+ excluded_cpp = [
+ "Association", "Network", "ServiceRole", "SCP", "SCU", "Transport"]
+ excluded_python = ["scu"]
+
+ arguments.exclude_regex = "{}{}{}".format(
+ arguments.exclude_regex or "",
+ "|" if arguments.exclude_regex else "",
+ "|".join(excluded_cpp))
+
+ arguments.exclude = "{}{}{}".format(
+ arguments.exclude or "",
+ "|" if arguments.exclude else "",
+ "|".join(excluded_python))
+
+ cpp_args = []
+ if arguments.test_regex:
+ cpp_args += ["-R", arguments.test_regex]
+ if arguments.exclude_regex:
+ cpp_args += ["-E", arguments.exclude_regex]
+
+ python_args = []
+ if arguments.exclude:
+ python_args += ["-e", arguments.exclude]
+ python_args += arguments.tests
+
+ directory, process = setUp(arguments.no_network)
+
+ environment = os.environ.copy()
+ environment["ODIL_OWN_AET"] = "LOCAL"
+ environment["ODIL_PEER_HOST_NAME"] = "127.0.0.1"
+ environment["ODIL_PEER_PORT"] = "11112"
+ environment["ODIL_PEER_AET"] = "REMOTE"
+ environment["PATH"] = os.pathsep.join(["tests/tools", environment["PATH"]])
+ environment["PYTHONPATH"] = os.pathsep.join(
+ ["wrappers", environment.get("PYTHONPATH", "")])
+
+ source_directory = None
+ with open("CMakeCache.txt") as fd:
+ lines = fd.readlines()
+ for line in lines:
+ match = re.match(r".*_SOURCE_DIR:.*=(.*)", line)
+ if match:
+ source_directory = match.group(1)
+ break
+
+ cpp_code = subprocess.call(
+ ["ctest", "--no-compress-output", "-T", "Test"]+cpp_args,
+ env=environment)
+ python_code = subprocess.call(
+ ["nosetests-2.7", os.path.join(source_directory, "tests", "wrappers")]+python_args,
+ env=environment)
+
+ tearDown(directory, process)
+
+ return max(cpp_code, python_code)
+
+def setUp(no_network):
+ directory = tempfile.mkdtemp()
+
+ process = None
+
+ if not no_network:
+ data = """
+HostTable BEGIN
+remote = (REMOTE, localhost, 11112)
+local = (LOCAL, localhost, 11113)
+HostTable END
+
+AETable BEGIN
+REMOTE {} RW (10, 1024mb) local
+AETable END
+ """.format(directory)
+ with open(os.path.join(directory, "config"), "w") as fd:
+ fd.write(data)
+
+ process = subprocess.Popen([
+ "dcmqrscp",
+ "-ll", "error",
+ "-c", os.path.join(directory, "config"),
+ "11112"])
+ time.sleep(1)
+
+ data = """
+ (0008,0016) UI =RawDataStorage
+ (0008,0018) UI [2.25.95090344942250266709587559073467305647]
+ (0010,0010) PN [Doe^John]
+ (0010,0020) LO [DJ001]
+ """
+
+ read, write = os.pipe()
+ os.write(write, data)
+ os.close(write)
+
+ subprocess.check_call(
+ [
+ "dump2dcm", "--write-xfer-little", "/dev/stdin",
+ os.path.join(directory, "data.dcm")
+ ],
+ stdin=read)
+ subprocess.check_call([
+ "storescu",
+ "-aet", "LOCAL", "-aec", "REMOTE",
+ "localhost", "11112",
+ os.path.join(directory, "data.dcm")])
+
+ return directory, process
+
+def tearDown(directory, process):
+ if process is not None:
+ process.terminate()
+ process.wait()
+ if directory is not None:
+ shutil.rmtree(directory)
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/tests/run.sh b/tests/run.sh
deleted file mode 100755
index 1177ea8..0000000
--- a/tests/run.sh
+++ /dev/null
@@ -1,57 +0,0 @@
-#! /bin/sh
-
-set -e
-set -u
-
-configure() {
- cat > ${directory}/config << EOF
-HostTable BEGIN
-remote = (REMOTE, localhost, 11112)
-local = (LOCAL, localhost, 11113)
-HostTable END
-
-AETable BEGIN
-REMOTE ${directory} RW (10, 1024mb) local
-AETable END
-EOF
-}
-
-add_data() {
-dump2dcm --write-xfer-little /dev/stdin ${directory}/data.dcm <<EOF
-(0008,0016) UI =RawDataStorage
-(0008,0018) UI [2.25.95090344942250266709587559073467305647]
-(0010,0010) PN [Doe^John]
-(0010,0020) LO [DJ001]
-EOF
-storescu -aet LOCAL -aec REMOTE localhost 11112 ${directory}/data.dcm
-}
-
-start_scp() {
- dcmqrscp -c ${directory}/config 11112 &
- PID=$!
- sleep 1
-}
-
-stop_scp() {
- kill ${PID}
-}
-
-clean() {
- rm -rf ${directory}
-}
-
-directory=$(mktemp -d odil.XXX)
-configure
-start_scp
-add_data
-
-export ODIL_OWN_AET=LOCAL
-export ODIL_PEER_HOST_NAME=127.0.0.1
-export ODIL_PEER_PORT=11112
-export ODIL_PEER_AET=REMOTE
-
-ctest --no-compress-output -T Test $@ || true
-
-stop_scp
-clean
-
diff --git a/tests/tools/CMakeLists.txt b/tests/tools/CMakeLists.txt
index 9895042..35b445e 100644
--- a/tests/tools/CMakeLists.txt
+++ b/tests/tools/CMakeLists.txt
@@ -7,5 +7,6 @@ link_directories(${DCMTK_LIBRARY_DIRS})
file(GLOB headers *.h)
file(GLOB files "*.cc")
-add_executable(getscu ${files} ${headers})
-target_link_libraries(getscu ${DCMTK_LIBRARIES})
+add_executable(dcmtk_getscu ${files} ${headers})
+target_link_libraries(dcmtk_getscu ${DCMTK_LIBRARIES})
+set_target_properties(dcmtk_getscu PROPERTIES OUTPUT_NAME getscu)
diff --git a/tests/wrappers/test_association.py b/tests/wrappers/test_association.py
new file mode 100644
index 0000000..036ea9e
--- /dev/null
+++ b/tests/wrappers/test_association.py
@@ -0,0 +1,48 @@
+import unittest
+
+import odil
+
+class TestAssociation(unittest.TestCase):
+ def test_default_constructor(self):
+ association = odil.Association()
+ self.assertFalse(association.is_associated())
+
+ def test_peer_host(self):
+ association = odil.Association()
+ association.set_peer_host("pacs.example.com")
+ self.assertEqual(association.get_peer_host(), "pacs.example.com")
+
+ def test_peer_port(self):
+ association = odil.Association()
+ association.set_peer_port(1234)
+ self.assertEqual(association.get_peer_port(), 1234)
+
+ def test_peer_port(self):
+ association = odil.Association()
+ association.set_peer_port(1234)
+ self.assertEqual(association.get_peer_port(), 1234)
+
+ def test_parameters(self):
+ parameters = odil.AssociationParameters()
+ parameters.set_called_ae_title("foo")
+
+ association = odil.Association()
+ association.set_parameters(parameters)
+ self.assertEqual(
+ association.get_parameters().get_called_ae_title(), "foo")
+
+ def test_update_parameters(self):
+ association = odil.Association()
+ association.update_parameters().set_called_ae_title("foo")
+ self.assertEqual(
+ association.get_parameters().get_called_ae_title(), "foo")
+
+ def test_next_message_id(self):
+ association = odil.Association()
+ id1 = association.next_message_id()
+ id2 = association.next_message_id()
+ self.assertNotEqual(id1, id2)
+
+if __name__ == "__main__":
+ unittest.main()
+
diff --git a/tests/wrappers/test_association_parameters.py b/tests/wrappers/test_association_parameters.py
new file mode 100644
index 0000000..fb4b8d0
--- /dev/null
+++ b/tests/wrappers/test_association_parameters.py
@@ -0,0 +1,116 @@
+import unittest
+
+import odil
+
+class TestAssociationParameters(unittest.TestCase):
+ def test_default_constructor(self):
+ parameters = odil.AssociationParameters()
+ self.assertEqual(parameters.get_called_ae_title(), "")
+ self.assertEqual(parameters.get_calling_ae_title(), "")
+ self.assertEqual(len(parameters.get_presentation_contexts()), 0)
+
+ user_identity = parameters.get_user_identity()
+ self.assertEqual(
+ user_identity.type,
+ odil.AssociationParameters.UserIdentity.Type.None)
+
+ self.assertEqual(parameters.get_maximum_length(), 16384)
+
+ def test_called_ae_title(self):
+ parameters = odil.AssociationParameters()
+ parameters.set_called_ae_title("foo")
+ self.assertEqual(parameters.get_called_ae_title(), "foo")
+
+ def test_calling_ae_title(self):
+ parameters = odil.AssociationParameters()
+ parameters.set_calling_ae_title("foo")
+ self.assertEqual(parameters.get_calling_ae_title(), "foo")
+
+ def test_presentation_contexts(self):
+ presentation_context = odil.AssociationParameters.PresentationContext()
+ presentation_context.id = 1
+ presentation_context.abstract_syntax = "foo"
+ presentation_context.transfer_syntaxes.append("bar")
+
+ parameters = odil.AssociationParameters()
+ parameters.set_presentation_contexts([presentation_context])
+
+ self.assertEqual(len(parameters.get_presentation_contexts()), 1)
+ self.assertEqual(
+ parameters.get_presentation_contexts()[0], presentation_context)
+
+ def test_user_identity_username(self):
+ parameters = odil.AssociationParameters()
+ parameters.set_user_identity_to_username("foo")
+
+ user_identity = parameters.get_user_identity()
+ self.assertEqual(
+ user_identity.type,
+ odil.AssociationParameters.UserIdentity.Type.Username)
+ self.assertEqual(user_identity.primary_field, "foo")
+
+ def test_user_identity_username_and_password(self):
+ parameters = odil.AssociationParameters()
+ parameters.set_user_identity_to_username_and_password("foo", "bar")
+
+ user_identity = parameters.get_user_identity()
+ self.assertEqual(
+ user_identity.type,
+ odil.AssociationParameters.UserIdentity.Type.UsernameAndPassword)
+ self.assertEqual(user_identity.primary_field, "foo")
+ self.assertEqual(user_identity.secondary_field, "bar")
+
+ def test_user_identity_kerberos(self):
+ parameters = odil.AssociationParameters()
+ parameters.set_user_identity_to_kerberos("foo")
+
+ user_identity = parameters.get_user_identity()
+ self.assertEqual(
+ user_identity.type,
+ odil.AssociationParameters.UserIdentity.Type.Kerberos)
+ self.assertEqual(user_identity.primary_field, "foo")
+
+ def test_user_identity_saml(self):
+ parameters = odil.AssociationParameters()
+ parameters.set_user_identity_to_saml("foo")
+
+ user_identity = parameters.get_user_identity()
+ self.assertEqual(
+ user_identity.type,
+ odil.AssociationParameters.UserIdentity.Type.SAML)
+ self.assertEqual(user_identity.primary_field, "foo")
+
+ def test_user_identity_none(self):
+ parameters = odil.AssociationParameters()
+ parameters.set_user_identity_to_saml("foo")
+ parameters.set_user_identity_to_none()
+
+ user_identity = parameters.get_user_identity()
+ self.assertEqual(
+ user_identity.type,
+ odil.AssociationParameters.UserIdentity.Type.None)
+
+ def test_maximum_length(self):
+ parameters = odil.AssociationParameters()
+ parameters.set_maximum_length(12345)
+ self.assertEqual(parameters.get_maximum_length(), 12345)
+
+ def test_chaining(self):
+ parameters = odil.AssociationParameters()
+ parameters.set_called_ae_title("foo").set_calling_ae_title("bar")
+ self.assertEqual(parameters.get_called_ae_title(), "foo")
+ self.assertEqual(parameters.get_calling_ae_title(), "bar")
+
+class TestPresentationContext(unittest.TestCase):
+ def test_constructor(self):
+ presentation_context = odil.AssociationParameters.PresentationContext(
+ 1, "foo", ["bar", "baz"], False, True)
+ self.assertEqual(presentation_context.id, 1)
+ self.assertEqual(presentation_context.abstract_syntax, "foo")
+ self.assertEqual(
+ [x for x in presentation_context.transfer_syntaxes], ["bar", "baz"])
+ self.assertEqual(presentation_context.scu_role_support, False)
+ self.assertEqual(presentation_context.scp_role_support, True)
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/wrappers/test_data_set.py b/tests/wrappers/test_data_set.py
new file mode 100644
index 0000000..c43e533
--- /dev/null
+++ b/tests/wrappers/test_data_set.py
@@ -0,0 +1,166 @@
+import unittest
+
+import odil
+
+class TestDataSet(unittest.TestCase):
+ def test_empty_constructor(self):
+ data_set = odil.DataSet()
+ self.assertTrue(data_set.empty())
+ self.assertEqual(data_set.size(), 0)
+ self.assertEqual(len(data_set), 0)
+
+ def test_empty_element(self):
+ tag = odil.registry.PatientName
+ data_set = odil.DataSet()
+ data_set.add(tag, odil.Element())
+
+ self.assertFalse(data_set.empty())
+ self.assertEqual(data_set.size(), 1)
+ self.assertEqual(len(data_set), 1)
+
+ self.assertEqual(data_set.get_vr(tag), odil.VR.INVALID)
+ self.assertTrue(data_set.empty(tag))
+ self.assertEqual(data_set.size(tag), 0)
+
+ def test_empty_element_tag(self):
+ tag = odil.registry.PatientName
+ data_set = odil.DataSet()
+ data_set.add(tag)
+
+ self.assertFalse(data_set.empty())
+ self.assertEqual(data_set.size(), 1)
+ self.assertEqual(len(data_set), 1)
+
+ self.assertEqual(data_set.get_vr(tag), odil.VR.PN)
+ self.assertTrue(data_set.empty(tag))
+ self.assertEqual(data_set.size(tag), 0)
+
+ def test_int_element(self):
+ tag = odil.registry.SelectorUSValue
+ value = [1, 2, 3]
+ data_set = odil.DataSet()
+ data_set.add(tag, odil.Value.Integers(value))
+
+ self.assertFalse(data_set.empty())
+ self.assertEqual(data_set.size(), 1)
+ self.assertEqual(len(data_set), 1)
+
+ self.assertEqual(data_set.get_vr(tag), odil.VR.US)
+ self.assertFalse(data_set.empty(tag))
+ self.assertEqual(data_set.size(tag), 3)
+ self.assertTrue(data_set.is_int(tag))
+ self.assertEqual([x for x in data_set.as_int(tag)], value)
+
+ def test_real_element(self):
+ tag = odil.registry.SelectorFLValue
+ value = [1.1, 2, 3.3]
+ data_set = odil.DataSet()
+ data_set.add(tag, odil.Value.Reals(value))
+
+ self.assertFalse(data_set.empty())
+ self.assertEqual(data_set.size(), 1)
+ self.assertEqual(len(data_set), 1)
+
+ self.assertEqual(data_set.get_vr(tag), odil.VR.FL)
+ self.assertFalse(data_set.empty(tag))
+ self.assertEqual(data_set.size(tag), 3)
+ self.assertTrue(data_set.is_real(tag))
+ self.assertEqual([x for x in data_set.as_real(tag)], value)
+
+ def test_string_element(self):
+ tag = odil.registry.SelectorCSValue
+ value = ["foo", "bar"]
+ data_set = odil.DataSet()
+ data_set.add(tag, odil.Value.Strings(value))
+
+ self.assertFalse(data_set.empty())
+ self.assertEqual(data_set.size(), 1)
+ self.assertEqual(len(data_set), 1)
+
+ self.assertEqual(data_set.get_vr(tag), odil.VR.CS)
+ self.assertFalse(data_set.empty(tag))
+ self.assertEqual(data_set.size(tag), 2)
+ self.assertTrue(data_set.is_string(tag))
+ self.assertEqual([x for x in data_set.as_string(tag)], value)
+
+ def test_data_set_element(self):
+ tag = odil.registry.SelectorCodeSequenceValue
+ value = [odil.DataSet(), odil.DataSet()]
+ data_set = odil.DataSet()
+ data_set.add(
+ tag, odil.Value.DataSets(value))
+
+ self.assertFalse(data_set.empty())
+ self.assertEqual(data_set.size(), 1)
+ self.assertEqual(len(data_set), 1)
+
+ self.assertEqual(data_set.get_vr(tag), odil.VR.SQ)
+ self.assertFalse(data_set.empty(tag))
+ self.assertEqual(data_set.size(tag), 2)
+ self.assertTrue(data_set.is_data_set(tag))
+ self.assertEqual([x for x in data_set.as_data_set(tag)], value)
+
+ def test_string_binary(self):
+ tag = odil.registry.RedPaletteColorLookupTableData
+ value = [odil.Value.BinaryItem("\x01\x02")]
+ data_set = odil.DataSet()
+ data_set.add(tag, odil.Value.Binary(value))
+
+ self.assertFalse(data_set.empty())
+ self.assertEqual(data_set.size(), 1)
+ self.assertEqual(len(data_set), 1)
+
+ self.assertEqual(data_set.get_vr(tag), odil.VR.OW)
+ self.assertFalse(data_set.empty(tag))
+ self.assertEqual(data_set.size(tag), 1)
+ self.assertTrue(data_set.is_binary(tag))
+ self.assertEqual(
+ [x for x in data_set.as_binary(tag)[0]], [x for x in value[0]])
+
+ def test_getitem(self):
+ data_set = odil.DataSet()
+ data_set.add(odil.registry.PatientName, odil.Value.Strings(["Doe^John"]))
+ self.assertEqual(
+ [x for x in data_set[odil.registry.PatientName].as_string()],
+ ["Doe^John"])
+ self.assertRaises(Exception, lambda x: data_set[odil.registry.PatientID])
+
+ def test_iter(self):
+ data_set = odil.DataSet()
+ data_set.add(odil.registry.PatientName, odil.Value.Strings(["Doe^John"]))
+ data_set.add(odil.registry.PatientID, odil.Value.Strings(["DJ123"]))
+ self.assertEqual(
+ [x.get_name() for x in data_set],
+ ["PatientName", "PatientID"])
+
+ def test_keys(self):
+ data_set = odil.DataSet()
+ data_set.add(odil.registry.PatientName, odil.Value.Strings(["Doe^John"]))
+ data_set.add(odil.registry.PatientID, odil.Value.Strings(["DJ123"]))
+ self.assertEqual(
+ [x.get_name() for x in data_set.keys()],
+ ["PatientName", "PatientID"])
+
+ def test_values(self):
+ data_set = odil.DataSet()
+ data_set.add(odil.registry.PatientName, odil.Value.Strings(["Doe^John"]))
+ data_set.add(odil.registry.PatientID, odil.Value.Strings(["DJ123"]))
+ self.assertEqual(
+ [
+ [item for item in element.as_string()]
+ for element in data_set.values()],
+ [["Doe^John"], ["DJ123"]])
+
+ def test_items(self):
+ data_set = odil.DataSet()
+ data_set.add(odil.registry.PatientName, odil.Value.Strings(["Doe^John"]))
+ data_set.add(odil.registry.PatientID, odil.Value.Strings(["DJ123"]))
+ self.assertEqual(
+ [
+ [tag.get_name(), [item for item in element.as_string()]]
+ for tag, element in data_set.items()],
+ [["PatientName", ["Doe^John"]], ["PatientID", ["DJ123"]]])
+
+if __name__ == "__main__":
+ unittest.main()
+
diff --git a/tests/wrappers/test_echo_scu.py b/tests/wrappers/test_echo_scu.py
new file mode 100644
index 0000000..43575b9
--- /dev/null
+++ b/tests/wrappers/test_echo_scu.py
@@ -0,0 +1,26 @@
+import os
+import sys
+import unittest
+
+import odil
+
+sys.path.append(os.path.dirname(os.path.dirname(__file__)))
+from peer_fixture_base import PeerFixtureBase
+
+class TestEchoSCU(PeerFixtureBase):
+ def setUp(self):
+ PeerFixtureBase.setUp(
+ self,
+ [
+ odil.AssociationParameters.PresentationContext(
+ 3, odil.registry.VerificationSOPClass,
+ [ odil.registry.ImplicitVRLittleEndian ], True, False)
+ ]
+ )
+
+ def test_echo(self):
+ echo = odil.EchoSCU(self.association)
+ echo.echo()
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/wrappers/test_element.py b/tests/wrappers/test_element.py
new file mode 100644
index 0000000..1fcbe74
--- /dev/null
+++ b/tests/wrappers/test_element.py
@@ -0,0 +1,55 @@
+import unittest
+
+import odil
+
+class TestElement(unittest.TestCase):
+ def test_empty_constructor(self):
+ element = odil.Element()
+ self.assertTrue(element.empty())
+ self.assertEqual(element.size(), 0)
+ self.assertEqual(len(element), 0)
+ self.assertEqual(element.vr, odil.VR.INVALID)
+
+ def test_integers_constructor(self):
+ items = [1, 2, 3]
+ element = odil.Element(odil.Value.Integers(items), odil.VR.US)
+ self.assertEqual([x for x in element.as_int()], items)
+ self.assertEqual(element.vr, odil.VR.US)
+ self.assertEqual(element.size(), 3)
+ self.assertEqual(len(element), 3)
+
+ def test_reals_constructor(self):
+ items = [1.1, 2, 3.3]
+ element = odil.Element(odil.Value.Reals(items), odil.VR.FL)
+ self.assertEqual([x for x in element.as_real()], items)
+ self.assertEqual(element.vr, odil.VR.FL)
+ self.assertEqual(element.size(), 3)
+ self.assertEqual(len(element), 3)
+
+ def test_strings_constructor(self):
+ items = ["foo", "bar"]
+ element = odil.Element(odil.Value.Strings(items), odil.VR.CS)
+ self.assertEqual([x for x in element.as_string()], items)
+ self.assertEqual(element.vr, odil.VR.CS)
+ self.assertEqual(element.size(), 2)
+ self.assertEqual(len(element), 2)
+
+ def test_data_sets_constructor(self):
+ items = [odil.DataSet(), odil.DataSet()]
+ element = odil.Element(odil.Value.DataSets(items), odil.VR.SQ)
+ self.assertEqual([x for x in element.as_data_set()], items)
+ self.assertEqual(element.vr, odil.VR.SQ)
+ self.assertEqual(element.size(), 2)
+ self.assertEqual(len(element), 2)
+
+ def test_binary_constructor(self):
+ items = [odil.Value.BinaryItem("\x01\x02\x03")]
+ element = odil.Element(odil.Value.Binary(items), odil.VR.OB)
+ self.assertEqual(
+ [x for x in element.as_binary()[0]], [x for x in items[0]])
+ self.assertEqual(element.vr, odil.VR.OB)
+ self.assertEqual(element.size(), 1)
+ self.assertEqual(len(element), 1)
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/wrappers/test_elements_dictionary.py b/tests/wrappers/test_elements_dictionary.py
new file mode 100644
index 0000000..1afee09
--- /dev/null
+++ b/tests/wrappers/test_elements_dictionary.py
@@ -0,0 +1,48 @@
+import unittest
+
+import odil
+
+class TestElementsDictionaryKey(unittest.TestCase):
+ def test_empty_constructor(self):
+ key = odil.ElementsDictionaryKey()
+
+ def test_tag_constructor(self):
+ key = odil.ElementsDictionaryKey(odil.registry.PatientName)
+
+ def test_string_constructor(self):
+ key = odil.ElementsDictionaryKey("60xx0010")
+
+class TestElementsDictionaryEntry(unittest.TestCase):
+ def test_constructor(self):
+ entry = odil.ElementsDictionaryEntry(
+ "Patient's Name", "PatientName", "PN", "1")
+ self.assertEqual(entry.name, "Patient's Name")
+ self.assertEqual(entry.keyword, "PatientName")
+ self.assertEqual(entry.vr, "PN")
+ self.assertEqual(entry.vm, "1")
+
+class TestElementsDictionary(unittest.TestCase):
+ def test_contains(self):
+ self.assertTrue(
+ odil.registry.PatientName in odil.registry.public_dictionary)
+ self.assertFalse(
+ odil.Tag(0x0001, 0x0001) in odil.registry.public_dictionary)
+ self.assertTrue("60xx0010" in odil.registry.public_dictionary)
+ self.assertFalse("foo" in odil.registry.public_dictionary)
+
+ def test_get_item_tag(self):
+ entry = odil.registry.public_dictionary[odil.registry.PatientName]
+ self.assertEqual(entry.name, "Patient's Name")
+ self.assertEqual(entry.keyword, "PatientName")
+ self.assertEqual(entry.vr, "PN")
+ self.assertEqual(entry.vm, "1")
+
+ def test_get_item_string(self):
+ entry = odil.registry.public_dictionary["60xx0010"]
+ self.assertEqual(entry.name, "Overlay Rows")
+ self.assertEqual(entry.keyword, "OverlayRows")
+ self.assertEqual(entry.vr, "US")
+ self.assertEqual(entry.vm, "1")
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/wrappers/test_find_scu.py b/tests/wrappers/test_find_scu.py
new file mode 100644
index 0000000..40eb177
--- /dev/null
+++ b/tests/wrappers/test_find_scu.py
@@ -0,0 +1,51 @@
+import os
+import sys
+import unittest
+
+import odil
+
+sys.path.append(os.path.dirname(os.path.dirname(__file__)))
+from peer_fixture_base import PeerFixtureBase
+
+class TestFindSCU(PeerFixtureBase):
+ def setUp(self):
+ PeerFixtureBase.setUp(
+ self,
+ [
+ odil.AssociationParameters.PresentationContext(
+ 1, odil.registry.PatientRootQueryRetrieveInformationModelFIND,
+ [ odil.registry.ImplicitVRLittleEndian ], True, False)
+ ]
+ )
+
+ self.query = odil.DataSet()
+ self.query.add(odil.registry.PatientName, odil.Value.Strings(["Doe^John"]))
+ self.query.add(odil.registry.QueryRetrieveLevel, odil.Value.Strings(["PATIENT"]))
+ self.query.add(odil.registry.PatientID)
+
+ def test_without_callback(self):
+ find = odil.FindSCU(self.association)
+ find.set_affected_sop_class(odil.registry.PatientRootQueryRetrieveInformationModelFIND)
+ data_sets = find.find(self.query)
+
+ self.assertEqual(len(data_sets), 1)
+ self.assertEqual(
+ [x for x in data_sets[0].as_string(odil.registry.PatientID)],
+ ["DJ001"])
+
+ def test_with_callback(self):
+ data_sets = []
+ def callback(data_set):
+ data_sets.append(data_set)
+
+ find = odil.FindSCU(self.association)
+ find.set_affected_sop_class(odil.registry.PatientRootQueryRetrieveInformationModelFIND)
+ find.find(self.query, callback)
+
+ self.assertEqual(len(data_sets), 1)
+ self.assertEqual(
+ [x for x in data_sets[0].as_string(odil.registry.PatientID)],
+ ["DJ001"])
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/wrappers/test_get_scu.py b/tests/wrappers/test_get_scu.py
new file mode 100644
index 0000000..7d2dd35
--- /dev/null
+++ b/tests/wrappers/test_get_scu.py
@@ -0,0 +1,54 @@
+import os
+import sys
+import unittest
+
+import odil
+
+sys.path.append(os.path.dirname(os.path.dirname(__file__)))
+from peer_fixture_base import PeerFixtureBase
+
+class TestGetSCU(PeerFixtureBase):
+ def setUp(self):
+ PeerFixtureBase.setUp(
+ self,
+ [
+ odil.AssociationParameters.PresentationContext(
+ 1, odil.registry.PatientRootQueryRetrieveInformationModelGET,
+ [ odil.registry.ImplicitVRLittleEndian ], True, False
+ ),
+ odil.AssociationParameters.PresentationContext(
+ 3, odil.registry.RawDataStorage,
+ [ odil.registry.ImplicitVRLittleEndian ], False, True
+ )
+ ])
+
+ self.query = odil.DataSet()
+ self.query.add(odil.registry.PatientName, odil.Value.Strings(["Doe^John"]))
+ self.query.add(odil.registry.QueryRetrieveLevel, odil.Value.Strings(["PATIENT"]))
+
+ def test_without_callback(self):
+ get = odil.GetSCU(self.association)
+ get.set_affected_sop_class(odil.registry.PatientRootQueryRetrieveInformationModelGET)
+ data_sets = get.get(self.query)
+
+ self.assertEqual(len(data_sets), 1)
+ self.assertEqual(
+ [x for x in data_sets[0].as_string(odil.registry.SOPInstanceUID)],
+ ["2.25.95090344942250266709587559073467305647"])
+
+ def test_with_callback(self):
+ data_sets = []
+ def callback(data_set):
+ data_sets.append(data_set)
+
+ get = odil.GetSCU(self.association)
+ get.set_affected_sop_class(odil.registry.PatientRootQueryRetrieveInformationModelGET)
+ get.get(self.query, callback)
+
+ self.assertEqual(len(data_sets), 1)
+ self.assertEqual(
+ [x for x in data_sets[0].as_string(odil.registry.SOPInstanceUID)],
+ ["2.25.95090344942250266709587559073467305647"])
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/wrappers/test_move_scu.py b/tests/wrappers/test_move_scu.py
new file mode 100644
index 0000000..0092a57
--- /dev/null
+++ b/tests/wrappers/test_move_scu.py
@@ -0,0 +1,65 @@
+import os
+import sys
+import unittest
+
+import odil
+
+sys.path.append(os.path.dirname(os.path.dirname(__file__)))
+from peer_fixture_base import PeerFixtureBase
+
+class TestMoveSCU(PeerFixtureBase):
+ def setUp(self):
+ PeerFixtureBase.setUp(
+ self,
+ [
+ odil.AssociationParameters.PresentationContext(
+ 1, odil.registry.PatientRootQueryRetrieveInformationModelMOVE,
+ [ odil.registry.ImplicitVRLittleEndian ], True, False
+ ),
+ odil.AssociationParameters.PresentationContext(
+ 3, odil.registry.RawDataStorage,
+ [ odil.registry.ImplicitVRLittleEndian ], False, True
+ )
+ ])
+
+ self.query = odil.DataSet()
+ self.query.add(odil.registry.PatientName, odil.Value.Strings(["Doe^John"]))
+ self.query.add(odil.registry.QueryRetrieveLevel, odil.Value.Strings(["PATIENT"]))
+
+ def test_default_constructor(self):
+ move = odil.MoveSCU(self.association)
+ self.assertEqual(move.get_move_destination(), "")
+
+ def test_move_destination(self):
+ move = odil.MoveSCU(self.association)
+ move.set_move_destination("remote")
+ self.assertEqual(move.get_move_destination(), "remote")
+
+ def test_without_callback(self):
+ move = odil.MoveSCU(self.association)
+ move.set_affected_sop_class(odil.registry.PatientRootQueryRetrieveInformationModelMOVE)
+ move.set_move_destination("LOCAL")
+ data_sets = move.move(self.query)
+
+ self.assertEqual(len(data_sets), 1)
+ self.assertEqual(
+ [x for x in data_sets[0].as_string(odil.registry.SOPInstanceUID)],
+ ["2.25.95090344942250266709587559073467305647"])
+
+ def test_with_callback(self):
+ data_sets = []
+ def callback(data_set):
+ data_sets.append(data_set)
+
+ move = odil.MoveSCU(self.association)
+ move.set_affected_sop_class(odil.registry.PatientRootQueryRetrieveInformationModelMOVE)
+ move.set_move_destination("LOCAL")
+ move.move(self.query, callback)
+
+ self.assertEqual(len(data_sets), 1)
+ self.assertEqual(
+ [x for x in data_sets[0].as_string(odil.registry.SOPInstanceUID)],
+ ["2.25.95090344942250266709587559073467305647"])
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/wrappers/test_registry.py b/tests/wrappers/test_registry.py
new file mode 100644
index 0000000..71d0adb
--- /dev/null
+++ b/tests/wrappers/test_registry.py
@@ -0,0 +1,29 @@
+import unittest
+
+import odil
+
+class TestRegistry(unittest.TestCase):
+ def test_attribute(self):
+ tag = odil.registry.PatientID
+ self.assertEqual(tag.group, 0x0010)
+ self.assertEqual(tag.element, 0x0020)
+
+ def test_public_dictionary(self):
+ tag = odil.registry.PatientID
+ self.assertTrue(tag in odil.registry.public_dictionary)
+ entry = odil.registry.public_dictionary[tag]
+ self.assertEqual(entry.name, "Patient ID")
+ self.assertEqual(entry.keyword, "PatientID")
+ self.assertEqual(entry.vr, "LO")
+ self.assertEqual(entry.vm, "1")
+
+ def test_uids_dictionary(self):
+ uid = "1.2.840.10008.1.2.1"
+ self.assertTrue(uid in odil.registry.uids_dictionary)
+ entry = odil.registry.uids_dictionary[uid]
+ self.assertEqual(entry.name, "Explicit VR Little Endian")
+ self.assertEqual(entry.keyword, "ExplicitVRLittleEndian")
+ self.assertEqual(entry.type, "Transfer Syntax")
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/wrappers/test_store_scu.py b/tests/wrappers/test_store_scu.py
new file mode 100644
index 0000000..20bb4fb
--- /dev/null
+++ b/tests/wrappers/test_store_scu.py
@@ -0,0 +1,52 @@
+import os
+import sys
+import unittest
+
+import odil
+
+sys.path.append(os.path.dirname(os.path.dirname(__file__)))
+from peer_fixture_base import PeerFixtureBase
+
+class TestStoreSCU(PeerFixtureBase):
+ def setUp(self):
+ PeerFixtureBase.setUp(
+ self,
+ [
+ odil.AssociationParameters.PresentationContext(
+ 1, odil.registry.RawDataStorage,
+ [ odil.registry.ImplicitVRLittleEndian ], True, False
+ )
+ ])
+
+ self.data_set = odil.DataSet()
+ self.data_set.add(
+ odil.registry.ImageType,
+ odil.Value.Strings(["ORIGINAL", "PRIMARY", "OTHER"]))
+ self.data_set.add(
+ odil.registry.PatientID, odil.Value.Strings(["1234"]))
+ self.data_set.add(
+ odil.registry.StudyInstanceUID,
+ odil.Value.Strings(["2.25.386726390606491051215227596277040710"]))
+ self.data_set.add(
+ odil.registry.SeriesInstanceUID,
+ odil.Value.Strings(["2.25.235367796740370588607388995952651763168"]))
+ self.data_set.add(
+ odil.registry.SOPClassUID,
+ odil.Value.Strings([odil.registry.RawDataStorage]))
+ self.data_set.add(
+ odil.registry.SOPInstanceUID,
+ odil.Value.Strings(["2.25.294312554735929033890522327215919068328"]))
+
+ def test_affected_sop_class_uid(self):
+ store = odil.StoreSCU(self.association)
+ store.set_affected_sop_class(self.data_set)
+ self.assertEqual(store.get_affected_sop_class(), odil.registry.RawDataStorage)
+
+ def test_store(self):
+ store = odil.StoreSCU(self.association)
+
+ store.set_affected_sop_class(self.data_set)
+ store.store(self.data_set)
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/wrappers/test_tag.py b/tests/wrappers/test_tag.py
new file mode 100644
index 0000000..cf21990
--- /dev/null
+++ b/tests/wrappers/test_tag.py
@@ -0,0 +1,87 @@
+import unittest
+
+import odil
+
+class TestTag(unittest.TestCase):
+ def test_two_ints_constructor(self):
+ tag = odil.Tag(0x1234, 0x5678)
+ self.assertEqual(tag.group, 0x1234)
+ self.assertEqual(tag.element, 0x5678)
+
+ def test_one_int_constructor(self):
+ tag = odil.Tag(0x12345678)
+ self.assertEqual(tag.group, 0x1234)
+ self.assertEqual(tag.element, 0x5678)
+
+ def test_string_constructor(self):
+ tag = odil.Tag("PatientID")
+ self.assertEqual(tag.group, 0x0010)
+ self.assertEqual(tag.element, 0x0020)
+
+ def test_group(self):
+ tag = odil.Tag(0x1234, 0x5678)
+ tag.group = 0x4321
+ self.assertEqual(tag.group, 0x4321)
+ self.assertEqual(tag.element, 0x5678)
+
+ def test_element(self):
+ tag = odil.Tag(0x1234, 0x5678)
+ tag.element = 0x8765
+ self.assertEqual(tag.group, 0x1234)
+ self.assertEqual(tag.element, 0x8765)
+
+ def test_is_private(self):
+ public = odil.Tag(0x1234, 0x5678)
+ self.assertTrue(not public.is_private())
+
+ private = odil.Tag(0x1235, 0x5678)
+ self.assertTrue(private.is_private())
+
+ def test_get_name(self):
+ tag = odil.Tag(0x0010, 0x0020)
+ self.assertEqual(tag.get_name(), "PatientID")
+
+ def test_equality(self):
+ tag1 = odil.Tag(0x1234, 0x5678)
+ tag2 = odil.Tag(0x4321, 0x8765)
+ self.assertTrue(tag1 == tag1)
+ self.assertFalse(tag1 == tag2)
+
+ def test_inequality(self):
+ tag1 = odil.Tag(0x1234, 0x5678)
+ tag2 = odil.Tag(0x4321, 0x8765)
+ self.assertFalse(tag1 != tag1)
+ self.assertTrue(tag1 != tag2)
+
+ def test_less(self):
+ tag1 = odil.Tag(0x1234, 0x5678)
+ tag2 = odil.Tag(0x4321, 0x8765)
+ self.assertTrue(tag1 < tag2)
+ self.assertFalse(tag2 < tag1)
+
+ def test_greater(self):
+ tag1 = odil.Tag(0x1234, 0x5678)
+ tag2 = odil.Tag(0x4321, 0x8765)
+ self.assertFalse(tag1 > tag2)
+ self.assertTrue(tag2 > tag1)
+
+ def test_less_or_equal(self):
+ tag1 = odil.Tag(0x1234, 0x5678)
+ tag2 = odil.Tag(0x4321, 0x8765)
+ self.assertTrue(tag1 <= tag2)
+ self.assertTrue(tag1 <= tag1)
+ self.assertFalse(tag2 <= tag1)
+
+ def test_greater_or_equal(self):
+ tag1 = odil.Tag(0x1234, 0x5678)
+ tag2 = odil.Tag(0x4321, 0x8765)
+ self.assertFalse(tag1 >= tag2)
+ self.assertTrue(tag1 >= tag1)
+ self.assertTrue(tag2 >= tag1)
+
+ def test_str(self):
+ tag = odil.Tag(0x1234, 0x5678)
+ self.assertEqual(str(tag), "12345678")
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/wrappers/test_uid.py b/tests/wrappers/test_uid.py
new file mode 100644
index 0000000..a80ec89
--- /dev/null
+++ b/tests/wrappers/test_uid.py
@@ -0,0 +1,14 @@
+import unittest
+
+import odil
+
+class TestUID(unittest.TestCase):
+ def test_uid_prefix(self):
+ self.assertEqual(odil.uid_prefix, "1.2.826.0.1.3680043.9.5560")
+
+ def test_uid_prefix(self):
+ uid = odil.generate_uid()
+ self.assertEqual(uid[:len(odil.uid_prefix)], odil.uid_prefix)
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/wrappers/test_uids_dictionary.py b/tests/wrappers/test_uids_dictionary.py
new file mode 100644
index 0000000..8088e6a
--- /dev/null
+++ b/tests/wrappers/test_uids_dictionary.py
@@ -0,0 +1,21 @@
+import unittest
+
+import odil
+
+class TestUIDsDictionaryEntry(unittest.TestCase):
+ def test_constructor(self):
+ entry = odil.UIDsDictionaryEntry(
+ "MR Image Storage", "MRImageStorage", "SOP Class")
+ self.assertEqual(entry.name, "MR Image Storage")
+ self.assertEqual(entry.keyword, "MRImageStorage")
+ self.assertEqual(entry.type, "SOP Class")
+
+class TestUIDsDictionary(unittest.TestCase):
+ def test_get_item(self):
+ entry = odil.registry.uids_dictionary["1.2.840.10008.5.1.4.1.1.4"]
+ self.assertEqual(entry.name, "MR Image Storage")
+ self.assertEqual(entry.keyword, "MRImageStorage")
+ self.assertEqual(entry.type, "SOP Class")
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/wrappers/test_value.py b/tests/wrappers/test_value.py
new file mode 100644
index 0000000..035b972
--- /dev/null
+++ b/tests/wrappers/test_value.py
@@ -0,0 +1,88 @@
+import unittest
+
+import odil
+
+class TestValue(unittest.TestCase):
+ def test_empty_constructor(self):
+ value = odil.Value()
+ self.assertTrue(value.empty())
+
+ def test_integers_constructor(self):
+ items = [1, 2, 3]
+ value = odil.Value(odil.Value.Integers(items))
+ self.assertEqual([x for x in value.as_integers()], items)
+
+ def test_reals_constructor(self):
+ items = [1.1, 2, 3.3]
+ value = odil.Value(odil.Value.Reals(items))
+ self.assertEqual([x for x in value.as_reals()], items)
+
+ def test_strings_constructor(self):
+ items = ["foo", "bar"]
+ value = odil.Value(odil.Value.Strings(items))
+ self.assertEqual([x for x in value.as_strings()], items)
+
+ def test_data_sets_constructor(self):
+ items = [odil.DataSet(), odil.DataSet()]
+ value = odil.Value(odil.Value.DataSets(items))
+ self.assertEqual([x for x in value.as_data_sets()], items)
+
+ def test_binary_constructor(self):
+ items = [odil.Value.BinaryItem("\x01\x02\x03")]
+ value = odil.Value(odil.Value.Binary(items))
+ self.assertEqual(
+ [x for x in value.as_binary()[0]], [x for x in items[0]])
+
+class TestValueIntegers(unittest.TestCase):
+ def test_empty_constructor(self):
+ data = odil.Value.Integers()
+ self.assertEqual([x for x in data], [])
+
+ def test_sequence_constructor(self):
+ items = [1, 2, 3]
+ data = odil.Value.Integers(items)
+ self.assertEqual([x for x in data], items)
+
+class TestValueReals(unittest.TestCase):
+ def test_empty_constructor(self):
+ data = odil.Value.Reals()
+ self.assertEqual([x for x in data], [])
+
+ def test_sequence_constructor(self):
+ items = [1.1, 2, 3.3]
+ data = odil.Value.Reals(items)
+ self.assertEqual([x for x in data], items)
+
+class TestValueStrings(unittest.TestCase):
+ def test_empty_constructor(self):
+ data = odil.Value.Strings()
+ self.assertEqual([x for x in data], [])
+
+ def test_sequence_constructor(self):
+ items = ["foo", "bar"]
+ data = odil.Value.Strings(items)
+ self.assertEqual([x for x in data], items)
+
+class TestValueDataSets(unittest.TestCase):
+ def test_empty_constructor(self):
+ data = odil.Value.DataSets()
+ self.assertEqual([x for x in data], [])
+
+ def test_sequence_constructor(self):
+ items = [odil.DataSet(), odil.DataSet()]
+ data = odil.Value.DataSets(items)
+ self.assertEqual([x for x in data], items)
+
+class TestValueBinary(unittest.TestCase):
+ def test_empty_constructor(self):
+ data = odil.Value.Binary()
+ self.assertEqual([x for x in data], [])
+
+ def test_sequence_constructor(self):
+ items = [odil.Value.BinaryItem("\x01\x02\x03")]
+ data = odil.Value.Binary(items)
+ self.assertEqual([x for x in data[0]], [x for x in items[0]])
+
+if __name__ == "__main__":
+ unittest.main()
+
diff --git a/tests/wrappers/test_vr.py b/tests/wrappers/test_vr.py
new file mode 100644
index 0000000..ef1660f
--- /dev/null
+++ b/tests/wrappers/test_vr.py
@@ -0,0 +1,12 @@
+import unittest
+
+import odil
+
+class TestVR(unittest.TestCase):
+ def test_string(self):
+ vr = odil.VR.AE
+ self.assertEqual(str(vr), "AE")
+
+if __name__ == "__main__":
+ unittest.main()
+
diff --git a/wrappers/Assocation.cpp b/wrappers/Assocation.cpp
new file mode 100644
index 0000000..9188ce5
--- /dev/null
+++ b/wrappers/Assocation.cpp
@@ -0,0 +1,62 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#include <boost/python.hpp>
+
+#include "odil/Association.h"
+
+void wrap_Association()
+{
+ using namespace boost::python;
+ using namespace odil;
+
+ scope association_scope = class_<Association>("Association", init<>())
+ .def("get_peer_host", &Association::get_peer_host,
+ return_value_policy<copy_const_reference>())
+ .def("set_peer_host", &Association::set_peer_host)
+ .def("get_peer_port", &Association::get_peer_port)
+ .def("set_peer_port", &Association::set_peer_port)
+ .def(
+ "get_parameters",
+ &Association::get_parameters,
+ return_value_policy<reference_existing_object>()
+ )
+ .def(
+ "set_parameters",
+ &Association::set_parameters,
+ return_value_policy<reference_existing_object>()
+ )
+ .def(
+ "update_parameters",
+ &Association::update_parameters,
+ return_value_policy<reference_existing_object>()
+ )
+ .def(
+ "get_negotiated_parameters",
+ &Association::get_negotiated_parameters,
+ return_value_policy<reference_existing_object>()
+ )
+ // TCP timeout
+ // Message timeout
+ .def("is_associated", &Association::is_associated)
+ .def("associate", &Association::associate)
+ // Receive association
+ //.def("reject", &Association::reject)
+ .def("release", &Association::release)
+ .def("abort", &Association::abort)
+ // Receive message
+ // Send message
+ .def("next_message_id", &Association::next_message_id)
+ ;
+
+ enum_<Association::Result>("Result")
+ .value("Accepted", Association::Accepted)
+ .value("RejectedPermanent", Association::RejectedPermanent)
+ .value("RejectedTransient", Association::RejectedTransient)
+ ;
+}
diff --git a/wrappers/AssocationParameters.cpp b/wrappers/AssocationParameters.cpp
new file mode 100644
index 0000000..f545c5e
--- /dev/null
+++ b/wrappers/AssocationParameters.cpp
@@ -0,0 +1,240 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#include <memory>
+
+#include <boost/python.hpp>
+#include <boost/python/suite/indexing/vector_indexing_suite.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include "odil/AssociationParameters.h"
+
+namespace
+{
+
+boost::shared_ptr<odil::AssociationParameters::PresentationContext>
+presentation_context_constructor(
+ uint8_t id, std::string const & abstract_syntax,
+ boost::python::list const & transfer_syntaxes,
+ bool scu_role_support, bool scp_role_support)
+{
+ std::vector<std::string> transfer_syntaxes_cpp(boost::python::len(transfer_syntaxes));
+ for(int i = 0; i<boost::python::len(transfer_syntaxes); ++i)
+ {
+ transfer_syntaxes_cpp[i] = boost::python::extract<std::string>(transfer_syntaxes[i]);
+ }
+ auto presentation_context = new odil::AssociationParameters::PresentationContext({
+ id, abstract_syntax, transfer_syntaxes_cpp,
+ scu_role_support, scp_role_support
+ });
+ // Old versions of Boost.Python (Debian 7, Ubuntu 12.04) do not like
+ // std::shared_ptr
+ return boost::shared_ptr<odil::AssociationParameters::PresentationContext>(
+ presentation_context);
+}
+
+boost::python::list
+get_presentation_contexts(odil::AssociationParameters const & parameters)
+{
+ boost::python::list presentation_contexts_python;
+ for(auto const & presentation_context: parameters.get_presentation_contexts())
+ {
+ presentation_contexts_python.append(presentation_context);
+ }
+
+ return presentation_contexts_python;
+}
+
+odil::AssociationParameters &
+set_presentation_contexts(
+ odil::AssociationParameters & parameters,
+ boost::python::list const & presentation_contexts)
+{
+ std::vector<odil::AssociationParameters::PresentationContext>
+ presentation_contexts_cpp(boost::python::len(presentation_contexts));
+ for(int i = 0; i<boost::python::len(presentation_contexts); ++i)
+ {
+ presentation_contexts_cpp[i] =
+ boost::python::extract<
+ odil::AssociationParameters::PresentationContext
+ >(presentation_contexts[i]);
+ }
+ parameters.set_presentation_contexts(presentation_contexts_cpp);
+
+ return parameters;
+}
+
+}
+
+void wrap_AssociationParameters()
+{
+ using namespace boost::python;
+ using namespace odil;
+
+ scope association_parameters_scope =
+ class_<AssociationParameters>("AssociationParameters", init<>())
+ // Construct from PDU
+ .def(
+ "get_called_ae_title",
+ &AssociationParameters::get_called_ae_title,
+ return_value_policy<copy_const_reference>()
+ )
+ .def(
+ "set_called_ae_title",
+ &AssociationParameters::set_called_ae_title,
+ return_value_policy<reference_existing_object>()
+ )
+ .def(
+ "get_calling_ae_title",
+ &AssociationParameters::get_calling_ae_title,
+ return_value_policy<copy_const_reference>()
+ )
+ .def(
+ "set_calling_ae_title",
+ &AssociationParameters::set_calling_ae_title,
+ return_value_policy<reference_existing_object>()
+ )
+ .def(
+ "get_presentation_contexts",
+ &get_presentation_contexts
+ )
+ .def(
+ "set_presentation_contexts",
+ &set_presentation_contexts,
+ return_value_policy<reference_existing_object>()
+ )
+ .def(
+ "get_user_identity", &AssociationParameters::get_user_identity,
+ return_value_policy<reference_existing_object>()
+ )
+ .def(
+ "set_user_identity_to_none",
+ &AssociationParameters::set_user_identity_to_none,
+ return_value_policy<reference_existing_object>()
+ )
+ .def(
+ "set_user_identity_to_username",
+ &AssociationParameters::set_user_identity_to_username,
+ return_value_policy<reference_existing_object>()
+ )
+ .def(
+ "set_user_identity_to_username_and_password",
+ &AssociationParameters::set_user_identity_to_username_and_password,
+ return_value_policy<reference_existing_object>()
+ )
+ .def(
+ "set_user_identity_to_kerberos",
+ &AssociationParameters::set_user_identity_to_kerberos,
+ return_value_policy<reference_existing_object>()
+ )
+ .def(
+ "set_user_identity_to_saml",
+ &AssociationParameters::set_user_identity_to_saml,
+ return_value_policy<reference_existing_object>()
+ )
+ .def(
+ "get_maximum_length",
+ &AssociationParameters::get_maximum_length
+ )
+ .def(
+ "set_maximum_length",
+ &AssociationParameters::set_maximum_length,
+ return_value_policy<reference_existing_object>()
+ )
+ ;
+
+ {
+ scope presentation_context_scope =
+ class_<AssociationParameters::PresentationContext>("PresentationContext")
+ .def(
+ "__init__",
+ make_constructor(&presentation_context_constructor))
+ .def_readwrite(
+ "id",
+ &AssociationParameters::PresentationContext::id
+ )
+ .def_readwrite(
+ "abstract_syntax",
+ &AssociationParameters::PresentationContext::abstract_syntax
+ )
+ .def_readwrite(
+ "transfer_syntaxes",
+ &AssociationParameters::PresentationContext::transfer_syntaxes
+ )
+ .def_readwrite(
+ "scu_role_support",
+ &AssociationParameters::PresentationContext::scu_role_support
+ )
+ .def_readwrite(
+ "scp_role_support",
+ &AssociationParameters::PresentationContext::scp_role_support
+ )
+ .def_readwrite(
+ "result",
+ &AssociationParameters::PresentationContext::result
+ )
+ .def(self == self)
+ ;
+
+ enum_<AssociationParameters::PresentationContext::Result>("Result")
+ .value(
+ "Acceptance",
+ AssociationParameters::PresentationContext::Result::Acceptance
+ )
+ .value(
+ "UserRejection",
+ AssociationParameters::PresentationContext::Result::UserRejection
+ )
+ .value(
+ "NoReason",
+ AssociationParameters::PresentationContext::Result::NoReason
+ )
+ .value(
+ "AbstractSyntaxNotSupported",
+ AssociationParameters::PresentationContext::Result::AbstractSyntaxNotSupported
+ )
+ .value(
+ "TransferSyntaxesNotSupported",
+ AssociationParameters::PresentationContext::Result::TransferSyntaxesNotSupported
+ )
+ ;
+ }
+
+ {
+ scope user_identity_scope =
+ class_<AssociationParameters::UserIdentity>("UserIdentity")
+ .def_readwrite(
+ "type",
+ &AssociationParameters::UserIdentity::type
+ )
+ .def_readwrite(
+ "primary_field",
+ &AssociationParameters::UserIdentity::primary_field
+ )
+ .def_readwrite(
+ "secondary_field",
+ &AssociationParameters::UserIdentity::secondary_field
+ )
+ .def(self == self)
+ ;
+
+ enum_<AssociationParameters::UserIdentity::Type>("Type")
+ .value("None", AssociationParameters::UserIdentity::Type::None)
+ .value(
+ "Username", AssociationParameters::UserIdentity::Type::Username
+ )
+ .value(
+ "UsernameAndPassword",
+ AssociationParameters::UserIdentity::Type::UsernameAndPassword
+ )
+ .value(
+ "Kerberos", AssociationParameters::UserIdentity::Type::Kerberos)
+ .value("SAML", AssociationParameters::UserIdentity::Type::SAML)
+ ;
+ }
+}
diff --git a/wrappers/CMakeLists.txt b/wrappers/CMakeLists.txt
new file mode 100644
index 0000000..a5a0de3
--- /dev/null
+++ b/wrappers/CMakeLists.txt
@@ -0,0 +1,19 @@
+find_package(Boost REQUIRED COMPONENTS python)
+find_package(JsonCpp REQUIRED)
+find_package(PythonLibs 2.7 REQUIRED)
+
+include_directories(
+ ${CMAKE_CURRENT_SOURCE_DIR}/../src ${Boost_INCLUDE_DIRS}
+ ${JsonCpp_INCLUDE_DIRS} ${PYTHON_INCLUDE_DIRS})
+link_directories(${Boost_LIBRARY_DIRS})
+
+file(GLOB_RECURSE files "*.cpp")
+python_add_module(pyodil SHARED ${files})
+set_target_properties(
+ pyodil PROPERTIES OUTPUT_NAME odil)
+if(APPLE)
+ set_target_properties(pyodil PROPERTIES SUFFIX ".so")
+endif()
+
+target_link_libraries(
+ pyodil ${Boost_LIBRARIES} ${JsonCpp_LIBRARIES} libodil ${PYTHON_LIBRARIES})
diff --git a/wrappers/DataSet.cpp b/wrappers/DataSet.cpp
new file mode 100644
index 0000000..8d079fe
--- /dev/null
+++ b/wrappers/DataSet.cpp
@@ -0,0 +1,225 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#include <iterator>
+
+#include <boost/python.hpp>
+#include <boost/python/suite/indexing/map_indexing_suite.hpp>
+
+#include "odil/DataSet.h"
+
+BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(add_overloads_2, odil::DataSet::add, 1, 2);
+BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(add_overloads_3, odil::DataSet::add, 2, 3);
+
+namespace
+{
+
+class ConstIteratorAdapter
+{
+public:
+ typedef odil::DataSet::const_iterator::difference_type difference_type;
+ typedef odil::DataSet::const_iterator::value_type::first_type value_type;
+ typedef odil::DataSet::const_iterator::value_type::first_type const * pointer;
+ typedef odil::DataSet::const_iterator::value_type::first_type const & reference;
+ typedef std::bidirectional_iterator_tag iterator_category;
+
+ ConstIteratorAdapter(odil::DataSet::const_iterator const & iterator)
+ : _iterator(iterator)
+ {
+ // Nothing else.
+ }
+
+ reference operator*() const
+ {
+ return this->_iterator->first;
+ }
+
+ ConstIteratorAdapter & operator++()
+ {
+ ++this->_iterator;
+ return *this;
+ }
+
+ ConstIteratorAdapter operator++(int)
+ {
+ ConstIteratorAdapter tmp(*this);
+ ++(*this);
+ return tmp;
+ }
+
+ ConstIteratorAdapter & operator--()
+ {
+ --this->_iterator;
+ return *this;
+ }
+
+ ConstIteratorAdapter operator--(int)
+ {
+ ConstIteratorAdapter tmp(*this);
+ --(*this);
+ return tmp;
+ }
+
+ bool operator==(ConstIteratorAdapter const & other)
+ {
+ return this->_iterator == other._iterator;
+ }
+
+ bool operator!=(ConstIteratorAdapter const & other)
+ {
+ return this->_iterator != other._iterator;
+ }
+
+private:
+ odil::DataSet::const_iterator _iterator;
+};
+
+ConstIteratorAdapter begin(odil::DataSet const & data_set)
+{
+ return ConstIteratorAdapter(data_set.begin());
+}
+
+ConstIteratorAdapter end(odil::DataSet const & data_set)
+{
+ return ConstIteratorAdapter(data_set.end());
+}
+
+boost::python::list keys(odil::DataSet const & data_set)
+{
+ boost::python::list result;
+ for(auto const & item: data_set)
+ {
+ result.append(item.first);
+ }
+
+ return result;
+}
+
+boost::python::list values(odil::DataSet const & data_set)
+{
+ boost::python::list result;
+ for(auto const & item: data_set)
+ {
+ result.append(item.second);
+ }
+
+ return result;
+}
+
+boost::python::list items(odil::DataSet const & data_set)
+{
+ boost::python::list result;
+ for(auto const & item: data_set)
+ {
+ result.append(boost::python::make_tuple(item.first, item.second));
+ }
+
+ return result;
+}
+
+}
+
+void wrap_DataSet()
+{
+ using namespace boost::python;
+ using namespace odil;
+
+
+ class_<DataSet>("DataSet", init<>())
+ .def(
+ "add",
+ static_cast<void (DataSet::*)(Tag const &, Element const &)>(
+ &DataSet::add))
+ .def(
+ "add",
+ static_cast<void (DataSet::*)(Tag const &, VR)>(&DataSet::add),
+ add_overloads_2())
+ .def(
+ "add",
+ static_cast<void (DataSet::*)(Tag const &, Value::Integers const &, VR)>(
+ &DataSet::add),
+ add_overloads_3())
+ .def(
+ "add",
+ static_cast<void (DataSet::*)(Tag const &, Value::Reals const &, VR)>(
+ &DataSet::add),
+ add_overloads_3())
+ .def(
+ "add",
+ static_cast<void (DataSet::*)(Tag const &, Value::Strings const &, VR)>(
+ &DataSet::add),
+ add_overloads_3())
+ .def(
+ "add",
+ static_cast<void (DataSet::*)(Tag const &, Value::DataSets const &, VR)>(
+ &DataSet::add),
+ add_overloads_3())
+ .def(
+ "add",
+ static_cast<void (DataSet::*)(Tag const &, Value::Binary const &, VR)>(
+ &DataSet::add),
+ add_overloads_3())
+ .def("remove", &DataSet::remove)
+ .def("has", &DataSet::has)
+ .def("empty", static_cast<bool (DataSet::*)() const>(&DataSet::empty))
+ .def(
+ "size",
+ static_cast<std::size_t (DataSet::*)() const>(&DataSet::size))
+ .def("get_vr", &DataSet::get_vr)
+ .def(
+ "empty",
+ static_cast<bool (DataSet::*)(Tag const &) const>(&DataSet::empty))
+ .def(
+ "size",
+ static_cast<std::size_t (DataSet::*)(Tag const &) const>(
+ &DataSet::size))
+ .def(
+ "__getitem__",
+ static_cast<Element & (DataSet::*)(Tag const&)>(&DataSet::operator[]),
+ return_value_policy<reference_existing_object>())
+ .def("is_int", &DataSet::is_int)
+ .def(
+ "as_int",
+ static_cast<Value::Integers & (DataSet::*)(Tag const &)>(
+ &DataSet::as_int),
+ return_value_policy<reference_existing_object>())
+ .def("is_real", &DataSet::is_real)
+ .def(
+ "as_real",
+ static_cast<Value::Reals & (DataSet::*)(Tag const &)>(
+ &DataSet::as_real),
+ return_value_policy<reference_existing_object>())
+ .def("is_string", &DataSet::is_string)
+ .def(
+ "as_string",
+ static_cast<Value::Strings & (DataSet::*)(Tag const &)>(
+ &DataSet::as_string),
+ return_value_policy<reference_existing_object>())
+ .def("is_data_set", &DataSet::is_data_set)
+ .def(
+ "as_data_set",
+ static_cast<Value::DataSets & (DataSet::*)(Tag const &)>(
+ &DataSet::as_data_set),
+ return_value_policy<reference_existing_object>())
+ .def("is_binary", &DataSet::is_binary)
+ .def(
+ "as_binary",
+ static_cast<Value::Binary & (DataSet::*)(Tag const &)>(
+ &DataSet::as_binary),
+ return_value_policy<reference_existing_object>())
+ .def("keys", &keys)
+ .def("__iter__", range(&begin, &end))
+ .def("values", &values)
+ .def("items", &items)
+ .def(self == self)
+ .def(self != self)
+ .def(
+ "__len__",
+ static_cast<std::size_t (DataSet::*)() const>(&DataSet::size))
+ ;
+}
diff --git a/wrappers/EchoSCU.cpp b/wrappers/EchoSCU.cpp
new file mode 100644
index 0000000..1f048c0
--- /dev/null
+++ b/wrappers/EchoSCU.cpp
@@ -0,0 +1,33 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#include <boost/python.hpp>
+
+#include "odil/EchoSCU.h"
+
+void wrap_EchoSCU()
+{
+ using namespace boost::python;
+ using namespace odil;
+
+ class_<EchoSCU>("EchoSCU", init<Association &>())
+ .def(
+ "get_affected_sop_class",
+ &EchoSCU::get_affected_sop_class,
+ return_value_policy<copy_const_reference>()
+ )
+ .def(
+ "set_affected_sop_class",
+ &EchoSCU::set_affected_sop_class
+ )
+ .def(
+ "echo",
+ &EchoSCU::echo
+ )
+ ;
+}
diff --git a/wrappers/Element.cpp b/wrappers/Element.cpp
new file mode 100644
index 0000000..55e01d2
--- /dev/null
+++ b/wrappers/Element.cpp
@@ -0,0 +1,63 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#include <boost/python.hpp>
+
+#include "odil/DataSet.h"
+#include "odil/Element.h"
+#include "odil/Value.h"
+
+void wrap_Element()
+{
+ using namespace boost::python;
+ using namespace odil;
+
+ typedef Value::Integers & (Element::*AsIntegers)();
+ typedef Value::Reals & (Element::*AsReals)();
+ typedef Value::Strings & (Element::*AsStrings)();
+ typedef Value::DataSets & (Element::*AsDataSets)();
+ typedef Value::Binary & (Element::*AsBinary)();
+
+ class_<Element>("Element", init<>())
+ .def_readwrite("vr", &Element::vr)
+ .def(init<Value, optional<VR>>())
+ .def(init<Value::Integers, optional<VR>>())
+ .def(init<Value::Reals, optional<VR>>())
+ .def(init<Value::Strings, optional<VR>>())
+ .def(init<Value::DataSets, optional<VR>>())
+ .def(init<Value::Binary, optional<VR>>())
+ .def("empty", &Element::empty)
+ .def("size", &Element::size)
+ .def(
+ "get_value", &Element::get_value,
+ return_value_policy<reference_existing_object>())
+ .def("is_int", &Element::is_int)
+ .def(
+ "as_int", AsIntegers(&Element::as_int),
+ return_value_policy<reference_existing_object>())
+ .def("is_real", &Element::is_real)
+ .def(
+ "as_real", AsReals(&Element::as_real),
+ return_value_policy<reference_existing_object>())
+ .def("is_string", &Element::is_string)
+ .def(
+ "as_string", AsStrings(&Element::as_string),
+ return_value_policy<reference_existing_object>())
+ .def("is_data_set", &Element::is_data_set)
+ .def(
+ "as_data_set", AsDataSets(&Element::as_data_set),
+ return_value_policy<reference_existing_object>())
+ .def("is_binary", &Element::is_binary)
+ .def(
+ "as_binary", AsBinary(&Element::as_binary),
+ return_value_policy<reference_existing_object>())
+ .def(self == self)
+ .def(self != self)
+ .def("__len__", &Element::size)
+ ;
+}
diff --git a/wrappers/ElementsDictionary.cpp b/wrappers/ElementsDictionary.cpp
new file mode 100644
index 0000000..a8330fc
--- /dev/null
+++ b/wrappers/ElementsDictionary.cpp
@@ -0,0 +1,96 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#include <boost/python.hpp>
+#include <boost/python/suite/indexing/map_indexing_suite.hpp>
+
+#include <odil/ElementsDictionary.h>
+
+namespace
+{
+
+bool contains(odil::ElementsDictionary const & container, odil::Tag const & key)
+{
+ return container.find(key) != container.end();
+}
+
+bool contains(odil::ElementsDictionary const & container, std::string const & key)
+{
+ return container.find(key) != container.end();
+}
+
+odil::ElementsDictionaryEntry &
+get_item(odil::ElementsDictionary & container, odil::Tag const & key)
+{
+ odil::ElementsDictionary::iterator iterator = container.find(key);
+ if(iterator == container.end())
+ {
+ PyErr_SetString(PyExc_KeyError, "Invalid key");
+ boost::python::throw_error_already_set();
+ }
+ return iterator->second;
+}
+
+odil::ElementsDictionaryEntry &
+get_item(odil::ElementsDictionary & container, std::string const & key)
+{
+ odil::ElementsDictionary::iterator iterator = container.find(key);
+ if(iterator == container.end())
+ {
+ PyErr_SetString(PyExc_KeyError, "Invalid key");
+ boost::python::throw_error_already_set();
+ }
+ return iterator->second;
+}
+
+
+}
+
+void wrap_ElementsDictionary()
+{
+ using namespace boost::python;
+ using namespace odil;
+
+ class_<ElementsDictionaryKey>("ElementsDictionaryKey", init<>())
+ .def(init<Tag>())
+ .def(init<std::string>())
+ ;
+
+ class_<ElementsDictionaryEntry>(
+ "ElementsDictionaryEntry",
+ init<std::string, std::string, std::string, std::string>()
+ )
+ .def_readwrite("name", &ElementsDictionaryEntry::name)
+ .def_readwrite("keyword", &ElementsDictionaryEntry::keyword)
+ .def_readwrite("vr", &ElementsDictionaryEntry::vr)
+ .def_readwrite("vm", &ElementsDictionaryEntry::vm)
+ ;
+
+ class_<ElementsDictionary>("ElementsDictionary")
+ .def(map_indexing_suite<ElementsDictionary>())
+ .def(
+ "__contains__",
+ static_cast<bool(*)(ElementsDictionary const &, Tag const &)>(contains)
+ )
+ .def(
+ "__contains__",
+ static_cast<bool(*)(ElementsDictionary const &, std::string const &)>(contains)
+ )
+ .def(
+ "__getitem__",
+ static_cast<ElementsDictionaryEntry & (*)(ElementsDictionary &, Tag const &)>(get_item),
+ return_value_policy<reference_existing_object>()
+ )
+ .def(
+ "__getitem__",
+ static_cast<ElementsDictionaryEntry & (*)(ElementsDictionary &, std::string const &)>(get_item),
+ return_value_policy<reference_existing_object>()
+ )
+ ;
+ ;
+}
diff --git a/wrappers/Exception.cpp b/wrappers/Exception.cpp
new file mode 100644
index 0000000..cc6085c
--- /dev/null
+++ b/wrappers/Exception.cpp
@@ -0,0 +1,25 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#include <boost/python.hpp>
+
+#include "odil/Exception.h"
+
+void translator(odil::Exception const & e)
+{
+ PyErr_SetString(PyExc_UserWarning, e.what());
+}
+
+void wrap_Exception()
+{
+ using namespace boost::python;
+ using namespace odil;
+
+ register_exception_translator<Exception>(translator);
+}
+
diff --git a/wrappers/FindSCU.cpp b/wrappers/FindSCU.cpp
new file mode 100644
index 0000000..701363e
--- /dev/null
+++ b/wrappers/FindSCU.cpp
@@ -0,0 +1,50 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#include <boost/python.hpp>
+
+#include "odil/FindSCU.h"
+
+namespace
+{
+
+void
+find_with_python_callback(
+ odil::FindSCU const & scu,
+ odil::DataSet const & query, boost::python::object const & f)
+{
+ scu.find(
+ query,
+ [f](odil::DataSet const & data_set)
+ {
+ boost::python::call<void>(f.ptr(), data_set);
+ }
+ );
+}
+
+}
+
+void wrap_FindSCU()
+{
+ using namespace boost::python;
+ using namespace odil;
+
+ class_<FindSCU>("FindSCU", init<Association &>())
+ .def(
+ "find",
+ &find_with_python_callback
+ )
+ .def(
+ "find",
+ static_cast<
+ std::vector<DataSet> (FindSCU::*)(DataSet const &) const
+ >(&FindSCU::find)
+ )
+ .def("set_affected_sop_class", &FindSCU::set_affected_sop_class)
+ ;
+}
diff --git a/wrappers/GetSCU.cpp b/wrappers/GetSCU.cpp
new file mode 100644
index 0000000..b954ad4
--- /dev/null
+++ b/wrappers/GetSCU.cpp
@@ -0,0 +1,50 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#include <boost/python.hpp>
+
+#include "odil/GetSCU.h"
+
+namespace
+{
+
+void
+get_with_python_callback(
+ odil::GetSCU const & scu,
+ odil::DataSet const & query, boost::python::object const & f)
+{
+ scu.get(
+ query,
+ [f](odil::DataSet const & data_set)
+ {
+ boost::python::call<void>(f.ptr(), data_set);
+ }
+ );
+}
+
+}
+
+void wrap_GetSCU()
+{
+ using namespace boost::python;
+ using namespace odil;
+
+ class_<GetSCU>("GetSCU", init<Association &>())
+ .def(
+ "get",
+ &get_with_python_callback
+ )
+ .def(
+ "get",
+ static_cast<
+ std::vector<DataSet> (GetSCU::*)(DataSet const &) const
+ >(&GetSCU::get)
+ )
+ .def("set_affected_sop_class", &GetSCU::set_affected_sop_class)
+ ;
+}
diff --git a/wrappers/MoveSCU.cpp b/wrappers/MoveSCU.cpp
new file mode 100644
index 0000000..565c47d
--- /dev/null
+++ b/wrappers/MoveSCU.cpp
@@ -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.
+ ************************************************************************/
+
+#include <boost/python.hpp>
+
+#include "odil/MoveSCU.h"
+
+namespace
+{
+
+void
+move_with_python_callback(
+ odil::MoveSCU const & scu,
+ odil::DataSet const & query, boost::python::object const & f)
+{
+ scu.move(
+ query,
+ [f](odil::DataSet const & data_set)
+ {
+ boost::python::call<void>(f.ptr(), data_set);
+ }
+ );
+}
+
+}
+
+void wrap_MoveSCU()
+{
+ using namespace boost::python;
+ using namespace odil;
+
+ class_<MoveSCU>("MoveSCU", init<Association &>())
+ .def(
+ "get_move_destination",
+ &MoveSCU::get_move_destination,
+ return_value_policy<copy_const_reference>()
+ )
+ .def(
+ "set_move_destination",
+ &MoveSCU::set_move_destination
+ )
+ .def(
+ "move",
+ &move_with_python_callback
+ )
+ .def(
+ "move",
+ static_cast<
+ std::vector<DataSet> (MoveSCU::*)(DataSet const &) const
+ >(&MoveSCU::move)
+ )
+ .def("set_affected_sop_class", &MoveSCU::set_affected_sop_class)
+ ;
+}
diff --git a/wrappers/StoreSCU.cpp b/wrappers/StoreSCU.cpp
new file mode 100644
index 0000000..266d731
--- /dev/null
+++ b/wrappers/StoreSCU.cpp
@@ -0,0 +1,33 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#include <boost/python.hpp>
+
+#include "odil/StoreSCU.h"
+
+void wrap_StoreSCU()
+{
+ using namespace boost::python;
+ using namespace odil;
+
+ class_<StoreSCU>("StoreSCU", init<Association &>())
+ .def(
+ "get_affected_sop_class",
+ &StoreSCU::get_affected_sop_class,
+ return_value_policy<copy_const_reference>()
+ )
+ .def(
+ "set_affected_sop_class",
+ &StoreSCU::set_affected_sop_class
+ )
+ .def(
+ "store",
+ &StoreSCU::store
+ )
+ ;
+}
diff --git a/wrappers/Tag.cpp b/wrappers/Tag.cpp
new file mode 100644
index 0000000..cf49c4e
--- /dev/null
+++ b/wrappers/Tag.cpp
@@ -0,0 +1,33 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#include <boost/python.hpp>
+
+#include "odil/Tag.h"
+
+void wrap_Tag()
+{
+ using namespace boost::python;
+ using namespace odil;
+
+ class_<Tag>("Tag", init<uint16_t, uint16_t>())
+ .def(init<uint32_t>())
+ .def(init<std::string>())
+ .def_readwrite("group", &Tag::group)
+ .def_readwrite("element", &Tag::element)
+ .def("is_private", &Tag::is_private)
+ .def("get_name", &Tag::get_name)
+ .def(self == self)
+ .def(self != self)
+ .def(self < self)
+ .def(self > self)
+ .def(self <= self)
+ .def(self >= self)
+ .def("__str__", &Tag::operator std::string)
+ ;
+}
diff --git a/wrappers/UIDsDictionary.cpp b/wrappers/UIDsDictionary.cpp
new file mode 100644
index 0000000..e085f80
--- /dev/null
+++ b/wrappers/UIDsDictionary.cpp
@@ -0,0 +1,32 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#include <boost/python.hpp>
+#include <boost/python/suite/indexing/map_indexing_suite.hpp>
+
+#include <odil/UIDsDictionary.h>
+
+void wrap_UIDsDictionary()
+{
+ using namespace boost::python;
+ using namespace odil;
+
+ class_<UIDsDictionaryEntry>(
+ "UIDsDictionaryEntry",
+ init<std::string, std::string, std::string>()
+ )
+ .def_readwrite("name", &UIDsDictionaryEntry::name)
+ .def_readwrite("keyword", &UIDsDictionaryEntry::keyword)
+ .def_readwrite("type", &UIDsDictionaryEntry::type)
+ ;
+
+ class_<UIDsDictionary>("UIDsDictionary")
+ .def(map_indexing_suite<UIDsDictionary>())
+ ;
+}
+
diff --git a/wrappers/VR.cpp b/wrappers/VR.cpp
new file mode 100644
index 0000000..1dcc4a6
--- /dev/null
+++ b/wrappers/VR.cpp
@@ -0,0 +1,52 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#include <boost/python.hpp>
+
+#include "odil/VR.h"
+
+void wrap_VR()
+{
+ using namespace boost::python;
+ using namespace odil;
+
+ enum_<VR>("VR")
+ .value("UNKNOWN", VR::UNKNOWN)
+ .value("AE", VR::AE)
+ .value("AS", VR::AS)
+ .value("AT", VR::AT)
+ .value("CS", VR::CS)
+ .value("DA", VR::DA)
+ .value("DS", VR::DS)
+ .value("DT", VR::DT)
+ .value("FD", VR::FD)
+ .value("FL", VR::FL)
+ .value("IS", VR::IS)
+ .value("LO", VR::LO)
+ .value("LT", VR::LT)
+ .value("PN", VR::PN)
+ .value("OB", VR::OB)
+ .value("OF", VR::OF)
+ .value("OW", VR::OW)
+ .value("SH", VR::SH)
+ .value("SL", VR::SL)
+ .value("SQ", VR::SQ)
+ .value("SS", VR::SS)
+ .value("ST", VR::ST)
+ .value("TM", VR::TM)
+ .value("UC", VR::UC)
+ .value("UI", VR::UI)
+ .value("UL", VR::UL)
+ .value("UN", VR::UN)
+ .value("UR", VR::UR)
+ .value("US", VR::US)
+ .value("UT", VR::UT)
+ .value("INVALID", VR::INVALID)
+ ;
+}
+
diff --git a/wrappers/Value.cpp b/wrappers/Value.cpp
new file mode 100644
index 0000000..f689294
--- /dev/null
+++ b/wrappers/Value.cpp
@@ -0,0 +1,110 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#include <boost/python.hpp>
+#include <boost/python/suite/indexing/vector_indexing_suite.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include "odil/DataSet.h"
+#include "odil/Value.h"
+
+template<typename T, typename python_type=typename T::value_type>
+boost::shared_ptr<T> create_value(boost::python::object const & sequence)
+{
+ typedef typename T::value_type value_type;
+
+ std::vector<value_type> values(boost::python::len(sequence));
+ for(long i=0; i<boost::python::len(sequence); ++i)
+ {
+ boost::python::object item = sequence[i];
+ values[i] = boost::python::extract<python_type>(item);
+ }
+
+ // Old versions of Boost.Python (Debian 7, Ubuntu 12.04) do not like
+ // std::shared_ptr
+ return boost::shared_ptr<T>(new T(values));
+}
+
+void wrap_Value()
+{
+ using namespace boost::python;
+ using namespace odil;
+
+ typedef Value::Integers & (Value::*AsIntegers)();
+ typedef Value::Reals & (Value::*AsReals)();
+ typedef Value::Strings & (Value::*AsStrings)();
+ typedef Value::DataSets & (Value::*AsDataSets)();
+ typedef Value::Binary & (Value::*AsBinary)();
+
+ // Define scope to enclose Integers, Reals, etc. in Value
+ scope value_scope = class_<Value>("Value", init<>())
+ .def(init<Value::Integers>())
+ .def(init<Value::Reals>())
+ .def(init<Value::Strings>())
+ .def(init<Value::DataSets>())
+ .def(init<Value::Binary>())
+ .def("get_type", &Value::get_type)
+ .def("empty", &Value::empty)
+ .def(
+ "as_integers", AsIntegers(&Value::as_integers),
+ return_value_policy<reference_existing_object>())
+ .def(
+ "as_reals", AsReals(&Value::as_reals),
+ return_value_policy<reference_existing_object>())
+ .def(
+ "as_strings", AsStrings(&Value::as_strings),
+ return_value_policy<reference_existing_object>())
+ .def(
+ "as_data_sets", AsDataSets(&Value::as_data_sets),
+ return_value_policy<reference_existing_object>())
+ .def(
+ "as_binary", AsBinary(&Value::as_binary),
+ return_value_policy<reference_existing_object>())
+ .def(self == self)
+ .def(self != self)
+ ;
+
+ class_<Value::Integers>("Integers")
+ .def(init<>())
+ .def("__init__", make_constructor(create_value<Value::Integers>))
+ .def(vector_indexing_suite<Value::Integers>())
+ ;
+
+ class_<Value::Reals>("Reals")
+ .def(init<>())
+ .def("__init__", make_constructor(create_value<Value::Reals>))
+ .def(vector_indexing_suite<Value::Reals>())
+ ;
+
+ class_<Value::Strings>("Strings")
+ .def(init<>())
+ .def("__init__", make_constructor(create_value<Value::Strings>))
+ .def(vector_indexing_suite<Value::Strings>())
+ ;
+
+ class_<Value::DataSets>("DataSets")
+ .def(init<>())
+ .def("__init__", make_constructor(create_value<Value::DataSets>))
+ .def(vector_indexing_suite<Value::DataSets>())
+ ;
+
+ class_<Value::Binary::value_type>("BinaryItem")
+ .def(init<>())
+ .def(
+ "__init__",
+ make_constructor(create_value<Value::Binary::value_type, char>))
+ .def(vector_indexing_suite<Value::Binary::value_type>())
+ ;
+
+ class_<Value::Binary>("Binary")
+ .def(init<>())
+ .def("__init__", make_constructor(create_value<Value::Binary>))
+ .def(vector_indexing_suite<Value::Binary>())
+ ;
+}
+
diff --git a/wrappers/json_converter.cpp b/wrappers/json_converter.cpp
new file mode 100644
index 0000000..9f271fe
--- /dev/null
+++ b/wrappers/json_converter.cpp
@@ -0,0 +1,44 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#include <boost/python.hpp>
+
+#include <json/json.h>
+
+#include "odil/DataSet.h"
+#include "odil/json_converter.h"
+
+namespace
+{
+
+std::string as_json(odil::DataSet const & data_set, bool pretty_print)
+{
+ auto const json = odil::as_json(data_set);
+
+ Json::Writer * writer = NULL;
+ if(pretty_print)
+ {
+ writer = new Json::StyledWriter();
+ }
+ else
+ {
+ writer = new Json::FastWriter();
+ }
+
+ auto const string = writer->write(json);
+ return string;
+}
+
+}
+
+void wrap_json_converter()
+{
+ using namespace boost::python;
+
+ def("as_json", &as_json);
+}
diff --git a/wrappers/odil.cpp b/wrappers/odil.cpp
new file mode 100644
index 0000000..1fea1e8
--- /dev/null
+++ b/wrappers/odil.cpp
@@ -0,0 +1,58 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#include <boost/python.hpp>
+
+void wrap_Association();
+void wrap_AssociationParameters();
+void wrap_DataSet();
+void wrap_EchoSCU();
+void wrap_Element();
+void wrap_ElementsDictionary();
+void wrap_Exception();
+void wrap_FindSCU();
+void wrap_GetSCU();
+void wrap_json_converter();
+void wrap_MoveSCU();
+void wrap_read();
+void wrap_StoreSCU();
+void wrap_Tag();
+void wrap_uid();
+void wrap_UIDsDictionary();
+void wrap_Value();
+void wrap_VR();
+void wrap_write();
+void wrap_xml_converter();
+
+void wrap_registry();
+
+BOOST_PYTHON_MODULE(odil)
+{
+ wrap_Association();
+ wrap_AssociationParameters();
+ wrap_DataSet();
+ wrap_EchoSCU();
+ wrap_Element();
+ wrap_ElementsDictionary();
+ wrap_Exception();
+ wrap_FindSCU();
+ wrap_GetSCU();
+ wrap_json_converter();
+ wrap_MoveSCU();
+ wrap_read();
+ wrap_StoreSCU();
+ wrap_Tag();
+ wrap_uid();
+ wrap_UIDsDictionary();
+ wrap_Value();
+ wrap_VR();
+ wrap_write();
+ wrap_xml_converter();
+
+ wrap_registry();
+}
diff --git a/wrappers/read.cpp b/wrappers/read.cpp
new file mode 100644
index 0000000..ba76e85
--- /dev/null
+++ b/wrappers/read.cpp
@@ -0,0 +1,48 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#include <fstream>
+#include <string>
+
+#include <boost/python.hpp>
+
+#include "odil/Exception.h"
+#include "odil/Reader.h"
+
+namespace
+{
+
+boost::python::tuple
+read(std::string const & path, bool keep_group_length=false)
+{
+ std::ifstream stream(path);
+ if(!stream)
+ {
+ throw odil::Exception("Could not open "+path);
+ }
+
+ auto const header_and_data_set =
+ odil::Reader::read_file(stream, keep_group_length);
+
+ return boost::python::make_tuple(
+ header_and_data_set.first, header_and_data_set.second);
+}
+
+}
+
+BOOST_PYTHON_FUNCTION_OVERLOADS(read_overloads, read, 1, 2)
+
+void wrap_read()
+{
+ using namespace boost::python;
+
+ def(
+ "read",
+ static_cast<boost::python::tuple (*)(std::string const &, bool)>(read),
+ read_overloads());
+}
diff --git a/wrappers/registry.cpp b/wrappers/registry.cpp
new file mode 100644
index 0000000..826a114
--- /dev/null
+++ b/wrappers/registry.cpp
@@ -0,0 +1,44 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#include <boost/python.hpp>
+
+#include <odil/registry.h>
+
+namespace
+{
+
+class Dummy
+{
+};
+
+}
+
+void wrap_registry()
+{
+ using namespace boost::python;
+ using namespace odil;
+
+ scope registry_scope = class_<Dummy>("registry");
+
+ for(auto const & entry: registry::public_dictionary)
+ {
+ if(entry.first.get_type() == ElementsDictionaryKey::Type::Tag)
+ {
+ registry_scope.attr(entry.second.keyword.c_str()) = entry.first.get_tag();
+ }
+ }
+
+ for(auto const & entry: registry::uids_dictionary)
+ {
+ registry_scope.attr(entry.second.keyword.c_str()) = entry.first;
+ }
+
+ registry_scope.attr("public_dictionary") = registry::public_dictionary;
+ registry_scope.attr("uids_dictionary") = registry::uids_dictionary;
+}
diff --git a/wrappers/uid.cpp b/wrappers/uid.cpp
new file mode 100644
index 0000000..cbdc8e7
--- /dev/null
+++ b/wrappers/uid.cpp
@@ -0,0 +1,22 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#include <boost/python.hpp>
+
+#include "odil/uid.h"
+
+void wrap_uid()
+{
+ using namespace boost::python;
+ using namespace odil;
+
+ scope().attr("uid_prefix") = uid_prefix;
+ scope().attr("implementation_class_uid") = implementation_class_uid;
+ scope().attr("implementation_version_name") = implementation_version_name;
+ def("generate_uid", generate_uid);
+}
diff --git a/wrappers/write.cpp b/wrappers/write.cpp
new file mode 100644
index 0000000..e6d4b01
--- /dev/null
+++ b/wrappers/write.cpp
@@ -0,0 +1,72 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#include <fstream>
+#include <string>
+
+#include <boost/python.hpp>
+
+#include "odil/Exception.h"
+#include "odil/Writer.h"
+
+namespace
+{
+
+void
+write(
+ odil::DataSet const & data_set, std::string const & path,
+ odil::DataSet const & meta_information = odil::DataSet(),
+ std::string const & transfer_syntax = odil::registry::ExplicitVRLittleEndian,
+ odil::Writer::ItemEncoding item_encoding=odil::Writer::ItemEncoding::ExplicitLength,
+ bool use_group_length=false
+)
+{
+ std::ofstream stream(path);
+ if(!stream)
+ {
+ throw odil::Exception("Could not open "+path);
+ }
+
+ odil::Writer::write_file(data_set, stream, meta_information,
+ transfer_syntax, item_encoding, use_group_length);
+}
+
+}
+
+class Dummy { };
+
+void wrap_write()
+{
+ using namespace boost::python;
+ using namespace odil;
+
+ {
+ scope writer_scope = class_<Dummy>("Writer");
+ enum_<Writer::ItemEncoding>("ItemEncoding")
+ .value("ExplicitLength", Writer::ItemEncoding::ExplicitLength)
+ .value("UndefinedLength", Writer::ItemEncoding::UndefinedLength)
+ ;
+ }
+
+ def(
+ "write",
+ static_cast<
+ void (*)(
+ DataSet const &, std::string const &, DataSet const &,
+ std::string const &, Writer::ItemEncoding, bool)
+ >(write),
+ (
+ arg("data_set"), arg("path"),
+ arg("meta_information")=odil::DataSet(),
+ arg("transfer_syntax")=odil::registry::ExplicitVRLittleEndian,
+ arg("item_encoding")=odil::Writer::ItemEncoding::ExplicitLength,
+ arg("use_group_length")=false
+ )
+ );
+}
+
diff --git a/wrappers/xml_converter.cpp b/wrappers/xml_converter.cpp
new file mode 100644
index 0000000..1b0054a
--- /dev/null
+++ b/wrappers/xml_converter.cpp
@@ -0,0 +1,45 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#include <boost/property_tree/xml_parser.hpp>
+#include <boost/python.hpp>
+
+#include "odil/DataSet.h"
+#include "odil/xml_converter.h"
+
+namespace
+{
+
+std::string as_xml(odil::DataSet const & data_set, bool pretty_print)
+{
+ auto const xml = odil::as_xml(data_set);
+ std::ostringstream stream;
+
+#if BOOST_VERSION >= 105600
+ typedef boost::property_tree::xml_writer_settings<std::string> SettingsType;
+#else
+ typedef boost::property_tree::xml_writer_settings<char> SettingsType;
+#endif
+
+ SettingsType settings;
+ if(pretty_print)
+ {
+ settings = SettingsType('\t', 1);
+ }
+ boost::property_tree::write_xml(stream, xml, settings);
+ return stream.str();
+}
+
+}
+
+void wrap_xml_converter()
+{
+ using namespace boost::python;
+
+ def("as_xml", &as_xml);
+}
--
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