changeset 0:a2d79b456440

initial commit
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 22 Apr 2022 17:58:59 +0200
parents
children ef9df856744c
files .hgignore AUTHORS CMakeLists.txt COPYING NEWS README Resources/CMake/NiftiCLib.cmake Resources/Orthanc/CMake/AutoGeneratedCode.cmake Resources/Orthanc/CMake/Compiler.cmake Resources/Orthanc/CMake/DownloadOrthancFramework.cmake Resources/Orthanc/CMake/DownloadPackage.cmake Resources/Orthanc/CMake/EmbedResources.py Resources/Orthanc/CMake/GoogleTestConfiguration.cmake Resources/Orthanc/CMake/WindowsResources.py Resources/Orthanc/CMake/WindowsResources.rc Resources/Orthanc/Plugins/ExportedSymbolsPlugins.list Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h Resources/Orthanc/Plugins/OrthancPluginException.h Resources/Orthanc/Plugins/OrthancPluginsExports.cmake Resources/Orthanc/Plugins/VersionScriptPlugins.map Resources/Orthanc/Sdk-1.10.1/orthanc/OrthancCPlugin.h Resources/Orthanc/Toolchains/LinuxStandardBaseToolchain.cmake Resources/Orthanc/Toolchains/MinGW-W64-Toolchain32.cmake Resources/Orthanc/Toolchains/MinGW-W64-Toolchain64.cmake Resources/Orthanc/Toolchains/MinGWToolchain.cmake Resources/SyncOrthancFolder.py Sources/Framework/BufferReader.cpp Sources/Framework/BufferReader.h Sources/Framework/CSAHeader.cpp Sources/Framework/CSAHeader.h Sources/Framework/CSATag.cpp Sources/Framework/CSATag.h Sources/Framework/DicomInstancesCollection.cpp Sources/Framework/DicomInstancesCollection.h Sources/Framework/IDicomFrameDecoder.cpp Sources/Framework/IDicomFrameDecoder.h Sources/Framework/InputDicomInstance.cpp Sources/Framework/InputDicomInstance.h Sources/Framework/NeuroEnumerations.h Sources/Framework/NeuroToolbox.cpp Sources/Framework/NeuroToolbox.h Sources/Framework/NiftiWriter.cpp Sources/Framework/NiftiWriter.h Sources/Framework/Slice.cpp Sources/Framework/Slice.h Sources/Plugin/OrthancExplorer.js Sources/Plugin/Plugin.cpp Sources/Plugin/PluginFrameDecoder.cpp Sources/Plugin/PluginFrameDecoder.h Sources/UnitTestsSources/NiftiTests.cpp Sources/UnitTestsSources/UnitTestsMain.cpp TODO
diffstat 53 files changed, 34641 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.hgignore	Fri Apr 22 17:58:59 2022 +0200
@@ -0,0 +1,4 @@
+syntax: glob
+*.cpp.orig
+*.h.orig
+*~
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/AUTHORS	Fri Apr 22 17:58:59 2022 +0200
@@ -0,0 +1,12 @@
+Neuroimaging plugin for Orthanc
+===============================
+
+
+Authors
+-------
+
+* Sebastien Jodogne <sebastien.jodogne@uclouvain.be>
+
+  UCLouvain, ICTEAM Institute
+  1348 Louvain-la-Neuve
+  Belgium
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CMakeLists.txt	Fri Apr 22 17:58:59 2022 +0200
@@ -0,0 +1,190 @@
+# Neuroimaging plugin for Orthanc
+# Copyright (C) 2021-2022 Sebastien Jodogne, UCLouvain, Belgium
+#
+# This program is free software: you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+# 
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+cmake_minimum_required(VERSION 2.8)
+
+project(OrthancNeuro)
+
+set(ORTHANC_PLUGIN_VERSION "mainline")
+
+if (ORTHANC_PLUGIN_VERSION STREQUAL "mainline")
+  set(ORTHANC_FRAMEWORK_DEFAULT_VERSION "mainline")
+  set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "hg")
+else()
+  set(ORTHANC_FRAMEWORK_DEFAULT_VERSION "1.10.1")
+  set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "web")
+endif()
+
+
+# Parameters of the build
+set(STATIC_BUILD OFF CACHE BOOL "Static build of the third-party libraries (necessary for Windows)")
+set(ALLOW_DOWNLOADS OFF CACHE BOOL "Allow CMake to download packages")
+set(ORTHANC_FRAMEWORK_SOURCE "${ORTHANC_FRAMEWORK_DEFAULT_SOURCE}" CACHE STRING "Source of the Orthanc framework (can be \"system\", \"hg\", \"archive\", \"web\" or \"path\")")
+set(ORTHANC_FRAMEWORK_VERSION "${ORTHANC_FRAMEWORK_DEFAULT_VERSION}" CACHE STRING "Version of the Orthanc framework")
+set(ORTHANC_FRAMEWORK_ARCHIVE "" CACHE STRING "Path to the Orthanc archive, if ORTHANC_FRAMEWORK_SOURCE is \"archive\"")
+set(ORTHANC_FRAMEWORK_ROOT "" CACHE STRING "Path to the Orthanc source directory, if ORTHANC_FRAMEWORK_SOURCE is \"path\"")
+
+set(USE_SYSTEM_NIFTILIB ON CACHE BOOL "Use the system version of niftilib")
+
+
+# Advanced parameters to fine-tune linking against system libraries
+set(USE_SYSTEM_ORTHANC_SDK ON CACHE BOOL "Use the system version of the Orthanc plugin SDK")
+set(ORTHANC_FRAMEWORK_STATIC OFF CACHE BOOL "If linking against the Orthanc framework system library, indicates whether this library was statically linked")
+mark_as_advanced(ORTHANC_FRAMEWORK_STATIC)
+
+
+# Download and setup the Orthanc framework
+include(${CMAKE_SOURCE_DIR}/Resources/Orthanc/CMake/DownloadOrthancFramework.cmake)
+
+if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "system")
+  if (ORTHANC_FRAMEWORK_USE_SHARED)
+    include(FindBoost)
+    find_package(Boost COMPONENTS filesystem thread)
+    
+    if (NOT Boost_FOUND)
+      message(FATAL_ERROR "Unable to locate Boost on this system")
+    endif()
+    
+    link_libraries(${Boost_LIBRARIES} jsoncpp)
+  endif()
+
+  link_libraries(${ORTHANC_FRAMEWORK_LIBRARIES})
+  
+  set(USE_SYSTEM_GOOGLE_TEST ON CACHE BOOL "Use the system version of Google Test")
+  set(USE_GOOGLE_TEST_DEBIAN_PACKAGE OFF CACHE BOOL "Use the sources of Google Test shipped with libgtest-dev (Debian only)")
+  mark_as_advanced(USE_GOOGLE_TEST_DEBIAN_PACKAGE)
+  include(${CMAKE_SOURCE_DIR}/Resources/Orthanc/CMake/GoogleTestConfiguration.cmake)
+
+else()
+  include(${ORTHANC_FRAMEWORK_ROOT}/../Resources/CMake/OrthancFrameworkParameters.cmake)
+  
+  set(ENABLE_GOOGLE_TEST ON)
+  set(ENABLE_ZLIB ON)
+
+  set(ENABLE_DCMTK OFF)   # Could be set to "ON" for debugging
+  set(ENABLE_PNG OFF)     # Could be set to "ON" for debugging
+  set(ENABLE_LOCALE OFF)  # Disable support for locales (notably in Boost)
+  set(ENABLE_MODULE_JOBS OFF CACHE INTERNAL "")
+
+  include(${ORTHANC_FRAMEWORK_ROOT}/../Resources/CMake/OrthancFrameworkConfiguration.cmake)
+  include_directories(${ORTHANC_FRAMEWORK_ROOT})
+endif()
+
+
+include(${CMAKE_SOURCE_DIR}/Resources/CMake/NiftiCLib.cmake)
+include(${CMAKE_SOURCE_DIR}/Resources/Orthanc/Plugins/OrthancPluginsExports.cmake)
+
+
+# Check that the Orthanc SDK headers are available
+if (STATIC_BUILD OR NOT USE_SYSTEM_ORTHANC_SDK)
+  include_directories(${CMAKE_SOURCE_DIR}/Resources/Orthanc/Sdk-1.10.1)
+else ()
+  CHECK_INCLUDE_FILE_CXX(orthanc/OrthancCPlugin.h HAVE_ORTHANC_H)
+  if (NOT HAVE_ORTHANC_H)
+    message(FATAL_ERROR "Please install the headers of the Orthanc plugins SDK")
+  endif()
+endif()
+
+
+add_definitions(
+  -DHAS_ORTHANC_EXCEPTION=1
+  -DORTHANC_PLUGIN_VERSION="${ORTHANC_PLUGIN_VERSION}"
+  )
+
+EmbedResources(
+  ORTHANC_EXPLORER  ${CMAKE_SOURCE_DIR}/Sources/Plugin/OrthancExplorer.js
+  )
+
+if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR
+    ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD" OR
+    ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD")
+  link_libraries(rt)
+elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
+  execute_process(
+    COMMAND 
+    ${PYTHON_EXECUTABLE} ${CMAKE_SOURCE_DIR}/Resources/Orthanc/CMake/WindowsResources.py
+    ${ORTHANC_PLUGIN_VERSION} "OrthancNeuro" OrthancNeuro.dll ""
+    ERROR_VARIABLE Failure
+    OUTPUT_FILE ${AUTOGENERATED_DIR}/Version.rc
+    )
+
+  if (Failure)
+    message(FATAL_ERROR "Error while computing the version information: ${Failure}")
+  endif()
+
+  list(APPEND AUTOGENERATED_SOURCES  ${AUTOGENERATED_DIR}/Version.rc)
+endif()
+
+
+set(NEURO_SOURCES
+  Sources/Framework/BufferReader.cpp
+  Sources/Framework/CSAHeader.cpp
+  Sources/Framework/CSATag.cpp
+  Sources/Framework/DicomInstancesCollection.cpp
+  Sources/Framework/IDicomFrameDecoder.cpp
+  Sources/Framework/InputDicomInstance.cpp
+  Sources/Framework/NeuroToolbox.cpp
+  Sources/Framework/NiftiWriter.cpp
+  Sources/Framework/Slice.cpp
+  
+  ${NIFTILIB_SOURCES}
+  ${AUTOGENERATED_SOURCES}
+  ${ORTHANC_CORE_SOURCES}
+  )
+
+
+add_custom_target(
+  AutogeneratedTarget
+  DEPENDS 
+  ${AUTOGENERATED_SOURCES}
+  )
+          
+add_library(OrthancNeuro SHARED
+  Sources/Plugin/Plugin.cpp
+  Sources/Plugin/PluginFrameDecoder.cpp
+
+  ${NEURO_SOURCES}
+  ${ORTHANC_FRAMEWORK_ROOT}/../../OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp
+  )
+
+add_dependencies(OrthancNeuro AutogeneratedTarget)
+
+add_executable(UnitTests
+  Sources/UnitTestsSources/NiftiTests.cpp
+  Sources/UnitTestsSources/UnitTestsMain.cpp
+
+  ${NEURO_SOURCES}
+  ${ORTHANC_FRAMEWORK_ROOT}/../../OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp
+  ${GOOGLE_TEST_SOURCES}
+  )
+
+add_dependencies(UnitTests AutogeneratedTarget)
+
+target_link_libraries(UnitTests ${GOOGLE_TEST_LIBRARIES})
+
+
+message("Setting the version of the library to ${ORTHANC_PLUGIN_VERSION}")
+
+set_target_properties(OrthancNeuro PROPERTIES 
+  VERSION ${ORTHANC_PLUGIN_VERSION} 
+  SOVERSION ${ORTHANC_PLUGIN_VERSION})
+
+install(
+  TARGETS OrthancNeuro
+  RUNTIME DESTINATION lib    # Destination for Windows
+  LIBRARY DESTINATION share/orthanc/plugins    # Destination for Linux
+  )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/COPYING	Fri Apr 22 17:58:59 2022 +0200
@@ -0,0 +1,675 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  You must make sure that they, too, receive
+or can get the source code.  And you must show them these terms so they
+know their rights.
+
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Use with the GNU Affero General Public License.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+    <program>  Copyright (C) <year>  <name of author>
+    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+  The GNU General Public License does not permit incorporating your program
+into proprietary programs.  If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.  But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/NEWS	Fri Apr 22 17:58:59 2022 +0200
@@ -0,0 +1,6 @@
+Pending changes in the mainline
+===============================
+
+=> Minimum SDK version: 1.10.1 <=
+
+* Initial release
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/README	Fri Apr 22 17:58:59 2022 +0200
@@ -0,0 +1,43 @@
+Neuroimaging plugin for Orthanc
+===============================
+
+
+General Information
+-------------------
+
+This repository contains the source code of a plugin that provides
+support for neuroimaging file formats (NIfTI and BIDS) in Orthanc.
+
+
+Installation and usage
+----------------------
+
+Build and usage instructions are available in the Orthanc Book:
+http://book.orthanc-server.com/plugins/neuro.html
+
+
+Licensing
+---------
+
+The neuroimaging plugin for Orthanc is licensed under the GPL license.
+
+We also kindly ask scientific works and clinical studies that make
+use of Orthanc to cite Orthanc in their associated publications.
+Similarly, we ask open-source and closed-source products that make
+use of Orthanc to warn us about this use. You can cite our work
+using the following BibTeX entry:
+
+@Article{Jodogne2018,
+  author="Jodogne, S{\'e}bastien",
+  title="The {O}rthanc Ecosystem for Medical Imaging",
+  journal="Journal of Digital Imaging",
+  year="2018",
+  month="Jun",
+  day="01",
+  volume="31",
+  number="3",
+  pages="341--352",
+  issn="1618-727X",
+  doi="10.1007/s10278-018-0082-y",
+  url="https://doi.org/10.1007/s10278-018-0082-y"
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/CMake/NiftiCLib.cmake	Fri Apr 22 17:58:59 2022 +0200
@@ -0,0 +1,57 @@
+# Neuroimaging plugin for Orthanc
+# Copyright (C) 2021-2022 Sebastien Jodogne, UCLouvain, Belgium
+#
+# This program is free software: you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+# 
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+if (STATIC_BUILD OR NOT USE_SYSTEM_NIFTILIB)
+  set(NIFTILIB_SOURCES_DIR ${CMAKE_BINARY_DIR}/nifti_clib-3.0.0)
+  DownloadPackage(
+    "ee40068103775a181522166e435ee82d"
+    "https://third-party.orthanc-labs.com/nifti_clib-3.0.0.tar.gz"
+    "${NIFTILIB_SOURCES_DIR}")
+
+  include_directories(
+    ${NIFTILIB_SOURCES_DIR}/niftilib
+    ${NIFTILIB_SOURCES_DIR}/znzlib
+    )
+
+  add_definitions(
+    -DHAVE_ZLIB=1
+    )
+
+  set(NIFTILIB_SOURCES
+    ${NIFTILIB_SOURCES_DIR}/niftilib/nifti1_io.c
+    ${NIFTILIB_SOURCES_DIR}/znzlib/znzlib.c
+    )
+
+else()
+  find_path(NIFTILIB_INCLUDE_DIR
+    NAMES nifti1.h
+    PATHS
+    /usr/include
+    /usr/include/nifti
+    /usr/local/include
+    /usr/local/include/nifti
+    )
+
+  check_include_file(${NIFTILIB_INCLUDE_DIR}/nifti1.h HAVE_NIFTILIB_H)
+  if (NOT HAVE_NIFTILIB_H)
+    message(FATAL_ERROR "Please install the libnifti-dev package")
+  endif()
+
+  include_directories(${NIFTILIB_INCLUDE_DIR})
+  
+  link_libraries(niftiio znz)
+endif()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/CMake/AutoGeneratedCode.cmake	Fri Apr 22 17:58:59 2022 +0200
@@ -0,0 +1,79 @@
+# Orthanc - A Lightweight, RESTful DICOM Store
+# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+# Department, University Hospital of Liege, Belgium
+# Copyright (C) 2017-2022 Osimis S.A., Belgium
+# Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+#
+# This program is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public License
+# as published by the Free Software Foundation, either version 3 of
+# the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this program. If not, see
+# <http://www.gnu.org/licenses/>.
+
+
+set(EMBED_RESOURCES_PYTHON "${CMAKE_CURRENT_LIST_DIR}/../EmbedResources.py"
+  CACHE INTERNAL "Path to the EmbedResources.py script from Orthanc")
+set(AUTOGENERATED_DIR "${CMAKE_CURRENT_BINARY_DIR}/AUTOGENERATED")
+set(AUTOGENERATED_SOURCES)
+
+file(MAKE_DIRECTORY ${AUTOGENERATED_DIR})
+include_directories(${AUTOGENERATED_DIR})
+
+macro(EmbedResources)
+  # Convert a semicolon separated list to a whitespace separated string
+  set(SCRIPT_OPTIONS)
+  set(SCRIPT_ARGUMENTS)
+  set(DEPENDENCIES)
+  set(IS_PATH_NAME false)
+
+  set(TARGET_BASE "${AUTOGENERATED_DIR}/EmbeddedResources")
+
+  # Loop over the arguments of the function
+  foreach(arg ${ARGN})
+    # Extract the first character of the argument
+    string(SUBSTRING "${arg}" 0 1 FIRST_CHAR)
+    if (${FIRST_CHAR} STREQUAL "-")
+      # If the argument starts with a dash "-", this is an option to
+      # EmbedResources.py
+      if (${arg} MATCHES "--target=.*")
+        # Does the argument starts with "--target="?
+        string(SUBSTRING "${arg}" 9 -1 TARGET)  # 9 is the length of "--target="
+        set(TARGET_BASE "${AUTOGENERATED_DIR}/${TARGET}")
+      else()
+        list(APPEND SCRIPT_OPTIONS ${arg})
+      endif()
+    else()
+      if (${IS_PATH_NAME})
+        list(APPEND SCRIPT_ARGUMENTS "${arg}")
+        list(APPEND DEPENDENCIES "${arg}")
+        set(IS_PATH_NAME false)
+      else()
+        list(APPEND SCRIPT_ARGUMENTS "${arg}")
+        set(IS_PATH_NAME true)
+      endif()
+    endif()
+  endforeach()
+
+  add_custom_command(
+    OUTPUT
+    "${TARGET_BASE}.h"
+    "${TARGET_BASE}.cpp"
+    COMMAND ${PYTHON_EXECUTABLE} ${EMBED_RESOURCES_PYTHON}
+            ${SCRIPT_OPTIONS} "${TARGET_BASE}" ${SCRIPT_ARGUMENTS}
+    DEPENDS
+    ${EMBED_RESOURCES_PYTHON}
+    ${DEPENDENCIES}
+    )
+
+  list(APPEND AUTOGENERATED_SOURCES
+    "${TARGET_BASE}.cpp"
+    ) 
+endmacro()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/CMake/Compiler.cmake	Fri Apr 22 17:58:59 2022 +0200
@@ -0,0 +1,264 @@
+# Orthanc - A Lightweight, RESTful DICOM Store
+# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+# Department, University Hospital of Liege, Belgium
+# Copyright (C) 2017-2022 Osimis S.A., Belgium
+# Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+#
+# This program is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public License
+# as published by the Free Software Foundation, either version 3 of
+# the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this program. If not, see
+# <http://www.gnu.org/licenses/>.
+
+
+# This file sets all the compiler-related flags
+
+
+# Save the current compiler flags to the cache every time cmake configures the project
+set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS}" CACHE STRING "compiler flags" FORCE)
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}" CACHE STRING "compiler flags" FORCE)
+
+
+include(CheckLibraryExists)
+
+if ((CMAKE_CROSSCOMPILING AND NOT
+      "${CMAKE_SYSTEM_VERSION}" STREQUAL "CrossToolNg") OR    
+    "${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase")
+  # Cross-compilation necessarily implies standalone and static build
+  SET(STATIC_BUILD ON)
+  SET(STANDALONE_BUILD ON)
+endif()
+
+
+if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase")
+  # Cache the environment variables "LSB_CC" and "LSB_CXX" for further
+  # use by "ExternalProject" in CMake
+  SET(CMAKE_LSB_CC $ENV{LSB_CC} CACHE STRING "")
+  SET(CMAKE_LSB_CXX $ENV{LSB_CXX} CACHE STRING "")
+endif()
+
+
+if (CMAKE_COMPILER_IS_GNUCXX)
+  set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wno-long-long")
+
+  # --std=c99 makes libcurl not to compile
+  # -pedantic gives a lot of warnings on OpenSSL 
+  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-long-long -Wno-variadic-macros")
+
+  if (CMAKE_CROSSCOMPILING)
+    # http://stackoverflow.com/a/3543845/881731
+    set(CMAKE_RC_COMPILE_OBJECT "<CMAKE_RC_COMPILER> -O coff -I<CMAKE_CURRENT_SOURCE_DIR> <SOURCE> <OBJECT>")
+  endif()
+
+elseif (MSVC)
+  # Use static runtime under Visual Studio
+  # http://www.cmake.org/Wiki/CMake_FAQ#Dynamic_Replace
+  # http://stackoverflow.com/a/6510446
+  foreach(flag_var
+    CMAKE_C_FLAGS_DEBUG
+    CMAKE_CXX_FLAGS_DEBUG
+    CMAKE_C_FLAGS_RELEASE 
+    CMAKE_CXX_FLAGS_RELEASE
+    CMAKE_C_FLAGS_MINSIZEREL 
+    CMAKE_CXX_FLAGS_MINSIZEREL 
+    CMAKE_C_FLAGS_RELWITHDEBINFO 
+    CMAKE_CXX_FLAGS_RELWITHDEBINFO) 
+    string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}")
+    string(REGEX REPLACE "/MDd" "/MTd" ${flag_var} "${${flag_var}}")
+  endforeach(flag_var)
+
+  # Add /Zm256 compiler option to Visual Studio to fix PCH errors
+  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zm256")
+
+  # New in Orthanc 1.5.5
+  if (MSVC_MULTIPLE_PROCESSES)
+    # "If you omit the processMax argument in the /MP option, the
+    # compiler obtains the number of effective processors from the
+    # operating system, and then creates one process per effective
+    # processor"
+    # https://blog.kitware.com/cmake-building-with-all-your-cores/
+    # https://docs.microsoft.com/en-us/cpp/build/reference/mp-build-with-multiple-processes
+    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /MP")
+    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP")
+  endif()
+    
+  add_definitions(
+    -D_CRT_SECURE_NO_WARNINGS=1
+    -D_CRT_SECURE_NO_DEPRECATE=1
+    )
+
+  if (MSVC_VERSION LESS 1600)
+    # Starting with Visual Studio >= 2010 (i.e. macro _MSC_VER >=
+    # 1600), Microsoft ships a standard-compliant <stdint.h>
+    # header. For earlier versions of Visual Studio, give access to a
+    # compatibility header.
+    # http://stackoverflow.com/a/70630/881731
+    # https://en.wikibooks.org/wiki/C_Programming/C_Reference/stdint.h#External_links
+    include_directories(${CMAKE_CURRENT_LIST_DIR}/../../Resources/ThirdParty/VisualStudio)
+  endif()
+
+  link_libraries(netapi32)
+endif()
+
+
+if (${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD" OR
+    ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD")
+  # In FreeBSD/OpenBSD, the "/usr/local/" folder contains the ports and need to be imported
+  SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -I/usr/local/include")
+  SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -I/usr/local/include")
+  SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -L/usr/local/lib")
+  SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -L/usr/local/lib")
+endif()
+
+
+if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR
+    ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD" OR
+    ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD" OR
+    ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD")
+
+  if (NOT ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD" AND
+      NOT ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD")
+    # The "--no-undefined" linker flag makes the shared libraries
+    # (plugins ModalityWorklists and ServeFolders) fail to compile on
+    # OpenBSD, and make the PostgreSQL plugin complain about missing
+    # "environ" global variable in FreeBSD
+    set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--no-undefined")
+    set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined")
+  endif()
+
+  # Remove the "-rdynamic" option
+  # http://www.mail-archive.com/cmake@cmake.org/msg08837.html
+  set(CMAKE_SHARED_LIBRARY_LINK_CXX_FLAGS "")
+  link_libraries(pthread)
+
+  if (NOT ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD")
+    link_libraries(rt)
+  endif()
+
+  if (NOT ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD" AND
+      NOT ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD")
+    link_libraries(dl)
+  endif()
+
+  if (NOT ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD" AND
+      NOT ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD")
+    # The "--as-needed" linker flag is not available on FreeBSD and OpenBSD
+    set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--as-needed")
+    set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--as-needed")
+    set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--as-needed")
+  endif()
+
+  if (NOT ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD" AND
+      NOT ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD")
+    # FreeBSD/OpenBSD have just one single interface for file
+    # handling, which is 64bit clean, so there is no need to define macro
+    # for LFS (Large File Support).
+    # https://ohse.de/uwe/articles/lfs.html
+    add_definitions(
+      -D_LARGEFILE64_SOURCE=1 
+      -D_FILE_OFFSET_BITS=64
+      )
+  endif()
+
+elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
+  if (MSVC)
+    message("MSVC compiler version = " ${MSVC_VERSION} "\n")
+    # Starting Visual Studio 2013 (version 1800), it is not possible
+    # to target Windows XP anymore
+    if (MSVC_VERSION LESS 1800)
+      add_definitions(
+        -DWINVER=0x0501
+        -D_WIN32_WINNT=0x0501
+        )
+    endif()
+  else()
+    add_definitions(
+      -DWINVER=0x0501
+      -D_WIN32_WINNT=0x0501
+      )
+  endif()
+
+  add_definitions(
+    -D_CRT_SECURE_NO_WARNINGS=1
+    )
+  link_libraries(rpcrt4 ws2_32 iphlpapi)  # "iphlpapi" is for "SystemToolbox::GetMacAddresses()"
+
+  if (CMAKE_COMPILER_IS_GNUCXX)
+    # Some additional C/C++ compiler flags for MinGW
+    SET(MINGW_NO_WARNINGS "-Wno-unused-function -Wno-unused-variable")
+    SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${MINGW_NO_WARNINGS} -Wno-pointer-to-int-cast -Wno-int-to-pointer-cast")
+    SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${MINGW_NO_WARNINGS}")
+
+    if (DYNAMIC_MINGW_STDLIB)
+    else()
+      # This is a patch for MinGW64
+      SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--allow-multiple-definition -static-libgcc -static-libstdc++")
+      SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--allow-multiple-definition -static-libgcc -static-libstdc++")
+    endif()
+
+    CHECK_LIBRARY_EXISTS(winpthread pthread_create "" HAVE_WIN_PTHREAD)
+    if (HAVE_WIN_PTHREAD)
+      if (DYNAMIC_MINGW_STDLIB)
+      else()
+        # This line is necessary to compile with recent versions of MinGW,
+        # otherwise "libwinpthread-1.dll" is not statically linked.
+        SET(CMAKE_CXX_STANDARD_LIBRARIES "${CMAKE_CXX_STANDARD_LIBRARIES} -Wl,-Bstatic -lstdc++ -lpthread -Wl,-Bdynamic")
+      endif()
+      add_definitions(-DHAVE_WIN_PTHREAD=1)
+    else()
+      add_definitions(-DHAVE_WIN_PTHREAD=0)
+    endif()
+  endif()
+
+elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin")
+  add_definitions(
+    -D_XOPEN_SOURCE=1
+    )
+  link_libraries(iconv)
+
+elseif (CMAKE_SYSTEM_NAME STREQUAL "Emscripten")
+  message("Building using Emscripten (for WebAssembly or asm.js targets)")
+  include(${CMAKE_CURRENT_LIST_DIR}/EmscriptenParameters.cmake)
+  
+elseif (CMAKE_SYSTEM_NAME STREQUAL "Android")
+
+else()
+  message("Unknown target platform: ${CMAKE_SYSTEM_NAME}")
+  message(FATAL_ERROR "Support your platform here")
+endif()
+
+
+if (DEFINED ENABLE_PROFILING AND ENABLE_PROFILING)
+  if (CMAKE_COMPILER_IS_GNUCXX)
+    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pg")
+    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pg")
+    set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pg")
+    set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -pg")
+    set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -pg")
+  else()
+    message(FATAL_ERROR "Don't know how to enable profiling on your configuration")
+  endif()
+endif()
+
+
+if (CMAKE_COMPILER_IS_GNUCXX)
+  # "When creating a static library using binutils (ar) and there
+  # exist a duplicate object name (e.g. a/Foo.cpp.o, b/Foo.cpp.o), the
+  # resulting static library can end up having only one of the
+  # duplicate objects. [...] This bug only happens if there are many
+  # objects." The trick consists in replacing the "r" argument
+  # ("replace") provided to "ar" (as used in CMake < 3.1) by the "q"
+  # argument ("quick append"). This is because of the fact that CMake
+  # will invoke "ar" several times with several batches of ".o"
+  # objects, and using "r" would overwrite symbols defined in
+  # preceding batches. https://cmake.org/Bug/view.php?id=14874
+  set(CMAKE_CXX_ARCHIVE_APPEND "<CMAKE_AR> <LINK_FLAGS> q <TARGET> <OBJECTS>")
+endif()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/CMake/DownloadOrthancFramework.cmake	Fri Apr 22 17:58:59 2022 +0200
@@ -0,0 +1,546 @@
+# Orthanc - A Lightweight, RESTful DICOM Store
+# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+# Department, University Hospital of Liege, Belgium
+# Copyright (C) 2017-2022 Osimis S.A., Belgium
+# Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+#
+# This program is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public License
+# as published by the Free Software Foundation, either version 3 of
+# the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this program. If not, see
+# <http://www.gnu.org/licenses/>.
+
+
+
+##
+## Check whether the parent script sets the mandatory variables
+##
+
+if (NOT DEFINED ORTHANC_FRAMEWORK_SOURCE OR
+    (NOT ORTHANC_FRAMEWORK_SOURCE STREQUAL "system" AND
+     NOT ORTHANC_FRAMEWORK_SOURCE STREQUAL "hg" AND
+     NOT ORTHANC_FRAMEWORK_SOURCE STREQUAL "web" AND
+     NOT ORTHANC_FRAMEWORK_SOURCE STREQUAL "archive" AND
+     NOT ORTHANC_FRAMEWORK_SOURCE STREQUAL "path"))
+  message(FATAL_ERROR "The variable ORTHANC_FRAMEWORK_SOURCE must be set to \"system\", \"hg\", \"web\", \"archive\" or \"path\"")
+endif()
+
+
+##
+## Detection of the requested version
+##
+
+if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "hg" OR
+    ORTHANC_FRAMEWORK_SOURCE STREQUAL "archive" OR
+    ORTHANC_FRAMEWORK_SOURCE STREQUAL "web")
+  if (NOT DEFINED ORTHANC_FRAMEWORK_VERSION)
+    message(FATAL_ERROR "The variable ORTHANC_FRAMEWORK_VERSION must be set")
+  endif()
+
+  if (DEFINED ORTHANC_FRAMEWORK_MAJOR OR
+      DEFINED ORTHANC_FRAMEWORK_MINOR OR
+      DEFINED ORTHANC_FRAMEWORK_REVISION OR
+      DEFINED ORTHANC_FRAMEWORK_MD5)
+    message(FATAL_ERROR "Some internal variable has been set")
+  endif()
+
+  set(ORTHANC_FRAMEWORK_MD5 "")
+
+  if (NOT DEFINED ORTHANC_FRAMEWORK_BRANCH)
+    if (ORTHANC_FRAMEWORK_VERSION STREQUAL "mainline")
+      set(ORTHANC_FRAMEWORK_BRANCH "default")
+      set(ORTHANC_FRAMEWORK_MAJOR 999)
+      set(ORTHANC_FRAMEWORK_MINOR 999)
+      set(ORTHANC_FRAMEWORK_REVISION 999)
+
+    else()
+      set(ORTHANC_FRAMEWORK_BRANCH "Orthanc-${ORTHANC_FRAMEWORK_VERSION}")
+
+      set(RE "^([0-9]+)\\.([0-9]+)\\.([0-9]+)$")
+      string(REGEX REPLACE ${RE} "\\1" ORTHANC_FRAMEWORK_MAJOR ${ORTHANC_FRAMEWORK_VERSION})
+      string(REGEX REPLACE ${RE} "\\2" ORTHANC_FRAMEWORK_MINOR ${ORTHANC_FRAMEWORK_VERSION})
+      string(REGEX REPLACE ${RE} "\\3" ORTHANC_FRAMEWORK_REVISION ${ORTHANC_FRAMEWORK_VERSION})
+
+      if (NOT ORTHANC_FRAMEWORK_MAJOR MATCHES "^[0-9]+$" OR
+          NOT ORTHANC_FRAMEWORK_MINOR MATCHES "^[0-9]+$" OR
+          NOT ORTHANC_FRAMEWORK_REVISION MATCHES "^[0-9]+$")
+        message("Bad version of the Orthanc framework, assuming a pre-release: ${ORTHANC_FRAMEWORK_VERSION}")
+        set(ORTHANC_FRAMEWORK_MAJOR 999)
+        set(ORTHANC_FRAMEWORK_MINOR 999)
+        set(ORTHANC_FRAMEWORK_REVISION 999)
+      endif()
+
+      if (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.3.1")
+        set(ORTHANC_FRAMEWORK_MD5 "dac95bd6cf86fb19deaf4e612961f378")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.3.2")
+        set(ORTHANC_FRAMEWORK_MD5 "d0ccdf68e855d8224331f13774992750")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.4.0")
+        set(ORTHANC_FRAMEWORK_MD5 "81e15f34d97ac32bbd7d26e85698835a")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.4.1")
+        set(ORTHANC_FRAMEWORK_MD5 "9b6f6114264b17ed421b574cd6476127")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.4.2")
+        set(ORTHANC_FRAMEWORK_MD5 "d1ee84927dcf668e60eb5868d24b9394")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.5.0")
+        set(ORTHANC_FRAMEWORK_MD5 "4429d8d9dea4ff6648df80ec3c64d79e")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.5.1")
+        set(ORTHANC_FRAMEWORK_MD5 "099671538865e5da96208b37494d6718")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.5.2")
+        set(ORTHANC_FRAMEWORK_MD5 "8867050f3e9a1ce6157c1ea7a9433b1b")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.5.3")
+        set(ORTHANC_FRAMEWORK_MD5 "bf2f5ed1adb8b0fc5f10d278e68e1dfe")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.5.4")
+        set(ORTHANC_FRAMEWORK_MD5 "404baef5d4c43e7c5d9410edda8ef5a5")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.5.5")
+        set(ORTHANC_FRAMEWORK_MD5 "cfc437e0687ae4bd725fd93dc1f08bc4")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.5.6")
+        set(ORTHANC_FRAMEWORK_MD5 "3c29de1e289b5472342947168f0105c0")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.5.7")
+        set(ORTHANC_FRAMEWORK_MD5 "e1b76f01116d9b5d4ac8cc39980560e3")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.5.8")
+        set(ORTHANC_FRAMEWORK_MD5 "82323e8c49a667f658a3639ea4dbc336")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.6.0")
+        set(ORTHANC_FRAMEWORK_MD5 "eab428d6e53f61e847fa360bb17ebe25")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.6.1")
+        set(ORTHANC_FRAMEWORK_MD5 "3971f5de96ba71dc9d3f3690afeaa7c0")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.7.0")
+        set(ORTHANC_FRAMEWORK_MD5 "ce5f689e852b01d3672bd3d2f952a5ef")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.7.1")
+        set(ORTHANC_FRAMEWORK_MD5 "3c171217f930abe80246997bdbcaf7cc")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.7.2")
+        set(ORTHANC_FRAMEWORK_MD5 "328f94dcbd78c169655a13f7ad58a2c2")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.7.3")
+        set(ORTHANC_FRAMEWORK_MD5 "3f1ba9502ec7c5449971d3b56087bcde")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.7.4")
+        set(ORTHANC_FRAMEWORK_MD5 "19fcb7c21876af86546baa048a22c6c0")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.8.0")
+        set(ORTHANC_FRAMEWORK_MD5 "f8ec7554ef5d23ea4ce474b1e8214de9")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.8.1")
+        set(ORTHANC_FRAMEWORK_MD5 "db094f96399cbe8b9bbdbce34884c220")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.8.2")
+        set(ORTHANC_FRAMEWORK_MD5 "8bfa10e66c9931e74111be0bfb1f4548")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.9.0")
+        set(ORTHANC_FRAMEWORK_MD5 "cea0b02ce184671eaf1bd668beefbf28")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.9.1")
+        set(ORTHANC_FRAMEWORK_MD5 "08eebc66ef93c3b40115c38501db5fbd")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.9.2")
+        set(ORTHANC_FRAMEWORK_MD5 "3ea66c09f64aca990016683b6375734e")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.9.3")
+        set(ORTHANC_FRAMEWORK_MD5 "9b86e6f00e03278293cd15643cc0233f")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.9.4")
+        set(ORTHANC_FRAMEWORK_MD5 "6d5ca4a73ac7d42445041ca79de1624d")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.9.5")
+        set(ORTHANC_FRAMEWORK_MD5 "10fc64de1254a095e5d3ed3931f0cfbb")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.9.6")
+        set(ORTHANC_FRAMEWORK_MD5 "4b5d05683d747c29b2860ad79d11e62e")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.9.7")
+        set(ORTHANC_FRAMEWORK_MD5 "c912bbb860d640d3ae3003b5c9698205")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.10.0")
+        set(ORTHANC_FRAMEWORK_MD5 "8610c82d9153f22e929f2110f8f60279")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.10.1")
+        set(ORTHANC_FRAMEWORK_MD5 "caf667fc5ea452b3d0c2f70bfd02599c")
+
+      # Below this point are development snapshots that were used to
+      # release some plugin, before an official release of the Orthanc
+      # framework was available. Here is the command to be used to
+      # generate a proper archive:
+      #
+      #   $ hg archive /tmp/Orthanc-`hg id -i | sed 's/\+//'`.tar.gz
+      #
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "ae0e3fd609df")
+        # DICOMweb 1.1 (framework pre-1.6.0)
+        set(ORTHANC_FRAMEWORK_MD5 "7e09e9b530a2f527854f0b782d7e0645")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "82652c5fc04f")
+        # Stone Web viewer 1.0 (framework pre-1.8.1)
+        set(ORTHANC_FRAMEWORK_MD5 "d77331d68917e66a3f4f9b807bbdab7f")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "4a3ba4bf4ba7")
+        # PostgreSQL 3.3 (framework pre-1.8.2)
+        set(ORTHANC_FRAMEWORK_MD5 "2d82bddf06f9cfe82095495cb3b8abde")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "23ad1b9c7800")
+        # For "Toolbox::ReadJson()" and "Toolbox::Write{...}Json()" (pre-1.9.0)
+        set(ORTHANC_FRAMEWORK_MD5 "9af92080e57c60dd288eba46ce606c00")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "b2e08d83e21d")
+        # WSI 1.1 (framework pre-1.10.0), to remove "-std=c++11"
+        set(ORTHANC_FRAMEWORK_MD5 "2eaa073cbb4b44ffba199ad93393b2b1")
+      endif()
+    endif()
+  endif()
+
+elseif (ORTHANC_FRAMEWORK_SOURCE STREQUAL "path")
+  message("Using the Orthanc framework from a path of the filesystem. Assuming mainline version.")
+  set(ORTHANC_FRAMEWORK_MAJOR 999)
+  set(ORTHANC_FRAMEWORK_MINOR 999)
+  set(ORTHANC_FRAMEWORK_REVISION 999)
+endif()
+
+
+
+##
+## Detection of the third-party software
+##
+
+if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "hg")
+  find_program(ORTHANC_FRAMEWORK_HG hg)
+  
+  if (${ORTHANC_FRAMEWORK_HG} MATCHES "ORTHANC_FRAMEWORK_HG-NOTFOUND")
+    message(FATAL_ERROR "Please install Mercurial")
+  endif()
+endif()
+
+
+if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "archive" OR
+    ORTHANC_FRAMEWORK_SOURCE STREQUAL "web")
+  if ("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Windows")
+    find_program(ORTHANC_FRAMEWORK_7ZIP 7z 
+      PATHS 
+      "$ENV{ProgramFiles}/7-Zip"
+      "$ENV{ProgramW6432}/7-Zip"
+      )
+
+    if (${ORTHANC_FRAMEWORK_7ZIP} MATCHES "ORTHANC_FRAMEWORK_7ZIP-NOTFOUND")
+      message(FATAL_ERROR "Please install the '7-zip' software (http://www.7-zip.org/)")
+    endif()
+
+  else()
+    find_program(ORTHANC_FRAMEWORK_TAR tar)
+    if (${ORTHANC_FRAMEWORK_TAR} MATCHES "ORTHANC_FRAMEWORK_TAR-NOTFOUND")
+      message(FATAL_ERROR "Please install the 'tar' package")
+    endif()
+  endif()
+endif()
+
+
+
+##
+## Case of the Orthanc framework specified as a path on the filesystem
+##
+
+if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "path")
+  if (NOT DEFINED ORTHANC_FRAMEWORK_ROOT OR
+      ORTHANC_FRAMEWORK_ROOT STREQUAL "")
+    message(FATAL_ERROR "The variable ORTHANC_FRAMEWORK_ROOT must provide the path to the sources of Orthanc")
+  endif()
+  
+  if (NOT EXISTS ${ORTHANC_FRAMEWORK_ROOT})
+    message(FATAL_ERROR "Non-existing directory: ${ORTHANC_FRAMEWORK_ROOT}")
+  endif()
+endif()
+
+
+
+##
+## Case of the Orthanc framework cloned using Mercurial
+##
+
+if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "hg")
+  if (NOT STATIC_BUILD AND NOT ALLOW_DOWNLOADS)
+    message(FATAL_ERROR "CMake is not allowed to download from Internet. Please set the ALLOW_DOWNLOADS option to ON")
+  endif()
+
+  set(ORTHANC_ROOT ${CMAKE_BINARY_DIR}/orthanc)
+
+  if (EXISTS ${ORTHANC_ROOT})
+    message("Updating the Orthanc source repository using Mercurial")
+    execute_process(
+      COMMAND ${ORTHANC_FRAMEWORK_HG} pull
+      WORKING_DIRECTORY ${ORTHANC_ROOT}
+      RESULT_VARIABLE Failure
+      )    
+  else()
+    message("Forking the Orthanc source repository using Mercurial")
+    execute_process(
+      COMMAND ${ORTHANC_FRAMEWORK_HG} clone "https://hg.orthanc-server.com/orthanc/"
+      WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+      RESULT_VARIABLE Failure
+      )    
+  endif()
+
+  if (Failure OR NOT EXISTS ${ORTHANC_ROOT})
+    message(FATAL_ERROR "Cannot fork the Orthanc repository")
+  endif()
+
+  message("Setting branch of the Orthanc repository to: ${ORTHANC_FRAMEWORK_BRANCH}")
+
+  execute_process(
+    COMMAND ${ORTHANC_FRAMEWORK_HG} update -c ${ORTHANC_FRAMEWORK_BRANCH}
+    WORKING_DIRECTORY ${ORTHANC_ROOT}
+    RESULT_VARIABLE Failure
+    )
+
+  if (Failure)
+    message(FATAL_ERROR "Error while running Mercurial")
+  endif()
+endif()
+
+
+
+##
+## Case of the Orthanc framework provided as a source archive on the
+## filesystem
+##
+
+if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "archive")
+  if (NOT DEFINED ORTHANC_FRAMEWORK_ARCHIVE OR
+      ORTHANC_FRAMEWORK_ARCHIVE STREQUAL "")
+    message(FATAL_ERROR "The variable ORTHANC_FRAMEWORK_ARCHIVE must provide the path to the sources of Orthanc")
+  endif()
+endif()
+
+
+
+##
+## Case of the Orthanc framework downloaded from the Web
+##
+
+if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "web")
+  if (DEFINED ORTHANC_FRAMEWORK_URL)
+    string(REGEX REPLACE "^.*/" "" ORTHANC_FRAMEMORK_FILENAME "${ORTHANC_FRAMEWORK_URL}")
+  else()
+    # Default case: Download from the official Web site
+    set(ORTHANC_FRAMEMORK_FILENAME Orthanc-${ORTHANC_FRAMEWORK_VERSION}.tar.gz)
+    set(ORTHANC_FRAMEWORK_URL "http://orthanc.osimis.io/ThirdPartyDownloads/orthanc-framework/${ORTHANC_FRAMEMORK_FILENAME}")
+  endif()
+
+  set(ORTHANC_FRAMEWORK_ARCHIVE "${CMAKE_SOURCE_DIR}/ThirdPartyDownloads/${ORTHANC_FRAMEMORK_FILENAME}")
+
+  if (NOT EXISTS "${ORTHANC_FRAMEWORK_ARCHIVE}")
+    if (NOT STATIC_BUILD AND NOT ALLOW_DOWNLOADS)
+      message(FATAL_ERROR "CMake is not allowed to download from Internet. Please set the ALLOW_DOWNLOADS option to ON")
+    endif()
+
+    message("Downloading: ${ORTHANC_FRAMEWORK_URL}")
+
+    file(DOWNLOAD
+      "${ORTHANC_FRAMEWORK_URL}" "${ORTHANC_FRAMEWORK_ARCHIVE}" 
+      SHOW_PROGRESS EXPECTED_MD5 "${ORTHANC_FRAMEWORK_MD5}"
+      TIMEOUT 60
+      INACTIVITY_TIMEOUT 60
+      )
+  else()
+    message("Using local copy of: ${ORTHANC_FRAMEWORK_URL}")
+  endif()  
+endif()
+
+
+
+
+##
+## Uncompressing the Orthanc framework, if it was retrieved from a
+## source archive on the filesystem, or from the official Web site
+##
+
+if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "archive" OR
+    ORTHANC_FRAMEWORK_SOURCE STREQUAL "web")
+
+  if (NOT DEFINED ORTHANC_FRAMEWORK_ARCHIVE OR
+      NOT DEFINED ORTHANC_FRAMEWORK_VERSION OR
+      NOT DEFINED ORTHANC_FRAMEWORK_MD5)
+    message(FATAL_ERROR "Internal error")
+  endif()
+
+  if (ORTHANC_FRAMEWORK_MD5 STREQUAL "")
+    message(FATAL_ERROR "Unknown release of Orthanc: ${ORTHANC_FRAMEWORK_VERSION}")
+  endif()
+
+  file(MD5 ${ORTHANC_FRAMEWORK_ARCHIVE} ActualMD5)
+
+  if (NOT "${ActualMD5}" STREQUAL "${ORTHANC_FRAMEWORK_MD5}")
+    message(FATAL_ERROR "The MD5 hash of the Orthanc archive is invalid: ${ORTHANC_FRAMEWORK_ARCHIVE}")
+  endif()
+
+  set(ORTHANC_ROOT "${CMAKE_BINARY_DIR}/Orthanc-${ORTHANC_FRAMEWORK_VERSION}")
+
+  if (NOT IS_DIRECTORY "${ORTHANC_ROOT}")
+    if (NOT ORTHANC_FRAMEWORK_ARCHIVE MATCHES ".tar.gz$")
+      message(FATAL_ERROR "Archive should have the \".tar.gz\" extension: ${ORTHANC_FRAMEWORK_ARCHIVE}")
+    endif()
+    
+    message("Uncompressing: ${ORTHANC_FRAMEWORK_ARCHIVE}")
+
+    if ("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Windows")
+      # How to silently extract files using 7-zip
+      # http://superuser.com/questions/331148/7zip-command-line-extract-silently-quietly
+
+      execute_process(
+        COMMAND ${ORTHANC_FRAMEWORK_7ZIP} e -y ${ORTHANC_FRAMEWORK_ARCHIVE}
+        WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+        RESULT_VARIABLE Failure
+        OUTPUT_QUIET
+        )
+      
+      if (Failure)
+        message(FATAL_ERROR "Error while running the uncompression tool")
+      endif()
+
+      get_filename_component(TMP_FILENAME "${ORTHANC_FRAMEWORK_ARCHIVE}" NAME)
+      string(REGEX REPLACE ".gz$" "" TMP_FILENAME2 "${TMP_FILENAME}")
+
+      execute_process(
+        COMMAND ${ORTHANC_FRAMEWORK_7ZIP} x -y ${TMP_FILENAME2}
+        WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+        RESULT_VARIABLE Failure
+        OUTPUT_QUIET
+        )
+
+    else()
+      execute_process(
+        COMMAND sh -c "${ORTHANC_FRAMEWORK_TAR} xfz ${ORTHANC_FRAMEWORK_ARCHIVE}"
+        WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+        RESULT_VARIABLE Failure
+        )
+    endif()
+   
+    if (Failure)
+      message(FATAL_ERROR "Error while running the uncompression tool")
+    endif()
+
+    if (NOT IS_DIRECTORY "${ORTHANC_ROOT}")
+      message(FATAL_ERROR "The Orthanc framework was not uncompressed at the proper location. Check the CMake instructions.")
+    endif()
+  endif()
+endif()
+
+
+
+##
+## Determine the path to the sources of the Orthanc framework
+##
+
+if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "archive" OR
+    ORTHANC_FRAMEWORK_SOURCE STREQUAL "hg" OR
+    ORTHANC_FRAMEWORK_SOURCE STREQUAL "web")
+  if (NOT DEFINED ORTHANC_ROOT OR
+      NOT DEFINED ORTHANC_FRAMEWORK_MAJOR OR
+      NOT DEFINED ORTHANC_FRAMEWORK_MINOR OR
+      NOT DEFINED ORTHANC_FRAMEWORK_REVISION)
+    message(FATAL_ERROR "Internal error in the DownloadOrthancFramework.cmake file")
+  endif()
+
+  unset(ORTHANC_FRAMEWORK_ROOT CACHE)
+
+  if ("${ORTHANC_FRAMEWORK_MAJOR}.${ORTHANC_FRAMEWORK_MINOR}.${ORTHANC_FRAMEWORK_REVISION}" VERSION_LESS "1.7.2")
+    set(ORTHANC_FRAMEWORK_ROOT "${ORTHANC_ROOT}/Core" CACHE
+      STRING "Path to the Orthanc framework source directory")
+    set(ENABLE_PLUGINS_VERSION_SCRIPT OFF)
+  else()
+    set(ORTHANC_FRAMEWORK_ROOT "${ORTHANC_ROOT}/OrthancFramework/Sources" CACHE
+      STRING "Path to the Orthanc framework source directory")
+  endif()
+
+  unset(ORTHANC_ROOT)
+endif()
+
+if (NOT ORTHANC_FRAMEWORK_SOURCE STREQUAL "system")
+  if (NOT EXISTS ${ORTHANC_FRAMEWORK_ROOT}/OrthancException.h OR
+      NOT EXISTS ${ORTHANC_FRAMEWORK_ROOT}/../Resources/CMake/OrthancFrameworkParameters.cmake)
+    message(FATAL_ERROR "Directory not containing the source code of the Orthanc framework: ${ORTHANC_FRAMEWORK_ROOT}")
+  endif()
+endif()
+
+
+
+##
+## Case of the Orthanc framework installed as a shared library in a
+## GNU/Linux distribution (typically Debian). New in Orthanc 1.7.2.
+##
+
+if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "system")
+  set(ORTHANC_FRAMEWORK_LIBDIR "" CACHE PATH "")
+  set(ORTHANC_FRAMEWORK_USE_SHARED ON CACHE BOOL "Whether to use the shared library or the static library")
+  set(ORTHANC_FRAMEWORK_ADDITIONAL_LIBRARIES "" CACHE STRING "Additional libraries to link against, separated by whitespaces, typically needed if using the static library (a common minimal value is \"boost_filesystem boost_iostreams boost_locale boost_regex boost_thread jsoncpp pugixml uuid\")")
+
+  if (CMAKE_SYSTEM_NAME STREQUAL "Windows" AND
+      CMAKE_COMPILER_IS_GNUCXX) # MinGW
+    set(DYNAMIC_MINGW_STDLIB ON)   # Disable static linking against libc (to throw exceptions)
+    set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -static-libstdc++")
+    set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static-libstdc++")
+  endif()
+  
+  include(CheckIncludeFile)
+  include(CheckIncludeFileCXX)
+  include(FindPythonInterp)
+  include(${CMAKE_CURRENT_LIST_DIR}/Compiler.cmake)
+  include(${CMAKE_CURRENT_LIST_DIR}/DownloadPackage.cmake)
+  include(${CMAKE_CURRENT_LIST_DIR}/AutoGeneratedCode.cmake)
+  set(EMBED_RESOURCES_PYTHON ${CMAKE_CURRENT_LIST_DIR}/EmbedResources.py)
+
+  if (ORTHANC_FRAMEWORK_USE_SHARED)
+    list(GET CMAKE_FIND_LIBRARY_PREFIXES 0 Prefix)
+    list(GET CMAKE_FIND_LIBRARY_SUFFIXES 0 Suffix)
+  else()
+    list(GET CMAKE_FIND_LIBRARY_PREFIXES 0 Prefix)
+    list(GET CMAKE_FIND_LIBRARY_SUFFIXES 1 Suffix)
+  endif()
+
+  # The "OrthancFramework" library must be the first one to be included
+  if ("${ORTHANC_FRAMEWORK_LIBDIR}" STREQUAL "")
+    set(ORTHANC_FRAMEWORK_LIBRARIES ${Prefix}OrthancFramework${Suffix})
+  else ()
+    set(ORTHANC_FRAMEWORK_LIBRARIES ${ORTHANC_FRAMEWORK_LIBDIR}/${Prefix}OrthancFramework${Suffix})
+  endif()
+
+  if (NOT ORTHANC_FRAMEWORK_ADDITIONAL_LIBRARIES STREQUAL "")
+    # https://stackoverflow.com/a/5272993/881731
+    string(REPLACE " " ";" tmp ${ORTHANC_FRAMEWORK_ADDITIONAL_LIBRARIES})
+    list(APPEND ORTHANC_FRAMEWORK_LIBRARIES ${tmp})
+  endif()
+
+  # Look for the version of the mandatory dependency JsonCpp (cf. JsonCppConfiguration.cmake)
+  if (CMAKE_CROSSCOMPILING)
+    set(JSONCPP_INCLUDE_DIR ${ORTHANC_FRAMEWORK_ROOT}/..)
+  else()
+    find_path(JSONCPP_INCLUDE_DIR json/reader.h
+      ${ORTHANC_FRAMEWORK_ROOT}/..
+      /usr/include/jsoncpp
+      /usr/local/include/jsoncpp
+      )
+  endif()
+
+  message("JsonCpp include dir: ${JSONCPP_INCLUDE_DIR}")
+  include_directories(${JSONCPP_INCLUDE_DIR})
+
+  CHECK_INCLUDE_FILE_CXX(${JSONCPP_INCLUDE_DIR}/json/reader.h HAVE_JSONCPP_H)
+  if (NOT HAVE_JSONCPP_H)
+    message(FATAL_ERROR "Please install the libjsoncpp-dev package")
+  endif()
+
+  # Look for Orthanc framework shared library
+  include(CheckCXXSymbolExists)
+
+  if ("${CMAKE_SYSTEM_NAME}" STREQUAL "Windows")
+    set(ORTHANC_FRAMEWORK_INCLUDE_DIR ${ORTHANC_FRAMEWORK_ROOT})
+  else()
+    find_path(ORTHANC_FRAMEWORK_INCLUDE_DIR OrthancFramework.h
+      /usr/include/orthanc-framework
+      /usr/local/include/orthanc-framework
+      ${ORTHANC_FRAMEWORK_ROOT}
+      )
+  endif()
+
+  if (${ORTHANC_FRAMEWORK_INCLUDE_DIR} STREQUAL "ORTHANC_FRAMEWORK_INCLUDE_DIR-NOTFOUND")
+    message(FATAL_ERROR "Cannot locate the OrthancFramework.h header")
+  endif()
+  
+  message("Orthanc framework include dir: ${ORTHANC_FRAMEWORK_INCLUDE_DIR}")
+  include_directories(${ORTHANC_FRAMEWORK_INCLUDE_DIR})
+
+  if (ORTHANC_FRAMEWORK_USE_SHARED)
+    set(CMAKE_REQUIRED_INCLUDES "${ORTHANC_FRAMEWORK_INCLUDE_DIR}")
+    set(CMAKE_REQUIRED_LIBRARIES "${ORTHANC_FRAMEWORK_LIBRARIES}")
+    
+    check_cxx_symbol_exists("Orthanc::InitializeFramework" "OrthancFramework.h" HAVE_ORTHANC_FRAMEWORK)
+    if (NOT HAVE_ORTHANC_FRAMEWORK)
+      message(FATAL_ERROR "Cannot find the Orthanc framework")
+    endif()
+    
+    unset(CMAKE_REQUIRED_INCLUDES)
+    unset(CMAKE_REQUIRED_LIBRARIES)
+  endif()
+endif()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/CMake/DownloadPackage.cmake	Fri Apr 22 17:58:59 2022 +0200
@@ -0,0 +1,279 @@
+# Orthanc - A Lightweight, RESTful DICOM Store
+# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+# Department, University Hospital of Liege, Belgium
+# Copyright (C) 2017-2022 Osimis S.A., Belgium
+# Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+#
+# This program is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public License
+# as published by the Free Software Foundation, either version 3 of
+# the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this program. If not, see
+# <http://www.gnu.org/licenses/>.
+
+
+macro(GetUrlFilename TargetVariable Url)
+  string(REGEX REPLACE "^.*/" "" ${TargetVariable} "${Url}")
+endmacro()
+
+
+macro(GetUrlExtension TargetVariable Url)
+  #string(REGEX REPLACE "^.*/[^.]*\\." "" TMP "${Url}")
+  string(REGEX REPLACE "^.*\\." "" TMP "${Url}")
+  string(TOLOWER "${TMP}" "${TargetVariable}")
+endmacro()
+
+
+
+##
+## Setup the patch command-line tool
+##
+
+if (NOT ORTHANC_DISABLE_PATCH)
+  if ("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Windows")
+    set(PATCH_EXECUTABLE ${CMAKE_CURRENT_LIST_DIR}/../ThirdParty/patch/patch.exe)
+    if (NOT EXISTS ${PATCH_EXECUTABLE})
+      message(FATAL_ERROR "Unable to find the patch.exe tool that is shipped with Orthanc")
+    endif()
+
+  else ()
+    find_program(PATCH_EXECUTABLE patch)
+    if (${PATCH_EXECUTABLE} MATCHES "PATCH_EXECUTABLE-NOTFOUND")
+      message(FATAL_ERROR "Please install the 'patch' standard command-line tool")
+    endif()
+  endif()
+endif()
+
+
+
+##
+## Check the existence of the required decompression tools
+##
+
+if ("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Windows")
+  find_program(ZIP_EXECUTABLE 7z 
+    PATHS 
+    "$ENV{ProgramFiles}/7-Zip"
+    "$ENV{ProgramW6432}/7-Zip"
+    )
+
+  if (${ZIP_EXECUTABLE} MATCHES "ZIP_EXECUTABLE-NOTFOUND")
+    message(FATAL_ERROR "Please install the '7-zip' software (http://www.7-zip.org/)")
+  endif()
+
+else()
+  find_program(UNZIP_EXECUTABLE unzip)
+  if (${UNZIP_EXECUTABLE} MATCHES "UNZIP_EXECUTABLE-NOTFOUND")
+    message(FATAL_ERROR "Please install the 'unzip' package")
+  endif()
+
+  find_program(TAR_EXECUTABLE tar)
+  if (${TAR_EXECUTABLE} MATCHES "TAR_EXECUTABLE-NOTFOUND")
+    message(FATAL_ERROR "Please install the 'tar' package")
+  endif()
+
+  find_program(GUNZIP_EXECUTABLE gunzip)
+  if (${GUNZIP_EXECUTABLE} MATCHES "GUNZIP_EXECUTABLE-NOTFOUND")
+    message(FATAL_ERROR "Please install the 'gzip' package")
+  endif()
+endif()
+
+
+macro(DownloadFile MD5 Url)
+  GetUrlFilename(TMP_FILENAME "${Url}")
+
+  set(TMP_PATH "${CMAKE_SOURCE_DIR}/ThirdPartyDownloads/${TMP_FILENAME}")
+  if (NOT EXISTS "${TMP_PATH}")
+    message("Downloading ${Url}")
+
+    # This fixes issue 6: "I think cmake shouldn't download the
+    # packages which are not in the system, it should stop and let
+    # user know."
+    # https://code.google.com/p/orthanc/issues/detail?id=6
+    if (NOT STATIC_BUILD AND NOT ALLOW_DOWNLOADS)
+      message(FATAL_ERROR "CMake is not allowed to download from Internet. Please set the ALLOW_DOWNLOADS option to ON")
+    endif()
+
+    if ("${MD5}" STREQUAL "no-check")
+      message(WARNING "Not checking the MD5 of: ${Url}")
+      file(DOWNLOAD "${Url}" "${TMP_PATH}"
+        SHOW_PROGRESS TIMEOUT 300 INACTIVITY_TIMEOUT 60
+        STATUS Failure)
+    else()
+      file(DOWNLOAD "${Url}" "${TMP_PATH}"
+        SHOW_PROGRESS TIMEOUT 300 INACTIVITY_TIMEOUT 60
+        EXPECTED_MD5 "${MD5}" STATUS Failure)
+    endif()
+
+    list(GET Failure 0 Status)
+    if (NOT Status EQUAL 0)
+      message(FATAL_ERROR "Cannot download file: ${Url}")
+    endif()
+    
+  else()
+    message("Using local copy of ${Url}")
+
+    if ("${MD5}" STREQUAL "no-check")
+      message(WARNING "Not checking the MD5 of: ${Url}")
+    else()
+      file(MD5 ${TMP_PATH} ActualMD5)
+      if (NOT "${ActualMD5}" STREQUAL "${MD5}")
+        message(FATAL_ERROR "The MD5 hash of a previously download file is invalid: ${TMP_PATH}")
+      endif()
+    endif()
+  endif()
+endmacro()
+
+
+macro(DownloadPackage MD5 Url TargetDirectory)
+  if (NOT IS_DIRECTORY "${TargetDirectory}")
+    DownloadFile("${MD5}" "${Url}")
+    
+    GetUrlExtension(TMP_EXTENSION "${Url}")
+    #message(${TMP_EXTENSION})
+    message("Uncompressing ${TMP_FILENAME}")
+
+    if ("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Windows")
+      # How to silently extract files using 7-zip
+      # http://superuser.com/questions/331148/7zip-command-line-extract-silently-quietly
+
+      if (("${TMP_EXTENSION}" STREQUAL "gz") OR 
+          ("${TMP_EXTENSION}" STREQUAL "tgz") OR
+          ("${TMP_EXTENSION}" STREQUAL "xz"))
+        execute_process(
+          COMMAND ${ZIP_EXECUTABLE} e -y ${TMP_PATH}
+          WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+          RESULT_VARIABLE Failure
+          OUTPUT_QUIET
+          )
+
+        if (Failure)
+          message(FATAL_ERROR "Error while running the uncompression tool")
+        endif()
+
+        if ("${TMP_EXTENSION}" STREQUAL "tgz")
+          string(REGEX REPLACE ".tgz$" ".tar" TMP_FILENAME2 "${TMP_FILENAME}")
+        elseif ("${TMP_EXTENSION}" STREQUAL "gz")
+          string(REGEX REPLACE ".gz$" "" TMP_FILENAME2 "${TMP_FILENAME}")
+        elseif ("${TMP_EXTENSION}" STREQUAL "xz")
+          string(REGEX REPLACE ".xz" "" TMP_FILENAME2 "${TMP_FILENAME}")
+        endif()
+
+        execute_process(
+          COMMAND ${ZIP_EXECUTABLE} x -y ${TMP_FILENAME2}
+          WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+          RESULT_VARIABLE Failure
+          OUTPUT_QUIET
+          )
+      elseif ("${TMP_EXTENSION}" STREQUAL "zip")
+        execute_process(
+          COMMAND ${ZIP_EXECUTABLE} x -y ${TMP_PATH}
+          WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+          RESULT_VARIABLE Failure
+          OUTPUT_QUIET
+          )
+      else()
+        message(FATAL_ERROR "Unsupported package extension: ${TMP_EXTENSION}")
+      endif()
+
+    else()
+      if ("${TMP_EXTENSION}" STREQUAL "zip")
+        execute_process(
+          COMMAND sh -c "${UNZIP_EXECUTABLE} -q ${TMP_PATH}"
+          WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+          RESULT_VARIABLE Failure
+        )
+      elseif (("${TMP_EXTENSION}" STREQUAL "gz") OR ("${TMP_EXTENSION}" STREQUAL "tgz"))
+        #message("tar xvfz ${TMP_PATH}")
+        execute_process(
+          COMMAND sh -c "${TAR_EXECUTABLE} xfz ${TMP_PATH}"
+          WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+          RESULT_VARIABLE Failure
+          )
+      elseif ("${TMP_EXTENSION}" STREQUAL "bz2")
+        execute_process(
+          COMMAND sh -c "${TAR_EXECUTABLE} xfj ${TMP_PATH}"
+          WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+          RESULT_VARIABLE Failure
+          )
+      elseif ("${TMP_EXTENSION}" STREQUAL "xz")
+        execute_process(
+          COMMAND sh -c "${TAR_EXECUTABLE} xf ${TMP_PATH}"
+          WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+          RESULT_VARIABLE Failure
+          )
+      else()
+        message(FATAL_ERROR "Unsupported package extension: ${TMP_EXTENSION}")
+      endif()
+    endif()
+   
+    if (Failure)
+      message(FATAL_ERROR "Error while running the uncompression tool")
+    endif()
+
+    if (NOT IS_DIRECTORY "${TargetDirectory}")
+      message(FATAL_ERROR "The package was not uncompressed at the proper location. Check the CMake instructions.")
+    endif()
+  endif()
+endmacro()
+
+
+
+macro(DownloadCompressedFile MD5 Url TargetFile)
+  if (NOT EXISTS "${TargetFile}")
+    DownloadFile("${MD5}" "${Url}")
+    
+    GetUrlExtension(TMP_EXTENSION "${Url}")
+    #message(${TMP_EXTENSION})
+    message("Uncompressing ${TMP_FILENAME}")
+
+    if ("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Windows")
+      # How to silently extract files using 7-zip
+      # http://superuser.com/questions/331148/7zip-command-line-extract-silently-quietly
+
+      if ("${TMP_EXTENSION}" STREQUAL "gz")
+        execute_process(
+          # "-so" writes uncompressed file to stdout
+          COMMAND ${ZIP_EXECUTABLE} e -so -y ${TMP_PATH}
+          OUTPUT_FILE "${TargetFile}"
+          WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+          RESULT_VARIABLE Failure
+          OUTPUT_QUIET
+          )
+
+        if (Failure)
+          message(FATAL_ERROR "Error while running the uncompression tool")
+        endif()
+
+      else()
+        message(FATAL_ERROR "Unsupported file extension: ${TMP_EXTENSION}")
+      endif()
+
+    else()
+      if ("${TMP_EXTENSION}" STREQUAL "gz")
+        execute_process(
+          COMMAND sh -c "${GUNZIP_EXECUTABLE} -c ${TMP_PATH}"
+          OUTPUT_FILE "${TargetFile}"
+          RESULT_VARIABLE Failure
+          )
+      else()
+        message(FATAL_ERROR "Unsupported file extension: ${TMP_EXTENSION}")
+      endif()
+    endif()
+   
+    if (Failure)
+      message(FATAL_ERROR "Error while running the uncompression tool")
+    endif()
+
+    if (NOT EXISTS "${TargetFile}")
+      message(FATAL_ERROR "The file was not uncompressed at the proper location. Check the CMake instructions.")
+    endif()
+  endif()
+endmacro()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/CMake/EmbedResources.py	Fri Apr 22 17:58:59 2022 +0200
@@ -0,0 +1,445 @@
+#!/usr/bin/python
+
+# Orthanc - A Lightweight, RESTful DICOM Store
+# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+# Department, University Hospital of Liege, Belgium
+# Copyright (C) 2017-2022 Osimis S.A., Belgium
+# Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+#
+# This program is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public License
+# as published by the Free Software Foundation, either version 3 of
+# the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this program. If not, see
+# <http://www.gnu.org/licenses/>.
+
+
+import sys
+import os
+import os.path
+import pprint
+import re
+
+UPCASE_CHECK = True
+USE_SYSTEM_EXCEPTION = False
+EXCEPTION_CLASS = 'OrthancException'
+OUT_OF_RANGE_EXCEPTION = '::Orthanc::OrthancException(::Orthanc::ErrorCode_ParameterOutOfRange)'
+INEXISTENT_PATH_EXCEPTION = '::Orthanc::OrthancException(::Orthanc::ErrorCode_InexistentItem)'
+NAMESPACE = 'Orthanc.EmbeddedResources'
+FRAMEWORK_PATH = None
+
+ARGS = []
+for i in range(len(sys.argv)):
+    if not sys.argv[i].startswith('--'):
+        ARGS.append(sys.argv[i])
+    elif sys.argv[i].lower() == '--no-upcase-check':
+        UPCASE_CHECK = False
+    elif sys.argv[i].lower() == '--system-exception':
+        USE_SYSTEM_EXCEPTION = True
+        EXCEPTION_CLASS = '::std::runtime_error'
+        OUT_OF_RANGE_EXCEPTION = '%s("Parameter out of range")' % EXCEPTION_CLASS
+        INEXISTENT_PATH_EXCEPTION = '%s("Unknown path in a directory resource")' % EXCEPTION_CLASS
+    elif sys.argv[i].startswith('--namespace='):
+        NAMESPACE = sys.argv[i][sys.argv[i].find('=') + 1 : ]
+    elif sys.argv[i].startswith('--framework-path='):
+        FRAMEWORK_PATH = sys.argv[i][sys.argv[i].find('=') + 1 : ]
+
+if len(ARGS) < 2 or len(ARGS) % 2 != 0:
+    print ('Usage:')
+    print ('python %s [--no-upcase-check] [--system-exception] [--namespace=<Namespace>] <TargetBaseFilename> [ <Name> <Source> ]*' % sys.argv[0])
+    exit(-1)
+
+TARGET_BASE_FILENAME = ARGS[1]
+SOURCES = ARGS[2:]
+
+try:
+    # Make sure the destination directory exists
+    os.makedirs(os.path.normpath(os.path.join(TARGET_BASE_FILENAME, '..')))
+except:
+    pass
+
+
+#####################################################################
+## Read each resource file
+#####################################################################
+
+def CheckNoUpcase(s):
+    global UPCASE_CHECK
+    if (UPCASE_CHECK and
+        re.search('[A-Z]', s) != None):
+        raise Exception("Path in a directory with an upcase letter: %s" % s)
+
+resources = {}
+
+counter = 0
+i = 0
+while i < len(SOURCES):
+    resourceName = SOURCES[i].upper()
+    pathName = SOURCES[i + 1]
+
+    if not os.path.exists(pathName):
+        raise Exception("Non existing path: %s" % pathName)
+
+    if resourceName in resources:
+        raise Exception("Twice the same resource: " + resourceName)
+    
+    if os.path.isdir(pathName):
+        # The resource is a directory: Recursively explore its files
+        content = {}
+        for root, dirs, files in os.walk(pathName):
+            dirs.sort()
+            files.sort()
+            base = os.path.relpath(root, pathName)
+
+            # Fix issue #24 (Build fails on OSX when directory has .DS_Store files):
+            # Ignore folders whose name starts with a dot (".")
+            if base.find('/.') != -1:
+                print('Ignoring folder: %s' % root)
+                continue
+
+            for f in files:
+                if f.find('~') == -1:  # Ignore Emacs backup files
+                    if base == '.':
+                        r = f
+                    else:
+                        r = os.path.join(base, f)
+
+                    CheckNoUpcase(r)
+                    r = '/' + r.replace('\\', '/')
+                    if r in content:
+                        raise Exception("Twice the same filename (check case): " + r)
+
+                    content[r] = {
+                        'Filename' : os.path.join(root, f),
+                        'Index' : counter
+                        }
+                    counter += 1
+
+        resources[resourceName] = {
+            'Type' : 'Directory',
+            'Files' : content
+            }
+
+    elif os.path.isfile(pathName):
+        resources[resourceName] = {
+            'Type' : 'File',
+            'Index' : counter,
+            'Filename' : pathName
+            }
+        counter += 1
+
+    else:
+        raise Exception("Not a regular file, nor a directory: " + pathName)
+
+    i += 2
+
+#pprint.pprint(resources)
+
+
+#####################################################################
+## Write .h header
+#####################################################################
+
+header = open(TARGET_BASE_FILENAME + '.h', 'w')
+
+header.write("""
+#pragma once
+
+#include <string>
+#include <list>
+
+#if defined(_MSC_VER)
+#  pragma warning(disable: 4065)  // "Switch statement contains 'default' but no 'case' labels"
+#endif
+
+""")
+
+
+for ns in NAMESPACE.split('.'):
+    header.write('namespace %s {\n' % ns)
+    
+
+header.write("""
+    enum FileResourceId
+    {
+""")
+
+isFirst = True
+for name in resources:
+    if resources[name]['Type'] == 'File':
+        if isFirst:
+            isFirst = False
+        else:    
+            header.write(',\n')
+        header.write('      %s' % name)
+
+header.write("""
+    };
+
+    enum DirectoryResourceId
+    {
+""")
+
+isFirst = True
+for name in resources:
+    if resources[name]['Type'] == 'Directory':
+        if isFirst:
+            isFirst = False
+        else:    
+            header.write(',\n')
+        header.write('      %s' % name)
+
+header.write("""
+    };
+
+    const void* GetFileResourceBuffer(FileResourceId id);
+    size_t GetFileResourceSize(FileResourceId id);
+    void GetFileResource(std::string& result, FileResourceId id);
+
+    const void* GetDirectoryResourceBuffer(DirectoryResourceId id, const char* path);
+    size_t GetDirectoryResourceSize(DirectoryResourceId id, const char* path);
+    void GetDirectoryResource(std::string& result, DirectoryResourceId id, const char* path);
+
+    void ListResources(std::list<std::string>& result, DirectoryResourceId id);
+
+""")
+
+
+for ns in NAMESPACE.split('.'):
+    header.write('}\n')
+
+header.close()
+
+
+
+#####################################################################
+## Write the resource content in the .cpp source
+#####################################################################
+
+PYTHON_MAJOR_VERSION = sys.version_info[0]
+
+def WriteResource(cpp, item):
+    cpp.write('    static const uint8_t resource%dBuffer[] = {' % item['Index'])
+
+    f = open(item['Filename'], "rb")
+    content = f.read()
+    f.close()
+
+    # http://stackoverflow.com/a/1035360
+    pos = 0
+    buffer = []  # instead of appending a few bytes at a time to the cpp file, 
+                 # we first append each chunk to a list, join it and write it 
+                 # to the file.  We've measured that it was 2-3 times faster in python3.
+                 # Note that speed is important since if generation is too slow,
+                 # cmake might try to compile the EmbeddedResources.cpp file while it is
+                 # still being generated !
+    for b in content:
+        if PYTHON_MAJOR_VERSION == 2:
+            c = ord(b[0])
+        else:
+            c = b
+
+        if pos > 0:
+            buffer.append(",")
+
+        if (pos % 16) == 0:
+            buffer.append("\n")
+
+        if c < 0:
+            raise Exception("Internal error")
+
+        buffer.append("0x%02x" % c)
+        pos += 1
+
+    cpp.write("".join(buffer))
+    # Zero-size array are disallowed, so we put one single void character in it.
+    if pos == 0:
+        cpp.write('  0')
+
+    cpp.write('  };\n')
+    cpp.write('    static const size_t resource%dSize = %d;\n' % (item['Index'], pos))
+
+
+cpp = open(TARGET_BASE_FILENAME + '.cpp', 'w')
+
+cpp.write('#include "%s.h"\n' % os.path.basename(TARGET_BASE_FILENAME))
+
+if USE_SYSTEM_EXCEPTION:
+    cpp.write('#include <stdexcept>')
+elif FRAMEWORK_PATH != None:
+    cpp.write('#include "%s/OrthancException.h"' % FRAMEWORK_PATH)
+else:
+    cpp.write('#include <OrthancException.h>')
+
+cpp.write("""
+#include <stdint.h>
+#include <string.h>
+
+""")
+
+for ns in NAMESPACE.split('.'):
+    cpp.write('namespace %s {\n' % ns)
+
+
+for name in resources:
+    if resources[name]['Type'] == 'File':
+        WriteResource(cpp, resources[name])
+    else:
+        for f in resources[name]['Files']:
+            WriteResource(cpp, resources[name]['Files'][f])
+
+
+
+#####################################################################
+## Write the accessors to the file resources in .cpp
+#####################################################################
+
+cpp.write("""
+    const void* GetFileResourceBuffer(FileResourceId id)
+    {
+      switch (id)
+      {
+""")
+for name in resources:
+    if resources[name]['Type'] == 'File':
+        cpp.write('      case %s:\n' % name)
+        cpp.write('        return resource%dBuffer;\n' % resources[name]['Index'])
+
+cpp.write("""
+      default:
+        throw %s;
+      }
+    }
+
+    size_t GetFileResourceSize(FileResourceId id)
+    {
+      switch (id)
+      {
+""" % OUT_OF_RANGE_EXCEPTION)
+
+for name in resources:
+    if resources[name]['Type'] == 'File':
+        cpp.write('      case %s:\n' % name)
+        cpp.write('        return resource%dSize;\n' % resources[name]['Index'])
+
+cpp.write("""
+      default:
+        throw %s;
+      }
+    }
+""" % OUT_OF_RANGE_EXCEPTION)
+
+
+
+#####################################################################
+## Write the accessors to the directory resources in .cpp
+#####################################################################
+
+cpp.write("""
+    const void* GetDirectoryResourceBuffer(DirectoryResourceId id, const char* path)
+    {
+      switch (id)
+      {
+""")
+
+for name in resources:
+    if resources[name]['Type'] == 'Directory':
+        cpp.write('      case %s:\n' % name)
+        isFirst = True
+        for path in resources[name]['Files']:
+            cpp.write('        if (!strcmp(path, "%s"))\n' % path)
+            cpp.write('          return resource%dBuffer;\n' % resources[name]['Files'][path]['Index'])
+        cpp.write('        throw %s;\n\n' % INEXISTENT_PATH_EXCEPTION)
+
+cpp.write("""      default:
+        throw %s;
+      }
+    }
+
+    size_t GetDirectoryResourceSize(DirectoryResourceId id, const char* path)
+    {
+      switch (id)
+      {
+""" % OUT_OF_RANGE_EXCEPTION)
+
+for name in resources:
+    if resources[name]['Type'] == 'Directory':
+        cpp.write('      case %s:\n' % name)
+        isFirst = True
+        for path in resources[name]['Files']:
+            cpp.write('        if (!strcmp(path, "%s"))\n' % path)
+            cpp.write('          return resource%dSize;\n' % resources[name]['Files'][path]['Index'])
+        cpp.write('        throw %s;\n\n' % INEXISTENT_PATH_EXCEPTION)
+
+cpp.write("""      default:
+        throw %s;
+      }
+    }
+""" % OUT_OF_RANGE_EXCEPTION)
+
+
+
+
+#####################################################################
+## List the resources in a directory
+#####################################################################
+
+cpp.write("""
+    void ListResources(std::list<std::string>& result, DirectoryResourceId id)
+    {
+      result.clear();
+
+      switch (id)
+      {
+""")
+
+for name in resources:
+    if resources[name]['Type'] == 'Directory':
+        cpp.write('      case %s:\n' % name)
+        for path in sorted(resources[name]['Files']):
+            cpp.write('        result.push_back("%s");\n' % path)
+        cpp.write('        break;\n\n')
+
+cpp.write("""      default:
+        throw %s;
+      }
+    }
+""" % OUT_OF_RANGE_EXCEPTION)
+
+
+
+
+#####################################################################
+## Write the convenience wrappers in .cpp
+#####################################################################
+
+cpp.write("""
+    void GetFileResource(std::string& result, FileResourceId id)
+    {
+      size_t size = GetFileResourceSize(id);
+      result.resize(size);
+      if (size > 0)
+        memcpy(&result[0], GetFileResourceBuffer(id), size);
+    }
+
+    void GetDirectoryResource(std::string& result, DirectoryResourceId id, const char* path)
+    {
+      size_t size = GetDirectoryResourceSize(id, path);
+      result.resize(size);
+      if (size > 0)
+        memcpy(&result[0], GetDirectoryResourceBuffer(id, path), size);
+    }
+""")
+
+
+for ns in NAMESPACE.split('.'):
+    cpp.write('}\n')
+
+cpp.close()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/CMake/GoogleTestConfiguration.cmake	Fri Apr 22 17:58:59 2022 +0200
@@ -0,0 +1,90 @@
+# Orthanc - A Lightweight, RESTful DICOM Store
+# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+# Department, University Hospital of Liege, Belgium
+# Copyright (C) 2017-2022 Osimis S.A., Belgium
+# Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+#
+# This program is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public License
+# as published by the Free Software Foundation, either version 3 of
+# the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this program. If not, see
+# <http://www.gnu.org/licenses/>.
+
+
+if (USE_GOOGLE_TEST_DEBIAN_PACKAGE)
+  find_path(GOOGLE_TEST_DEBIAN_SOURCES_DIR
+    NAMES src/gtest-all.cc
+    PATHS
+    ${CROSSTOOL_NG_IMAGE}/usr/src/gtest
+    ${CROSSTOOL_NG_IMAGE}/usr/src/googletest/googletest
+    PATH_SUFFIXES src
+    )
+
+  find_path(GOOGLE_TEST_DEBIAN_INCLUDE_DIR
+    NAMES gtest.h
+    PATHS
+    ${CROSSTOOL_NG_IMAGE}/usr/include/gtest
+    )
+
+  message("Path to the Debian Google Test sources: ${GOOGLE_TEST_DEBIAN_SOURCES_DIR}")
+  message("Path to the Debian Google Test includes: ${GOOGLE_TEST_DEBIAN_INCLUDE_DIR}")
+
+  set(GOOGLE_TEST_SOURCES
+    ${GOOGLE_TEST_DEBIAN_SOURCES_DIR}/src/gtest-all.cc
+    )
+
+  include_directories(${GOOGLE_TEST_DEBIAN_SOURCES_DIR})
+
+  if (NOT EXISTS ${GOOGLE_TEST_SOURCES} OR
+      NOT EXISTS ${GOOGLE_TEST_DEBIAN_INCLUDE_DIR}/gtest.h)
+    message(FATAL_ERROR "Please install the libgtest-dev package")
+  endif()
+
+elseif (STATIC_BUILD OR NOT USE_SYSTEM_GOOGLE_TEST)
+  set(GOOGLE_TEST_SOURCES_DIR ${CMAKE_BINARY_DIR}/googletest-release-1.8.1)
+  set(GOOGLE_TEST_URL "http://orthanc.osimis.io/ThirdPartyDownloads/gtest-1.8.1.tar.gz")
+  set(GOOGLE_TEST_MD5 "2e6fbeb6a91310a16efe181886c59596")
+
+  DownloadPackage(${GOOGLE_TEST_MD5} ${GOOGLE_TEST_URL} "${GOOGLE_TEST_SOURCES_DIR}")
+
+  include_directories(
+    ${GOOGLE_TEST_SOURCES_DIR}/googletest
+    ${GOOGLE_TEST_SOURCES_DIR}/googletest/include
+    ${GOOGLE_TEST_SOURCES_DIR}
+    )
+
+  set(GOOGLE_TEST_SOURCES
+    ${GOOGLE_TEST_SOURCES_DIR}/googletest/src/gtest-all.cc
+    )
+
+  # https://code.google.com/p/googletest/issues/detail?id=412
+  if (MSVC) # VS2012 does not support tuples correctly yet
+    add_definitions(/D _VARIADIC_MAX=10)
+  endif()
+  
+  if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase")
+    add_definitions(-DGTEST_HAS_CLONE=0)
+  endif()
+  
+  source_group(ThirdParty\\GoogleTest REGULAR_EXPRESSION ${GOOGLE_TEST_SOURCES_DIR}/.*)
+
+else()
+  include(FindGTest)
+  if (NOT GTEST_FOUND)
+    message(FATAL_ERROR "Unable to find GoogleTest")
+  endif()
+
+  include_directories(${GTEST_INCLUDE_DIRS})
+
+  # The variable GTEST_LIBRARIES contains the shared library of
+  # Google Test, create an alias for more uniformity
+  set(GOOGLE_TEST_LIBRARIES ${GTEST_LIBRARIES})
+endif()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/CMake/WindowsResources.py	Fri Apr 22 17:58:59 2022 +0200
@@ -0,0 +1,80 @@
+#!/usr/bin/python
+
+# Orthanc - A Lightweight, RESTful DICOM Store
+# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+# Department, University Hospital of Liege, Belgium
+# Copyright (C) 2017-2022 Osimis S.A., Belgium
+# Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+#
+# This program is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public License
+# as published by the Free Software Foundation, either version 3 of
+# the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this program. If not, see
+# <http://www.gnu.org/licenses/>.
+
+
+import os
+import sys
+import datetime
+
+if len(sys.argv) != 5:
+    sys.stderr.write('Usage: %s <Version> <ProductName> <Filename> <Description>\n\n' % sys.argv[0])
+    sys.stderr.write('Example: %s 0.9.1 Orthanc Orthanc.exe "Lightweight, RESTful DICOM server for medical imaging"\n' % sys.argv[0])
+    sys.exit(-1)
+
+SOURCE = os.path.join(os.path.dirname(__file__), 'WindowsResources.rc')
+
+VERSION = sys.argv[1]
+PRODUCT = sys.argv[2]
+FILENAME = sys.argv[3]
+DESCRIPTION = sys.argv[4]
+
+if VERSION == 'mainline':
+    VERSION = '999.999.999'
+    RELEASE = 'This is a mainline build, not an official release'
+else:
+    RELEASE = 'Release %s' % VERSION
+
+v = VERSION.split('.')
+if len(v) != 2 and len(v) != 3:
+    sys.stderr.write('Bad version number: %s\n' % VERSION)
+    sys.exit(-1)
+
+if len(v) == 2:
+    v.append('0')
+
+extension = os.path.splitext(FILENAME)[1]
+if extension.lower() == '.dll':
+    BLOCK = '040904E4'
+    TYPE = 'VFT_DLL'
+elif extension.lower() == '.exe':
+    #BLOCK = '040904B0'   # LANG_ENGLISH/SUBLANG_ENGLISH_US,
+    BLOCK = '040904E4'   # Lang=US English, CharSet=Windows Multilingual
+    TYPE = 'VFT_APP'
+else:
+    sys.stderr.write('Unsupported extension (.EXE or .DLL only): %s\n' % extension)
+    sys.exit(-1)
+
+
+with open(SOURCE, 'r') as source:
+    content = source.read()
+    content = content.replace('${VERSION_MAJOR}', v[0])
+    content = content.replace('${VERSION_MINOR}', v[1])
+    content = content.replace('${VERSION_PATCH}', v[2])
+    content = content.replace('${RELEASE}', RELEASE)
+    content = content.replace('${DESCRIPTION}', DESCRIPTION)
+    content = content.replace('${PRODUCT}', PRODUCT)   
+    content = content.replace('${FILENAME}', FILENAME)   
+    content = content.replace('${YEAR}', str(datetime.datetime.now().year))
+    content = content.replace('${BLOCK}', BLOCK)
+    content = content.replace('${TYPE}', TYPE)
+
+    sys.stdout.write(content)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/CMake/WindowsResources.rc	Fri Apr 22 17:58:59 2022 +0200
@@ -0,0 +1,30 @@
+#include <winver.h>
+
+VS_VERSION_INFO VERSIONINFO
+   FILEVERSION ${VERSION_MAJOR},${VERSION_MINOR},0,${VERSION_PATCH}
+   PRODUCTVERSION ${VERSION_MAJOR},${VERSION_MINOR},0,0
+   FILEOS VOS_NT_WINDOWS32
+   FILETYPE ${TYPE}
+   BEGIN
+      BLOCK "StringFileInfo"
+      BEGIN
+         BLOCK "${BLOCK}"
+         BEGIN
+            VALUE "Comments", "${RELEASE}"
+            VALUE "CompanyName", "The Orthanc project"
+            VALUE "FileDescription", "${DESCRIPTION}"
+            VALUE "FileVersion", "${VERSION_MAJOR}.${VERSION_MINOR}.0.${VERSION_PATCH}"
+            VALUE "InternalName", "${PRODUCT}"
+            VALUE "LegalCopyright", "(c) 2012-${YEAR}, Sebastien Jodogne, University Hospital of Liege, Osimis S.A., and ICTEAM UCLouvain"
+            VALUE "LegalTrademarks", "Licensing information is available at http://www.orthanc-server.com/"
+            VALUE "OriginalFilename", "${FILENAME}"
+            VALUE "ProductName", "${PRODUCT}"
+            VALUE "ProductVersion", "${VERSION_MAJOR}.${VERSION_MINOR}"
+         END
+      END
+
+      BLOCK "VarFileInfo"
+      BEGIN
+        VALUE "Translation", 0x409, 1252  // U.S. English
+      END
+   END
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Plugins/ExportedSymbolsPlugins.list	Fri Apr 22 17:58:59 2022 +0200
@@ -0,0 +1,7 @@
+# This is the list of the symbols that must be exported by Orthanc
+# plugins, if targeting OS X
+
+_OrthancPluginInitialize
+_OrthancPluginFinalize
+_OrthancPluginGetName
+_OrthancPluginGetVersion
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp	Fri Apr 22 17:58:59 2022 +0200
@@ -0,0 +1,3791 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2022 Osimis S.A., Belgium
+ * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "OrthancPluginCppWrapper.h"
+
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/move/unique_ptr.hpp>
+#include <boost/thread.hpp>
+
+
+#include <json/reader.h>
+#include <json/version.h>
+#include <json/writer.h>
+
+#if !defined(JSONCPP_VERSION_MAJOR) || !defined(JSONCPP_VERSION_MINOR)
+#  error Cannot access the version of JsonCpp
+#endif
+
+
+/**
+ * We use deprecated "Json::Reader", "Json::StyledWriter" and
+ * "Json::FastWriter" if JsonCpp < 1.7.0. This choice is rather
+ * arbitrary, but if Json >= 1.9.0, gcc generates explicit deprecation
+ * warnings (clang was warning in earlier versions). For reference,
+ * these classes seem to have been deprecated since JsonCpp 1.4.0 (on
+ * February 2015) by the following changeset:
+ * https://github.com/open-source-parsers/jsoncpp/commit/8df98f6112890d6272734975dd6d70cf8999bb22
+ **/
+#if (JSONCPP_VERSION_MAJOR >= 2 ||                                      \
+     (JSONCPP_VERSION_MAJOR == 1 && JSONCPP_VERSION_MINOR >= 8))
+#  define JSONCPP_USE_DEPRECATED 0
+#else
+#  define JSONCPP_USE_DEPRECATED 1
+#endif
+
+
+#if !ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 2, 0)
+static const OrthancPluginErrorCode OrthancPluginErrorCode_NullPointer = OrthancPluginErrorCode_Plugin;
+#endif
+
+
+namespace OrthancPlugins
+{
+  static OrthancPluginContext* globalContext_ = NULL;
+
+
+  void SetGlobalContext(OrthancPluginContext* context)
+  {
+    if (context == NULL)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(NullPointer);
+    }
+    else if (globalContext_ == NULL)
+    {
+      globalContext_ = context;
+    }
+    else
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(BadSequenceOfCalls);
+    }
+  }
+
+
+  bool HasGlobalContext()
+  {
+    return globalContext_ != NULL;
+  }
+
+
+  OrthancPluginContext* GetGlobalContext()
+  {
+    if (globalContext_ == NULL)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(BadSequenceOfCalls);
+    }
+    else
+    {
+      return globalContext_;
+    }
+  }
+
+
+  void MemoryBuffer::Check(OrthancPluginErrorCode code)
+  {
+    if (code != OrthancPluginErrorCode_Success)
+    {
+      // Prevent using garbage information
+      buffer_.data = NULL;
+      buffer_.size = 0;
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code);
+    }
+  }
+
+
+  bool MemoryBuffer::CheckHttp(OrthancPluginErrorCode code)
+  {
+    if (code != OrthancPluginErrorCode_Success)
+    {
+      // Prevent using garbage information
+      buffer_.data = NULL;
+      buffer_.size = 0;
+    }
+
+    if (code == OrthancPluginErrorCode_Success)
+    {
+      return true;
+    }
+    else if (code == OrthancPluginErrorCode_UnknownResource ||
+             code == OrthancPluginErrorCode_InexistentItem)
+    {
+      return false;
+    }
+    else
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code);
+    }
+  }
+
+
+  MemoryBuffer::MemoryBuffer()
+  {
+    buffer_.data = NULL;
+    buffer_.size = 0;
+  }
+
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+  MemoryBuffer::MemoryBuffer(const void* buffer,
+                             size_t size)
+  {
+    uint32_t s = static_cast<uint32_t>(size);
+    if (static_cast<size_t>(s) != size)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(NotEnoughMemory);
+    }
+    else if (OrthancPluginCreateMemoryBuffer(GetGlobalContext(), &buffer_, s) !=
+             OrthancPluginErrorCode_Success)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(NotEnoughMemory);
+    }
+    else
+    {
+      memcpy(buffer_.data, buffer, size);
+    }
+  }
+#endif
+
+
+  void MemoryBuffer::Clear()
+  {
+    if (buffer_.data != NULL)
+    {
+      OrthancPluginFreeMemoryBuffer(GetGlobalContext(), &buffer_);
+      buffer_.data = NULL;
+      buffer_.size = 0;
+    }
+  }
+
+
+  void MemoryBuffer::Assign(OrthancPluginMemoryBuffer& other)
+  {
+    Clear();
+
+    buffer_.data = other.data;
+    buffer_.size = other.size;
+
+    other.data = NULL;
+    other.size = 0;
+  }
+
+
+  void MemoryBuffer::Swap(MemoryBuffer& other)
+  {
+    std::swap(buffer_.data, other.buffer_.data);
+    std::swap(buffer_.size, other.buffer_.size);
+  }
+
+
+  OrthancPluginMemoryBuffer MemoryBuffer::Release()
+  {
+    OrthancPluginMemoryBuffer result = buffer_;
+
+    buffer_.data = NULL;
+    buffer_.size = 0;
+
+    return result;
+  }
+
+
+  void MemoryBuffer::ToString(std::string& target) const
+  {
+    if (buffer_.size == 0)
+    {
+      target.clear();
+    }
+    else
+    {
+      target.assign(reinterpret_cast<const char*>(buffer_.data), buffer_.size);
+    }
+  }
+
+
+  void MemoryBuffer::ToJson(Json::Value& target) const
+  {
+    if (buffer_.data == NULL ||
+        buffer_.size == 0)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+    }
+
+    if (!ReadJson(target, buffer_.data, buffer_.size))
+    {
+      LogError("Cannot convert some memory buffer to JSON");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+    }
+  }
+
+
+  bool MemoryBuffer::RestApiGet(const std::string& uri,
+                                bool applyPlugins)
+  {
+    Clear();
+
+    if (applyPlugins)
+    {
+      return CheckHttp(OrthancPluginRestApiGetAfterPlugins(GetGlobalContext(), &buffer_, uri.c_str()));
+    }
+    else
+    {
+      return CheckHttp(OrthancPluginRestApiGet(GetGlobalContext(), &buffer_, uri.c_str()));
+    }
+  }
+
+  bool MemoryBuffer::RestApiGet(const std::string& uri,
+                                const std::map<std::string, std::string>& httpHeaders,
+                                bool applyPlugins)
+  {
+    Clear();
+
+    std::vector<const char*> headersKeys;
+    std::vector<const char*> headersValues;
+    
+    for (std::map<std::string, std::string>::const_iterator
+           it = httpHeaders.begin(); it != httpHeaders.end(); it++)
+    {
+      headersKeys.push_back(it->first.c_str());
+      headersValues.push_back(it->second.c_str());
+    }
+
+    return CheckHttp(OrthancPluginRestApiGet2(
+                       GetGlobalContext(), &buffer_, uri.c_str(), httpHeaders.size(),
+                       (headersKeys.empty() ? NULL : &headersKeys[0]),
+                       (headersValues.empty() ? NULL : &headersValues[0]), applyPlugins));
+  }
+
+  bool MemoryBuffer::RestApiPost(const std::string& uri,
+                                 const void* body,
+                                 size_t bodySize,
+                                 bool applyPlugins)
+  {
+    Clear();
+    
+    // Cast for compatibility with Orthanc SDK <= 1.5.6
+    const char* b = reinterpret_cast<const char*>(body);
+
+    if (applyPlugins)
+    {
+      return CheckHttp(OrthancPluginRestApiPostAfterPlugins(GetGlobalContext(), &buffer_, uri.c_str(), b, bodySize));
+    }
+    else
+    {
+      return CheckHttp(OrthancPluginRestApiPost(GetGlobalContext(), &buffer_, uri.c_str(), b, bodySize));
+    }
+  }
+
+
+  bool MemoryBuffer::RestApiPut(const std::string& uri,
+                                const void* body,
+                                size_t bodySize,
+                                bool applyPlugins)
+  {
+    Clear();
+
+    // Cast for compatibility with Orthanc SDK <= 1.5.6
+    const char* b = reinterpret_cast<const char*>(body);
+
+    if (applyPlugins)
+    {
+      return CheckHttp(OrthancPluginRestApiPutAfterPlugins(GetGlobalContext(), &buffer_, uri.c_str(), b, bodySize));
+    }
+    else
+    {
+      return CheckHttp(OrthancPluginRestApiPut(GetGlobalContext(), &buffer_, uri.c_str(), b, bodySize));
+    }
+  }
+
+
+  static bool ReadJsonInternal(Json::Value& target,
+                               const void* buffer,
+                               size_t size,
+                               bool collectComments)
+  {
+#if JSONCPP_USE_DEPRECATED == 1
+    Json::Reader reader;
+    return reader.parse(reinterpret_cast<const char*>(buffer),
+                        reinterpret_cast<const char*>(buffer) + size, target, collectComments);
+#else
+    Json::CharReaderBuilder builder;
+    builder.settings_["collectComments"] = collectComments;
+    
+    const std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
+    assert(reader.get() != NULL);
+    
+    JSONCPP_STRING err;
+    if (reader->parse(reinterpret_cast<const char*>(buffer),
+                      reinterpret_cast<const char*>(buffer) + size, &target, &err))
+    {
+      return true;
+    }
+    else
+    {
+      LogError("Cannot parse JSON: " + std::string(err));
+      return false;
+    }
+#endif
+  }
+
+
+  bool ReadJson(Json::Value& target,
+                const std::string& source)
+  {
+    return ReadJson(target, source.empty() ? NULL : source.c_str(), source.size());
+  }
+  
+
+  bool ReadJson(Json::Value& target,
+                const void* buffer,
+                size_t size)
+  {
+    return ReadJsonInternal(target, buffer, size, true);
+  }
+  
+
+  bool ReadJsonWithoutComments(Json::Value& target,
+                               const std::string& source)
+  {
+    return ReadJsonWithoutComments(target, source.empty() ? NULL : source.c_str(), source.size());
+  }
+  
+
+  bool ReadJsonWithoutComments(Json::Value& target,
+                               const void* buffer,
+                               size_t size)
+  {
+    return ReadJsonInternal(target, buffer, size, false);
+  }
+
+
+  void WriteFastJson(std::string& target,
+                     const Json::Value& source)
+  {
+#if JSONCPP_USE_DEPRECATED == 1
+    Json::FastWriter writer;
+    target = writer.write(source);
+#else
+    Json::StreamWriterBuilder builder;
+    builder.settings_["indentation"] = "";
+    target = Json::writeString(builder, source);
+#endif
+  }
+  
+
+  void WriteStyledJson(std::string& target,
+                       const Json::Value& source)
+  {
+#if JSONCPP_USE_DEPRECATED == 1
+    Json::StyledWriter writer;
+    target = writer.write(source);
+#else
+    Json::StreamWriterBuilder builder;
+    builder.settings_["indentation"] = "   ";
+    target = Json::writeString(builder, source);
+#endif
+  }
+
+
+  bool MemoryBuffer::RestApiPost(const std::string& uri,
+                                 const Json::Value& body,
+                                 bool applyPlugins)
+  {
+    std::string s;
+    WriteFastJson(s, body);
+    return RestApiPost(uri, s, applyPlugins);
+  }
+
+
+  bool MemoryBuffer::RestApiPut(const std::string& uri,
+                                const Json::Value& body,
+                                bool applyPlugins)
+  {
+    std::string s;
+    WriteFastJson(s, body);
+    return RestApiPut(uri, s, applyPlugins);
+  }
+
+
+  void MemoryBuffer::CreateDicom(const Json::Value& tags,
+                                 OrthancPluginCreateDicomFlags flags)
+  {
+    Clear();
+
+    std::string s;
+    WriteFastJson(s, tags);
+
+    Check(OrthancPluginCreateDicom(GetGlobalContext(), &buffer_, s.c_str(), NULL, flags));
+  }
+
+  void MemoryBuffer::CreateDicom(const Json::Value& tags,
+                                 const OrthancImage& pixelData,
+                                 OrthancPluginCreateDicomFlags flags)
+  {
+    Clear();
+
+    std::string s;
+    WriteFastJson(s, tags);
+
+    Check(OrthancPluginCreateDicom(GetGlobalContext(), &buffer_, s.c_str(), pixelData.GetObject(), flags));
+  }
+
+
+  void MemoryBuffer::ReadFile(const std::string& path)
+  {
+    Clear();
+    Check(OrthancPluginReadFile(GetGlobalContext(), &buffer_, path.c_str()));
+  }
+
+
+  void MemoryBuffer::GetDicomQuery(const OrthancPluginWorklistQuery* query)
+  {
+    Clear();
+    Check(OrthancPluginWorklistGetDicomQuery(GetGlobalContext(), &buffer_, query));
+  }
+
+
+  void OrthancString::Assign(char* str)
+  {
+    Clear();
+
+    if (str != NULL)
+    {
+      str_ = str;
+    }
+  }
+
+
+  void OrthancString::Clear()
+  {
+    if (str_ != NULL)
+    {
+      OrthancPluginFreeString(GetGlobalContext(), str_);
+      str_ = NULL;
+    }
+  }
+
+
+  void OrthancString::ToString(std::string& target) const
+  {
+    if (str_ == NULL)
+    {
+      target.clear();
+    }
+    else
+    {
+      target.assign(str_);
+    }
+  }
+
+
+  void OrthancString::ToJson(Json::Value& target) const
+  {
+    if (str_ == NULL)
+    {
+      LogError("Cannot convert an empty memory buffer to JSON");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+    }
+
+    if (!ReadJson(target, str_))
+    {
+      LogError("Cannot convert some memory buffer to JSON");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+    }
+  }
+
+
+  void OrthancString::ToJsonWithoutComments(Json::Value& target) const
+  {
+    if (str_ == NULL)
+    {
+      LogError("Cannot convert an empty memory buffer to JSON");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+    }
+
+    if (!ReadJsonWithoutComments(target, str_))
+    {
+      LogError("Cannot convert some memory buffer to JSON");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+    }
+  }
+
+
+  void MemoryBuffer::DicomToJson(Json::Value& target,
+                                 OrthancPluginDicomToJsonFormat format,
+                                 OrthancPluginDicomToJsonFlags flags,
+                                 uint32_t maxStringLength)
+  {
+    OrthancString str;
+    str.Assign(OrthancPluginDicomBufferToJson
+               (GetGlobalContext(), GetData(), GetSize(), format, flags, maxStringLength));
+    str.ToJson(target);
+  }
+
+
+  bool MemoryBuffer::HttpGet(const std::string& url,
+                             const std::string& username,
+                             const std::string& password)
+  {
+    Clear();
+    return CheckHttp(OrthancPluginHttpGet(GetGlobalContext(), &buffer_, url.c_str(),
+                                          username.empty() ? NULL : username.c_str(),
+                                          password.empty() ? NULL : password.c_str()));
+  }
+
+
+  bool MemoryBuffer::HttpPost(const std::string& url,
+                              const std::string& body,
+                              const std::string& username,
+                              const std::string& password)
+  {
+    Clear();
+
+    if (body.size() > 0xffffffffu)
+    {
+      LogError("Cannot handle body size > 4GB");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+    }
+
+    return CheckHttp(OrthancPluginHttpPost(GetGlobalContext(), &buffer_, url.c_str(),
+                                           body.c_str(), body.size(),
+                                           username.empty() ? NULL : username.c_str(),
+                                           password.empty() ? NULL : password.c_str()));
+  }
+
+
+  bool MemoryBuffer::HttpPut(const std::string& url,
+                             const std::string& body,
+                             const std::string& username,
+                             const std::string& password)
+  {
+    Clear();
+
+    if (body.size() > 0xffffffffu)
+    {
+      LogError("Cannot handle body size > 4GB");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+    }
+
+    return CheckHttp(OrthancPluginHttpPut(GetGlobalContext(), &buffer_, url.c_str(),
+                                          body.empty() ? NULL : body.c_str(),
+                                          body.size(),
+                                          username.empty() ? NULL : username.c_str(),
+                                          password.empty() ? NULL : password.c_str()));
+  }
+
+
+  void MemoryBuffer::GetDicomInstance(const std::string& instanceId)
+  {
+    Clear();
+    Check(OrthancPluginGetDicomForInstance(GetGlobalContext(), &buffer_, instanceId.c_str()));
+  }
+
+
+  bool HttpDelete(const std::string& url,
+                  const std::string& username,
+                  const std::string& password)
+  {
+    OrthancPluginErrorCode error = OrthancPluginHttpDelete
+      (GetGlobalContext(), url.c_str(),
+       username.empty() ? NULL : username.c_str(),
+       password.empty() ? NULL : password.c_str());
+
+    if (error == OrthancPluginErrorCode_Success)
+    {
+      return true;
+    }
+    else if (error == OrthancPluginErrorCode_UnknownResource ||
+             error == OrthancPluginErrorCode_InexistentItem)
+    {
+      return false;
+    }
+    else
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error);
+    }
+  }
+
+
+  void LogError(const std::string& message)
+  {
+    if (HasGlobalContext())
+    {
+      OrthancPluginLogError(GetGlobalContext(), message.c_str());
+    }
+  }
+
+
+  void LogWarning(const std::string& message)
+  {
+    if (HasGlobalContext())
+    {
+      OrthancPluginLogWarning(GetGlobalContext(), message.c_str());
+    }
+  }
+
+
+  void LogInfo(const std::string& message)
+  {
+    if (HasGlobalContext())
+    {
+      OrthancPluginLogInfo(GetGlobalContext(), message.c_str());
+    }
+  }
+
+
+  void OrthancConfiguration::LoadConfiguration()
+  {
+    OrthancString str;
+    str.Assign(OrthancPluginGetConfiguration(GetGlobalContext()));
+
+    if (str.GetContent() == NULL)
+    {
+      LogError("Cannot access the Orthanc configuration");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+    }
+
+    str.ToJsonWithoutComments(configuration_);
+
+    if (configuration_.type() != Json::objectValue)
+    {
+      LogError("Unable to read the Orthanc configuration");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+    }
+  }
+    
+
+  OrthancConfiguration::OrthancConfiguration()
+  {
+    LoadConfiguration();
+  }
+
+
+  OrthancConfiguration::OrthancConfiguration(bool loadConfiguration)
+  {
+    if (loadConfiguration)
+    {
+      LoadConfiguration();
+    }
+    else
+    {
+      configuration_ = Json::objectValue;
+    }
+  }
+
+
+  std::string OrthancConfiguration::GetPath(const std::string& key) const
+  {
+    if (path_.empty())
+    {
+      return key;
+    }
+    else
+    {
+      return path_ + "." + key;
+    }
+  }
+
+
+  bool OrthancConfiguration::IsSection(const std::string& key) const
+  {
+    assert(configuration_.type() == Json::objectValue);
+
+    return (configuration_.isMember(key) &&
+            configuration_[key].type() == Json::objectValue);
+  }
+
+
+  void OrthancConfiguration::GetSection(OrthancConfiguration& target,
+                                        const std::string& key) const
+  {
+    assert(configuration_.type() == Json::objectValue);
+
+    target.path_ = GetPath(key);
+
+    if (!configuration_.isMember(key))
+    {
+      target.configuration_ = Json::objectValue;
+    }
+    else
+    {
+      if (configuration_[key].type() != Json::objectValue)
+      {
+        LogError("The configuration section \"" + target.path_ +
+                 "\" is not an associative array as expected");
+
+        ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+      }
+
+      target.configuration_ = configuration_[key];
+    }
+  }
+
+
+  bool OrthancConfiguration::LookupStringValue(std::string& target,
+                                               const std::string& key) const
+  {
+    assert(configuration_.type() == Json::objectValue);
+
+    if (!configuration_.isMember(key))
+    {
+      return false;
+    }
+
+    if (configuration_[key].type() != Json::stringValue)
+    {
+      LogError("The configuration option \"" + GetPath(key) +
+               "\" is not a string as expected");
+
+      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+    }
+
+    target = configuration_[key].asString();
+    return true;
+  }
+
+
+  bool OrthancConfiguration::LookupIntegerValue(int& target,
+                                                const std::string& key) const
+  {
+    assert(configuration_.type() == Json::objectValue);
+
+    if (!configuration_.isMember(key))
+    {
+      return false;
+    }
+
+    switch (configuration_[key].type())
+    {
+      case Json::intValue:
+        target = configuration_[key].asInt();
+        return true;
+
+      case Json::uintValue:
+        target = configuration_[key].asUInt();
+        return true;
+
+      default:
+        LogError("The configuration option \"" + GetPath(key) +
+                 "\" is not an integer as expected");
+
+        ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+    }
+  }
+
+
+  bool OrthancConfiguration::LookupUnsignedIntegerValue(unsigned int& target,
+                                                        const std::string& key) const
+  {
+    int tmp;
+    if (!LookupIntegerValue(tmp, key))
+    {
+      return false;
+    }
+
+    if (tmp < 0)
+    {
+      LogError("The configuration option \"" + GetPath(key) +
+               "\" is not a positive integer as expected");
+
+      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+    }
+    else
+    {
+      target = static_cast<unsigned int>(tmp);
+      return true;
+    }
+  }
+
+
+  bool OrthancConfiguration::LookupBooleanValue(bool& target,
+                                                const std::string& key) const
+  {
+    assert(configuration_.type() == Json::objectValue);
+
+    if (!configuration_.isMember(key))
+    {
+      return false;
+    }
+
+    if (configuration_[key].type() != Json::booleanValue)
+    {
+      LogError("The configuration option \"" + GetPath(key) +
+               "\" is not a Boolean as expected");
+
+      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+    }
+
+    target = configuration_[key].asBool();
+    return true;
+  }
+
+
+  bool OrthancConfiguration::LookupFloatValue(float& target,
+                                              const std::string& key) const
+  {
+    assert(configuration_.type() == Json::objectValue);
+
+    if (!configuration_.isMember(key))
+    {
+      return false;
+    }
+
+    switch (configuration_[key].type())
+    {
+      case Json::realValue:
+        target = configuration_[key].asFloat();
+        return true;
+
+      case Json::intValue:
+        target = static_cast<float>(configuration_[key].asInt());
+        return true;
+
+      case Json::uintValue:
+        target = static_cast<float>(configuration_[key].asUInt());
+        return true;
+
+      default:
+        LogError("The configuration option \"" + GetPath(key) +
+                 "\" is not an integer as expected");
+
+        ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+    }
+  }
+
+
+  bool OrthancConfiguration::LookupListOfStrings(std::list<std::string>& target,
+                                                 const std::string& key,
+                                                 bool allowSingleString) const
+  {
+    assert(configuration_.type() == Json::objectValue);
+
+    target.clear();
+
+    if (!configuration_.isMember(key))
+    {
+      return false;
+    }
+
+    switch (configuration_[key].type())
+    {
+      case Json::arrayValue:
+      {
+        bool ok = true;
+
+        for (Json::Value::ArrayIndex i = 0; ok && i < configuration_[key].size(); i++)
+        {
+          if (configuration_[key][i].type() == Json::stringValue)
+          {
+            target.push_back(configuration_[key][i].asString());
+          }
+          else
+          {
+            ok = false;
+          }
+        }
+
+        if (ok)
+        {
+          return true;
+        }
+
+        break;
+      }
+
+      case Json::stringValue:
+        if (allowSingleString)
+        {
+          target.push_back(configuration_[key].asString());
+          return true;
+        }
+
+        break;
+
+      default:
+        break;
+    }
+
+    LogError("The configuration option \"" + GetPath(key) +
+             "\" is not a list of strings as expected");
+
+    ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+  }
+
+
+  bool OrthancConfiguration::LookupSetOfStrings(std::set<std::string>& target,
+                                                const std::string& key,
+                                                bool allowSingleString) const
+  {
+    std::list<std::string> lst;
+
+    if (LookupListOfStrings(lst, key, allowSingleString))
+    {
+      target.clear();
+
+      for (std::list<std::string>::const_iterator
+             it = lst.begin(); it != lst.end(); ++it)
+      {
+        target.insert(*it);
+      }
+
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  std::string OrthancConfiguration::GetStringValue(const std::string& key,
+                                                   const std::string& defaultValue) const
+  {
+    std::string tmp;
+    if (LookupStringValue(tmp, key))
+    {
+      return tmp;
+    }
+    else
+    {
+      return defaultValue;
+    }
+  }
+
+
+  int OrthancConfiguration::GetIntegerValue(const std::string& key,
+                                            int defaultValue) const
+  {
+    int tmp;
+    if (LookupIntegerValue(tmp, key))
+    {
+      return tmp;
+    }
+    else
+    {
+      return defaultValue;
+    }
+  }
+
+
+  unsigned int OrthancConfiguration::GetUnsignedIntegerValue(const std::string& key,
+                                                             unsigned int defaultValue) const
+  {
+    unsigned int tmp;
+    if (LookupUnsignedIntegerValue(tmp, key))
+    {
+      return tmp;
+    }
+    else
+    {
+      return defaultValue;
+    }
+  }
+
+
+  bool OrthancConfiguration::GetBooleanValue(const std::string& key,
+                                             bool defaultValue) const
+  {
+    bool tmp;
+    if (LookupBooleanValue(tmp, key))
+    {
+      return tmp;
+    }
+    else
+    {
+      return defaultValue;
+    }
+  }
+
+
+  float OrthancConfiguration::GetFloatValue(const std::string& key,
+                                            float defaultValue) const
+  {
+    float tmp;
+    if (LookupFloatValue(tmp, key))
+    {
+      return tmp;
+    }
+    else
+    {
+      return defaultValue;
+    }
+  }
+
+
+  void OrthancConfiguration::GetDictionary(std::map<std::string, std::string>& target,
+                                           const std::string& key) const
+  {
+    assert(configuration_.type() == Json::objectValue);
+
+    target.clear();
+
+    if (!configuration_.isMember(key))
+    {
+      return;
+    }
+
+    if (configuration_[key].type() != Json::objectValue)
+    {
+      LogError("The configuration option \"" + GetPath(key) +
+               "\" is not a string as expected");
+
+      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+    }
+
+    Json::Value::Members members = configuration_[key].getMemberNames();
+
+    for (size_t i = 0; i < members.size(); i++)
+    {
+      const Json::Value& value = configuration_[key][members[i]];
+
+      if (value.type() == Json::stringValue)
+      {
+        target[members[i]] = value.asString();
+      }
+      else
+      {
+        LogError("The configuration option \"" + GetPath(key) +
+                 "\" is not a dictionary mapping strings to strings");
+
+        ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+      }
+    }
+  }
+
+
+  void OrthancImage::Clear()
+  {
+    if (image_ != NULL)
+    {
+      OrthancPluginFreeImage(GetGlobalContext(), image_);
+      image_ = NULL;
+    }
+  }
+
+
+  void OrthancImage::CheckImageAvailable() const
+  {
+    if (image_ == NULL)
+    {
+      LogError("Trying to access a NULL image");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
+    }
+  }
+
+
+  OrthancImage::OrthancImage() :
+    image_(NULL)
+  {
+  }
+
+
+  OrthancImage::OrthancImage(OrthancPluginImage*  image) :
+    image_(image)
+  {
+  }
+
+
+  OrthancImage::OrthancImage(OrthancPluginPixelFormat  format,
+                             uint32_t                  width,
+                             uint32_t                  height)
+  {
+    image_ = OrthancPluginCreateImage(GetGlobalContext(), format, width, height);
+
+    if (image_ == NULL)
+    {
+      LogError("Cannot create an image");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+    }
+  }
+
+
+  OrthancImage::OrthancImage(OrthancPluginPixelFormat  format,
+                             uint32_t                  width,
+                             uint32_t                  height,
+                             uint32_t                  pitch,
+                             void*                     buffer)
+  {
+    image_ = OrthancPluginCreateImageAccessor
+      (GetGlobalContext(), format, width, height, pitch, buffer);
+
+    if (image_ == NULL)
+    {
+      LogError("Cannot create an image accessor");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+    }
+  }
+
+  void OrthancImage::UncompressPngImage(const void* data,
+                                        size_t size)
+  {
+    Clear();
+
+    image_ = OrthancPluginUncompressImage(GetGlobalContext(), data, size, OrthancPluginImageFormat_Png);
+
+    if (image_ == NULL)
+    {
+      LogError("Cannot uncompress a PNG image");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
+    }
+  }
+
+
+  void OrthancImage::UncompressJpegImage(const void* data,
+                                         size_t size)
+  {
+    Clear();
+    image_ = OrthancPluginUncompressImage(GetGlobalContext(), data, size, OrthancPluginImageFormat_Jpeg);
+    if (image_ == NULL)
+    {
+      LogError("Cannot uncompress a JPEG image");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
+    }
+  }
+
+
+  void OrthancImage::DecodeDicomImage(const void* data,
+                                      size_t size,
+                                      unsigned int frame)
+  {
+    Clear();
+    image_ = OrthancPluginDecodeDicomImage(GetGlobalContext(), data, size, frame);
+    if (image_ == NULL)
+    {
+      LogError("Cannot uncompress a DICOM image");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
+    }
+  }
+
+
+  OrthancPluginPixelFormat OrthancImage::GetPixelFormat() const
+  {
+    CheckImageAvailable();
+    return OrthancPluginGetImagePixelFormat(GetGlobalContext(), image_);
+  }
+
+
+  unsigned int OrthancImage::GetWidth() const
+  {
+    CheckImageAvailable();
+    return OrthancPluginGetImageWidth(GetGlobalContext(), image_);
+  }
+
+
+  unsigned int OrthancImage::GetHeight() const
+  {
+    CheckImageAvailable();
+    return OrthancPluginGetImageHeight(GetGlobalContext(), image_);
+  }
+
+
+  unsigned int OrthancImage::GetPitch() const
+  {
+    CheckImageAvailable();
+    return OrthancPluginGetImagePitch(GetGlobalContext(), image_);
+  }
+
+
+  void* OrthancImage::GetBuffer() const
+  {
+    CheckImageAvailable();
+    return OrthancPluginGetImageBuffer(GetGlobalContext(), image_);
+  }
+
+
+  void OrthancImage::CompressPngImage(MemoryBuffer& target) const
+  {
+    CheckImageAvailable();
+
+    OrthancPlugins::MemoryBuffer answer;
+    OrthancPluginCompressPngImage(GetGlobalContext(), *answer, GetPixelFormat(),
+                                  GetWidth(), GetHeight(), GetPitch(), GetBuffer());
+
+    target.Swap(answer);
+  }
+
+
+  void OrthancImage::CompressJpegImage(MemoryBuffer& target,
+                                       uint8_t quality) const
+  {
+    CheckImageAvailable();
+
+    OrthancPlugins::MemoryBuffer answer;
+    OrthancPluginCompressJpegImage(GetGlobalContext(), *answer, GetPixelFormat(),
+                                   GetWidth(), GetHeight(), GetPitch(), GetBuffer(), quality);
+
+    target.Swap(answer);
+  }
+
+
+  void OrthancImage::AnswerPngImage(OrthancPluginRestOutput* output) const
+  {
+    CheckImageAvailable();
+    OrthancPluginCompressAndAnswerPngImage(GetGlobalContext(), output, GetPixelFormat(),
+                                           GetWidth(), GetHeight(), GetPitch(), GetBuffer());
+  }
+
+
+  void OrthancImage::AnswerJpegImage(OrthancPluginRestOutput* output,
+                                     uint8_t quality) const
+  {
+    CheckImageAvailable();
+    OrthancPluginCompressAndAnswerJpegImage(GetGlobalContext(), output, GetPixelFormat(),
+                                            GetWidth(), GetHeight(), GetPitch(), GetBuffer(), quality);
+  }
+
+
+  OrthancPluginImage* OrthancImage::Release()
+  {
+    CheckImageAvailable();
+    OrthancPluginImage* tmp = image_;
+    image_ = NULL;
+    return tmp;
+  }
+
+
+#if HAS_ORTHANC_PLUGIN_FIND_MATCHER == 1
+  FindMatcher::FindMatcher(const OrthancPluginWorklistQuery* worklist) :
+    matcher_(NULL),
+    worklist_(worklist)
+  {
+    if (worklist_ == NULL)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
+    }
+  }
+
+
+  void FindMatcher::SetupDicom(const void*  query,
+                               uint32_t     size)
+  {
+    worklist_ = NULL;
+
+    matcher_ = OrthancPluginCreateFindMatcher(GetGlobalContext(), query, size);
+    if (matcher_ == NULL)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+    }
+  }
+
+
+  FindMatcher::~FindMatcher()
+  {
+    // The "worklist_" field
+
+    if (matcher_ != NULL)
+    {
+      OrthancPluginFreeFindMatcher(GetGlobalContext(), matcher_);
+    }
+  }
+
+
+
+  bool FindMatcher::IsMatch(const void*  dicom,
+                            uint32_t     size) const
+  {
+    int32_t result;
+
+    if (matcher_ != NULL)
+    {
+      result = OrthancPluginFindMatcherIsMatch(GetGlobalContext(), matcher_, dicom, size);
+    }
+    else if (worklist_ != NULL)
+    {
+      result = OrthancPluginWorklistIsMatch(GetGlobalContext(), worklist_, dicom, size);
+    }
+    else
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+    }
+
+    if (result == 0)
+    {
+      return false;
+    }
+    else if (result == 1)
+    {
+      return true;
+    }
+    else
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+    }
+  }
+
+#endif /* HAS_ORTHANC_PLUGIN_FIND_MATCHER == 1 */
+
+  void AnswerJson(const Json::Value& value,
+                  OrthancPluginRestOutput* output)
+  {
+    std::string bodyString;
+    WriteStyledJson(bodyString, value);    
+    OrthancPluginAnswerBuffer(GetGlobalContext(), output, bodyString.c_str(), bodyString.size(), "application/json");
+  }
+
+  void AnswerString(const std::string& answer,
+                    const char* mimeType,
+                    OrthancPluginRestOutput* output)
+  {
+    OrthancPluginAnswerBuffer(GetGlobalContext(), output, answer.c_str(), answer.size(), mimeType);
+  }
+
+  void AnswerHttpError(uint16_t httpError, OrthancPluginRestOutput *output)
+  {
+    OrthancPluginSendHttpStatusCode(GetGlobalContext(), output, httpError);
+  }
+
+  void AnswerMethodNotAllowed(OrthancPluginRestOutput *output, const char* allowedMethods)
+  {
+    OrthancPluginSendMethodNotAllowed(GetGlobalContext(), output, allowedMethods);
+  }
+
+  bool RestApiGetString(std::string& result,
+                        const std::string& uri,
+                        bool applyPlugins)
+  {
+    MemoryBuffer answer;
+    if (!answer.RestApiGet(uri, applyPlugins))
+    {
+      return false;
+    }
+    else
+    {
+      answer.ToString(result);
+      return true;
+    }
+  }
+
+  bool RestApiGetString(std::string& result,
+                        const std::string& uri,
+                        const std::map<std::string, std::string>& httpHeaders,
+                        bool applyPlugins)
+  {
+    MemoryBuffer answer;
+    if (!answer.RestApiGet(uri, httpHeaders, applyPlugins))
+    {
+      return false;
+    }
+    else
+    {
+      answer.ToString(result);
+      return true;
+    }
+  }
+
+
+
+  bool RestApiGet(Json::Value& result,
+                  const std::string& uri,
+                  bool applyPlugins)
+  {
+    MemoryBuffer answer;
+
+    if (!answer.RestApiGet(uri, applyPlugins))
+    {
+      return false;
+    }
+    else
+    {
+      if (!answer.IsEmpty())
+      {
+        answer.ToJson(result);
+      }
+      return true;
+    }
+  }
+
+
+  bool RestApiPost(std::string& result,
+                   const std::string& uri,
+                   const void* body,
+                   size_t bodySize,
+                   bool applyPlugins)
+  {
+    MemoryBuffer answer;
+
+    if (!answer.RestApiPost(uri, body, bodySize, applyPlugins))
+    {
+      return false;
+    }
+    else
+    {
+      if (!answer.IsEmpty())
+      {
+        result.assign(answer.GetData(), answer.GetSize());
+      }
+      return true;
+    }
+  }
+
+
+  bool RestApiPost(Json::Value& result,
+                   const std::string& uri,
+                   const void* body,
+                   size_t bodySize,
+                   bool applyPlugins)
+  {
+    MemoryBuffer answer;
+
+    if (!answer.RestApiPost(uri, body, bodySize, applyPlugins))
+    {
+      return false;
+    }
+    else
+    {
+      if (!answer.IsEmpty())
+      {
+        answer.ToJson(result);
+      }
+      return true;
+    }
+  }
+
+
+  bool RestApiPost(Json::Value& result,
+                   const std::string& uri,
+                   const Json::Value& body,
+                   bool applyPlugins)
+  {
+    std::string s;
+    WriteFastJson(s, body);
+    return RestApiPost(result, uri, s, applyPlugins);
+  }
+
+
+  bool RestApiPut(Json::Value& result,
+                  const std::string& uri,
+                  const void* body,
+                  size_t bodySize,
+                  bool applyPlugins)
+  {
+    MemoryBuffer answer;
+
+    if (!answer.RestApiPut(uri, body, bodySize, applyPlugins))
+    {
+      return false;
+    }
+    else
+    {
+      if (!answer.IsEmpty()) // i.e, on a PUT to metadata/..., orthanc returns an empty response
+      {
+        answer.ToJson(result);
+      }
+      return true;
+    }
+  }
+
+
+  bool RestApiPut(Json::Value& result,
+                  const std::string& uri,
+                  const Json::Value& body,
+                  bool applyPlugins)
+  {
+    std::string s;
+    WriteFastJson(s, body);
+    return RestApiPut(result, uri, s, applyPlugins);
+  }
+
+
+  bool RestApiDelete(const std::string& uri,
+                     bool applyPlugins)
+  {
+    OrthancPluginErrorCode error;
+
+    if (applyPlugins)
+    {
+      error = OrthancPluginRestApiDeleteAfterPlugins(GetGlobalContext(), uri.c_str());
+    }
+    else
+    {
+      error = OrthancPluginRestApiDelete(GetGlobalContext(), uri.c_str());
+    }
+
+    if (error == OrthancPluginErrorCode_Success)
+    {
+      return true;
+    }
+    else if (error == OrthancPluginErrorCode_UnknownResource ||
+             error == OrthancPluginErrorCode_InexistentItem)
+    {
+      return false;
+    }
+    else
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error);
+    }
+  }
+
+
+  void ReportMinimalOrthancVersion(unsigned int major,
+                                   unsigned int minor,
+                                   unsigned int revision)
+  {
+    LogError("Your version of the Orthanc core (" +
+             std::string(GetGlobalContext()->orthancVersion) +
+             ") is too old to run this plugin (version " +
+             boost::lexical_cast<std::string>(major) + "." +
+             boost::lexical_cast<std::string>(minor) + "." +
+             boost::lexical_cast<std::string>(revision) +
+             " is required)");
+  }
+
+
+  bool CheckMinimalOrthancVersion(unsigned int major,
+                                  unsigned int minor,
+                                  unsigned int revision)
+  {
+    if (!HasGlobalContext())
+    {
+      LogError("Bad Orthanc context in the plugin");
+      return false;
+    }
+
+    if (!strcmp(GetGlobalContext()->orthancVersion, "mainline"))
+    {
+      // Assume compatibility with the mainline
+      return true;
+    }
+
+    // Parse the version of the Orthanc core
+    int aa, bb, cc;
+    if (
+#ifdef _MSC_VER
+      sscanf_s
+#else
+      sscanf
+#endif
+      (GetGlobalContext()->orthancVersion, "%4d.%4d.%4d", &aa, &bb, &cc) != 3 ||
+      aa < 0 ||
+      bb < 0 ||
+      cc < 0)
+    {
+      return false;
+    }
+
+    unsigned int a = static_cast<unsigned int>(aa);
+    unsigned int b = static_cast<unsigned int>(bb);
+    unsigned int c = static_cast<unsigned int>(cc);
+
+    // Check the major version number
+
+    if (a > major)
+    {
+      return true;
+    }
+
+    if (a < major)
+    {
+      return false;
+    }
+
+
+    // Check the minor version number
+    assert(a == major);
+
+    if (b > minor)
+    {
+      return true;
+    }
+
+    if (b < minor)
+    {
+      return false;
+    }
+
+    // Check the patch level version number
+    assert(a == major && b == minor);
+
+    if (c >= revision)
+    {
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 0)
+  const char* AutodetectMimeType(const std::string& path)
+  {
+    const char* mime = OrthancPluginAutodetectMimeType(GetGlobalContext(), path.c_str());
+
+    if (mime == NULL)
+    {
+      // Should never happen, just for safety
+      return "application/octet-stream";
+    }
+    else
+    {
+      return mime;
+    }
+  }
+#endif
+
+
+#if HAS_ORTHANC_PLUGIN_PEERS == 1
+  size_t OrthancPeers::GetPeerIndex(const std::string& name) const
+  {
+    size_t index;
+    if (LookupName(index, name))
+    {
+      return index;
+    }
+    else
+    {
+      LogError("Inexistent peer: " + name);
+      ORTHANC_PLUGINS_THROW_EXCEPTION(UnknownResource);
+    }
+  }
+
+
+  OrthancPeers::OrthancPeers() :
+    peers_(NULL),
+    timeout_(0)
+  {
+    peers_ = OrthancPluginGetPeers(GetGlobalContext());
+
+    if (peers_ == NULL)
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin);
+    }
+
+    uint32_t count = OrthancPluginGetPeersCount(GetGlobalContext(), peers_);
+
+    for (uint32_t i = 0; i < count; i++)
+    {
+      const char* name = OrthancPluginGetPeerName(GetGlobalContext(), peers_, i);
+      if (name == NULL)
+      {
+        OrthancPluginFreePeers(GetGlobalContext(), peers_);
+        ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin);
+      }
+
+      index_[name] = i;
+    }
+  }
+
+
+  OrthancPeers::~OrthancPeers()
+  {
+    if (peers_ != NULL)
+    {
+      OrthancPluginFreePeers(GetGlobalContext(), peers_);
+    }
+  }
+
+
+  bool OrthancPeers::LookupName(size_t& target,
+                                const std::string& name) const
+  {
+    Index::const_iterator found = index_.find(name);
+
+    if (found == index_.end())
+    {
+      return false;
+    }
+    else
+    {
+      target = found->second;
+      return true;
+    }
+  }
+
+
+  std::string OrthancPeers::GetPeerName(size_t index) const
+  {
+    if (index >= index_.size())
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      const char* s = OrthancPluginGetPeerName(GetGlobalContext(), peers_, static_cast<uint32_t>(index));
+      if (s == NULL)
+      {
+        ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin);
+      }
+      else
+      {
+        return s;
+      }
+    }
+  }
+
+
+  std::string OrthancPeers::GetPeerUrl(size_t index) const
+  {
+    if (index >= index_.size())
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      const char* s = OrthancPluginGetPeerUrl(GetGlobalContext(), peers_, static_cast<uint32_t>(index));
+      if (s == NULL)
+      {
+        ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin);
+      }
+      else
+      {
+        return s;
+      }
+    }
+  }
+
+
+  std::string OrthancPeers::GetPeerUrl(const std::string& name) const
+  {
+    return GetPeerUrl(GetPeerIndex(name));
+  }
+
+
+  bool OrthancPeers::LookupUserProperty(std::string& value,
+                                        size_t index,
+                                        const std::string& key) const
+  {
+    if (index >= index_.size())
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      const char* s = OrthancPluginGetPeerUserProperty(GetGlobalContext(), peers_, static_cast<uint32_t>(index), key.c_str());
+      if (s == NULL)
+      {
+        return false;
+      }
+      else
+      {
+        value.assign(s);
+        return true;
+      }
+    }
+  }
+
+
+  bool OrthancPeers::LookupUserProperty(std::string& value,
+                                        const std::string& peer,
+                                        const std::string& key) const
+  {
+    return LookupUserProperty(value, GetPeerIndex(peer), key);
+  }
+
+
+  bool OrthancPeers::DoGet(MemoryBuffer& target,
+                           size_t index,
+                           const std::string& uri) const
+  {
+    if (index >= index_.size())
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
+    }
+
+    OrthancPlugins::MemoryBuffer answer;
+    uint16_t status;
+    OrthancPluginErrorCode code = OrthancPluginCallPeerApi
+      (GetGlobalContext(), *answer, NULL, &status, peers_,
+       static_cast<uint32_t>(index), OrthancPluginHttpMethod_Get, uri.c_str(),
+       0, NULL, NULL, NULL, 0, timeout_);
+
+    if (code == OrthancPluginErrorCode_Success)
+    {
+      target.Swap(answer);
+      return (status == 200);
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  bool OrthancPeers::DoGet(MemoryBuffer& target,
+                           const std::string& name,
+                           const std::string& uri) const
+  {
+    size_t index;
+    return (LookupName(index, name) &&
+            DoGet(target, index, uri));
+  }
+
+
+  bool OrthancPeers::DoGet(Json::Value& target,
+                           size_t index,
+                           const std::string& uri) const
+  {
+    MemoryBuffer buffer;
+
+    if (DoGet(buffer, index, uri))
+    {
+      buffer.ToJson(target);
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  bool OrthancPeers::DoGet(Json::Value& target,
+                           const std::string& name,
+                           const std::string& uri) const
+  {
+    MemoryBuffer buffer;
+
+    if (DoGet(buffer, name, uri))
+    {
+      buffer.ToJson(target);
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  bool OrthancPeers::DoPost(MemoryBuffer& target,
+                            const std::string& name,
+                            const std::string& uri,
+                            const std::string& body) const
+  {
+    size_t index;
+    return (LookupName(index, name) &&
+            DoPost(target, index, uri, body));
+  }
+
+
+  bool OrthancPeers::DoPost(Json::Value& target,
+                            size_t index,
+                            const std::string& uri,
+                            const std::string& body) const
+  {
+    MemoryBuffer buffer;
+
+    if (DoPost(buffer, index, uri, body))
+    {
+      buffer.ToJson(target);
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  bool OrthancPeers::DoPost(Json::Value& target,
+                            const std::string& name,
+                            const std::string& uri,
+                            const std::string& body) const
+  {
+    MemoryBuffer buffer;
+
+    if (DoPost(buffer, name, uri, body))
+    {
+      buffer.ToJson(target);
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  bool OrthancPeers::DoPost(MemoryBuffer& target,
+                            size_t index,
+                            const std::string& uri,
+                            const std::string& body) const
+  {
+    if (index >= index_.size())
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
+    }
+
+    if (body.size() > 0xffffffffu)
+    {
+      LogError("Cannot handle body size > 4GB");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+    }
+
+    OrthancPlugins::MemoryBuffer answer;
+    uint16_t status;
+    OrthancPluginErrorCode code = OrthancPluginCallPeerApi
+      (GetGlobalContext(), *answer, NULL, &status, peers_,
+       static_cast<uint32_t>(index), OrthancPluginHttpMethod_Post, uri.c_str(),
+       0, NULL, NULL, body.empty() ? NULL : body.c_str(), body.size(), timeout_);
+
+    if (code == OrthancPluginErrorCode_Success)
+    {
+      target.Swap(answer);
+      return (status == 200);
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  bool OrthancPeers::DoPut(size_t index,
+                           const std::string& uri,
+                           const std::string& body) const
+  {
+    if (index >= index_.size())
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
+    }
+
+    if (body.size() > 0xffffffffu)
+    {
+      LogError("Cannot handle body size > 4GB");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+    }
+
+    OrthancPlugins::MemoryBuffer answer;
+    uint16_t status;
+    OrthancPluginErrorCode code = OrthancPluginCallPeerApi
+      (GetGlobalContext(), *answer, NULL, &status, peers_,
+       static_cast<uint32_t>(index), OrthancPluginHttpMethod_Put, uri.c_str(),
+       0, NULL, NULL, body.empty() ? NULL : body.c_str(), body.size(), timeout_);
+
+    if (code == OrthancPluginErrorCode_Success)
+    {
+      return (status == 200);
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  bool OrthancPeers::DoPut(const std::string& name,
+                           const std::string& uri,
+                           const std::string& body) const
+  {
+    size_t index;
+    return (LookupName(index, name) &&
+            DoPut(index, uri, body));
+  }
+
+
+  bool OrthancPeers::DoDelete(size_t index,
+                              const std::string& uri) const
+  {
+    if (index >= index_.size())
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
+    }
+
+    OrthancPlugins::MemoryBuffer answer;
+    uint16_t status;
+    OrthancPluginErrorCode code = OrthancPluginCallPeerApi
+      (GetGlobalContext(), *answer, NULL, &status, peers_,
+       static_cast<uint32_t>(index), OrthancPluginHttpMethod_Delete, uri.c_str(),
+       0, NULL, NULL, NULL, 0, timeout_);
+
+    if (code == OrthancPluginErrorCode_Success)
+    {
+      return (status == 200);
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  bool OrthancPeers::DoDelete(const std::string& name,
+                              const std::string& uri) const
+  {
+    size_t index;
+    return (LookupName(index, name) &&
+            DoDelete(index, uri));
+  }
+#endif
+
+
+
+
+
+  /******************************************************************
+   ** JOBS
+   ******************************************************************/
+
+#if HAS_ORTHANC_PLUGIN_JOB == 1
+  void OrthancJob::CallbackFinalize(void* job)
+  {
+    if (job != NULL)
+    {
+      delete reinterpret_cast<OrthancJob*>(job);
+    }
+  }
+
+
+  float OrthancJob::CallbackGetProgress(void* job)
+  {
+    assert(job != NULL);
+
+    try
+    {
+      return reinterpret_cast<OrthancJob*>(job)->progress_;
+    }
+    catch (...)
+    {
+      return 0;
+    }
+  }
+
+
+  const char* OrthancJob::CallbackGetContent(void* job)
+  {
+    assert(job != NULL);
+
+    try
+    {
+      return reinterpret_cast<OrthancJob*>(job)->content_.c_str();
+    }
+    catch (...)
+    {
+      return 0;
+    }
+  }
+
+
+  const char* OrthancJob::CallbackGetSerialized(void* job)
+  {
+    assert(job != NULL);
+
+    try
+    {
+      const OrthancJob& tmp = *reinterpret_cast<OrthancJob*>(job);
+
+      if (tmp.hasSerialized_)
+      {
+        return tmp.serialized_.c_str();
+      }
+      else
+      {
+        return NULL;
+      }
+    }
+    catch (...)
+    {
+      return 0;
+    }
+  }
+
+
+  OrthancPluginJobStepStatus OrthancJob::CallbackStep(void* job)
+  {
+    assert(job != NULL);
+
+    try
+    {
+      return reinterpret_cast<OrthancJob*>(job)->Step();
+    }
+    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS&)
+    {
+      return OrthancPluginJobStepStatus_Failure;
+    }
+    catch (...)
+    {
+      return OrthancPluginJobStepStatus_Failure;
+    }
+  }
+
+
+  OrthancPluginErrorCode OrthancJob::CallbackStop(void* job,
+                                                  OrthancPluginJobStopReason reason)
+  {
+    assert(job != NULL);
+
+    try
+    {
+      reinterpret_cast<OrthancJob*>(job)->Stop(reason);
+      return OrthancPluginErrorCode_Success;
+    }
+    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
+    {
+      return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
+    }
+    catch (...)
+    {
+      return OrthancPluginErrorCode_Plugin;
+    }
+  }
+
+
+  OrthancPluginErrorCode OrthancJob::CallbackReset(void* job)
+  {
+    assert(job != NULL);
+
+    try
+    {
+      reinterpret_cast<OrthancJob*>(job)->Reset();
+      return OrthancPluginErrorCode_Success;
+    }
+    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
+    {
+      return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
+    }
+    catch (...)
+    {
+      return OrthancPluginErrorCode_Plugin;
+    }
+  }
+
+
+  void OrthancJob::ClearContent()
+  {
+    Json::Value empty = Json::objectValue;
+    UpdateContent(empty);
+  }
+
+
+  void OrthancJob::UpdateContent(const Json::Value& content)
+  {
+    if (content.type() != Json::objectValue)
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_BadFileFormat);
+    }
+    else
+    {
+      WriteFastJson(content_, content);
+    }
+  }
+
+
+  void OrthancJob::ClearSerialized()
+  {
+    hasSerialized_ = false;
+    serialized_.clear();
+  }
+
+
+  void OrthancJob::UpdateSerialized(const Json::Value& serialized)
+  {
+    if (serialized.type() != Json::objectValue)
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_BadFileFormat);
+    }
+    else
+    {
+      WriteFastJson(serialized_, serialized);
+      hasSerialized_ = true;
+    }
+  }
+
+
+  void OrthancJob::UpdateProgress(float progress)
+  {
+    if (progress < 0 ||
+        progress > 1)
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
+    }
+
+    progress_ = progress;
+  }
+
+
+  OrthancJob::OrthancJob(const std::string& jobType) :
+    jobType_(jobType),
+    progress_(0)
+  {
+    ClearContent();
+    ClearSerialized();
+  }
+
+
+  OrthancPluginJob* OrthancJob::Create(OrthancJob* job)
+  {
+    if (job == NULL)
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_NullPointer);
+    }
+
+    OrthancPluginJob* orthanc = OrthancPluginCreateJob(
+      GetGlobalContext(), job, CallbackFinalize, job->jobType_.c_str(),
+      CallbackGetProgress, CallbackGetContent, CallbackGetSerialized,
+      CallbackStep, CallbackStop, CallbackReset);
+
+    if (orthanc == NULL)
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin);
+    }
+    else
+    {
+      return orthanc;
+    }
+  }
+
+
+  std::string OrthancJob::Submit(OrthancJob* job,
+                                 int priority)
+  {
+    if (job == NULL)
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_NullPointer);
+    }
+
+    OrthancPluginJob* orthanc = Create(job);
+
+    char* id = OrthancPluginSubmitJob(GetGlobalContext(), orthanc, priority);
+
+    if (id == NULL)
+    {
+      LogError("Plugin cannot submit job");
+      OrthancPluginFreeJob(GetGlobalContext(), orthanc);
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin);
+    }
+    else
+    {
+      std::string tmp(id);
+      tmp.assign(id);
+      OrthancPluginFreeString(GetGlobalContext(), id);
+
+      return tmp;
+    }
+  }
+
+
+  void OrthancJob::SubmitAndWait(Json::Value& result,
+                                 OrthancJob* job /* takes ownership */,
+                                 int priority)
+  {
+    std::string id = Submit(job, priority);
+
+    for (;;)
+    {
+      boost::this_thread::sleep(boost::posix_time::milliseconds(100));
+
+      Json::Value status;
+      if (!RestApiGet(status, "/jobs/" + id, false) ||
+          !status.isMember("State") ||
+          status["State"].type() != Json::stringValue)
+      {
+        ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_InexistentItem);        
+      }
+
+      const std::string state = status["State"].asString();
+      if (state == "Success")
+      {
+        if (status.isMember("Content"))
+        {
+          result = status["Content"];
+        }
+        else
+        {
+          result = Json::objectValue;
+        }
+
+        return;
+      }
+      else if (state == "Running")
+      {
+        continue;
+      }
+      else if (!status.isMember("ErrorCode") ||
+               status["ErrorCode"].type() != Json::intValue)
+      {
+        ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_InternalError);
+      }
+      else
+      {
+        if (!status.isMember("ErrorDescription") ||
+            status["ErrorDescription"].type() != Json::stringValue)
+        {
+          ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(status["ErrorCode"].asInt());
+        }
+        else
+        {
+#if HAS_ORTHANC_EXCEPTION == 1
+          throw Orthanc::OrthancException(static_cast<Orthanc::ErrorCode>(status["ErrorCode"].asInt()),
+                                          status["ErrorDescription"].asString());
+#else
+          LogError("Exception while executing the job: " + status["ErrorDescription"].asString());
+          ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(status["ErrorCode"].asInt());          
+#endif
+        }
+      }
+    }
+  }
+
+
+  void OrthancJob::SubmitFromRestApiPost(OrthancPluginRestOutput* output,
+                                         const Json::Value& body,
+                                         OrthancJob* job)
+  {
+    static const char* KEY_SYNCHRONOUS = "Synchronous";
+    static const char* KEY_ASYNCHRONOUS = "Asynchronous";
+    static const char* KEY_PRIORITY = "Priority";
+
+    boost::movelib::unique_ptr<OrthancJob> protection(job);
+  
+    if (body.type() != Json::objectValue)
+    {
+#if HAS_ORTHANC_EXCEPTION == 1
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
+                                      "Expected a JSON object in the body");
+#else
+      LogError("Expected a JSON object in the body");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+#endif
+    }
+
+    bool synchronous = true;
+  
+    if (body.isMember(KEY_SYNCHRONOUS))
+    {
+      if (body[KEY_SYNCHRONOUS].type() != Json::booleanValue)
+      {
+#if HAS_ORTHANC_EXCEPTION == 1
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
+                                        "Option \"" + std::string(KEY_SYNCHRONOUS) +
+                                        "\" must be Boolean");
+#else
+        LogError("Option \"" + std::string(KEY_SYNCHRONOUS) + "\" must be Boolean");
+        ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+#endif
+      }
+      else
+      {
+        synchronous = body[KEY_SYNCHRONOUS].asBool();
+      }
+    }
+
+    if (body.isMember(KEY_ASYNCHRONOUS))
+    {
+      if (body[KEY_ASYNCHRONOUS].type() != Json::booleanValue)
+      {
+#if HAS_ORTHANC_EXCEPTION == 1
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
+                                        "Option \"" + std::string(KEY_ASYNCHRONOUS) +
+                                        "\" must be Boolean");
+#else
+        LogError("Option \"" + std::string(KEY_ASYNCHRONOUS) + "\" must be Boolean");
+        ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+#endif
+      }
+      else
+      {
+        synchronous = !body[KEY_ASYNCHRONOUS].asBool();
+      }
+    }
+
+    int priority = 0;
+
+    if (body.isMember(KEY_PRIORITY))
+    {
+      if (body[KEY_PRIORITY].type() != Json::booleanValue)
+      {
+#if HAS_ORTHANC_EXCEPTION == 1
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
+                                        "Option \"" + std::string(KEY_PRIORITY) +
+                                        "\" must be an integer");
+#else
+        LogError("Option \"" + std::string(KEY_PRIORITY) + "\" must be an integer");
+        ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+#endif
+      }
+      else
+      {
+        priority = !body[KEY_PRIORITY].asInt();
+      }
+    }
+  
+    Json::Value result;
+
+    if (synchronous)
+    {
+      OrthancPlugins::OrthancJob::SubmitAndWait(result, protection.release(), priority);
+    }
+    else
+    {
+      std::string id = OrthancPlugins::OrthancJob::Submit(protection.release(), priority);
+
+      result = Json::objectValue;
+      result["ID"] = id;
+      result["Path"] = "/jobs/" + id;
+    }
+
+    std::string s = result.toStyledString();
+    OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, s.c_str(),
+                              s.size(), "application/json");
+  }
+
+#endif
+
+
+
+
+  /******************************************************************
+   ** METRICS
+   ******************************************************************/
+
+#if HAS_ORTHANC_PLUGIN_METRICS == 1
+  MetricsTimer::MetricsTimer(const char* name) :
+    name_(name)
+  {
+    start_ = boost::posix_time::microsec_clock::universal_time();
+  }
+  
+  MetricsTimer::~MetricsTimer()
+  {
+    const boost::posix_time::ptime stop = boost::posix_time::microsec_clock::universal_time();
+    const boost::posix_time::time_duration diff = stop - start_;
+    OrthancPluginSetMetricsValue(GetGlobalContext(), name_.c_str(), static_cast<float>(diff.total_milliseconds()),
+                                 OrthancPluginMetricsType_Timer);
+  }
+#endif
+
+
+
+
+  /******************************************************************
+   ** HTTP CLIENT
+   ******************************************************************/
+
+#if HAS_ORTHANC_PLUGIN_HTTP_CLIENT == 1
+  class HttpClient::RequestBodyWrapper : public boost::noncopyable
+  {
+  private:
+    static RequestBodyWrapper& GetObject(void* body)
+    {
+      assert(body != NULL);
+      return *reinterpret_cast<RequestBodyWrapper*>(body);
+    }
+
+    IRequestBody&  body_;
+    bool           done_;
+    std::string    chunk_;
+
+  public:
+    RequestBodyWrapper(IRequestBody& body) :
+      body_(body),
+      done_(false)
+    {
+    }      
+
+    static uint8_t IsDone(void* body)
+    {
+      return GetObject(body).done_;
+    }
+    
+    static const void* GetChunkData(void* body)
+    {
+      return GetObject(body).chunk_.c_str();
+    }
+    
+    static uint32_t GetChunkSize(void* body)
+    {
+      return static_cast<uint32_t>(GetObject(body).chunk_.size());
+    }
+
+    static OrthancPluginErrorCode Next(void* body)
+    {
+      RequestBodyWrapper& that = GetObject(body);
+        
+      if (that.done_)
+      {
+        return OrthancPluginErrorCode_BadSequenceOfCalls;
+      }
+      else
+      {
+        try
+        {
+          that.done_ = !that.body_.ReadNextChunk(that.chunk_);
+          return OrthancPluginErrorCode_Success;
+        }
+        catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
+        {
+          return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
+        }
+        catch (...)
+        {
+          return OrthancPluginErrorCode_Plugin;
+        }
+      }
+    }    
+  };
+
+
+#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1
+  static OrthancPluginErrorCode AnswerAddHeaderCallback(void* answer,
+                                                        const char* key,
+                                                        const char* value)
+  {
+    assert(answer != NULL && key != NULL && value != NULL);
+
+    try
+    {
+      reinterpret_cast<HttpClient::IAnswer*>(answer)->AddHeader(key, value);
+      return OrthancPluginErrorCode_Success;
+    }
+    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
+    {
+      return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
+    }
+    catch (...)
+    {
+      return OrthancPluginErrorCode_Plugin;
+    }
+  }
+#endif
+
+
+#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1
+  static OrthancPluginErrorCode AnswerAddChunkCallback(void* answer,
+                                                       const void* data,
+                                                       uint32_t size)
+  {
+    assert(answer != NULL);
+
+    try
+    {
+      reinterpret_cast<HttpClient::IAnswer*>(answer)->AddChunk(data, size);
+      return OrthancPluginErrorCode_Success;
+    }
+    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
+    {
+      return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
+    }
+    catch (...)
+    {
+      return OrthancPluginErrorCode_Plugin;
+    }
+  }
+#endif
+
+
+  HttpClient::HttpClient() :
+    httpStatus_(0),
+    method_(OrthancPluginHttpMethod_Get),
+    timeout_(0),
+    pkcs11_(false),
+    chunkedBody_(NULL),
+    allowChunkedTransfers_(true)
+  {
+  }
+
+
+  void HttpClient::AddHeaders(const HttpHeaders& headers)
+  {
+    for (HttpHeaders::const_iterator it = headers.begin();
+         it != headers.end(); ++it)
+    {
+      headers_[it->first] = it->second;
+    }
+  }
+
+  
+  void HttpClient::SetCredentials(const std::string& username,
+                                  const std::string& password)
+  {
+    username_ = username;
+    password_ = password;
+  }
+
+  
+  void HttpClient::ClearCredentials()
+  {
+    username_.clear();
+    password_.clear();
+  }
+
+
+  void HttpClient::SetCertificate(const std::string& certificateFile,
+                                  const std::string& keyFile,
+                                  const std::string& keyPassword)
+  {
+    certificateFile_ = certificateFile;
+    certificateKeyFile_ = keyFile;
+    certificateKeyPassword_ = keyPassword;
+  }
+
+  
+  void HttpClient::ClearCertificate()
+  {
+    certificateFile_.clear();
+    certificateKeyFile_.clear();
+    certificateKeyPassword_.clear();
+  }
+
+
+  void HttpClient::ClearBody()
+  {
+    fullBody_.clear();
+    chunkedBody_ = NULL;
+  }
+
+  
+  void HttpClient::SwapBody(std::string& body)
+  {
+    fullBody_.swap(body);
+    chunkedBody_ = NULL;
+  }
+
+  
+  void HttpClient::SetBody(const std::string& body)
+  {
+    fullBody_ = body;
+    chunkedBody_ = NULL;
+  }
+
+  
+  void HttpClient::SetBody(IRequestBody& body)
+  {
+    fullBody_.clear();
+    chunkedBody_ = &body;
+  }
+
+
+  namespace
+  {
+    class HeadersWrapper : public boost::noncopyable
+    {
+    private:
+      std::vector<const char*>  headersKeys_;
+      std::vector<const char*>  headersValues_;
+
+    public:
+      HeadersWrapper(const HttpClient::HttpHeaders& headers)
+      {
+        headersKeys_.reserve(headers.size());
+        headersValues_.reserve(headers.size());
+
+        for (HttpClient::HttpHeaders::const_iterator it = headers.begin(); it != headers.end(); ++it)
+        {
+          headersKeys_.push_back(it->first.c_str());
+          headersValues_.push_back(it->second.c_str());
+        }
+      }
+
+      void AddStaticString(const char* key,
+                           const char* value)
+      {
+        headersKeys_.push_back(key);
+        headersValues_.push_back(value);
+      }
+
+      uint32_t GetCount() const
+      {
+        return headersKeys_.size();
+      }
+
+      const char* const* GetKeys() const
+      {
+        return headersKeys_.empty() ? NULL : &headersKeys_[0];
+      }
+
+      const char* const* GetValues() const
+      {
+        return headersValues_.empty() ? NULL : &headersValues_[0];
+      }
+    };
+
+
+    class MemoryRequestBody : public HttpClient::IRequestBody
+    {
+    private:
+      std::string  body_;
+      bool         done_;
+
+    public:
+      MemoryRequestBody(const std::string& body) :
+        body_(body),
+        done_(false)
+      {
+        if (body_.empty())
+        {
+          done_ = true;
+        }
+      }
+
+      virtual bool ReadNextChunk(std::string& chunk)
+      {
+        if (done_)
+        {
+          return false;
+        }
+        else
+        {
+          chunk.swap(body_);
+          done_ = true;
+          return true;
+        }
+      }
+    };
+
+
+    // This class mimics Orthanc::ChunkedBuffer
+    class ChunkedBuffer : public boost::noncopyable
+    {
+    private:
+      typedef std::list<std::string*>  Content;
+
+      Content  content_;
+      size_t   size_;
+
+    public:
+      ChunkedBuffer() :
+        size_(0)
+      {
+      }
+
+      ~ChunkedBuffer()
+      {
+        Clear();
+      }
+
+      void Clear()
+      {
+        for (Content::iterator it = content_.begin(); it != content_.end(); ++it)
+        {
+          assert(*it != NULL);
+          delete *it;
+        }
+
+        size_ = 0;
+        content_.clear();
+      }
+
+      /**
+       * Since Orthanc 1.9.3, this function also clears the content of
+       * the ChunkedBuffer in order to mimic the behavior of the
+       * original class "Orthanc::ChunkedBuffer". This prevents the
+       * forgetting of calling "Clear()" in order to reduce memory
+       * consumption.
+       **/
+      void Flatten(std::string& target)
+      {
+        target.resize(size_);
+
+        size_t pos = 0;
+
+        for (Content::const_iterator it = content_.begin(); it != content_.end(); ++it)
+        {
+          assert(*it != NULL);
+          size_t s = (*it)->size();
+
+          if (s != 0)
+          {
+            memcpy(&target[pos], (*it)->c_str(), s);
+            pos += s;
+          }
+
+          delete *it;
+        }
+
+        assert(pos == target.size());
+
+        size_ = 0;
+        content_.clear();
+      }
+
+      void AddChunk(const void* data,
+                    size_t size)
+      {
+        content_.push_back(new std::string(reinterpret_cast<const char*>(data), size));
+        size_ += size;
+      }
+
+      void AddChunk(const std::string& chunk)
+      {
+        content_.push_back(new std::string(chunk));
+        size_ += chunk.size();
+      }
+    };
+
+
+#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1
+    class MemoryAnswer : public HttpClient::IAnswer
+    {
+    private:
+      HttpClient::HttpHeaders  headers_;
+      ChunkedBuffer            body_;
+
+    public:
+      const HttpClient::HttpHeaders& GetHeaders() const
+      {
+        return headers_;
+      }
+
+      ChunkedBuffer& GetBody()
+      {
+        return body_;
+      }
+
+      virtual void AddHeader(const std::string& key,
+                             const std::string& value)
+      {
+        headers_[key] = value;
+      }
+
+      virtual void AddChunk(const void* data,
+                            size_t size)
+      {
+        body_.AddChunk(data, size);
+      }
+    };
+#endif
+  }
+
+
+#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1
+  void HttpClient::ExecuteWithStream(uint16_t& httpStatus,
+                                     IAnswer& answer,
+                                     IRequestBody& body) const
+  {
+    HeadersWrapper h(headers_);
+
+    if (method_ == OrthancPluginHttpMethod_Post ||
+        method_ == OrthancPluginHttpMethod_Put)
+    {
+      // Automatically set the "Transfer-Encoding" header if absent
+      bool found = false;
+
+      for (HttpHeaders::const_iterator it = headers_.begin(); it != headers_.end(); ++it)
+      {
+        if (boost::iequals(it->first, "Transfer-Encoding"))
+        {
+          found = true;
+          break;
+        }
+      }
+
+      if (!found)
+      {
+        h.AddStaticString("Transfer-Encoding", "chunked");
+      }
+    }
+
+    RequestBodyWrapper request(body);
+        
+    OrthancPluginErrorCode error = OrthancPluginChunkedHttpClient(
+      GetGlobalContext(),
+      &answer,
+      AnswerAddChunkCallback,
+      AnswerAddHeaderCallback,
+      &httpStatus,
+      method_,
+      url_.c_str(),
+      h.GetCount(),
+      h.GetKeys(),
+      h.GetValues(),
+      &request,
+      RequestBodyWrapper::IsDone,
+      RequestBodyWrapper::GetChunkData,
+      RequestBodyWrapper::GetChunkSize,
+      RequestBodyWrapper::Next,
+      username_.empty() ? NULL : username_.c_str(),
+      password_.empty() ? NULL : password_.c_str(),
+      timeout_,
+      certificateFile_.empty() ? NULL : certificateFile_.c_str(),
+      certificateFile_.empty() ? NULL : certificateKeyFile_.c_str(),
+      certificateFile_.empty() ? NULL : certificateKeyPassword_.c_str(),
+      pkcs11_ ? 1 : 0);
+
+    if (error != OrthancPluginErrorCode_Success)
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error);
+    }
+  }
+#endif    
+
+
+  void HttpClient::ExecuteWithoutStream(uint16_t& httpStatus,
+                                        HttpHeaders& answerHeaders,
+                                        std::string& answerBody,
+                                        const std::string& body) const
+  {
+    HeadersWrapper headers(headers_);
+
+    MemoryBuffer answerBodyBuffer, answerHeadersBuffer;
+
+    if (body.size() > 0xffffffffu)
+    {
+      LogError("Cannot handle body size > 4GB");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+    }
+
+    OrthancPluginErrorCode error = OrthancPluginHttpClient(
+      GetGlobalContext(),
+      *answerBodyBuffer,
+      *answerHeadersBuffer,
+      &httpStatus,
+      method_,
+      url_.c_str(),
+      headers.GetCount(),
+      headers.GetKeys(),
+      headers.GetValues(),
+      body.empty() ? NULL : body.c_str(),
+      body.size(),
+      username_.empty() ? NULL : username_.c_str(),
+      password_.empty() ? NULL : password_.c_str(),
+      timeout_,
+      certificateFile_.empty() ? NULL : certificateFile_.c_str(),
+      certificateFile_.empty() ? NULL : certificateKeyFile_.c_str(),
+      certificateFile_.empty() ? NULL : certificateKeyPassword_.c_str(),
+      pkcs11_ ? 1 : 0);
+
+    if (error != OrthancPluginErrorCode_Success)
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error);
+    }
+
+    Json::Value v;
+    answerHeadersBuffer.ToJson(v);
+
+    if (v.type() != Json::objectValue)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+    }
+
+    Json::Value::Members members = v.getMemberNames();
+    answerHeaders.clear();
+
+    for (size_t i = 0; i < members.size(); i++)
+    {
+      const Json::Value& h = v[members[i]];
+      if (h.type() != Json::stringValue)
+      {
+        ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+      }
+      else
+      {
+        answerHeaders[members[i]] = h.asString();
+      }
+    }
+
+    answerBodyBuffer.ToString(answerBody);
+  }
+
+
+  void HttpClient::Execute(IAnswer& answer)
+  {
+#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1
+    if (allowChunkedTransfers_)
+    {
+      if (chunkedBody_ != NULL)
+      {
+        ExecuteWithStream(httpStatus_, answer, *chunkedBody_);
+      }
+      else
+      {
+        MemoryRequestBody wrapper(fullBody_);
+        ExecuteWithStream(httpStatus_, answer, wrapper);
+      }
+
+      return;
+    }
+#endif
+    
+    // Compatibility mode for Orthanc SDK <= 1.5.6 or if chunked
+    // transfers are disabled. This results in higher memory usage
+    // (all chunks from the answer body are sent at once)
+
+    HttpHeaders answerHeaders;
+    std::string answerBody;
+    Execute(answerHeaders, answerBody);
+
+    for (HttpHeaders::const_iterator it = answerHeaders.begin(); 
+         it != answerHeaders.end(); ++it)
+    {
+      answer.AddHeader(it->first, it->second);      
+    }
+
+    if (!answerBody.empty())
+    {
+      answer.AddChunk(answerBody.c_str(), answerBody.size());
+    }
+  }
+
+
+  void HttpClient::Execute(HttpHeaders& answerHeaders /* out */,
+                           std::string& answerBody /* out */)
+  {
+#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1
+    if (allowChunkedTransfers_)
+    {
+      MemoryAnswer answer;
+      Execute(answer);
+      answerHeaders = answer.GetHeaders();
+      answer.GetBody().Flatten(answerBody);
+      return;
+    }
+#endif
+    
+    // Compatibility mode for Orthanc SDK <= 1.5.6 or if chunked
+    // transfers are disabled. This results in higher memory usage
+    // (all chunks from the request body are sent at once)
+
+    if (chunkedBody_ != NULL)
+    {
+      ChunkedBuffer buffer;
+      
+      std::string chunk;
+      while (chunkedBody_->ReadNextChunk(chunk))
+      {
+        buffer.AddChunk(chunk);
+      }
+
+      std::string body;
+      buffer.Flatten(body);
+
+      ExecuteWithoutStream(httpStatus_, answerHeaders, answerBody, body);
+    }
+    else
+    {
+      ExecuteWithoutStream(httpStatus_, answerHeaders, answerBody, fullBody_);
+    }
+  }
+
+
+  void HttpClient::Execute(HttpHeaders& answerHeaders /* out */,
+                           Json::Value& answerBody /* out */)
+  {
+    std::string body;
+    Execute(answerHeaders, body);
+    
+    if (!ReadJson(answerBody, body))
+    {
+      LogError("Cannot convert HTTP answer body to JSON");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+    }
+  }
+
+
+  void HttpClient::Execute()
+  {
+    HttpHeaders answerHeaders;
+    std::string body;
+    Execute(answerHeaders, body);
+  }
+
+#endif  /* HAS_ORTHANC_PLUGIN_HTTP_CLIENT == 1 */
+
+
+
+
+
+  /******************************************************************
+   ** CHUNKED HTTP SERVER
+   ******************************************************************/
+
+  namespace Internals
+  {
+    void NullRestCallback(OrthancPluginRestOutput* output,
+                          const char* url,
+                          const OrthancPluginHttpRequest* request)
+    {
+    }
+  
+    IChunkedRequestReader *NullChunkedRestCallback(const char* url,
+                                                   const OrthancPluginHttpRequest* request)
+    {
+      return NULL;
+    }
+
+
+#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_SERVER == 1
+
+    OrthancPluginErrorCode ChunkedRequestReaderAddChunk(
+      OrthancPluginServerChunkedRequestReader* reader,
+      const void*                              data,
+      uint32_t                                 size)
+    {
+      try
+      {
+        if (reader == NULL)
+        {
+          return OrthancPluginErrorCode_InternalError;
+        }
+
+        reinterpret_cast<IChunkedRequestReader*>(reader)->AddChunk(data, size);
+        return OrthancPluginErrorCode_Success;
+      }
+      catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
+      {
+        return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
+      }
+      catch (boost::bad_lexical_cast&)
+      {
+        return OrthancPluginErrorCode_BadFileFormat;
+      }
+      catch (...)
+      {
+        return OrthancPluginErrorCode_Plugin;
+      }
+    }
+
+    
+    OrthancPluginErrorCode ChunkedRequestReaderExecute(
+      OrthancPluginServerChunkedRequestReader* reader,
+      OrthancPluginRestOutput*                 output)
+    {
+      try
+      {
+        if (reader == NULL)
+        {
+          return OrthancPluginErrorCode_InternalError;
+        }
+
+        reinterpret_cast<IChunkedRequestReader*>(reader)->Execute(output);
+        return OrthancPluginErrorCode_Success;
+      }
+      catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
+      {
+        return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
+      }
+      catch (boost::bad_lexical_cast&)
+      {
+        return OrthancPluginErrorCode_BadFileFormat;
+      }
+      catch (...)
+      {
+        return OrthancPluginErrorCode_Plugin;
+      }
+    }
+
+    
+    void ChunkedRequestReaderFinalize(
+      OrthancPluginServerChunkedRequestReader* reader)
+    {
+      if (reader != NULL)
+      {
+        delete reinterpret_cast<IChunkedRequestReader*>(reader);
+      }
+    }
+
+#else
+    
+    OrthancPluginErrorCode ChunkedRestCompatibility(OrthancPluginRestOutput* output,
+                                                    const char* url,
+                                                    const OrthancPluginHttpRequest* request,
+                                                    RestCallback         GetHandler,
+                                                    ChunkedRestCallback  PostHandler,
+                                                    RestCallback         DeleteHandler,
+                                                    ChunkedRestCallback  PutHandler)
+    {
+      try
+      {
+        std::string allowed;
+
+        if (GetHandler != Internals::NullRestCallback)
+        {
+          allowed += "GET";
+        }
+
+        if (PostHandler != Internals::NullChunkedRestCallback)
+        {
+          if (!allowed.empty())
+          {
+            allowed += ",";
+          }
+        
+          allowed += "POST";
+        }
+
+        if (DeleteHandler != Internals::NullRestCallback)
+        {
+          if (!allowed.empty())
+          {
+            allowed += ",";
+          }
+        
+          allowed += "DELETE";
+        }
+
+        if (PutHandler != Internals::NullChunkedRestCallback)
+        {
+          if (!allowed.empty())
+          {
+            allowed += ",";
+          }
+        
+          allowed += "PUT";
+        }
+      
+        switch (request->method)
+        {
+          case OrthancPluginHttpMethod_Get:
+            if (GetHandler == Internals::NullRestCallback)
+            {
+              OrthancPluginSendMethodNotAllowed(GetGlobalContext(), output, allowed.c_str());
+            }
+            else
+            {
+              GetHandler(output, url, request);
+            }
+
+            break;
+
+          case OrthancPluginHttpMethod_Post:
+            if (PostHandler == Internals::NullChunkedRestCallback)
+            {
+              OrthancPluginSendMethodNotAllowed(GetGlobalContext(), output, allowed.c_str());
+            }
+            else
+            {
+              boost::movelib::unique_ptr<IChunkedRequestReader> reader(PostHandler(url, request));
+              if (reader.get() == NULL)
+              {
+                ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin);
+              }
+              else
+              {
+                reader->AddChunk(request->body, request->bodySize);
+                reader->Execute(output);
+              }
+            }
+
+            break;
+
+          case OrthancPluginHttpMethod_Delete:
+            if (DeleteHandler == Internals::NullRestCallback)
+            {
+              OrthancPluginSendMethodNotAllowed(GetGlobalContext(), output, allowed.c_str());
+            }
+            else
+            {
+              DeleteHandler(output, url, request);
+            }
+
+            break;
+
+          case OrthancPluginHttpMethod_Put:
+            if (PutHandler == Internals::NullChunkedRestCallback)
+            {
+              OrthancPluginSendMethodNotAllowed(GetGlobalContext(), output, allowed.c_str());
+            }
+            else
+            {
+              boost::movelib::unique_ptr<IChunkedRequestReader> reader(PutHandler(url, request));
+              if (reader.get() == NULL)
+              {
+                ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin);
+              }
+              else
+              {
+                reader->AddChunk(request->body, request->bodySize);
+                reader->Execute(output);
+              }
+            }
+
+            break;
+
+          default:
+            ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+        }
+
+        return OrthancPluginErrorCode_Success;
+      }
+      catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
+      {
+#if HAS_ORTHANC_EXCEPTION == 1 && HAS_ORTHANC_PLUGIN_EXCEPTION_DETAILS == 1
+        if (HasGlobalContext() &&
+            e.HasDetails())
+        {
+          // The "false" instructs Orthanc not to log the detailed
+          // error message. This is to avoid duplicating the details,
+          // because "OrthancException" already does it on construction.
+          OrthancPluginSetHttpErrorDetails
+            (GetGlobalContext(), output, e.GetDetails(), false);
+        }
+#endif
+
+        return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
+      }
+      catch (boost::bad_lexical_cast&)
+      {
+        return OrthancPluginErrorCode_BadFileFormat;
+      }
+      catch (...)
+      {
+        return OrthancPluginErrorCode_Plugin;
+      }
+    }
+#endif
+  }
+
+
+#if HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP == 1
+  OrthancPluginErrorCode IStorageCommitmentScpHandler::Lookup(
+    OrthancPluginStorageCommitmentFailureReason* target,
+    void* rawHandler,
+    const char* sopClassUid,
+    const char* sopInstanceUid)
+  {
+    assert(target != NULL &&
+           rawHandler != NULL);
+      
+    try
+    {
+      IStorageCommitmentScpHandler& handler = *reinterpret_cast<IStorageCommitmentScpHandler*>(rawHandler);
+      *target = handler.Lookup(sopClassUid, sopInstanceUid);
+      return OrthancPluginErrorCode_Success;
+    }
+    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
+    {
+      return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
+    }
+    catch (...)
+    {
+      return OrthancPluginErrorCode_Plugin;
+    }
+  }
+#endif
+
+
+#if HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP == 1
+  void IStorageCommitmentScpHandler::Destructor(void* rawHandler)
+  {
+    assert(rawHandler != NULL);
+    delete reinterpret_cast<IStorageCommitmentScpHandler*>(rawHandler);
+  }
+#endif
+
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1)    
+  DicomInstance::DicomInstance(const OrthancPluginDicomInstance* instance) :
+    toFree_(false),
+    instance_(instance)
+  {
+  }
+#else
+  DicomInstance::DicomInstance(OrthancPluginDicomInstance* instance) :
+    toFree_(false),
+    instance_(instance)
+  {
+  }
+#endif
+
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+  DicomInstance::DicomInstance(const void* buffer,
+                               size_t size) :
+    toFree_(true),
+    instance_(OrthancPluginCreateDicomInstance(GetGlobalContext(), buffer, size))
+  {
+    if (instance_ == NULL)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(NullPointer);
+    }
+  }
+#endif
+
+
+  DicomInstance::~DicomInstance()
+  {
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+    if (toFree_ &&
+        instance_ != NULL)
+    {
+      OrthancPluginFreeDicomInstance(
+        GetGlobalContext(), const_cast<OrthancPluginDicomInstance*>(instance_));
+    }
+#endif
+  }
+
+  
+  std::string DicomInstance::GetRemoteAet() const
+  {
+    const char* s = OrthancPluginGetInstanceRemoteAet(GetGlobalContext(), instance_);
+    if (s == NULL)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin);
+    }
+    else
+    {
+      return std::string(s);
+    }
+  }
+
+
+  void DicomInstance::GetJson(Json::Value& target) const
+  {
+    OrthancString s;
+    s.Assign(OrthancPluginGetInstanceJson(GetGlobalContext(), instance_));
+    s.ToJson(target);
+  }
+  
+
+  void DicomInstance::GetSimplifiedJson(Json::Value& target) const
+  {
+    OrthancString s;
+    s.Assign(OrthancPluginGetInstanceSimplifiedJson(GetGlobalContext(), instance_));
+    s.ToJson(target);
+  }
+
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1)
+  std::string DicomInstance::GetTransferSyntaxUid() const
+  {
+    OrthancString s;
+    s.Assign(OrthancPluginGetInstanceTransferSyntaxUid(GetGlobalContext(), instance_));
+
+    std::string result;
+    s.ToString(result);
+    return result;
+  }
+#endif
+
+  
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1)
+  bool DicomInstance::HasPixelData() const
+  {
+    int32_t result = OrthancPluginHasInstancePixelData(GetGlobalContext(), instance_);
+    if (result < 0)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin);
+    }
+    else
+    {
+      return (result != 0);
+    }
+  }
+#endif
+
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)  
+  void DicomInstance::GetRawFrame(std::string& target,
+                                  unsigned int frameIndex) const
+  {
+    MemoryBuffer buffer;
+    OrthancPluginErrorCode code = OrthancPluginGetInstanceRawFrame(
+      GetGlobalContext(), *buffer, instance_, frameIndex);
+
+    if (code == OrthancPluginErrorCode_Success)
+    {
+      buffer.ToString(target);
+    }
+    else
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code);
+    }
+  }
+#endif
+
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)  
+  OrthancImage* DicomInstance::GetDecodedFrame(unsigned int frameIndex) const
+  {
+    OrthancPluginImage* image = OrthancPluginGetInstanceDecodedFrame(
+      GetGlobalContext(), instance_, frameIndex);
+
+    if (image == NULL)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin);
+    }
+    else
+    {
+      return new OrthancImage(image);
+    }
+  }
+#endif  
+
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+  void DicomInstance::Serialize(std::string& target) const
+  {
+    MemoryBuffer buffer;
+    OrthancPluginErrorCode code = OrthancPluginSerializeDicomInstance(
+      GetGlobalContext(), *buffer, instance_);
+
+    if (code == OrthancPluginErrorCode_Success)
+    {
+      buffer.ToString(target);
+    }
+    else
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code);
+    }
+  }
+#endif
+  
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+  DicomInstance* DicomInstance::Transcode(const void* buffer,
+                                          size_t size,
+                                          const std::string& transferSyntax)
+  {
+    OrthancPluginDicomInstance* instance = OrthancPluginTranscodeDicomInstance(
+      GetGlobalContext(), buffer, size, transferSyntax.c_str());
+
+    if (instance == NULL)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin);
+    }
+    else
+    {
+      boost::movelib::unique_ptr<DicomInstance> result(new DicomInstance(instance));
+      result->toFree_ = true;
+      return result.release();
+    }
+  }
+#endif
+
+
+#if HAS_ORTHANC_PLUGIN_WEBDAV == 1
+  static std::vector<std::string> WebDavConvertPath(uint32_t pathSize,
+                                                    const char* const*  pathItems)
+  {
+    std::vector<std::string> result(pathSize);
+
+    for (uint32_t i = 0; i < pathSize; i++)
+    {
+      result[i] = pathItems[i];
+    }
+
+    return result;
+  }
+#endif
+  
+    
+#if HAS_ORTHANC_PLUGIN_WEBDAV == 1
+  static OrthancPluginErrorCode WebDavIsExistingFolder(uint8_t*            isExisting,
+                                                       uint32_t            pathSize,
+                                                       const char* const*  pathItems,
+                                                       void*               payload)
+  {
+    IWebDavCollection& that = *reinterpret_cast<IWebDavCollection*>(payload);
+
+    try
+    {
+      *isExisting = (that.IsExistingFolder(WebDavConvertPath(pathSize, pathItems)) ? 1 : 0);
+      return OrthancPluginErrorCode_Success;
+    }
+    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
+    {
+      return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
+    }
+    catch (...)
+    {
+      return OrthancPluginErrorCode_Plugin;
+    }
+  }
+#endif
+
+  
+#if HAS_ORTHANC_PLUGIN_WEBDAV == 1
+  static OrthancPluginErrorCode WebDavListFolder(uint8_t*                        isExisting,
+                                                 OrthancPluginWebDavCollection*  collection,
+                                                 OrthancPluginWebDavAddFile      addFile,
+                                                 OrthancPluginWebDavAddFolder    addFolder,
+                                                 uint32_t                        pathSize,
+                                                 const char* const*              pathItems,
+                                                 void*                           payload)
+  {
+    IWebDavCollection& that = *reinterpret_cast<IWebDavCollection*>(payload);
+      
+    try
+    {
+      std::list<IWebDavCollection::FileInfo> files;
+      std::list<IWebDavCollection::FolderInfo> subfolders;
+      
+      if (!that.ListFolder(files, subfolders, WebDavConvertPath(pathSize, pathItems)))
+      {
+        *isExisting = 0;
+      }
+      else
+      {
+        *isExisting = 1;
+      
+        for (std::list<IWebDavCollection::FileInfo>::const_iterator
+               it = files.begin(); it != files.end(); ++it)
+        {
+          OrthancPluginErrorCode code = addFile(
+            collection, it->GetName().c_str(), it->GetContentSize(),
+            it->GetMimeType().c_str(), it->GetDateTime().c_str());
+        
+          if (code != OrthancPluginErrorCode_Success)
+          {
+            return code;
+          }
+        }
+      
+        for (std::list<IWebDavCollection::FolderInfo>::const_iterator it =
+               subfolders.begin(); it != subfolders.end(); ++it)
+        {
+          OrthancPluginErrorCode code = addFolder(
+            collection, it->GetName().c_str(), it->GetDateTime().c_str());
+        
+          if (code != OrthancPluginErrorCode_Success)
+          {
+            return code;
+          }
+        }
+      }
+      
+      return OrthancPluginErrorCode_Success;
+    }
+    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
+    {
+      return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
+    }
+    catch (...)
+    {
+      return OrthancPluginErrorCode_Plugin;
+    }
+  }
+#endif    
+
+
+#if HAS_ORTHANC_PLUGIN_WEBDAV == 1
+  static OrthancPluginErrorCode WebDavRetrieveFile(OrthancPluginWebDavCollection*   collection,
+                                                   OrthancPluginWebDavRetrieveFile  retrieveFile,
+                                                   uint32_t                         pathSize,
+                                                   const char* const*               pathItems,
+                                                   void*                            payload)
+  {
+    IWebDavCollection& that = *reinterpret_cast<IWebDavCollection*>(payload);
+
+    try
+    {
+      std::string content, mime, dateTime;
+        
+      if (that.GetFile(content, mime, dateTime, WebDavConvertPath(pathSize, pathItems)))
+      {
+        return retrieveFile(collection, content.empty() ? NULL : content.c_str(),
+                            content.size(), mime.c_str(), dateTime.c_str());
+      }
+      else
+      {
+        // Inexisting file
+        return OrthancPluginErrorCode_Success;
+      }
+    }
+    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
+    {
+      return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
+    }
+    catch (...)
+    {
+      return OrthancPluginErrorCode_InternalError;
+    }
+  }  
+#endif
+
+
+#if HAS_ORTHANC_PLUGIN_WEBDAV == 1
+  static OrthancPluginErrorCode WebDavStoreFileCallback(uint8_t*            isReadOnly, /* out */
+                                                        uint32_t            pathSize,
+                                                        const char* const*  pathItems,
+                                                        const void*         data,
+                                                        uint64_t            size,
+                                                        void*               payload)
+  {
+    IWebDavCollection& that = *reinterpret_cast<IWebDavCollection*>(payload);
+
+    try
+    {
+      if (static_cast<uint64_t>(static_cast<size_t>(size)) != size)
+      {
+        ORTHANC_PLUGINS_THROW_EXCEPTION(NotEnoughMemory);
+      }
+      
+      *isReadOnly = (that.StoreFile(WebDavConvertPath(pathSize, pathItems), data,
+                                    static_cast<size_t>(size)) ? 1 : 0);
+      return OrthancPluginErrorCode_Success;
+    }
+    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
+    {
+      return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
+    }
+    catch (...)
+    {
+      return OrthancPluginErrorCode_InternalError;
+    }
+  }
+#endif
+
+  
+#if HAS_ORTHANC_PLUGIN_WEBDAV == 1
+  static OrthancPluginErrorCode WebDavCreateFolderCallback(uint8_t*            isReadOnly, /* out */
+                                                           uint32_t            pathSize,
+                                                           const char* const*  pathItems,
+                                                           void*               payload)
+  {
+    IWebDavCollection& that = *reinterpret_cast<IWebDavCollection*>(payload);
+
+    try
+    {
+      *isReadOnly = (that.CreateFolder(WebDavConvertPath(pathSize, pathItems)) ? 1 : 0);
+      return OrthancPluginErrorCode_Success;
+    }
+    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
+    {
+      return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
+    }
+    catch (...)
+    {
+      return OrthancPluginErrorCode_InternalError;
+    }
+  }
+#endif
+  
+  
+#if HAS_ORTHANC_PLUGIN_WEBDAV == 1
+  static OrthancPluginErrorCode WebDavDeleteItemCallback(uint8_t*            isReadOnly, /* out */
+                                                         uint32_t            pathSize,
+                                                         const char* const*  pathItems,
+                                                         void*               payload)
+  {
+    IWebDavCollection& that = *reinterpret_cast<IWebDavCollection*>(payload);
+
+    try
+    {
+      *isReadOnly = (that.DeleteItem(WebDavConvertPath(pathSize, pathItems)) ? 1 : 0);
+      return OrthancPluginErrorCode_Success;
+    }
+    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
+    {
+      return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
+    }
+    catch (...)
+    {
+      return OrthancPluginErrorCode_InternalError;
+    }
+  }
+#endif
+
+  
+#if HAS_ORTHANC_PLUGIN_WEBDAV == 1
+  void IWebDavCollection::Register(const std::string& uri,
+                                   IWebDavCollection& collection)
+  {
+    OrthancPluginErrorCode code = OrthancPluginRegisterWebDavCollection(
+      GetGlobalContext(), uri.c_str(), WebDavIsExistingFolder, WebDavListFolder, WebDavRetrieveFile,
+      WebDavStoreFileCallback, WebDavCreateFolderCallback, WebDavDeleteItemCallback, &collection);
+
+    if (code != OrthancPluginErrorCode_Success)
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code);
+    }
+  }
+#endif
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h	Fri Apr 22 17:58:59 2022 +0200
@@ -0,0 +1,1360 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2022 Osimis S.A., Belgium
+ * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "OrthancPluginException.h"
+
+#include <orthanc/OrthancCPlugin.h>
+#include <boost/noncopyable.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <json/value.h>
+#include <vector>
+#include <list>
+#include <set>
+#include <map>
+
+
+
+/**
+ * The definition of ORTHANC_PLUGINS_VERSION_IS_ABOVE below is for
+ * backward compatibility with Orthanc SDK <= 1.3.0.
+ * 
+ *   $ hg diff -r Orthanc-1.3.0:Orthanc-1.3.1 ../../../Plugins/Include/orthanc/OrthancCPlugin.h
+ *
+ **/
+#if !defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE)
+#define ORTHANC_PLUGINS_VERSION_IS_ABOVE(major, minor, revision)        \
+  (ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER > major ||                      \
+   (ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER == major &&                    \
+    (ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER > minor ||                    \
+     (ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER == minor &&                  \
+      ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER >= revision))))
+#endif
+
+
+#if !defined(ORTHANC_FRAMEWORK_VERSION_IS_ABOVE)
+#define ORTHANC_FRAMEWORK_VERSION_IS_ABOVE(major, minor, revision)      \
+  (ORTHANC_VERSION_MAJOR > major ||                                     \
+   (ORTHANC_VERSION_MAJOR == major &&                                   \
+    (ORTHANC_VERSION_MINOR > minor ||                                   \
+     (ORTHANC_VERSION_MINOR == minor &&                                 \
+      ORTHANC_VERSION_REVISION >= revision))))
+#endif
+
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 2, 0)
+// The "OrthancPluginFindMatcher()" primitive was introduced in Orthanc 1.2.0
+#  define HAS_ORTHANC_PLUGIN_FIND_MATCHER  1
+#else
+#  define HAS_ORTHANC_PLUGIN_FIND_MATCHER  0
+#endif
+
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 4, 2)
+#  define HAS_ORTHANC_PLUGIN_PEERS  1
+#  define HAS_ORTHANC_PLUGIN_JOB    1
+#else
+#  define HAS_ORTHANC_PLUGIN_PEERS  0
+#  define HAS_ORTHANC_PLUGIN_JOB    0
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 0)
+#  define HAS_ORTHANC_PLUGIN_EXCEPTION_DETAILS  1
+#else
+#  define HAS_ORTHANC_PLUGIN_EXCEPTION_DETAILS  0
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 4)
+#  define HAS_ORTHANC_PLUGIN_METRICS  1
+#else
+#  define HAS_ORTHANC_PLUGIN_METRICS  0
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 1, 0)
+#  define HAS_ORTHANC_PLUGIN_HTTP_CLIENT  1
+#else
+#  define HAS_ORTHANC_PLUGIN_HTTP_CLIENT  0
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 7)
+#  define HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT  1
+#else
+#  define HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT  0
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 7)
+#  define HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_SERVER  1
+#else
+#  define HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_SERVER  0
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 0)
+#  define HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP  1
+#else
+#  define HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP  0
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 10, 1)
+#  define HAS_ORTHANC_PLUGIN_WEBDAV  1
+#else
+#  define HAS_ORTHANC_PLUGIN_WEBDAV  0
+#endif
+
+
+
+namespace OrthancPlugins
+{
+  typedef void (*RestCallback) (OrthancPluginRestOutput* output,
+                                const char* url,
+                                const OrthancPluginHttpRequest* request);
+
+  void SetGlobalContext(OrthancPluginContext* context);
+
+  bool HasGlobalContext();
+
+  OrthancPluginContext* GetGlobalContext();
+
+  
+  class OrthancImage;
+
+
+  class MemoryBuffer : public boost::noncopyable
+  {
+  private:
+    OrthancPluginMemoryBuffer  buffer_;
+
+    void Check(OrthancPluginErrorCode code);
+
+    bool CheckHttp(OrthancPluginErrorCode code);
+
+  public:
+    MemoryBuffer();
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+    // This constructor makes a copy of the given buffer in the memory
+    // handled by the Orthanc core
+    MemoryBuffer(const void* buffer,
+                 size_t size);
+#endif
+
+    ~MemoryBuffer()
+    {
+      Clear();
+    }
+
+    OrthancPluginMemoryBuffer* operator*()
+    {
+      return &buffer_;
+    }
+
+    // This transfers ownership from "other" to "this"
+    void Assign(OrthancPluginMemoryBuffer& other);
+
+    void Swap(MemoryBuffer& other);
+
+    OrthancPluginMemoryBuffer Release();
+
+    const char* GetData() const
+    {
+      if (buffer_.size > 0)
+      {
+        return reinterpret_cast<const char*>(buffer_.data);
+      }
+      else
+      {
+        return NULL;
+      }
+    }
+
+    size_t GetSize() const
+    {
+      return buffer_.size;
+    }
+
+    bool IsEmpty() const
+    {
+      return GetSize() == 0 || GetData() == NULL;
+    }
+
+    void Clear();
+
+    void ToString(std::string& target) const;
+
+    void ToJson(Json::Value& target) const;
+
+    bool RestApiGet(const std::string& uri,
+                    bool applyPlugins);
+
+    bool RestApiGet(const std::string& uri,
+                    const std::map<std::string, std::string>& httpHeaders,
+                    bool applyPlugins);
+
+    bool RestApiPost(const std::string& uri,
+                     const void* body,
+                     size_t bodySize,
+                     bool applyPlugins);
+
+    bool RestApiPut(const std::string& uri,
+                    const void* body,
+                    size_t bodySize,
+                    bool applyPlugins);
+
+    bool RestApiPost(const std::string& uri,
+                     const Json::Value& body,
+                     bool applyPlugins);
+
+    bool RestApiPut(const std::string& uri,
+                    const Json::Value& body,
+                    bool applyPlugins);
+
+    bool RestApiPost(const std::string& uri,
+                     const std::string& body,
+                     bool applyPlugins)
+    {
+      return RestApiPost(uri, body.empty() ? NULL : body.c_str(), body.size(), applyPlugins);
+    }
+
+    bool RestApiPut(const std::string& uri,
+                    const std::string& body,
+                    bool applyPlugins)
+    {
+      return RestApiPut(uri, body.empty() ? NULL : body.c_str(), body.size(), applyPlugins);
+    }
+
+    void CreateDicom(const Json::Value& tags,
+                     OrthancPluginCreateDicomFlags flags);
+
+    void CreateDicom(const Json::Value& tags,
+                     const OrthancImage& pixelData,
+                     OrthancPluginCreateDicomFlags flags);
+
+    void ReadFile(const std::string& path);
+
+    void GetDicomQuery(const OrthancPluginWorklistQuery* query);
+
+    void DicomToJson(Json::Value& target,
+                     OrthancPluginDicomToJsonFormat format,
+                     OrthancPluginDicomToJsonFlags flags,
+                     uint32_t maxStringLength);
+
+    bool HttpGet(const std::string& url,
+                 const std::string& username,
+                 const std::string& password);
+
+    bool HttpPost(const std::string& url,
+                  const std::string& body,
+                  const std::string& username,
+                  const std::string& password);
+
+    bool HttpPut(const std::string& url,
+                 const std::string& body,
+                 const std::string& username,
+                 const std::string& password);
+
+    void GetDicomInstance(const std::string& instanceId);
+  };
+
+
+  class OrthancString : public boost::noncopyable
+  {
+  private:
+    char*   str_;
+
+    void Clear();
+
+  public:
+    OrthancString() :
+      str_(NULL)
+    {
+    }
+
+    ~OrthancString()
+    {
+      Clear();
+    }
+
+    // This transfers ownership, warning: The string must have been
+    // allocated by the Orthanc core
+    void Assign(char* str);
+
+    const char* GetContent() const
+    {
+      return str_;
+    }
+
+    void ToString(std::string& target) const;
+
+    void ToJson(Json::Value& target) const;
+  
+    void ToJsonWithoutComments(Json::Value& target) const;
+};
+
+
+  class OrthancConfiguration : public boost::noncopyable
+  {
+  private:
+    Json::Value  configuration_;  // Necessarily a Json::objectValue
+    std::string  path_;
+
+    std::string GetPath(const std::string& key) const;
+
+    void LoadConfiguration();
+    
+  public:
+    OrthancConfiguration();
+
+    explicit OrthancConfiguration(bool load);
+
+    const Json::Value& GetJson() const
+    {
+      return configuration_;
+    }
+
+    bool IsSection(const std::string& key) const;
+
+    void GetSection(OrthancConfiguration& target,
+                    const std::string& key) const;
+
+    bool LookupStringValue(std::string& target,
+                           const std::string& key) const;
+    
+    bool LookupIntegerValue(int& target,
+                            const std::string& key) const;
+
+    bool LookupUnsignedIntegerValue(unsigned int& target,
+                                    const std::string& key) const;
+
+    bool LookupBooleanValue(bool& target,
+                            const std::string& key) const;
+
+    bool LookupFloatValue(float& target,
+                          const std::string& key) const;
+
+    bool LookupListOfStrings(std::list<std::string>& target,
+                             const std::string& key,
+                             bool allowSingleString) const;
+
+    bool LookupSetOfStrings(std::set<std::string>& target,
+                            const std::string& key,
+                            bool allowSingleString) const;
+
+    std::string GetStringValue(const std::string& key,
+                               const std::string& defaultValue) const;
+
+    int GetIntegerValue(const std::string& key,
+                        int defaultValue) const;
+
+    unsigned int GetUnsignedIntegerValue(const std::string& key,
+                                         unsigned int defaultValue) const;
+
+    bool GetBooleanValue(const std::string& key,
+                         bool defaultValue) const;
+
+    float GetFloatValue(const std::string& key,
+                        float defaultValue) const;
+
+    void GetDictionary(std::map<std::string, std::string>& target,
+                       const std::string& key) const;
+  };
+
+  class OrthancImage : public boost::noncopyable
+  {
+  private:
+    OrthancPluginImage*    image_;
+
+    void Clear();
+
+    void CheckImageAvailable() const;
+
+  public:
+    OrthancImage();
+
+    explicit OrthancImage(OrthancPluginImage* image);
+
+    OrthancImage(OrthancPluginPixelFormat  format,
+                 uint32_t                  width,
+                 uint32_t                  height);
+
+    OrthancImage(OrthancPluginPixelFormat  format,
+                 uint32_t                  width,
+                 uint32_t                  height,
+                 uint32_t                  pitch,
+                 void*                     buffer);
+
+    ~OrthancImage()
+    {
+      Clear();
+    }
+
+    void UncompressPngImage(const void* data,
+                            size_t size);
+
+    void UncompressJpegImage(const void* data,
+                             size_t size);
+
+    void DecodeDicomImage(const void* data,
+                          size_t size,
+                          unsigned int frame);
+
+    OrthancPluginPixelFormat GetPixelFormat() const;
+
+    unsigned int GetWidth() const;
+
+    unsigned int GetHeight() const;
+
+    unsigned int GetPitch() const;
+    
+    void* GetBuffer() const;
+
+    const OrthancPluginImage* GetObject() const
+    {
+      return image_;
+    }
+
+    void CompressPngImage(MemoryBuffer& target) const;
+
+    void CompressJpegImage(MemoryBuffer& target,
+                           uint8_t quality) const;
+
+    void AnswerPngImage(OrthancPluginRestOutput* output) const;
+
+    void AnswerJpegImage(OrthancPluginRestOutput* output,
+                         uint8_t quality) const;
+    
+    void* GetWriteableBuffer();
+
+    OrthancPluginImage* Release();
+  };
+
+
+#if HAS_ORTHANC_PLUGIN_FIND_MATCHER == 1
+  class FindMatcher : public boost::noncopyable
+  {
+  private:
+    OrthancPluginFindMatcher*          matcher_;
+    const OrthancPluginWorklistQuery*  worklist_;
+
+    void SetupDicom(const void*            query,
+                    uint32_t               size);
+
+  public:
+    explicit FindMatcher(const OrthancPluginWorklistQuery*  worklist);
+
+    FindMatcher(const void*  query,
+                uint32_t     size)
+    {
+      SetupDicom(query, size);
+    }
+
+    explicit FindMatcher(const MemoryBuffer&  dicom)
+    {
+      SetupDicom(dicom.GetData(), dicom.GetSize());
+    }
+
+    ~FindMatcher();
+
+    bool IsMatch(const void*  dicom,
+                 uint32_t     size) const;
+
+    bool IsMatch(const MemoryBuffer& dicom) const
+    {
+      return IsMatch(dicom.GetData(), dicom.GetSize());
+    }
+  };
+#endif
+
+
+  bool ReadJson(Json::Value& target,
+                const std::string& source);
+  
+  bool ReadJson(Json::Value& target,
+                const void* buffer,
+                size_t size);
+
+  bool ReadJsonWithoutComments(Json::Value& target,
+                               const std::string& source);  
+
+  bool ReadJsonWithoutComments(Json::Value& target,
+                               const void* buffer,
+                               size_t size);
+
+  void WriteFastJson(std::string& target,
+                     const Json::Value& source);
+
+  void WriteStyledJson(std::string& target,
+                       const Json::Value& source);
+
+  bool RestApiGet(Json::Value& result,
+                  const std::string& uri,
+                  bool applyPlugins);
+
+  bool RestApiGetString(std::string& result,
+                        const std::string& uri,
+                        bool applyPlugins);
+
+  bool RestApiGetString(std::string& result,
+                        const std::string& uri,
+                        const std::map<std::string, std::string>& httpHeaders,
+                        bool applyPlugins);
+
+  bool RestApiPost(std::string& result,
+                   const std::string& uri,
+                   const void* body,
+                   size_t bodySize,
+                   bool applyPlugins);
+
+  bool RestApiPost(Json::Value& result,
+                   const std::string& uri,
+                   const void* body,
+                   size_t bodySize,
+                   bool applyPlugins);
+
+  bool RestApiPost(Json::Value& result,
+                   const std::string& uri,
+                   const Json::Value& body,
+                   bool applyPlugins);
+
+  inline bool RestApiPost(Json::Value& result,
+                          const std::string& uri,
+                          const std::string& body,
+                          bool applyPlugins)
+  {
+    return RestApiPost(result, uri, body.empty() ? NULL : body.c_str(),
+                       body.size(), applyPlugins);
+  }
+
+  inline bool RestApiPost(Json::Value& result,
+                          const std::string& uri,
+                          const MemoryBuffer& body,
+                          bool applyPlugins)
+  {
+    return RestApiPost(result, uri, body.GetData(),
+                       body.GetSize(), applyPlugins);
+  }
+
+  bool RestApiPut(Json::Value& result,
+                  const std::string& uri,
+                  const void* body,
+                  size_t bodySize,
+                  bool applyPlugins);
+
+  bool RestApiPut(Json::Value& result,
+                  const std::string& uri,
+                  const Json::Value& body,
+                  bool applyPlugins);
+
+  inline bool RestApiPut(Json::Value& result,
+                         const std::string& uri,
+                         const std::string& body,
+                         bool applyPlugins)
+  {
+    return RestApiPut(result, uri, body.empty() ? NULL : body.c_str(),
+                      body.size(), applyPlugins);
+  }
+
+  bool RestApiDelete(const std::string& uri,
+                     bool applyPlugins);
+
+  bool HttpDelete(const std::string& url,
+                  const std::string& username,
+                  const std::string& password);
+
+  void AnswerJson(const Json::Value& value,
+                  OrthancPluginRestOutput* output);
+
+  void AnswerString(const std::string& answer,
+                    const char* mimeType,
+                    OrthancPluginRestOutput* output);
+
+  void AnswerHttpError(uint16_t httpError,
+                       OrthancPluginRestOutput* output);
+
+  void AnswerMethodNotAllowed(OrthancPluginRestOutput* output, const char* allowedMethods);
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 0)
+  const char* AutodetectMimeType(const std::string& path);
+#endif
+
+  void LogError(const std::string& message);
+
+  void LogWarning(const std::string& message);
+
+  void LogInfo(const std::string& message);
+
+  void ReportMinimalOrthancVersion(unsigned int major,
+                                   unsigned int minor,
+                                   unsigned int revision);
+  
+  bool CheckMinimalOrthancVersion(unsigned int major,
+                                  unsigned int minor,
+                                  unsigned int revision);
+
+
+  namespace Internals
+  {
+    template <RestCallback Callback>
+    static OrthancPluginErrorCode Protect(OrthancPluginRestOutput* output,
+                                          const char* url,
+                                          const OrthancPluginHttpRequest* request)
+    {
+      try
+      {
+        Callback(output, url, request);
+        return OrthancPluginErrorCode_Success;
+      }
+      catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
+      {
+#if HAS_ORTHANC_EXCEPTION == 1 && HAS_ORTHANC_PLUGIN_EXCEPTION_DETAILS == 1
+        if (HasGlobalContext() &&
+            e.HasDetails())
+        {
+          // The "false" instructs Orthanc not to log the detailed
+          // error message. This is to avoid duplicating the details,
+          // because "OrthancException" already does it on construction.
+          OrthancPluginSetHttpErrorDetails
+            (GetGlobalContext(), output, e.GetDetails(), false);
+        }
+#endif
+
+        return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
+      }
+      catch (boost::bad_lexical_cast&)
+      {
+        return OrthancPluginErrorCode_BadFileFormat;
+      }
+      catch (...)
+      {
+        return OrthancPluginErrorCode_Plugin;
+      }
+    }
+  }
+
+  
+  template <RestCallback Callback>
+  void RegisterRestCallback(const std::string& uri,
+                            bool isThreadSafe)
+  {
+    if (isThreadSafe)
+    {
+      OrthancPluginRegisterRestCallbackNoLock
+        (GetGlobalContext(), uri.c_str(), Internals::Protect<Callback>);
+    }
+    else
+    {
+      OrthancPluginRegisterRestCallback
+        (GetGlobalContext(), uri.c_str(), Internals::Protect<Callback>);
+    }
+  }
+
+
+#if HAS_ORTHANC_PLUGIN_PEERS == 1
+  class OrthancPeers : public boost::noncopyable
+  {
+  private:
+    typedef std::map<std::string, uint32_t>   Index;
+
+    OrthancPluginPeers   *peers_;
+    Index                 index_;
+    uint32_t              timeout_;
+
+    size_t GetPeerIndex(const std::string& name) const;
+
+  public:
+    OrthancPeers();
+
+    ~OrthancPeers();
+
+    uint32_t GetTimeout() const
+    {
+      return timeout_;
+    }
+
+    void SetTimeout(uint32_t timeout)
+    {
+      timeout_ = timeout;
+    }
+
+    bool LookupName(size_t& target,
+                    const std::string& name) const;
+
+    std::string GetPeerName(size_t index) const;
+
+    std::string GetPeerUrl(size_t index) const;
+
+    std::string GetPeerUrl(const std::string& name) const;
+
+    size_t GetPeersCount() const
+    {
+      return index_.size();
+    }
+
+    bool LookupUserProperty(std::string& value,
+                            size_t index,
+                            const std::string& key) const;
+
+    bool LookupUserProperty(std::string& value,
+                            const std::string& peer,
+                            const std::string& key) const;
+
+    bool DoGet(MemoryBuffer& target,
+               size_t index,
+               const std::string& uri) const;
+
+    bool DoGet(MemoryBuffer& target,
+               const std::string& name,
+               const std::string& uri) const;
+
+    bool DoGet(Json::Value& target,
+               size_t index,
+               const std::string& uri) const;
+
+    bool DoGet(Json::Value& target,
+               const std::string& name,
+               const std::string& uri) const;
+
+    bool DoPost(MemoryBuffer& target,
+                size_t index,
+                const std::string& uri,
+                const std::string& body) const;
+
+    bool DoPost(MemoryBuffer& target,
+                const std::string& name,
+                const std::string& uri,
+                const std::string& body) const;
+
+    bool DoPost(Json::Value& target,
+                size_t index,
+                const std::string& uri,
+                const std::string& body) const;
+
+    bool DoPost(Json::Value& target,
+                const std::string& name,
+                const std::string& uri,
+                const std::string& body) const;
+
+    bool DoPut(size_t index,
+               const std::string& uri,
+               const std::string& body) const;
+
+    bool DoPut(const std::string& name,
+               const std::string& uri,
+               const std::string& body) const;
+
+    bool DoDelete(size_t index,
+                  const std::string& uri) const;
+
+    bool DoDelete(const std::string& name,
+                  const std::string& uri) const;
+  };
+#endif
+
+
+
+#if HAS_ORTHANC_PLUGIN_JOB == 1
+  class OrthancJob : public boost::noncopyable
+  {
+  private:
+    std::string   jobType_;
+    std::string   content_;
+    bool          hasSerialized_;
+    std::string   serialized_;
+    float         progress_;
+
+    static void CallbackFinalize(void* job);
+
+    static float CallbackGetProgress(void* job);
+
+    static const char* CallbackGetContent(void* job);
+
+    static const char* CallbackGetSerialized(void* job);
+
+    static OrthancPluginJobStepStatus CallbackStep(void* job);
+
+    static OrthancPluginErrorCode CallbackStop(void* job,
+                                               OrthancPluginJobStopReason reason);
+
+    static OrthancPluginErrorCode CallbackReset(void* job);
+
+  protected:
+    void ClearContent();
+
+    void UpdateContent(const Json::Value& content);
+
+    void ClearSerialized();
+
+    void UpdateSerialized(const Json::Value& serialized);
+
+    void UpdateProgress(float progress);
+    
+  public:
+    explicit OrthancJob(const std::string& jobType);
+    
+    virtual ~OrthancJob()
+    {
+    }
+
+    virtual OrthancPluginJobStepStatus Step() = 0;
+
+    virtual void Stop(OrthancPluginJobStopReason reason) = 0;
+    
+    virtual void Reset() = 0;
+
+    static OrthancPluginJob* Create(OrthancJob* job /* takes ownership */);
+
+    static std::string Submit(OrthancJob* job /* takes ownership */,
+                              int priority);
+
+    static void SubmitAndWait(Json::Value& result,
+                              OrthancJob* job /* takes ownership */,
+                              int priority);
+
+    // Submit a job from a POST on the REST API with the same
+    // conventions as in the Orthanc core (according to the
+    // "Synchronous" and "Priority" options)
+    static void SubmitFromRestApiPost(OrthancPluginRestOutput* output,
+                                      const Json::Value& body,
+                                      OrthancJob* job);
+  };
+#endif
+
+
+#if HAS_ORTHANC_PLUGIN_METRICS == 1
+  inline void SetMetricsValue(char* name,
+                              float value)
+  {
+    OrthancPluginSetMetricsValue(GetGlobalContext(), name,
+                                 value, OrthancPluginMetricsType_Default);
+  }
+
+  class MetricsTimer : public boost::noncopyable
+  {
+  private:
+    std::string               name_;
+    boost::posix_time::ptime  start_;
+
+  public:
+    explicit MetricsTimer(const char* name);
+
+    ~MetricsTimer();
+  };
+#endif
+
+
+#if HAS_ORTHANC_PLUGIN_HTTP_CLIENT == 1
+  class HttpClient : public boost::noncopyable
+  {
+  public:
+    typedef std::map<std::string, std::string>  HttpHeaders;
+
+    class IRequestBody : public boost::noncopyable
+    {
+    public:
+      virtual ~IRequestBody()
+      {
+      }
+
+      virtual bool ReadNextChunk(std::string& chunk) = 0;
+    };
+
+
+    class IAnswer : public boost::noncopyable
+    {
+    public:
+      virtual ~IAnswer()
+      {
+      }
+
+      virtual void AddHeader(const std::string& key,
+                             const std::string& value) = 0;
+
+      virtual void AddChunk(const void* data,
+                            size_t size) = 0;
+    };
+
+
+  private:
+    class RequestBodyWrapper;
+
+    uint16_t                 httpStatus_;
+    OrthancPluginHttpMethod  method_;
+    std::string              url_;
+    HttpHeaders              headers_;
+    std::string              username_;
+    std::string              password_;
+    uint32_t                 timeout_;
+    std::string              certificateFile_;
+    std::string              certificateKeyFile_;
+    std::string              certificateKeyPassword_;
+    bool                     pkcs11_;
+    std::string              fullBody_;
+    IRequestBody*            chunkedBody_;
+    bool                     allowChunkedTransfers_;
+
+#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1
+    void ExecuteWithStream(uint16_t& httpStatus,  // out
+                           IAnswer& answer,       // out
+                           IRequestBody& body) const;
+#endif
+
+    void ExecuteWithoutStream(uint16_t& httpStatus,        // out
+                              HttpHeaders& answerHeaders,  // out
+                              std::string& answerBody,     // out
+                              const std::string& body) const;
+    
+  public:
+    HttpClient();
+
+    uint16_t GetHttpStatus() const
+    {
+      return httpStatus_;
+    }
+
+    void SetMethod(OrthancPluginHttpMethod method)
+    {
+      method_ = method;
+    }
+
+    const std::string& GetUrl() const
+    {
+      return url_;
+    }
+
+    void SetUrl(const std::string& url)
+    {
+      url_ = url;
+    }
+
+    void SetHeaders(const HttpHeaders& headers)
+    {
+      headers_ = headers;
+    }
+
+    void AddHeader(const std::string& key,
+                   const std::string& value)
+    {
+      headers_[key] = value;
+    }
+
+    void AddHeaders(const HttpHeaders& headers);
+
+    void SetCredentials(const std::string& username,
+                        const std::string& password);
+
+    void ClearCredentials();
+
+    void SetTimeout(unsigned int timeout)  // 0 for default timeout
+    {
+      timeout_ = timeout;
+    }
+
+    void SetCertificate(const std::string& certificateFile,
+                        const std::string& keyFile,
+                        const std::string& keyPassword);
+
+    void ClearCertificate();
+
+    void SetPkcs11(bool pkcs11)
+    {
+      pkcs11_ = pkcs11;
+    }
+
+    void ClearBody();
+
+    void SwapBody(std::string& body);
+
+    void SetBody(const std::string& body);
+
+    void SetBody(IRequestBody& body);
+
+    // This function can be used to disable chunked transfers if the
+    // remote server is Orthanc with a version <= 1.5.6.
+    void SetChunkedTransfersAllowed(bool allow)
+    {
+      allowChunkedTransfers_ = allow;
+    }
+
+    bool IsChunkedTransfersAllowed() const
+    {
+      return allowChunkedTransfers_;
+    }
+
+    void Execute(IAnswer& answer);
+
+    void Execute(HttpHeaders& answerHeaders /* out */,
+                 std::string& answerBody /* out */);
+
+    void Execute(HttpHeaders& answerHeaders /* out */,
+                 Json::Value& answerBody /* out */);
+
+    void Execute();
+  };
+#endif
+
+
+
+  class IChunkedRequestReader : public boost::noncopyable
+  {
+  public:
+    virtual ~IChunkedRequestReader()
+    {
+    }
+
+    virtual void AddChunk(const void* data,
+                          size_t size) = 0;
+
+    virtual void Execute(OrthancPluginRestOutput* output) = 0;
+  };
+
+
+  typedef IChunkedRequestReader* (*ChunkedRestCallback) (const char* url,
+                                                         const OrthancPluginHttpRequest* request);
+
+
+  namespace Internals
+  {
+    void NullRestCallback(OrthancPluginRestOutput* output,
+                          const char* url,
+                          const OrthancPluginHttpRequest* request);
+  
+    IChunkedRequestReader *NullChunkedRestCallback(const char* url,
+                                                   const OrthancPluginHttpRequest* request);
+
+
+#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_SERVER == 1
+    template <ChunkedRestCallback Callback>
+    static OrthancPluginErrorCode ChunkedProtect(OrthancPluginServerChunkedRequestReader** reader,
+                                                const char* url,
+                                                const OrthancPluginHttpRequest* request)
+    {
+      try
+      {
+        if (reader == NULL)
+        {
+          return OrthancPluginErrorCode_InternalError;
+        }
+        else
+        {
+          *reader = reinterpret_cast<OrthancPluginServerChunkedRequestReader*>(Callback(url, request));
+          if (*reader == NULL)
+          {
+            return OrthancPluginErrorCode_Plugin;
+          }
+          else
+          {
+            return OrthancPluginErrorCode_Success;
+          }
+        }
+      }
+      catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
+      {
+        return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
+      }
+      catch (boost::bad_lexical_cast&)
+      {
+        return OrthancPluginErrorCode_BadFileFormat;
+      }
+      catch (...)
+      {
+        return OrthancPluginErrorCode_Plugin;
+      }
+    }
+
+    OrthancPluginErrorCode ChunkedRequestReaderAddChunk(
+      OrthancPluginServerChunkedRequestReader* reader,
+      const void*                              data,
+      uint32_t                                 size);
+
+    OrthancPluginErrorCode ChunkedRequestReaderExecute(
+      OrthancPluginServerChunkedRequestReader* reader,
+      OrthancPluginRestOutput*                 output);
+
+    void ChunkedRequestReaderFinalize(
+      OrthancPluginServerChunkedRequestReader* reader);
+
+#else  
+
+    OrthancPluginErrorCode ChunkedRestCompatibility(OrthancPluginRestOutput* output,
+                                                    const char* url,
+                                                    const OrthancPluginHttpRequest* request,
+                                                    RestCallback GetHandler,
+                                                    ChunkedRestCallback PostHandler,
+                                                    RestCallback DeleteHandler,
+                                                    ChunkedRestCallback PutHandler);
+
+    template<
+      RestCallback         GetHandler,
+      ChunkedRestCallback  PostHandler,
+      RestCallback         DeleteHandler,
+      ChunkedRestCallback  PutHandler
+      >
+    inline OrthancPluginErrorCode ChunkedRestCompatibility(OrthancPluginRestOutput* output,
+                                                           const char* url,
+                                                           const OrthancPluginHttpRequest* request)
+    {
+      return ChunkedRestCompatibility(output, url, request, GetHandler,
+                                      PostHandler, DeleteHandler, PutHandler);
+    }
+#endif
+  }
+
+
+
+  // NB: We use a templated class instead of a templated function, because
+  // default values are only available in functions since C++11
+  template<
+    RestCallback         GetHandler    = Internals::NullRestCallback,
+    ChunkedRestCallback  PostHandler   = Internals::NullChunkedRestCallback,
+    RestCallback         DeleteHandler = Internals::NullRestCallback,
+    ChunkedRestCallback  PutHandler    = Internals::NullChunkedRestCallback
+    >
+  class ChunkedRestRegistration : public boost::noncopyable
+  {
+  public:
+    static void Apply(const std::string& uri)
+    {
+#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_SERVER == 1
+      OrthancPluginRegisterChunkedRestCallback(
+        GetGlobalContext(), uri.c_str(),
+        GetHandler == Internals::NullRestCallback         ? NULL : Internals::Protect<GetHandler>,
+        PostHandler == Internals::NullChunkedRestCallback ? NULL : Internals::ChunkedProtect<PostHandler>,
+        DeleteHandler == Internals::NullRestCallback      ? NULL : Internals::Protect<DeleteHandler>,
+        PutHandler == Internals::NullChunkedRestCallback  ? NULL : Internals::ChunkedProtect<PutHandler>,
+        Internals::ChunkedRequestReaderAddChunk,
+        Internals::ChunkedRequestReaderExecute,
+        Internals::ChunkedRequestReaderFinalize);
+#else
+      OrthancPluginRegisterRestCallbackNoLock(
+        GetGlobalContext(), uri.c_str(), 
+        Internals::ChunkedRestCompatibility<GetHandler, PostHandler, DeleteHandler, PutHandler>);
+#endif
+    }
+  };
+
+  
+
+#if HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP == 1
+  class IStorageCommitmentScpHandler : public boost::noncopyable
+  {
+  public:
+    virtual ~IStorageCommitmentScpHandler()
+    {
+    }
+    
+    virtual OrthancPluginStorageCommitmentFailureReason Lookup(const std::string& sopClassUid,
+                                                               const std::string& sopInstanceUid) = 0;
+    
+    static OrthancPluginErrorCode Lookup(OrthancPluginStorageCommitmentFailureReason* target,
+                                         void* rawHandler,
+                                         const char* sopClassUid,
+                                         const char* sopInstanceUid);
+
+    static void Destructor(void* rawHandler);
+  };
+#endif
+
+
+  class DicomInstance : public boost::noncopyable
+  {
+  private:
+    bool toFree_;
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1)    
+    const OrthancPluginDicomInstance*  instance_;
+#else
+    OrthancPluginDicomInstance*  instance_;
+#endif
+    
+  public:
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1)    
+    explicit DicomInstance(const OrthancPluginDicomInstance* instance);
+#else
+    explicit DicomInstance(OrthancPluginDicomInstance* instance);
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+    DicomInstance(const void* buffer,
+                  size_t size);
+#endif
+
+    ~DicomInstance();
+
+    std::string GetRemoteAet() const;
+
+    const void* GetBuffer() const
+    {
+      return OrthancPluginGetInstanceData(GetGlobalContext(), instance_);
+    }
+
+    size_t GetSize() const
+    {
+      return static_cast<size_t>(OrthancPluginGetInstanceSize(GetGlobalContext(), instance_));
+    }
+
+    void GetJson(Json::Value& target) const;
+
+    void GetSimplifiedJson(Json::Value& target) const;
+
+    OrthancPluginInstanceOrigin GetOrigin() const
+    {
+      return OrthancPluginGetInstanceOrigin(GetGlobalContext(), instance_);
+    }
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1)
+    std::string GetTransferSyntaxUid() const;
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1)
+    bool HasPixelData() const;
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+    unsigned int GetFramesCount() const
+    {
+      return OrthancPluginGetInstanceFramesCount(GetGlobalContext(), instance_);
+    }
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+    void GetRawFrame(std::string& target,
+                     unsigned int frameIndex) const;
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+    OrthancImage* GetDecodedFrame(unsigned int frameIndex) const;
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+    void Serialize(std::string& target) const;
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+    static DicomInstance* Transcode(const void* buffer,
+                                    size_t size,
+                                    const std::string& transferSyntax);
+#endif
+  };
+
+
+
+#if HAS_ORTHANC_PLUGIN_WEBDAV == 1
+  class IWebDavCollection : public boost::noncopyable
+  {
+  public:
+    class FileInfo
+    {
+    private:
+      std::string  name_;
+      uint64_t     contentSize_;
+      std::string  mime_;
+      std::string  dateTime_;
+
+    public:
+      FileInfo(const std::string& name,
+               uint64_t contentSize,
+               const std::string& dateTime) :
+        name_(name),
+        contentSize_(contentSize),
+        dateTime_(dateTime)
+      {
+      }
+
+      const std::string& GetName() const
+      {
+        return name_;
+      }
+
+      uint64_t GetContentSize() const
+      {
+        return contentSize_;
+      }
+
+      void SetMimeType(const std::string& mime)
+      {
+        mime_ = mime;
+      }
+
+      const std::string& GetMimeType() const
+      {
+        return mime_;
+      }
+
+      const std::string& GetDateTime() const
+      {
+        return dateTime_;
+      }
+    };
+  
+    class FolderInfo
+    {
+    private:
+      std::string  name_;
+      std::string  dateTime_;
+
+    public:
+      FolderInfo(const std::string& name,
+                 const std::string& dateTime) :
+        name_(name),
+        dateTime_(dateTime)
+      {
+      }
+
+      const std::string& GetName() const
+      {
+        return name_;
+      }
+
+      const std::string& GetDateTime() const
+      {
+        return dateTime_;
+      }
+    };
+  
+    virtual ~IWebDavCollection()
+    {
+    }
+
+    virtual bool IsExistingFolder(const std::vector<std::string>& path) = 0;
+
+    virtual bool ListFolder(std::list<FileInfo>& files,
+                            std::list<FolderInfo>& subfolders,
+                            const std::vector<std::string>& path) = 0;
+  
+    virtual bool GetFile(std::string& content /* out */,
+                         std::string& mime /* out */,
+                         std::string& dateTime /* out */,
+                         const std::vector<std::string>& path) = 0;
+
+    virtual bool StoreFile(const std::vector<std::string>& path,
+                           const void* data,
+                           size_t size) = 0;
+
+    virtual bool CreateFolder(const std::vector<std::string>& path) = 0;
+
+    virtual bool DeleteItem(const std::vector<std::string>& path) = 0;
+
+    static void Register(const std::string& uri,
+                         IWebDavCollection& collection);
+  };
+#endif
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Plugins/OrthancPluginException.h	Fri Apr 22 17:58:59 2022 +0200
@@ -0,0 +1,90 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2022 Osimis S.A., Belgium
+ * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#if !defined(HAS_ORTHANC_EXCEPTION)
+#  error The macro HAS_ORTHANC_EXCEPTION must be defined
+#endif
+
+
+#if HAS_ORTHANC_EXCEPTION == 1
+#  include <OrthancException.h>
+#  define ORTHANC_PLUGINS_ERROR_ENUMERATION     ::Orthanc::ErrorCode
+#  define ORTHANC_PLUGINS_EXCEPTION_CLASS       ::Orthanc::OrthancException
+#  define ORTHANC_PLUGINS_GET_ERROR_CODE(code)  ::Orthanc::ErrorCode_ ## code
+#else
+#  include <orthanc/OrthancCPlugin.h>
+#  define ORTHANC_PLUGINS_ERROR_ENUMERATION     ::OrthancPluginErrorCode
+#  define ORTHANC_PLUGINS_EXCEPTION_CLASS       ::OrthancPlugins::PluginException
+#  define ORTHANC_PLUGINS_GET_ERROR_CODE(code)  ::OrthancPluginErrorCode_ ## code
+#endif
+
+
+#define ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code)                   \
+  throw ORTHANC_PLUGINS_EXCEPTION_CLASS(static_cast<ORTHANC_PLUGINS_ERROR_ENUMERATION>(code));
+
+
+#define ORTHANC_PLUGINS_THROW_EXCEPTION(code)                           \
+  throw ORTHANC_PLUGINS_EXCEPTION_CLASS(ORTHANC_PLUGINS_GET_ERROR_CODE(code));
+                                                  
+
+#define ORTHANC_PLUGINS_CHECK_ERROR(code)                           \
+  if (code != ORTHANC_PLUGINS_GET_ERROR_CODE(Success))              \
+  {                                                                 \
+    ORTHANC_PLUGINS_THROW_EXCEPTION(code);                          \
+  }
+
+
+namespace OrthancPlugins
+{
+#if HAS_ORTHANC_EXCEPTION == 0
+  class PluginException
+  {
+  private:
+    OrthancPluginErrorCode  code_;
+
+  public:
+    explicit PluginException(OrthancPluginErrorCode code) : code_(code)
+    {
+    }
+
+    OrthancPluginErrorCode GetErrorCode() const
+    {
+      return code_;
+    }
+
+    const char* What(OrthancPluginContext* context) const
+    {
+      const char* description = OrthancPluginGetErrorDescription(context, code_);
+      if (description)
+      {
+        return description;
+      }
+      else
+      {
+        return "No description available";
+      }
+    }
+  };
+#endif
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Plugins/OrthancPluginsExports.cmake	Fri Apr 22 17:58:59 2022 +0200
@@ -0,0 +1,32 @@
+# Orthanc - A Lightweight, RESTful DICOM Store
+# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+# Department, University Hospital of Liege, Belgium
+# Copyright (C) 2017-2022 Osimis S.A., Belgium
+# Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+#
+# This program is free software: you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+# 
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+# In Orthanc <= 1.7.1, the instructions below were part of
+# "Compiler.cmake", and were protected by the (now unused) option
+# "ENABLE_PLUGINS_VERSION_SCRIPT" in CMake
+
+if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR
+    ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD" OR
+    ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD" OR
+    ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD")
+  set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--version-script=${CMAKE_CURRENT_LIST_DIR}/VersionScriptPlugins.map")
+elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin")
+  set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -exported_symbols_list ${CMAKE_CURRENT_LIST_DIR}/ExportedSymbolsPlugins.list")
+endif()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Plugins/VersionScriptPlugins.map	Fri Apr 22 17:58:59 2022 +0200
@@ -0,0 +1,12 @@
+# This is a version-script for Orthanc plugins
+
+{
+global:
+  OrthancPluginInitialize;
+  OrthancPluginFinalize;
+  OrthancPluginGetName;
+  OrthancPluginGetVersion;
+
+local:
+  *;
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Sdk-1.10.1/orthanc/OrthancCPlugin.h	Fri Apr 22 17:58:59 2022 +0200
@@ -0,0 +1,9012 @@
+/**
+ * \mainpage
+ *
+ * This C/C++ SDK allows external developers to create plugins that
+ * can be loaded into Orthanc to extend its functionality. Each
+ * Orthanc plugin must expose 4 public functions with the following
+ * signatures:
+ * 
+ * -# <tt>int32_t OrthancPluginInitialize(const OrthancPluginContext* context)</tt>:
+ *    This function is invoked by Orthanc when it loads the plugin on startup.
+ *    The plugin must:
+ *    - Check its compatibility with the Orthanc version using
+ *      ::OrthancPluginCheckVersion().
+ *    - Store the context pointer so that it can use the plugin 
+ *      services of Orthanc.
+ *    - Register all its REST callbacks using ::OrthancPluginRegisterRestCallback().
+ *    - Possibly register its callback for received DICOM instances using ::OrthancPluginRegisterOnStoredInstanceCallback().
+ *    - Possibly register its callback for changes to the DICOM store using ::OrthancPluginRegisterOnChangeCallback().
+ *    - Possibly register a custom storage area using ::OrthancPluginRegisterStorageArea2().
+ *    - Possibly register a custom database back-end area using OrthancPluginRegisterDatabaseBackendV3().
+ *    - Possibly register a handler for C-Find SCP using OrthancPluginRegisterFindCallback().
+ *    - Possibly register a handler for C-Find SCP against DICOM worklists using OrthancPluginRegisterWorklistCallback().
+ *    - Possibly register a handler for C-Move SCP using OrthancPluginRegisterMoveCallback().
+ *    - Possibly register a custom decoder for DICOM images using OrthancPluginRegisterDecodeImageCallback().
+ *    - Possibly register a callback to filter incoming HTTP requests using OrthancPluginRegisterIncomingHttpRequestFilter2().
+ *    - Possibly register a callback to unserialize jobs using OrthancPluginRegisterJobsUnserializer().
+ *    - Possibly register a callback to refresh its metrics using OrthancPluginRegisterRefreshMetricsCallback().
+ *    - Possibly register a callback to answer chunked HTTP transfers using ::OrthancPluginRegisterChunkedRestCallback().
+ *    - Possibly register a callback for Storage Commitment SCP using ::OrthancPluginRegisterStorageCommitmentScpCallback().
+ *    - Possibly register a callback to keep/discard/modify incoming DICOM instances using OrthancPluginRegisterReceivedInstanceCallback().
+ *    - Possibly register a custom transcoder for DICOM images using OrthancPluginRegisterTranscoderCallback().
+ *    - Possibly register a callback to discard instances received through DICOM C-STORE using OrthancPluginRegisterIncomingCStoreInstanceFilter().
+ *    - Possibly register a callback to branch a WebDAV virtual filesystem using OrthancPluginRegisterWebDavCollection().
+ * -# <tt>void OrthancPluginFinalize()</tt>:
+ *    This function is invoked by Orthanc during its shutdown. The plugin
+ *    must free all its memory.
+ * -# <tt>const char* OrthancPluginGetName()</tt>:
+ *    The plugin must return a short string to identify itself.
+ * -# <tt>const char* OrthancPluginGetVersion()</tt>:
+ *    The plugin must return a string containing its version number.
+ *
+ * The name and the version of a plugin is only used to prevent it
+ * from being loaded twice. Note that, in C++, it is mandatory to
+ * declare these functions within an <tt>extern "C"</tt> section.
+ * 
+ * To ensure multi-threading safety, the various REST callbacks are
+ * guaranteed to be executed in mutual exclusion since Orthanc
+ * 0.8.5. If this feature is undesired (notably when developing
+ * high-performance plugins handling simultaneous requests), use
+ * ::OrthancPluginRegisterRestCallbackNoLock().
+ **/
+
+
+
+/**
+ * @defgroup Images Images and compression
+ * @brief Functions to deal with images and compressed buffers.
+ *
+ * @defgroup REST REST
+ * @brief Functions to answer REST requests in a callback.
+ *
+ * @defgroup Callbacks Callbacks
+ * @brief Functions to register and manage callbacks by the plugins.
+ *
+ * @defgroup DicomCallbacks DicomCallbacks
+ * @brief Functions to register and manage DICOM callbacks (worklists, C-FIND, C-MOVE, storage commitment).
+ *
+ * @defgroup Orthanc Orthanc
+ * @brief Functions to access the content of the Orthanc server.
+ *
+ * @defgroup DicomInstance DicomInstance
+ * @brief Functions to access DICOM images that are managed by the Orthanc core.
+ **/
+
+
+
+/**
+ * @defgroup Toolbox Toolbox
+ * @brief Generic functions to help with the creation of plugins.
+ **/
+
+
+
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2022 Osimis S.A., Belgium
+ * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+
+#pragma once
+
+
+#include <stdio.h>
+#include <string.h>
+
+#ifdef WIN32
+#  define ORTHANC_PLUGINS_API __declspec(dllexport)
+#elif __GNUC__ >= 4
+#  define ORTHANC_PLUGINS_API __attribute__ ((visibility ("default")))
+#else
+#  define ORTHANC_PLUGINS_API
+#endif
+
+#define ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER     1
+#define ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER     10
+#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER  1
+
+
+#if !defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE)
+#define ORTHANC_PLUGINS_VERSION_IS_ABOVE(major, minor, revision)        \
+  (ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER > major ||                      \
+   (ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER == major &&                    \
+    (ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER > minor ||                    \
+     (ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER == minor &&                  \
+      ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER >= revision))))
+#endif
+
+
+
+/********************************************************************
+ ** Check that function inlining is properly supported. The use of
+ ** inlining is required, to avoid the duplication of object code
+ ** between two compilation modules that would use the Orthanc Plugin
+ ** API.
+ ********************************************************************/
+
+/* If the auto-detection of the "inline" keyword below does not work
+   automatically and that your compiler is known to properly support
+   inlining, uncomment the following #define and adapt the definition
+   of "static inline". */
+
+/* #define ORTHANC_PLUGIN_INLINE static inline */
+
+#ifndef ORTHANC_PLUGIN_INLINE
+#  if __STDC_VERSION__ >= 199901L
+/*   This is C99 or above: http://predef.sourceforge.net/prestd.html */
+#    define ORTHANC_PLUGIN_INLINE static inline
+#  elif defined(__cplusplus)
+/*   This is C++ */
+#    define ORTHANC_PLUGIN_INLINE static inline
+#  elif defined(__GNUC__)
+/*   This is GCC running in C89 mode */
+#    define ORTHANC_PLUGIN_INLINE static __inline
+#  elif defined(_MSC_VER)
+/*   This is Visual Studio running in C89 mode */
+#    define ORTHANC_PLUGIN_INLINE static __inline
+#  else
+#    error Your compiler is not known to support the "inline" keyword
+#  endif
+#endif
+
+
+
+/********************************************************************
+ ** Inclusion of standard libraries.
+ ********************************************************************/
+
+/**
+ * For Microsoft Visual Studio, a compatibility "stdint.h" can be
+ * downloaded at the following URL:
+ * https://hg.orthanc-server.com/orthanc/raw-file/tip/Resources/ThirdParty/VisualStudio/stdint.h
+ **/
+#include <stdint.h>
+
+#include <stdlib.h>
+
+
+
+/********************************************************************
+ ** Definition of the Orthanc Plugin API.
+ ********************************************************************/
+
+/** @{ */
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+  /**
+   * The various error codes that can be returned by the Orthanc core.
+   **/
+  typedef enum
+  {
+    OrthancPluginErrorCode_InternalError = -1    /*!< Internal error */,
+    OrthancPluginErrorCode_Success = 0    /*!< Success */,
+    OrthancPluginErrorCode_Plugin = 1    /*!< Error encountered within the plugin engine */,
+    OrthancPluginErrorCode_NotImplemented = 2    /*!< Not implemented yet */,
+    OrthancPluginErrorCode_ParameterOutOfRange = 3    /*!< Parameter out of range */,
+    OrthancPluginErrorCode_NotEnoughMemory = 4    /*!< The server hosting Orthanc is running out of memory */,
+    OrthancPluginErrorCode_BadParameterType = 5    /*!< Bad type for a parameter */,
+    OrthancPluginErrorCode_BadSequenceOfCalls = 6    /*!< Bad sequence of calls */,
+    OrthancPluginErrorCode_InexistentItem = 7    /*!< Accessing an inexistent item */,
+    OrthancPluginErrorCode_BadRequest = 8    /*!< Bad request */,
+    OrthancPluginErrorCode_NetworkProtocol = 9    /*!< Error in the network protocol */,
+    OrthancPluginErrorCode_SystemCommand = 10    /*!< Error while calling a system command */,
+    OrthancPluginErrorCode_Database = 11    /*!< Error with the database engine */,
+    OrthancPluginErrorCode_UriSyntax = 12    /*!< Badly formatted URI */,
+    OrthancPluginErrorCode_InexistentFile = 13    /*!< Inexistent file */,
+    OrthancPluginErrorCode_CannotWriteFile = 14    /*!< Cannot write to file */,
+    OrthancPluginErrorCode_BadFileFormat = 15    /*!< Bad file format */,
+    OrthancPluginErrorCode_Timeout = 16    /*!< Timeout */,
+    OrthancPluginErrorCode_UnknownResource = 17    /*!< Unknown resource */,
+    OrthancPluginErrorCode_IncompatibleDatabaseVersion = 18    /*!< Incompatible version of the database */,
+    OrthancPluginErrorCode_FullStorage = 19    /*!< The file storage is full */,
+    OrthancPluginErrorCode_CorruptedFile = 20    /*!< Corrupted file (e.g. inconsistent MD5 hash) */,
+    OrthancPluginErrorCode_InexistentTag = 21    /*!< Inexistent tag */,
+    OrthancPluginErrorCode_ReadOnly = 22    /*!< Cannot modify a read-only data structure */,
+    OrthancPluginErrorCode_IncompatibleImageFormat = 23    /*!< Incompatible format of the images */,
+    OrthancPluginErrorCode_IncompatibleImageSize = 24    /*!< Incompatible size of the images */,
+    OrthancPluginErrorCode_SharedLibrary = 25    /*!< Error while using a shared library (plugin) */,
+    OrthancPluginErrorCode_UnknownPluginService = 26    /*!< Plugin invoking an unknown service */,
+    OrthancPluginErrorCode_UnknownDicomTag = 27    /*!< Unknown DICOM tag */,
+    OrthancPluginErrorCode_BadJson = 28    /*!< Cannot parse a JSON document */,
+    OrthancPluginErrorCode_Unauthorized = 29    /*!< Bad credentials were provided to an HTTP request */,
+    OrthancPluginErrorCode_BadFont = 30    /*!< Badly formatted font file */,
+    OrthancPluginErrorCode_DatabasePlugin = 31    /*!< The plugin implementing a custom database back-end does not fulfill the proper interface */,
+    OrthancPluginErrorCode_StorageAreaPlugin = 32    /*!< Error in the plugin implementing a custom storage area */,
+    OrthancPluginErrorCode_EmptyRequest = 33    /*!< The request is empty */,
+    OrthancPluginErrorCode_NotAcceptable = 34    /*!< Cannot send a response which is acceptable according to the Accept HTTP header */,
+    OrthancPluginErrorCode_NullPointer = 35    /*!< Cannot handle a NULL pointer */,
+    OrthancPluginErrorCode_DatabaseUnavailable = 36    /*!< The database is currently not available (probably a transient situation) */,
+    OrthancPluginErrorCode_CanceledJob = 37    /*!< This job was canceled */,
+    OrthancPluginErrorCode_BadGeometry = 38    /*!< Geometry error encountered in Stone */,
+    OrthancPluginErrorCode_SslInitialization = 39    /*!< Cannot initialize SSL encryption, check out your certificates */,
+    OrthancPluginErrorCode_DiscontinuedAbi = 40    /*!< Calling a function that has been removed from the Orthanc Framework */,
+    OrthancPluginErrorCode_BadRange = 41    /*!< Incorrect range request */,
+    OrthancPluginErrorCode_DatabaseCannotSerialize = 42    /*!< Database could not serialize access due to concurrent update, the transaction should be retried */,
+    OrthancPluginErrorCode_Revision = 43    /*!< A bad revision number was provided, which might indicate conflict between multiple writers */,
+    OrthancPluginErrorCode_SQLiteNotOpened = 1000    /*!< SQLite: The database is not opened */,
+    OrthancPluginErrorCode_SQLiteAlreadyOpened = 1001    /*!< SQLite: Connection is already open */,
+    OrthancPluginErrorCode_SQLiteCannotOpen = 1002    /*!< SQLite: Unable to open the database */,
+    OrthancPluginErrorCode_SQLiteStatementAlreadyUsed = 1003    /*!< SQLite: This cached statement is already being referred to */,
+    OrthancPluginErrorCode_SQLiteExecute = 1004    /*!< SQLite: Cannot execute a command */,
+    OrthancPluginErrorCode_SQLiteRollbackWithoutTransaction = 1005    /*!< SQLite: Rolling back a nonexistent transaction (have you called Begin()?) */,
+    OrthancPluginErrorCode_SQLiteCommitWithoutTransaction = 1006    /*!< SQLite: Committing a nonexistent transaction */,
+    OrthancPluginErrorCode_SQLiteRegisterFunction = 1007    /*!< SQLite: Unable to register a function */,
+    OrthancPluginErrorCode_SQLiteFlush = 1008    /*!< SQLite: Unable to flush the database */,
+    OrthancPluginErrorCode_SQLiteCannotRun = 1009    /*!< SQLite: Cannot run a cached statement */,
+    OrthancPluginErrorCode_SQLiteCannotStep = 1010    /*!< SQLite: Cannot step over a cached statement */,
+    OrthancPluginErrorCode_SQLiteBindOutOfRange = 1011    /*!< SQLite: Bing a value while out of range (serious error) */,
+    OrthancPluginErrorCode_SQLitePrepareStatement = 1012    /*!< SQLite: Cannot prepare a cached statement */,
+    OrthancPluginErrorCode_SQLiteTransactionAlreadyStarted = 1013    /*!< SQLite: Beginning the same transaction twice */,
+    OrthancPluginErrorCode_SQLiteTransactionCommit = 1014    /*!< SQLite: Failure when committing the transaction */,
+    OrthancPluginErrorCode_SQLiteTransactionBegin = 1015    /*!< SQLite: Cannot start a transaction */,
+    OrthancPluginErrorCode_DirectoryOverFile = 2000    /*!< The directory to be created is already occupied by a regular file */,
+    OrthancPluginErrorCode_FileStorageCannotWrite = 2001    /*!< Unable to create a subdirectory or a file in the file storage */,
+    OrthancPluginErrorCode_DirectoryExpected = 2002    /*!< The specified path does not point to a directory */,
+    OrthancPluginErrorCode_HttpPortInUse = 2003    /*!< The TCP port of the HTTP server is privileged or already in use */,
+    OrthancPluginErrorCode_DicomPortInUse = 2004    /*!< The TCP port of the DICOM server is privileged or already in use */,
+    OrthancPluginErrorCode_BadHttpStatusInRest = 2005    /*!< This HTTP status is not allowed in a REST API */,
+    OrthancPluginErrorCode_RegularFileExpected = 2006    /*!< The specified path does not point to a regular file */,
+    OrthancPluginErrorCode_PathToExecutable = 2007    /*!< Unable to get the path to the executable */,
+    OrthancPluginErrorCode_MakeDirectory = 2008    /*!< Cannot create a directory */,
+    OrthancPluginErrorCode_BadApplicationEntityTitle = 2009    /*!< An application entity title (AET) cannot be empty or be longer than 16 characters */,
+    OrthancPluginErrorCode_NoCFindHandler = 2010    /*!< No request handler factory for DICOM C-FIND SCP */,
+    OrthancPluginErrorCode_NoCMoveHandler = 2011    /*!< No request handler factory for DICOM C-MOVE SCP */,
+    OrthancPluginErrorCode_NoCStoreHandler = 2012    /*!< No request handler factory for DICOM C-STORE SCP */,
+    OrthancPluginErrorCode_NoApplicationEntityFilter = 2013    /*!< No application entity filter */,
+    OrthancPluginErrorCode_NoSopClassOrInstance = 2014    /*!< DicomUserConnection: Unable to find the SOP class and instance */,
+    OrthancPluginErrorCode_NoPresentationContext = 2015    /*!< DicomUserConnection: No acceptable presentation context for modality */,
+    OrthancPluginErrorCode_DicomFindUnavailable = 2016    /*!< DicomUserConnection: The C-FIND command is not supported by the remote SCP */,
+    OrthancPluginErrorCode_DicomMoveUnavailable = 2017    /*!< DicomUserConnection: The C-MOVE command is not supported by the remote SCP */,
+    OrthancPluginErrorCode_CannotStoreInstance = 2018    /*!< Cannot store an instance */,
+    OrthancPluginErrorCode_CreateDicomNotString = 2019    /*!< Only string values are supported when creating DICOM instances */,
+    OrthancPluginErrorCode_CreateDicomOverrideTag = 2020    /*!< Trying to override a value inherited from a parent module */,
+    OrthancPluginErrorCode_CreateDicomUseContent = 2021    /*!< Use \"Content\" to inject an image into a new DICOM instance */,
+    OrthancPluginErrorCode_CreateDicomNoPayload = 2022    /*!< No payload is present for one instance in the series */,
+    OrthancPluginErrorCode_CreateDicomUseDataUriScheme = 2023    /*!< The payload of the DICOM instance must be specified according to Data URI scheme */,
+    OrthancPluginErrorCode_CreateDicomBadParent = 2024    /*!< Trying to attach a new DICOM instance to an inexistent resource */,
+    OrthancPluginErrorCode_CreateDicomParentIsInstance = 2025    /*!< Trying to attach a new DICOM instance to an instance (must be a series, study or patient) */,
+    OrthancPluginErrorCode_CreateDicomParentEncoding = 2026    /*!< Unable to get the encoding of the parent resource */,
+    OrthancPluginErrorCode_UnknownModality = 2027    /*!< Unknown modality */,
+    OrthancPluginErrorCode_BadJobOrdering = 2028    /*!< Bad ordering of filters in a job */,
+    OrthancPluginErrorCode_JsonToLuaTable = 2029    /*!< Cannot convert the given JSON object to a Lua table */,
+    OrthancPluginErrorCode_CannotCreateLua = 2030    /*!< Cannot create the Lua context */,
+    OrthancPluginErrorCode_CannotExecuteLua = 2031    /*!< Cannot execute a Lua command */,
+    OrthancPluginErrorCode_LuaAlreadyExecuted = 2032    /*!< Arguments cannot be pushed after the Lua function is executed */,
+    OrthancPluginErrorCode_LuaBadOutput = 2033    /*!< The Lua function does not give the expected number of outputs */,
+    OrthancPluginErrorCode_NotLuaPredicate = 2034    /*!< The Lua function is not a predicate (only true/false outputs allowed) */,
+    OrthancPluginErrorCode_LuaReturnsNoString = 2035    /*!< The Lua function does not return a string */,
+    OrthancPluginErrorCode_StorageAreaAlreadyRegistered = 2036    /*!< Another plugin has already registered a custom storage area */,
+    OrthancPluginErrorCode_DatabaseBackendAlreadyRegistered = 2037    /*!< Another plugin has already registered a custom database back-end */,
+    OrthancPluginErrorCode_DatabaseNotInitialized = 2038    /*!< Plugin trying to call the database during its initialization */,
+    OrthancPluginErrorCode_SslDisabled = 2039    /*!< Orthanc has been built without SSL support */,
+    OrthancPluginErrorCode_CannotOrderSlices = 2040    /*!< Unable to order the slices of the series */,
+    OrthancPluginErrorCode_NoWorklistHandler = 2041    /*!< No request handler factory for DICOM C-Find Modality SCP */,
+    OrthancPluginErrorCode_AlreadyExistingTag = 2042    /*!< Cannot override the value of a tag that already exists */,
+    OrthancPluginErrorCode_NoStorageCommitmentHandler = 2043    /*!< No request handler factory for DICOM N-ACTION SCP (storage commitment) */,
+    OrthancPluginErrorCode_NoCGetHandler = 2044    /*!< No request handler factory for DICOM C-GET SCP */,
+    OrthancPluginErrorCode_UnsupportedMediaType = 3000    /*!< Unsupported media type */,
+
+    _OrthancPluginErrorCode_INTERNAL = 0x7fffffff
+  } OrthancPluginErrorCode;
+
+
+  /**
+   * Forward declaration of one of the mandatory functions for Orthanc
+   * plugins.
+   **/
+  ORTHANC_PLUGINS_API const char* OrthancPluginGetName();
+
+
+  /**
+   * The various HTTP methods for a REST call.
+   **/
+  typedef enum
+  {
+    OrthancPluginHttpMethod_Get = 1,    /*!< GET request */
+    OrthancPluginHttpMethod_Post = 2,   /*!< POST request */
+    OrthancPluginHttpMethod_Put = 3,    /*!< PUT request */
+    OrthancPluginHttpMethod_Delete = 4, /*!< DELETE request */
+
+    _OrthancPluginHttpMethod_INTERNAL = 0x7fffffff
+  } OrthancPluginHttpMethod;
+
+
+  /**
+   * @brief The parameters of a REST request.
+   * @ingroup Callbacks
+   **/
+  typedef struct
+  {
+    /**
+     * @brief The HTTP method.
+     **/
+    OrthancPluginHttpMethod method;
+
+    /**
+     * @brief The number of groups of the regular expression.
+     **/
+    uint32_t                groupsCount;
+
+    /**
+     * @brief The matched values for the groups of the regular expression.
+     **/
+    const char* const*      groups;
+
+    /**
+     * @brief For a GET request, the number of GET parameters.
+     **/
+    uint32_t                getCount;
+
+    /**
+     * @brief For a GET request, the keys of the GET parameters.
+     **/
+    const char* const*      getKeys;
+
+    /**
+     * @brief For a GET request, the values of the GET parameters.
+     **/
+    const char* const*      getValues;
+
+    /**
+     * @brief For a PUT or POST request, the content of the body.
+     **/
+    const void*             body;
+
+    /**
+     * @brief For a PUT or POST request, the number of bytes of the body.
+     **/
+    uint32_t                bodySize;
+
+
+    /* --------------------------------------------------
+       New in version 0.8.1
+       -------------------------------------------------- */
+
+    /**
+     * @brief The number of HTTP headers.
+     **/
+    uint32_t                headersCount;
+
+    /**
+     * @brief The keys of the HTTP headers (always converted to low-case).
+     **/
+    const char* const*      headersKeys;
+
+    /**
+     * @brief The values of the HTTP headers.
+     **/
+    const char* const*      headersValues;
+
+  } OrthancPluginHttpRequest;
+
+
+  typedef enum 
+  {
+    /* Generic services */
+    _OrthancPluginService_LogInfo = 1,
+    _OrthancPluginService_LogWarning = 2,
+    _OrthancPluginService_LogError = 3,
+    _OrthancPluginService_GetOrthancPath = 4,
+    _OrthancPluginService_GetOrthancDirectory = 5,
+    _OrthancPluginService_GetConfigurationPath = 6,
+    _OrthancPluginService_SetPluginProperty = 7,
+    _OrthancPluginService_GetGlobalProperty = 8,
+    _OrthancPluginService_SetGlobalProperty = 9,
+    _OrthancPluginService_GetCommandLineArgumentsCount = 10,
+    _OrthancPluginService_GetCommandLineArgument = 11,
+    _OrthancPluginService_GetExpectedDatabaseVersion = 12,
+    _OrthancPluginService_GetConfiguration = 13,
+    _OrthancPluginService_BufferCompression = 14,
+    _OrthancPluginService_ReadFile = 15,
+    _OrthancPluginService_WriteFile = 16,
+    _OrthancPluginService_GetErrorDescription = 17,
+    _OrthancPluginService_CallHttpClient = 18,
+    _OrthancPluginService_RegisterErrorCode = 19,
+    _OrthancPluginService_RegisterDictionaryTag = 20,
+    _OrthancPluginService_DicomBufferToJson = 21,
+    _OrthancPluginService_DicomInstanceToJson = 22,
+    _OrthancPluginService_CreateDicom = 23,
+    _OrthancPluginService_ComputeMd5 = 24,
+    _OrthancPluginService_ComputeSha1 = 25,
+    _OrthancPluginService_LookupDictionary = 26,
+    _OrthancPluginService_CallHttpClient2 = 27,
+    _OrthancPluginService_GenerateUuid = 28,
+    _OrthancPluginService_RegisterPrivateDictionaryTag = 29,
+    _OrthancPluginService_AutodetectMimeType = 30,
+    _OrthancPluginService_SetMetricsValue = 31,
+    _OrthancPluginService_EncodeDicomWebJson = 32,
+    _OrthancPluginService_EncodeDicomWebXml = 33,
+    _OrthancPluginService_ChunkedHttpClient = 34,    /* New in Orthanc 1.5.7 */
+    _OrthancPluginService_GetTagName = 35,           /* New in Orthanc 1.5.7 */
+    _OrthancPluginService_EncodeDicomWebJson2 = 36,  /* New in Orthanc 1.7.0 */
+    _OrthancPluginService_EncodeDicomWebXml2 = 37,   /* New in Orthanc 1.7.0 */
+    _OrthancPluginService_CreateMemoryBuffer = 38,   /* New in Orthanc 1.7.0 */
+    _OrthancPluginService_GenerateRestApiAuthorizationToken = 39,   /* New in Orthanc 1.8.1 */
+    _OrthancPluginService_CreateMemoryBuffer64 = 40, /* New in Orthanc 1.9.0 */
+    _OrthancPluginService_CreateDicom2 = 41,         /* New in Orthanc 1.9.0 */
+    
+    /* Registration of callbacks */
+    _OrthancPluginService_RegisterRestCallback = 1000,
+    _OrthancPluginService_RegisterOnStoredInstanceCallback = 1001,
+    _OrthancPluginService_RegisterStorageArea = 1002,
+    _OrthancPluginService_RegisterOnChangeCallback = 1003,
+    _OrthancPluginService_RegisterRestCallbackNoLock = 1004,
+    _OrthancPluginService_RegisterWorklistCallback = 1005,
+    _OrthancPluginService_RegisterDecodeImageCallback = 1006,
+    _OrthancPluginService_RegisterIncomingHttpRequestFilter = 1007,
+    _OrthancPluginService_RegisterFindCallback = 1008,
+    _OrthancPluginService_RegisterMoveCallback = 1009,
+    _OrthancPluginService_RegisterIncomingHttpRequestFilter2 = 1010,
+    _OrthancPluginService_RegisterRefreshMetricsCallback = 1011,
+    _OrthancPluginService_RegisterChunkedRestCallback = 1012,  /* New in Orthanc 1.5.7 */
+    _OrthancPluginService_RegisterStorageCommitmentScpCallback = 1013,
+    _OrthancPluginService_RegisterIncomingDicomInstanceFilter = 1014,
+    _OrthancPluginService_RegisterTranscoderCallback = 1015,   /* New in Orthanc 1.7.0 */
+    _OrthancPluginService_RegisterStorageArea2 = 1016,         /* New in Orthanc 1.9.0 */
+    _OrthancPluginService_RegisterIncomingCStoreInstanceFilter = 1017,  /* New in Orthanc 1.10.0 */
+    _OrthancPluginService_RegisterReceivedInstanceCallback = 1018,  /* New in Orthanc 1.10.0 */
+    _OrthancPluginService_RegisterWebDavCollection = 1019,     /* New in Orthanc 1.10.1 */
+
+    /* Sending answers to REST calls */
+    _OrthancPluginService_AnswerBuffer = 2000,
+    _OrthancPluginService_CompressAndAnswerPngImage = 2001,  /* Unused as of Orthanc 0.9.4 */
+    _OrthancPluginService_Redirect = 2002,
+    _OrthancPluginService_SendHttpStatusCode = 2003,
+    _OrthancPluginService_SendUnauthorized = 2004,
+    _OrthancPluginService_SendMethodNotAllowed = 2005,
+    _OrthancPluginService_SetCookie = 2006,
+    _OrthancPluginService_SetHttpHeader = 2007,
+    _OrthancPluginService_StartMultipartAnswer = 2008,
+    _OrthancPluginService_SendMultipartItem = 2009,
+    _OrthancPluginService_SendHttpStatus = 2010,
+    _OrthancPluginService_CompressAndAnswerImage = 2011,
+    _OrthancPluginService_SendMultipartItem2 = 2012,
+    _OrthancPluginService_SetHttpErrorDetails = 2013,
+
+    /* Access to the Orthanc database and API */
+    _OrthancPluginService_GetDicomForInstance = 3000,
+    _OrthancPluginService_RestApiGet = 3001,
+    _OrthancPluginService_RestApiPost = 3002,
+    _OrthancPluginService_RestApiDelete = 3003,
+    _OrthancPluginService_RestApiPut = 3004,
+    _OrthancPluginService_LookupPatient = 3005,
+    _OrthancPluginService_LookupStudy = 3006,
+    _OrthancPluginService_LookupSeries = 3007,
+    _OrthancPluginService_LookupInstance = 3008,
+    _OrthancPluginService_LookupStudyWithAccessionNumber = 3009,
+    _OrthancPluginService_RestApiGetAfterPlugins = 3010,
+    _OrthancPluginService_RestApiPostAfterPlugins = 3011,
+    _OrthancPluginService_RestApiDeleteAfterPlugins = 3012,
+    _OrthancPluginService_RestApiPutAfterPlugins = 3013,
+    _OrthancPluginService_ReconstructMainDicomTags = 3014,
+    _OrthancPluginService_RestApiGet2 = 3015,
+    _OrthancPluginService_CallRestApi = 3016,              /* New in Orthanc 1.9.2 */
+
+    /* Access to DICOM instances */
+    _OrthancPluginService_GetInstanceRemoteAet = 4000,
+    _OrthancPluginService_GetInstanceSize = 4001,
+    _OrthancPluginService_GetInstanceData = 4002,
+    _OrthancPluginService_GetInstanceJson = 4003,
+    _OrthancPluginService_GetInstanceSimplifiedJson = 4004,
+    _OrthancPluginService_HasInstanceMetadata = 4005,
+    _OrthancPluginService_GetInstanceMetadata = 4006,
+    _OrthancPluginService_GetInstanceOrigin = 4007,
+    _OrthancPluginService_GetInstanceTransferSyntaxUid = 4008,
+    _OrthancPluginService_HasInstancePixelData = 4009,
+    _OrthancPluginService_CreateDicomInstance = 4010,      /* New in Orthanc 1.7.0 */
+    _OrthancPluginService_FreeDicomInstance = 4011,        /* New in Orthanc 1.7.0 */
+    _OrthancPluginService_GetInstanceFramesCount = 4012,   /* New in Orthanc 1.7.0 */
+    _OrthancPluginService_GetInstanceRawFrame = 4013,      /* New in Orthanc 1.7.0 */
+    _OrthancPluginService_GetInstanceDecodedFrame = 4014,  /* New in Orthanc 1.7.0 */
+    _OrthancPluginService_TranscodeDicomInstance = 4015,   /* New in Orthanc 1.7.0 */
+    _OrthancPluginService_SerializeDicomInstance = 4016,   /* New in Orthanc 1.7.0 */
+    _OrthancPluginService_GetInstanceAdvancedJson = 4017,  /* New in Orthanc 1.7.0 */
+    _OrthancPluginService_GetInstanceDicomWebJson = 4018,  /* New in Orthanc 1.7.0 */
+    _OrthancPluginService_GetInstanceDicomWebXml = 4019,   /* New in Orthanc 1.7.0 */
+    
+    /* Services for plugins implementing a database back-end */
+    _OrthancPluginService_RegisterDatabaseBackend = 5000,    /* New in Orthanc 0.8.6 */
+    _OrthancPluginService_DatabaseAnswer = 5001,
+    _OrthancPluginService_RegisterDatabaseBackendV2 = 5002,  /* New in Orthanc 0.9.4 */
+    _OrthancPluginService_StorageAreaCreate = 5003,
+    _OrthancPluginService_StorageAreaRead = 5004,
+    _OrthancPluginService_StorageAreaRemove = 5005,
+    _OrthancPluginService_RegisterDatabaseBackendV3 = 5006,  /* New in Orthanc 1.9.2 */
+
+    /* Primitives for handling images */
+    _OrthancPluginService_GetImagePixelFormat = 6000,
+    _OrthancPluginService_GetImageWidth = 6001,
+    _OrthancPluginService_GetImageHeight = 6002,
+    _OrthancPluginService_GetImagePitch = 6003,
+    _OrthancPluginService_GetImageBuffer = 6004,
+    _OrthancPluginService_UncompressImage = 6005,
+    _OrthancPluginService_FreeImage = 6006,
+    _OrthancPluginService_CompressImage = 6007,
+    _OrthancPluginService_ConvertPixelFormat = 6008,
+    _OrthancPluginService_GetFontsCount = 6009,
+    _OrthancPluginService_GetFontInfo = 6010,
+    _OrthancPluginService_DrawText = 6011,
+    _OrthancPluginService_CreateImage = 6012,
+    _OrthancPluginService_CreateImageAccessor = 6013,
+    _OrthancPluginService_DecodeDicomImage = 6014,
+
+    /* Primitives for handling C-Find, C-Move and worklists */
+    _OrthancPluginService_WorklistAddAnswer = 7000,
+    _OrthancPluginService_WorklistMarkIncomplete = 7001,
+    _OrthancPluginService_WorklistIsMatch = 7002,
+    _OrthancPluginService_WorklistGetDicomQuery = 7003,
+    _OrthancPluginService_FindAddAnswer = 7004,
+    _OrthancPluginService_FindMarkIncomplete = 7005,
+    _OrthancPluginService_GetFindQuerySize = 7006,
+    _OrthancPluginService_GetFindQueryTag = 7007,
+    _OrthancPluginService_GetFindQueryTagName = 7008,
+    _OrthancPluginService_GetFindQueryValue = 7009,
+    _OrthancPluginService_CreateFindMatcher = 7010,
+    _OrthancPluginService_FreeFindMatcher = 7011,
+    _OrthancPluginService_FindMatcherIsMatch = 7012,
+
+    /* Primitives for accessing Orthanc Peers (new in 1.4.2) */
+    _OrthancPluginService_GetPeers = 8000,
+    _OrthancPluginService_FreePeers = 8001,
+    _OrthancPluginService_GetPeersCount = 8003,
+    _OrthancPluginService_GetPeerName = 8004,
+    _OrthancPluginService_GetPeerUrl = 8005,
+    _OrthancPluginService_CallPeerApi = 8006,
+    _OrthancPluginService_GetPeerUserProperty = 8007,
+
+    /* Primitives for handling jobs (new in 1.4.2) */
+    _OrthancPluginService_CreateJob = 9000,
+    _OrthancPluginService_FreeJob = 9001,
+    _OrthancPluginService_SubmitJob = 9002,
+    _OrthancPluginService_RegisterJobsUnserializer = 9003,
+    
+    _OrthancPluginService_INTERNAL = 0x7fffffff
+  } _OrthancPluginService;
+
+
+  typedef enum
+  {
+    _OrthancPluginProperty_Description = 1,
+    _OrthancPluginProperty_RootUri = 2,
+    _OrthancPluginProperty_OrthancExplorer = 3,
+
+    _OrthancPluginProperty_INTERNAL = 0x7fffffff
+  } _OrthancPluginProperty;
+
+
+
+  /**
+   * The memory layout of the pixels of an image.
+   * @ingroup Images
+   **/
+  typedef enum
+  {
+    /**
+     * @brief Graylevel 8bpp image.
+     *
+     * The image is graylevel. Each pixel is unsigned and stored in
+     * one byte.
+     **/
+    OrthancPluginPixelFormat_Grayscale8 = 1,
+
+    /**
+     * @brief Graylevel, unsigned 16bpp image.
+     *
+     * The image is graylevel. Each pixel is unsigned and stored in
+     * two bytes.
+     **/
+    OrthancPluginPixelFormat_Grayscale16 = 2,
+
+    /**
+     * @brief Graylevel, signed 16bpp image.
+     *
+     * The image is graylevel. Each pixel is signed and stored in two
+     * bytes.
+     **/
+    OrthancPluginPixelFormat_SignedGrayscale16 = 3,
+
+    /**
+     * @brief Color image in RGB24 format.
+     *
+     * This format describes a color image. The pixels are stored in 3
+     * consecutive bytes. The memory layout is RGB.
+     **/
+    OrthancPluginPixelFormat_RGB24 = 4,
+
+    /**
+     * @brief Color image in RGBA32 format.
+     *
+     * This format describes a color image. The pixels are stored in 4
+     * consecutive bytes. The memory layout is RGBA.
+     **/
+    OrthancPluginPixelFormat_RGBA32 = 5,
+
+    OrthancPluginPixelFormat_Unknown = 6,   /*!< Unknown pixel format */
+
+    /**
+     * @brief Color image in RGB48 format.
+     *
+     * This format describes a color image. The pixels are stored in 6
+     * consecutive bytes. The memory layout is RRGGBB.
+     **/
+    OrthancPluginPixelFormat_RGB48 = 7,
+
+    /**
+     * @brief Graylevel, unsigned 32bpp image.
+     *
+     * The image is graylevel. Each pixel is unsigned and stored in
+     * four bytes.
+     **/
+    OrthancPluginPixelFormat_Grayscale32 = 8,
+
+    /**
+     * @brief Graylevel, floating-point 32bpp image.
+     *
+     * The image is graylevel. Each pixel is floating-point and stored
+     * in four bytes.
+     **/
+    OrthancPluginPixelFormat_Float32 = 9,
+
+    /**
+     * @brief Color image in BGRA32 format.
+     *
+     * This format describes a color image. The pixels are stored in 4
+     * consecutive bytes. The memory layout is BGRA.
+     **/
+    OrthancPluginPixelFormat_BGRA32 = 10,
+
+    /**
+     * @brief Graylevel, unsigned 64bpp image.
+     *
+     * The image is graylevel. Each pixel is unsigned and stored in
+     * eight bytes.
+     **/
+    OrthancPluginPixelFormat_Grayscale64 = 11,
+
+    _OrthancPluginPixelFormat_INTERNAL = 0x7fffffff
+  } OrthancPluginPixelFormat;
+
+
+
+  /**
+   * The content types that are supported by Orthanc plugins.
+   **/
+  typedef enum
+  {
+    OrthancPluginContentType_Unknown = 0,             /*!< Unknown content type */
+    OrthancPluginContentType_Dicom = 1,               /*!< DICOM */
+    OrthancPluginContentType_DicomAsJson = 2,         /*!< JSON summary of a DICOM file */
+    OrthancPluginContentType_DicomUntilPixelData = 3, /*!< DICOM Header till pixel data */
+
+    _OrthancPluginContentType_INTERNAL = 0x7fffffff
+  } OrthancPluginContentType;
+
+
+
+  /**
+   * The supported types of DICOM resources.
+   **/
+  typedef enum
+  {
+    OrthancPluginResourceType_Patient = 0,     /*!< Patient */
+    OrthancPluginResourceType_Study = 1,       /*!< Study */
+    OrthancPluginResourceType_Series = 2,      /*!< Series */
+    OrthancPluginResourceType_Instance = 3,    /*!< Instance */
+    OrthancPluginResourceType_None = 4,        /*!< Unavailable resource type */
+
+    _OrthancPluginResourceType_INTERNAL = 0x7fffffff
+  } OrthancPluginResourceType;
+
+
+
+  /**
+   * The supported types of changes that can be signaled to the change callback.
+   * @ingroup Callbacks
+   **/
+  typedef enum
+  {
+    OrthancPluginChangeType_CompletedSeries = 0,    /*!< Series is now complete */
+    OrthancPluginChangeType_Deleted = 1,            /*!< Deleted resource */
+    OrthancPluginChangeType_NewChildInstance = 2,   /*!< A new instance was added to this resource */
+    OrthancPluginChangeType_NewInstance = 3,        /*!< New instance received */
+    OrthancPluginChangeType_NewPatient = 4,         /*!< New patient created */
+    OrthancPluginChangeType_NewSeries = 5,          /*!< New series created */
+    OrthancPluginChangeType_NewStudy = 6,           /*!< New study created */
+    OrthancPluginChangeType_StablePatient = 7,      /*!< Timeout: No new instance in this patient */
+    OrthancPluginChangeType_StableSeries = 8,       /*!< Timeout: No new instance in this series */
+    OrthancPluginChangeType_StableStudy = 9,        /*!< Timeout: No new instance in this study */
+    OrthancPluginChangeType_OrthancStarted = 10,    /*!< Orthanc has started */
+    OrthancPluginChangeType_OrthancStopped = 11,    /*!< Orthanc is stopping */
+    OrthancPluginChangeType_UpdatedAttachment = 12, /*!< Some user-defined attachment has changed for this resource */
+    OrthancPluginChangeType_UpdatedMetadata = 13,   /*!< Some user-defined metadata has changed for this resource */
+    OrthancPluginChangeType_UpdatedPeers = 14,      /*!< The list of Orthanc peers has changed */
+    OrthancPluginChangeType_UpdatedModalities = 15, /*!< The list of DICOM modalities has changed */
+    OrthancPluginChangeType_JobSubmitted = 16,      /*!< New Job submitted */
+    OrthancPluginChangeType_JobSuccess = 17,        /*!< A Job has completed successfully */
+    OrthancPluginChangeType_JobFailure = 18,        /*!< A Job has failed */
+
+    _OrthancPluginChangeType_INTERNAL = 0x7fffffff
+  } OrthancPluginChangeType;
+
+
+  /**
+   * The compression algorithms that are supported by the Orthanc core.
+   * @ingroup Images
+   **/
+  typedef enum
+  {
+    OrthancPluginCompressionType_Zlib = 0,          /*!< Standard zlib compression */
+    OrthancPluginCompressionType_ZlibWithSize = 1,  /*!< zlib, prefixed with uncompressed size (uint64_t) */
+    OrthancPluginCompressionType_Gzip = 2,          /*!< Standard gzip compression */
+    OrthancPluginCompressionType_GzipWithSize = 3,  /*!< gzip, prefixed with uncompressed size (uint64_t) */
+
+    _OrthancPluginCompressionType_INTERNAL = 0x7fffffff
+  } OrthancPluginCompressionType;
+
+
+  /**
+   * The image formats that are supported by the Orthanc core.
+   * @ingroup Images
+   **/
+  typedef enum
+  {
+    OrthancPluginImageFormat_Png = 0,    /*!< Image compressed using PNG */
+    OrthancPluginImageFormat_Jpeg = 1,   /*!< Image compressed using JPEG */
+    OrthancPluginImageFormat_Dicom = 2,  /*!< Image compressed using DICOM */
+
+    _OrthancPluginImageFormat_INTERNAL = 0x7fffffff
+  } OrthancPluginImageFormat;
+
+
+  /**
+   * The value representations present in the DICOM standard (version 2013).
+   * @ingroup Toolbox
+   **/
+  typedef enum
+  {
+    OrthancPluginValueRepresentation_AE = 1,   /*!< Application Entity */
+    OrthancPluginValueRepresentation_AS = 2,   /*!< Age String */
+    OrthancPluginValueRepresentation_AT = 3,   /*!< Attribute Tag */
+    OrthancPluginValueRepresentation_CS = 4,   /*!< Code String */
+    OrthancPluginValueRepresentation_DA = 5,   /*!< Date */
+    OrthancPluginValueRepresentation_DS = 6,   /*!< Decimal String */
+    OrthancPluginValueRepresentation_DT = 7,   /*!< Date Time */
+    OrthancPluginValueRepresentation_FD = 8,   /*!< Floating Point Double */
+    OrthancPluginValueRepresentation_FL = 9,   /*!< Floating Point Single */
+    OrthancPluginValueRepresentation_IS = 10,  /*!< Integer String */
+    OrthancPluginValueRepresentation_LO = 11,  /*!< Long String */
+    OrthancPluginValueRepresentation_LT = 12,  /*!< Long Text */
+    OrthancPluginValueRepresentation_OB = 13,  /*!< Other Byte String */
+    OrthancPluginValueRepresentation_OF = 14,  /*!< Other Float String */
+    OrthancPluginValueRepresentation_OW = 15,  /*!< Other Word String */
+    OrthancPluginValueRepresentation_PN = 16,  /*!< Person Name */
+    OrthancPluginValueRepresentation_SH = 17,  /*!< Short String */
+    OrthancPluginValueRepresentation_SL = 18,  /*!< Signed Long */
+    OrthancPluginValueRepresentation_SQ = 19,  /*!< Sequence of Items */
+    OrthancPluginValueRepresentation_SS = 20,  /*!< Signed Short */
+    OrthancPluginValueRepresentation_ST = 21,  /*!< Short Text */
+    OrthancPluginValueRepresentation_TM = 22,  /*!< Time */
+    OrthancPluginValueRepresentation_UI = 23,  /*!< Unique Identifier (UID) */
+    OrthancPluginValueRepresentation_UL = 24,  /*!< Unsigned Long */
+    OrthancPluginValueRepresentation_UN = 25,  /*!< Unknown */
+    OrthancPluginValueRepresentation_US = 26,  /*!< Unsigned Short */
+    OrthancPluginValueRepresentation_UT = 27,  /*!< Unlimited Text */
+
+    _OrthancPluginValueRepresentation_INTERNAL = 0x7fffffff
+  } OrthancPluginValueRepresentation;
+
+
+  /**
+   * The possible output formats for a DICOM-to-JSON conversion.
+   * @ingroup Toolbox
+   * @see OrthancPluginDicomToJson()
+   **/
+  typedef enum
+  {
+    OrthancPluginDicomToJsonFormat_Full = 1,    /*!< Full output, with most details */
+    OrthancPluginDicomToJsonFormat_Short = 2,   /*!< Tags output as hexadecimal numbers */
+    OrthancPluginDicomToJsonFormat_Human = 3,   /*!< Human-readable JSON */
+
+    _OrthancPluginDicomToJsonFormat_INTERNAL = 0x7fffffff
+  } OrthancPluginDicomToJsonFormat;
+
+
+  /**
+   * Flags to customize a DICOM-to-JSON conversion. By default, binary
+   * tags are formatted using Data URI scheme.
+   * @ingroup Toolbox
+   **/
+  typedef enum
+  {
+    OrthancPluginDicomToJsonFlags_None                  = 0,
+    OrthancPluginDicomToJsonFlags_IncludeBinary         = (1 << 0),  /*!< Include the binary tags */
+    OrthancPluginDicomToJsonFlags_IncludePrivateTags    = (1 << 1),  /*!< Include the private tags */
+    OrthancPluginDicomToJsonFlags_IncludeUnknownTags    = (1 << 2),  /*!< Include the tags unknown by the dictionary */
+    OrthancPluginDicomToJsonFlags_IncludePixelData      = (1 << 3),  /*!< Include the pixel data */
+    OrthancPluginDicomToJsonFlags_ConvertBinaryToAscii  = (1 << 4),  /*!< Output binary tags as-is, dropping non-ASCII */
+    OrthancPluginDicomToJsonFlags_ConvertBinaryToNull   = (1 << 5),  /*!< Signal binary tags as null values */
+    OrthancPluginDicomToJsonFlags_StopAfterPixelData    = (1 << 6),  /*!< Stop processing after pixel data (new in 1.9.1) */
+    OrthancPluginDicomToJsonFlags_SkipGroupLengths      = (1 << 7),  /*!< Skip tags whose element is zero (new in 1.9.1) */
+
+    _OrthancPluginDicomToJsonFlags_INTERNAL = 0x7fffffff
+  } OrthancPluginDicomToJsonFlags;
+
+
+  /**
+   * Flags to the creation of a DICOM file.
+   * @ingroup Toolbox
+   * @see OrthancPluginCreateDicom()
+   **/
+  typedef enum
+  {
+    OrthancPluginCreateDicomFlags_None                  = 0,
+    OrthancPluginCreateDicomFlags_DecodeDataUriScheme   = (1 << 0),  /*!< Decode fields encoded using data URI scheme */
+    OrthancPluginCreateDicomFlags_GenerateIdentifiers   = (1 << 1),  /*!< Automatically generate DICOM identifiers */
+
+    _OrthancPluginCreateDicomFlags_INTERNAL = 0x7fffffff
+  } OrthancPluginCreateDicomFlags;
+
+
+  /**
+   * The constraints on the DICOM identifiers that must be supported
+   * by the database plugins.
+   * @deprecated Plugins using OrthancPluginConstraintType will be faster
+   **/
+  typedef enum
+  {
+    OrthancPluginIdentifierConstraint_Equal = 1,           /*!< Equal */
+    OrthancPluginIdentifierConstraint_SmallerOrEqual = 2,  /*!< Less or equal */
+    OrthancPluginIdentifierConstraint_GreaterOrEqual = 3,  /*!< More or equal */
+    OrthancPluginIdentifierConstraint_Wildcard = 4,        /*!< Case-sensitive wildcard matching (with * and ?) */
+
+    _OrthancPluginIdentifierConstraint_INTERNAL = 0x7fffffff
+  } OrthancPluginIdentifierConstraint;
+
+
+  /**
+   * The constraints on the tags (main DICOM tags and identifier tags)
+   * that must be supported by the database plugins.
+   **/
+  typedef enum
+  {
+    OrthancPluginConstraintType_Equal = 1,           /*!< Equal */
+    OrthancPluginConstraintType_SmallerOrEqual = 2,  /*!< Less or equal */
+    OrthancPluginConstraintType_GreaterOrEqual = 3,  /*!< More or equal */
+    OrthancPluginConstraintType_Wildcard = 4,        /*!< Wildcard matching */
+    OrthancPluginConstraintType_List = 5,            /*!< List of values */
+
+    _OrthancPluginConstraintType_INTERNAL = 0x7fffffff
+  } OrthancPluginConstraintType;
+
+
+  /**
+   * The origin of a DICOM instance that has been received by Orthanc.
+   **/
+  typedef enum
+  {
+    OrthancPluginInstanceOrigin_Unknown = 1,        /*!< Unknown origin */
+    OrthancPluginInstanceOrigin_DicomProtocol = 2,  /*!< Instance received through DICOM protocol */
+    OrthancPluginInstanceOrigin_RestApi = 3,        /*!< Instance received through REST API of Orthanc */
+    OrthancPluginInstanceOrigin_Plugin = 4,         /*!< Instance added to Orthanc by a plugin */
+    OrthancPluginInstanceOrigin_Lua = 5,            /*!< Instance added to Orthanc by a Lua script */
+    OrthancPluginInstanceOrigin_WebDav = 6,         /*!< Instance received through WebDAV (new in 1.8.0) */
+
+    _OrthancPluginInstanceOrigin_INTERNAL = 0x7fffffff
+  } OrthancPluginInstanceOrigin;
+
+
+  /**
+   * The possible status for one single step of a job.
+   **/
+  typedef enum
+  {
+    OrthancPluginJobStepStatus_Success = 1,   /*!< The job has successfully executed all its steps */
+    OrthancPluginJobStepStatus_Failure = 2,   /*!< The job has failed while executing this step */
+    OrthancPluginJobStepStatus_Continue = 3   /*!< The job has still data to process after this step */
+  } OrthancPluginJobStepStatus;
+
+
+  /**
+   * Explains why the job should stop and release the resources it has
+   * allocated. This is especially important to disambiguate between
+   * the "paused" condition and the "final" conditions (success,
+   * failure, or canceled).
+   **/
+  typedef enum
+  {
+    OrthancPluginJobStopReason_Success = 1,  /*!< The job has succeeded */
+    OrthancPluginJobStopReason_Paused = 2,   /*!< The job was paused, and will be resumed later */
+    OrthancPluginJobStopReason_Failure = 3,  /*!< The job has failed, and might be resubmitted later */
+    OrthancPluginJobStopReason_Canceled = 4  /*!< The job was canceled, and might be resubmitted later */
+  } OrthancPluginJobStopReason;
+
+
+  /**
+   * The available types of metrics.
+   **/
+  typedef enum
+  {
+    OrthancPluginMetricsType_Default = 0,   /*!< Default metrics */
+
+    /**
+     * This metrics represents a time duration. Orthanc will keep the
+     * maximum value of the metrics over a sliding window of ten
+     * seconds, which is useful if the metrics is sampled frequently.
+     **/
+    OrthancPluginMetricsType_Timer = 1
+  } OrthancPluginMetricsType;
+  
+
+  /**
+   * The available modes to export a binary DICOM tag into a DICOMweb
+   * JSON or XML document.
+   **/
+  typedef enum
+  {
+    OrthancPluginDicomWebBinaryMode_Ignore = 0,        /*!< Don't include binary tags */
+    OrthancPluginDicomWebBinaryMode_InlineBinary = 1,  /*!< Inline encoding using Base64 */
+    OrthancPluginDicomWebBinaryMode_BulkDataUri = 2    /*!< Use a bulk data URI field */
+  } OrthancPluginDicomWebBinaryMode;
+
+
+  /**
+   * The available values for the Failure Reason (0008,1197) during
+   * storage commitment.
+   * http://dicom.nema.org/medical/dicom/2019e/output/chtml/part03/sect_C.14.html#sect_C.14.1.1
+   **/
+  typedef enum
+  {
+    OrthancPluginStorageCommitmentFailureReason_Success = 0,
+    /*!< Success: The DICOM instance is properly stored in the SCP */
+
+    OrthancPluginStorageCommitmentFailureReason_ProcessingFailure = 1,
+    /*!< 0110H: A general failure in processing the operation was encountered */
+
+    OrthancPluginStorageCommitmentFailureReason_NoSuchObjectInstance = 2,
+    /*!< 0112H: One or more of the elements in the Referenced SOP
+      Instance Sequence was not available */
+
+    OrthancPluginStorageCommitmentFailureReason_ResourceLimitation = 3,
+    /*!< 0213H: The SCP does not currently have enough resources to
+      store the requested SOP Instance(s) */
+
+    OrthancPluginStorageCommitmentFailureReason_ReferencedSOPClassNotSupported = 4,
+    /*!< 0122H: Storage Commitment has been requested for a SOP
+      Instance with a SOP Class that is not supported by the SCP */
+
+    OrthancPluginStorageCommitmentFailureReason_ClassInstanceConflict = 5,
+    /*!< 0119H: The SOP Class of an element in the Referenced SOP
+      Instance Sequence did not correspond to the SOP class registered
+      for this SOP Instance at the SCP */
+
+    OrthancPluginStorageCommitmentFailureReason_DuplicateTransactionUID = 6
+    /*!< 0131H: The Transaction UID of the Storage Commitment Request
+      is already in use */
+  } OrthancPluginStorageCommitmentFailureReason;
+
+
+  /**
+   * The action to be taken after ReceivedInstanceCallback is triggered
+   **/
+  typedef enum
+  {
+    OrthancPluginReceivedInstanceAction_KeepAsIs = 1, /*!< Keep the instance as is */
+    OrthancPluginReceivedInstanceAction_Modify = 2,   /*!< Modify the instance */
+    OrthancPluginReceivedInstanceAction_Discard = 3,  /*!< Discard the instance */
+
+    _OrthancPluginReceivedInstanceAction_INTERNAL = 0x7fffffff
+  } OrthancPluginReceivedInstanceAction;
+
+
+  /**
+   * @brief A 32-bit memory buffer allocated by the core system of Orthanc.
+   *
+   * A memory buffer allocated by the core system of Orthanc. When the
+   * content of the buffer is not useful anymore, it must be free by a
+   * call to ::OrthancPluginFreeMemoryBuffer().
+   **/
+  typedef struct
+  {
+    /**
+     * @brief The content of the buffer.
+     **/
+    void*      data;
+
+    /**
+     * @brief The number of bytes in the buffer.
+     **/
+    uint32_t   size;
+  } OrthancPluginMemoryBuffer;
+
+
+
+  /**
+   * @brief A 64-bit memory buffer allocated by the core system of Orthanc.
+   *
+   * A memory buffer allocated by the core system of Orthanc. When the
+   * content of the buffer is not useful anymore, it must be free by a
+   * call to ::OrthancPluginFreeMemoryBuffer64().
+   **/
+  typedef struct
+  {
+    /**
+     * @brief The content of the buffer.
+     **/
+    void*      data;
+
+    /**
+     * @brief The number of bytes in the buffer.
+     **/
+    uint64_t   size;
+  } OrthancPluginMemoryBuffer64;
+
+
+
+
+  /**
+   * @brief Opaque structure that represents the HTTP connection to the client application.
+   * @ingroup Callbacks
+   **/
+  typedef struct _OrthancPluginRestOutput_t OrthancPluginRestOutput;
+
+
+
+  /**
+   * @brief Opaque structure that represents a DICOM instance that is managed by the Orthanc core.
+   * @ingroup DicomInstance
+   **/
+  typedef struct _OrthancPluginDicomInstance_t OrthancPluginDicomInstance;
+
+
+
+  /**
+   * @brief Opaque structure that represents an image that is uncompressed in memory.
+   * @ingroup Images
+   **/
+  typedef struct _OrthancPluginImage_t OrthancPluginImage;
+
+
+
+  /**
+   * @brief Opaque structure that represents the storage area that is actually used by Orthanc.
+   * @ingroup Images
+   **/
+  typedef struct _OrthancPluginStorageArea_t OrthancPluginStorageArea;
+
+
+
+  /**
+   * @brief Opaque structure to an object that represents a C-Find query for worklists.
+   * @ingroup DicomCallbacks
+   **/
+  typedef struct _OrthancPluginWorklistQuery_t OrthancPluginWorklistQuery;
+
+
+
+  /**
+   * @brief Opaque structure to an object that represents the answers to a C-Find query for worklists.
+   * @ingroup DicomCallbacks
+   **/
+  typedef struct _OrthancPluginWorklistAnswers_t OrthancPluginWorklistAnswers;
+
+
+
+  /**
+   * @brief Opaque structure to an object that represents a C-Find query.
+   * @ingroup DicomCallbacks
+   **/
+  typedef struct _OrthancPluginFindQuery_t OrthancPluginFindQuery;
+
+
+
+  /**
+   * @brief Opaque structure to an object that represents the answers to a C-Find query for worklists.
+   * @ingroup DicomCallbacks
+   **/
+  typedef struct _OrthancPluginFindAnswers_t OrthancPluginFindAnswers;
+
+
+
+  /**
+   * @brief Opaque structure to an object that can be used to check whether a DICOM instance matches a C-Find query.
+   * @ingroup Toolbox
+   **/
+  typedef struct _OrthancPluginFindMatcher_t OrthancPluginFindMatcher;
+
+
+  
+  /**
+   * @brief Opaque structure to the set of remote Orthanc Peers that are known to the local Orthanc server.
+   * @ingroup Toolbox
+   **/
+  typedef struct _OrthancPluginPeers_t OrthancPluginPeers;
+
+
+
+  /**
+   * @brief Opaque structure to a job to be executed by Orthanc.
+   * @ingroup Toolbox
+   **/
+  typedef struct _OrthancPluginJob_t OrthancPluginJob;  
+
+
+
+  /**
+   * @brief Opaque structure that represents a node in a JSON or XML
+   * document used in DICOMweb.
+   * @ingroup Toolbox
+   **/
+  typedef struct _OrthancPluginDicomWebNode_t OrthancPluginDicomWebNode;
+
+  
+
+  /**
+   * @brief Signature of a callback function that answers to a REST request.
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginRestCallback) (
+    OrthancPluginRestOutput* output,
+    const char* url,
+    const OrthancPluginHttpRequest* request);
+
+
+
+  /**
+   * @brief Signature of a callback function that is triggered when Orthanc stores a new DICOM instance.
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginOnStoredInstanceCallback) (
+    const OrthancPluginDicomInstance* instance,
+    const char* instanceId);
+
+
+
+  /**
+   * @brief Signature of a callback function that is triggered when a change happens to some DICOM resource.
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginOnChangeCallback) (
+    OrthancPluginChangeType changeType,
+    OrthancPluginResourceType resourceType,
+    const char* resourceId);
+
+
+
+  /**
+   * @brief Signature of a callback function to decode a DICOM instance as an image.
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginDecodeImageCallback) (
+    OrthancPluginImage** target,
+    const void* dicom,
+    const uint32_t size,
+    uint32_t frameIndex);
+
+
+
+  /**
+   * @brief Signature of a function to free dynamic memory.
+   * @ingroup Callbacks
+   **/
+  typedef void (*OrthancPluginFree) (void* buffer);
+
+
+
+  /**
+   * @brief Signature of a function to set the content of a node
+   * encoding a binary DICOM tag, into a JSON or XML document
+   * generated for DICOMweb.
+   * @ingroup Callbacks
+   **/
+  typedef void (*OrthancPluginDicomWebSetBinaryNode) (
+    OrthancPluginDicomWebNode*       node,
+    OrthancPluginDicomWebBinaryMode  mode,
+    const char*                      bulkDataUri);
+    
+
+
+  /**
+   * @brief Callback for writing to the storage area.
+   *
+   * Signature of a callback function that is triggered when Orthanc writes a file to the storage area.
+   *
+   * @param uuid The UUID of the file.
+   * @param content The content of the file.
+   * @param size The size of the file.
+   * @param type The content type corresponding to this file. 
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginStorageCreate) (
+    const char* uuid,
+    const void* content,
+    int64_t size,
+    OrthancPluginContentType type);
+
+
+
+  /**
+   * @brief Callback for reading from the storage area.
+   *
+   * Signature of a callback function that is triggered when Orthanc reads a file from the storage area.
+   *
+   * @param content The content of the file (output).
+   * @param size The size of the file (output).
+   * @param uuid The UUID of the file of interest.
+   * @param type The content type corresponding to this file. 
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   * @deprecated New plugins should use OrthancPluginStorageRead2
+   * 
+   * @warning The "content" buffer *must* have been allocated using
+   * the "malloc()" function of your C standard library (i.e. nor
+   * "new[]", neither a pointer to a buffer). The "free()" function of
+   * your C standard library will automatically be invoked on the
+   * "content" pointer.
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginStorageRead) (
+    void** content,
+    int64_t* size,
+    const char* uuid,
+    OrthancPluginContentType type);
+
+
+
+  /**
+   * @brief Callback for reading a whole file from the storage area.
+   *
+   * Signature of a callback function that is triggered when Orthanc
+   * reads a whole file from the storage area.
+   *
+   * @param target Memory buffer where to store the content of the file. It must be allocated by the
+   * plugin using OrthancPluginCreateMemoryBuffer64(). The core of Orthanc will free it.
+   * @param uuid The UUID of the file of interest.
+   * @param type The content type corresponding to this file. 
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginStorageReadWhole) (
+    OrthancPluginMemoryBuffer64* target,
+    const char* uuid,
+    OrthancPluginContentType type);
+
+
+
+  /**
+   * @brief Callback for reading a range of a file from the storage area.
+   *
+   * Signature of a callback function that is triggered when Orthanc
+   * reads a portion of a file from the storage area. Orthanc
+   * indicates the start position and the length of the range.
+   *
+   * @param target Memory buffer where to store the content of the range.
+   * The memory buffer is allocated and freed by Orthanc. The length of the range
+   * of interest corresponds to the size of this buffer.
+   * @param uuid The UUID of the file of interest.
+   * @param type The content type corresponding to this file. 
+   * @param rangeStart Start position of the requested range in the file.
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginStorageReadRange) (
+    OrthancPluginMemoryBuffer64* target,
+    const char* uuid,
+    OrthancPluginContentType type,
+    uint64_t rangeStart);
+
+
+
+  /**
+   * @brief Callback for removing a file from the storage area.
+   *
+   * Signature of a callback function that is triggered when Orthanc deletes a file from the storage area.
+   *
+   * @param uuid The UUID of the file to be removed.
+   * @param type The content type corresponding to this file. 
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginStorageRemove) (
+    const char* uuid,
+    OrthancPluginContentType type);
+
+
+
+  /**
+   * @brief Callback to handle the C-Find SCP requests for worklists.
+   *
+   * Signature of a callback function that is triggered when Orthanc
+   * receives a C-Find SCP request against modality worklists.
+   *
+   * @param answers The target structure where answers must be stored.
+   * @param query The worklist query.
+   * @param issuerAet The Application Entity Title (AET) of the modality from which the request originates.
+   * @param calledAet The Application Entity Title (AET) of the modality that is called by the request.
+   * @return 0 if success, other value if error.
+   * @ingroup DicomCallbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginWorklistCallback) (
+    OrthancPluginWorklistAnswers*     answers,
+    const OrthancPluginWorklistQuery* query,
+    const char*                       issuerAet,
+    const char*                       calledAet);
+
+
+
+  /**
+   * @brief Callback to filter incoming HTTP requests received by Orthanc.
+   *
+   * Signature of a callback function that is triggered whenever
+   * Orthanc receives an HTTP/REST request, and that answers whether
+   * this request should be allowed. If the callback returns "0"
+   * ("false"), the server answers with HTTP status code 403
+   * (Forbidden).
+   *
+   * Pay attention to the fact that this function may be invoked
+   * concurrently by different threads of the Web server of
+   * Orthanc. You must implement proper locking if applicable.
+   *
+   * @param method The HTTP method used by the request.
+   * @param uri The URI of interest.
+   * @param ip The IP address of the HTTP client.
+   * @param headersCount The number of HTTP headers.
+   * @param headersKeys The keys of the HTTP headers (always converted to low-case).
+   * @param headersValues The values of the HTTP headers.
+   * @return 0 if forbidden access, 1 if allowed access, -1 if error.
+   * @ingroup Callbacks
+   * @deprecated Please instead use OrthancPluginIncomingHttpRequestFilter2()
+   **/
+  typedef int32_t (*OrthancPluginIncomingHttpRequestFilter) (
+    OrthancPluginHttpMethod  method,
+    const char*              uri,
+    const char*              ip,
+    uint32_t                 headersCount,
+    const char* const*       headersKeys,
+    const char* const*       headersValues);
+
+
+
+  /**
+   * @brief Callback to filter incoming HTTP requests received by Orthanc.
+   *
+   * Signature of a callback function that is triggered whenever
+   * Orthanc receives an HTTP/REST request, and that answers whether
+   * this request should be allowed. If the callback returns "0"
+   * ("false"), the server answers with HTTP status code 403
+   * (Forbidden).
+   *
+   * Pay attention to the fact that this function may be invoked
+   * concurrently by different threads of the Web server of
+   * Orthanc. You must implement proper locking if applicable.
+   *
+   * @param method The HTTP method used by the request.
+   * @param uri The URI of interest.
+   * @param ip The IP address of the HTTP client.
+   * @param headersCount The number of HTTP headers.
+   * @param headersKeys The keys of the HTTP headers (always converted to low-case).
+   * @param headersValues The values of the HTTP headers.
+   * @param getArgumentsCount The number of GET arguments (only for the GET HTTP method).
+   * @param getArgumentsKeys The keys of the GET arguments (only for the GET HTTP method).
+   * @param getArgumentsValues The values of the GET arguments (only for the GET HTTP method).
+   * @return 0 if forbidden access, 1 if allowed access, -1 if error.
+   * @ingroup Callbacks
+   **/
+  typedef int32_t (*OrthancPluginIncomingHttpRequestFilter2) (
+    OrthancPluginHttpMethod  method,
+    const char*              uri,
+    const char*              ip,
+    uint32_t                 headersCount,
+    const char* const*       headersKeys,
+    const char* const*       headersValues,
+    uint32_t                 getArgumentsCount,
+    const char* const*       getArgumentsKeys,
+    const char* const*       getArgumentsValues);
+
+
+
+  /**
+   * @brief Callback to handle incoming C-Find SCP requests.
+   *
+   * Signature of a callback function that is triggered whenever
+   * Orthanc receives a C-Find SCP request not concerning modality
+   * worklists.
+   *
+   * @param answers The target structure where answers must be stored.
+   * @param query The worklist query.
+   * @param issuerAet The Application Entity Title (AET) of the modality from which the request originates.
+   * @param calledAet The Application Entity Title (AET) of the modality that is called by the request.
+   * @return 0 if success, other value if error.
+   * @ingroup DicomCallbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginFindCallback) (
+    OrthancPluginFindAnswers*     answers,
+    const OrthancPluginFindQuery* query,
+    const char*                   issuerAet,
+    const char*                   calledAet);
+
+
+
+  /**
+   * @brief Callback to handle incoming C-Move SCP requests.
+   *
+   * Signature of a callback function that is triggered whenever
+   * Orthanc receives a C-Move SCP request. The callback receives the
+   * type of the resource of interest (study, series, instance...)
+   * together with the DICOM tags containing its identifiers. In turn,
+   * the plugin must create a driver object that will be responsible
+   * for driving the successive move suboperations.
+   *
+   * @param resourceType The type of the resource of interest. Note
+   * that this might be set to ResourceType_None if the
+   * QueryRetrieveLevel (0008,0052) tag was not provided by the
+   * issuer (i.e. the originator modality).
+   * @param patientId Content of the PatientID (0x0010, 0x0020) tag of the resource of interest. Might be NULL.
+   * @param accessionNumber Content of the AccessionNumber (0x0008, 0x0050) tag. Might be NULL.
+   * @param studyInstanceUid Content of the StudyInstanceUID (0x0020, 0x000d) tag. Might be NULL.
+   * @param seriesInstanceUid Content of the SeriesInstanceUID (0x0020, 0x000e) tag. Might be NULL.
+   * @param sopInstanceUid Content of the SOPInstanceUID (0x0008, 0x0018) tag. Might be NULL.
+   * @param originatorAet The Application Entity Title (AET) of the
+   * modality from which the request originates.
+   * @param sourceAet The Application Entity Title (AET) of the
+   * modality that should send its DICOM files to another modality.
+   * @param targetAet The Application Entity Title (AET) of the
+   * modality that should receive the DICOM files.
+   * @param originatorId The Message ID issued by the originator modality,
+   * as found in tag (0000,0110) of the DICOM query emitted by the issuer.
+   *
+   * @return The NULL value if the plugin cannot deal with this query,
+   * or a pointer to the driver object that is responsible for
+   * handling the successive move suboperations.
+   * 
+   * @note If targetAet equals sourceAet, this is actually a query/retrieve operation.
+   * @ingroup DicomCallbacks
+   **/
+  typedef void* (*OrthancPluginMoveCallback) (
+    OrthancPluginResourceType  resourceType,
+    const char*                patientId,
+    const char*                accessionNumber,
+    const char*                studyInstanceUid,
+    const char*                seriesInstanceUid,
+    const char*                sopInstanceUid,
+    const char*                originatorAet,
+    const char*                sourceAet,
+    const char*                targetAet,
+    uint16_t                   originatorId);
+    
+
+  /**
+   * @brief Callback to read the size of a C-Move driver.
+   * 
+   * Signature of a callback function that returns the number of
+   * C-Move suboperations that are to be achieved by the given C-Move
+   * driver. This driver is the return value of a previous call to the
+   * OrthancPluginMoveCallback() callback.
+   *
+   * @param moveDriver The C-Move driver of interest.
+   * @return The number of suboperations. 
+   * @ingroup DicomCallbacks
+   **/
+  typedef uint32_t (*OrthancPluginGetMoveSize) (void* moveDriver);
+
+
+  /**
+   * @brief Callback to apply one C-Move suboperation.
+   * 
+   * Signature of a callback function that applies the next C-Move
+   * suboperation that os to be achieved by the given C-Move
+   * driver. This driver is the return value of a previous call to the
+   * OrthancPluginMoveCallback() callback.
+   *
+   * @param moveDriver The C-Move driver of interest.
+   * @return 0 if success, or the error code if failure.
+   * @ingroup DicomCallbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginApplyMove) (void* moveDriver);
+
+
+  /**
+   * @brief Callback to free one C-Move driver.
+   * 
+   * Signature of a callback function that releases the resources
+   * allocated by the given C-Move driver. This driver is the return
+   * value of a previous call to the OrthancPluginMoveCallback()
+   * callback.
+   *
+   * @param moveDriver The C-Move driver of interest.
+   * @ingroup DicomCallbacks
+   **/
+  typedef void (*OrthancPluginFreeMove) (void* moveDriver);
+
+
+  /**
+   * @brief Callback to finalize one custom job.
+   * 
+   * Signature of a callback function that releases all the resources
+   * allocated by the given job. This job is the argument provided to
+   * OrthancPluginCreateJob().
+   *
+   * @param job The job of interest.
+   * @ingroup Toolbox
+   **/  
+  typedef void (*OrthancPluginJobFinalize) (void* job);
+
+
+  /**
+   * @brief Callback to check the progress of one custom job.
+   * 
+   * Signature of a callback function that returns the progress of the
+   * job.
+   *
+   * @param job The job of interest.
+   * @return The progress, as a floating-point number ranging from 0 to 1.
+   * @ingroup Toolbox
+   **/  
+  typedef float (*OrthancPluginJobGetProgress) (void* job);
+
+  
+  /**
+   * @brief Callback to retrieve the content of one custom job.
+   * 
+   * Signature of a callback function that returns human-readable
+   * statistics about the job. This statistics must be formatted as a
+   * JSON object. This information is notably displayed in the "Jobs"
+   * tab of "Orthanc Explorer".
+   *
+   * @param job The job of interest.
+   * @return The statistics, as a JSON object encoded as a string.
+   * @ingroup Toolbox
+   **/  
+  typedef const char* (*OrthancPluginJobGetContent) (void* job);
+
+
+  /**
+   * @brief Callback to serialize one custom job.
+   * 
+   * Signature of a callback function that returns a serialized
+   * version of the job, formatted as a JSON object. This
+   * serialization is stored in the Orthanc database, and is used to
+   * reload the job on the restart of Orthanc. The "unserialization"
+   * callback (with OrthancPluginJobsUnserializer signature) will
+   * receive this serialized object.
+   *
+   * @param job The job of interest.
+   * @return The serialized job, as a JSON object encoded as a string.
+   * @see OrthancPluginRegisterJobsUnserializer()
+   * @ingroup Toolbox
+   **/  
+  typedef const char* (*OrthancPluginJobGetSerialized) (void* job);
+
+
+  /**
+   * @brief Callback to execute one step of a custom job.
+   * 
+   * Signature of a callback function that executes one step in the
+   * job. The jobs engine of Orthanc will make successive calls to
+   * this method, as long as it returns
+   * OrthancPluginJobStepStatus_Continue.
+   *
+   * @param job The job of interest.
+   * @return The status of execution.
+   * @ingroup Toolbox
+   **/  
+  typedef OrthancPluginJobStepStatus (*OrthancPluginJobStep) (void* job);
+
+
+  /**
+   * @brief Callback executed once one custom job leaves the "running" state.
+   * 
+   * Signature of a callback function that is invoked once a job
+   * leaves the "running" state. This can happen if the previous call
+   * to OrthancPluginJobStep has failed/succeeded, if the host Orthanc
+   * server is being stopped, or if the user manually tags the job as
+   * paused/canceled. This callback allows the plugin to free
+   * resources allocated for running this custom job (e.g. to stop
+   * threads, or to remove temporary files).
+   * 
+   * Note that handling pauses might involves a specific treatment
+   * (such a stopping threads, but keeping temporary files on the
+   * disk). This "paused" situation can be checked by looking at the
+   * "reason" parameter.
+   *
+   * @param job The job of interest.
+   * @param reason The reason for leaving the "running" state. 
+   * @return 0 if success, or the error code if failure.
+   * @ingroup Toolbox
+   **/    
+  typedef OrthancPluginErrorCode (*OrthancPluginJobStop) (void* job, 
+                                                          OrthancPluginJobStopReason reason);
+
+
+  /**
+   * @brief Callback executed once one stopped custom job is started again.
+   * 
+   * Signature of a callback function that is invoked once a job
+   * leaves the "failure/canceled" state, to be started again. This
+   * function will typically reset the progress to zero. Note that
+   * before being actually executed, the job would first be tagged as
+   * "pending" in the Orthanc jobs engine.
+   *
+   * @param job The job of interest.
+   * @return 0 if success, or the error code if failure.
+   * @ingroup Toolbox
+   **/    
+  typedef OrthancPluginErrorCode (*OrthancPluginJobReset) (void* job);
+
+
+  /**
+   * @brief Callback executed to unserialize a custom job.
+   * 
+   * Signature of a callback function that unserializes a job that was
+   * saved in the Orthanc database.
+   *
+   * @param jobType The type of the job, as provided to OrthancPluginCreateJob().
+   * @param serialized The serialization of the job, as provided by OrthancPluginJobGetSerialized.
+   * @return The unserialized job (as created by OrthancPluginCreateJob()), or NULL
+   * if this unserializer cannot handle this job type.
+   * @see OrthancPluginRegisterJobsUnserializer()
+   * @ingroup Callbacks
+   **/    
+  typedef OrthancPluginJob* (*OrthancPluginJobsUnserializer) (const char* jobType,
+                                                              const char* serialized);
+  
+
+
+  /**
+   * @brief Callback executed to update the metrics of the plugin.
+   * 
+   * Signature of a callback function that is called by Orthanc
+   * whenever a monitoring tool (such as Prometheus) asks the current
+   * values of the metrics. This callback gives the plugin a chance to
+   * update its metrics, by calling OrthancPluginSetMetricsValue().
+   * This is typically useful for metrics that are expensive to
+   * acquire.
+   * 
+   * @see OrthancPluginRegisterRefreshMetrics()
+   * @ingroup Callbacks
+   **/
+  typedef void (*OrthancPluginRefreshMetricsCallback) ();
+
+  
+
+  /**
+   * @brief Callback executed to encode a binary tag in DICOMweb.
+   * 
+   * Signature of a callback function that is called by Orthanc
+   * whenever a DICOM tag that contains a binary value must be written
+   * to a JSON or XML node, while a DICOMweb document is being
+   * generated. The value representation (VR) of the DICOM tag can be
+   * OB, OD, OF, OL, OW, or UN.
+   * 
+   * @see OrthancPluginEncodeDicomWebJson() and OrthancPluginEncodeDicomWebXml()
+   * @param node The node being generated, as provided by Orthanc.
+   * @param setter The setter to be used to encode the content of the node. If
+   * the setter is not called, the binary tag is not written to the output document.
+   * @param levelDepth The depth of the node in the DICOM hierarchy of sequences.
+   * This parameter gives the number of elements in the "levelTagGroup", 
+   * "levelTagElement", and "levelIndex" arrays.
+   * @param levelTagGroup The group of the parent DICOM tags in the hierarchy.
+   * @param levelTagElement The element of the parent DICOM tags in the hierarchy.
+   * @param levelIndex The index of the node in the parent sequences of the hierarchy.
+   * @param tagGroup The group of the DICOM tag of interest.
+   * @param tagElement The element of the DICOM tag of interest.
+   * @param vr The value representation of the binary DICOM node.
+   * @ingroup Callbacks
+   **/
+  typedef void (*OrthancPluginDicomWebBinaryCallback) (
+    OrthancPluginDicomWebNode*          node,
+    OrthancPluginDicomWebSetBinaryNode  setter,
+    uint32_t                            levelDepth,
+    const uint16_t*                     levelTagGroup,
+    const uint16_t*                     levelTagElement,
+    const uint32_t*                     levelIndex,
+    uint16_t                            tagGroup,
+    uint16_t                            tagElement,
+    OrthancPluginValueRepresentation    vr);
+
+
+
+  /**
+   * @brief Callback executed to encode a binary tag in DICOMweb.
+   * 
+   * Signature of a callback function that is called by Orthanc
+   * whenever a DICOM tag that contains a binary value must be written
+   * to a JSON or XML node, while a DICOMweb document is being
+   * generated. The value representation (VR) of the DICOM tag can be
+   * OB, OD, OF, OL, OW, or UN.
+   * 
+   * @see OrthancPluginEncodeDicomWebJson() and OrthancPluginEncodeDicomWebXml()
+   * @param node The node being generated, as provided by Orthanc.
+   * @param setter The setter to be used to encode the content of the node. If
+   * the setter is not called, the binary tag is not written to the output document.
+   * @param levelDepth The depth of the node in the DICOM hierarchy of sequences.
+   * This parameter gives the number of elements in the "levelTagGroup", 
+   * "levelTagElement", and "levelIndex" arrays.
+   * @param levelTagGroup The group of the parent DICOM tags in the hierarchy.
+   * @param levelTagElement The element of the parent DICOM tags in the hierarchy.
+   * @param levelIndex The index of the node in the parent sequences of the hierarchy.
+   * @param tagGroup The group of the DICOM tag of interest.
+   * @param tagElement The element of the DICOM tag of interest.
+   * @param vr The value representation of the binary DICOM node.
+   * @param payload The user payload.
+   * @ingroup Callbacks
+   **/
+  typedef void (*OrthancPluginDicomWebBinaryCallback2) (
+    OrthancPluginDicomWebNode*          node,
+    OrthancPluginDicomWebSetBinaryNode  setter,
+    uint32_t                            levelDepth,
+    const uint16_t*                     levelTagGroup,
+    const uint16_t*                     levelTagElement,
+    const uint32_t*                     levelIndex,
+    uint16_t                            tagGroup,
+    uint16_t                            tagElement,
+    OrthancPluginValueRepresentation    vr,
+    void*                               payload);
+
+
+
+  /**
+   * @brief Data structure that contains information about the Orthanc core.
+   **/
+  typedef struct _OrthancPluginContext_t
+  {
+    void*                     pluginsManager;
+    const char*               orthancVersion;
+    OrthancPluginFree         Free;
+    OrthancPluginErrorCode  (*InvokeService) (struct _OrthancPluginContext_t* context,
+                                              _OrthancPluginService service,
+                                              const void* params);
+  } OrthancPluginContext;
+
+
+  
+  /**
+   * @brief An entry in the dictionary of DICOM tags.
+   **/
+  typedef struct
+  {
+    uint16_t                          group;            /*!< The group of the tag */
+    uint16_t                          element;          /*!< The element of the tag */
+    OrthancPluginValueRepresentation  vr;               /*!< The value representation of the tag */
+    uint32_t                          minMultiplicity;  /*!< The minimum multiplicity of the tag */
+    uint32_t                          maxMultiplicity;  /*!< The maximum multiplicity of the tag (0 means arbitrary) */
+  } OrthancPluginDictionaryEntry;
+
+
+
+  /**
+   * @brief Free a string.
+   * 
+   * Free a string that was allocated by the core system of Orthanc.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param str The string to be freed.
+   **/
+  ORTHANC_PLUGIN_INLINE void  OrthancPluginFreeString(
+    OrthancPluginContext* context, 
+    char* str)
+  {
+    if (str != NULL)
+    {
+      context->Free(str);
+    }
+  }
+
+
+  /**
+   * @brief Check that the version of the hosting Orthanc is above a given version.
+   * 
+   * This function checks whether the version of the Orthanc server
+   * running this plugin, is above the given version. Contrarily to
+   * OrthancPluginCheckVersion(), it is up to the developer of the
+   * plugin to make sure that all the Orthanc SDK services called by
+   * the plugin are actually implemented in the given version of
+   * Orthanc.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param expectedMajor Expected major version.
+   * @param expectedMinor Expected minor version.
+   * @param expectedRevision Expected revision.
+   * @return 1 if and only if the versions are compatible. If the
+   * result is 0, the initialization of the plugin should fail.
+   * @see OrthancPluginCheckVersion
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE int  OrthancPluginCheckVersionAdvanced(
+    OrthancPluginContext* context,
+    int expectedMajor,
+    int expectedMinor,
+    int expectedRevision)
+  {
+    int major, minor, revision;
+
+    if (sizeof(int32_t) != sizeof(OrthancPluginErrorCode) ||
+        sizeof(int32_t) != sizeof(OrthancPluginHttpMethod) ||
+        sizeof(int32_t) != sizeof(_OrthancPluginService) ||
+        sizeof(int32_t) != sizeof(_OrthancPluginProperty) ||
+        sizeof(int32_t) != sizeof(OrthancPluginPixelFormat) ||
+        sizeof(int32_t) != sizeof(OrthancPluginContentType) ||
+        sizeof(int32_t) != sizeof(OrthancPluginResourceType) ||
+        sizeof(int32_t) != sizeof(OrthancPluginChangeType) ||
+        sizeof(int32_t) != sizeof(OrthancPluginCompressionType) ||
+        sizeof(int32_t) != sizeof(OrthancPluginImageFormat) ||
+        sizeof(int32_t) != sizeof(OrthancPluginValueRepresentation) ||
+        sizeof(int32_t) != sizeof(OrthancPluginDicomToJsonFormat) ||
+        sizeof(int32_t) != sizeof(OrthancPluginDicomToJsonFlags) ||
+        sizeof(int32_t) != sizeof(OrthancPluginCreateDicomFlags) ||
+        sizeof(int32_t) != sizeof(OrthancPluginIdentifierConstraint) ||
+        sizeof(int32_t) != sizeof(OrthancPluginInstanceOrigin) ||
+        sizeof(int32_t) != sizeof(OrthancPluginJobStepStatus) ||
+        sizeof(int32_t) != sizeof(OrthancPluginConstraintType) ||
+        sizeof(int32_t) != sizeof(OrthancPluginMetricsType) ||
+        sizeof(int32_t) != sizeof(OrthancPluginDicomWebBinaryMode) ||
+        sizeof(int32_t) != sizeof(OrthancPluginStorageCommitmentFailureReason) ||
+        sizeof(int32_t) != sizeof(OrthancPluginReceivedInstanceAction))
+    {
+      /* Mismatch in the size of the enumerations */
+      return 0;
+    }
+
+    /* Assume compatibility with the mainline */
+    if (!strcmp(context->orthancVersion, "mainline"))
+    {
+      return 1;
+    }
+
+    /* Parse the version of the Orthanc core */
+    if ( 
+#ifdef _MSC_VER
+      sscanf_s
+#else
+      sscanf
+#endif
+      (context->orthancVersion, "%4d.%4d.%4d", &major, &minor, &revision) != 3)
+    {
+      return 0;
+    }
+
+    /* Check the major number of the version */
+
+    if (major > expectedMajor)
+    {
+      return 1;
+    }
+
+    if (major < expectedMajor)
+    {
+      return 0;
+    }
+
+    /* Check the minor number of the version */
+
+    if (minor > expectedMinor)
+    {
+      return 1;
+    }
+
+    if (minor < expectedMinor)
+    {
+      return 0;
+    }
+
+    /* Check the revision number of the version */
+
+    if (revision >= expectedRevision)
+    {
+      return 1;
+    }
+    else
+    {
+      return 0;
+    }
+  }
+
+
+  /**
+   * @brief Check the compatibility of the plugin wrt. the version of its hosting Orthanc.
+   * 
+   * This function checks whether the version of the Orthanc server
+   * running this plugin, is above the version of the current Orthanc
+   * SDK header. This guarantees that the plugin is compatible with
+   * the hosting Orthanc (i.e. it will not call unavailable services).
+   * The result of this function should always be checked in the
+   * OrthancPluginInitialize() entry point of the plugin.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return 1 if and only if the versions are compatible. If the
+   * result is 0, the initialization of the plugin should fail.
+   * @see OrthancPluginCheckVersionAdvanced
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE int  OrthancPluginCheckVersion(
+    OrthancPluginContext* context)
+  {
+    return OrthancPluginCheckVersionAdvanced(
+      context,
+      ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER,
+      ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER,
+      ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER);
+  }
+
+
+  /**
+   * @brief Free a memory buffer.
+   * 
+   * Free a memory buffer that was allocated by the core system of Orthanc.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param buffer The memory buffer to release.
+   **/
+  ORTHANC_PLUGIN_INLINE void  OrthancPluginFreeMemoryBuffer(
+    OrthancPluginContext* context, 
+    OrthancPluginMemoryBuffer* buffer)
+  {
+    context->Free(buffer->data);
+  }
+
+
+  /**
+   * @brief Free a memory buffer.
+   * 
+   * Free a memory buffer that was allocated by the core system of Orthanc.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param buffer The memory buffer to release.
+   **/
+  ORTHANC_PLUGIN_INLINE void  OrthancPluginFreeMemoryBuffer64(
+    OrthancPluginContext* context, 
+    OrthancPluginMemoryBuffer64* buffer)
+  {
+    context->Free(buffer->data);
+  }
+
+
+  /**
+   * @brief Log an error.
+   *
+   * Log an error message using the Orthanc logging system.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param message The message to be logged.
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginLogError(
+    OrthancPluginContext* context,
+    const char* message)
+  {
+    context->InvokeService(context, _OrthancPluginService_LogError, message);
+  }
+
+
+  /**
+   * @brief Log a warning.
+   *
+   * Log a warning message using the Orthanc logging system.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param message The message to be logged.
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginLogWarning(
+    OrthancPluginContext* context,
+    const char* message)
+  {
+    context->InvokeService(context, _OrthancPluginService_LogWarning, message);
+  }
+
+
+  /**
+   * @brief Log an information.
+   *
+   * Log an information message using the Orthanc logging system.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param message The message to be logged.
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginLogInfo(
+    OrthancPluginContext* context,
+    const char* message)
+  {
+    context->InvokeService(context, _OrthancPluginService_LogInfo, message);
+  }
+
+
+
+  typedef struct
+  {
+    const char* pathRegularExpression;
+    OrthancPluginRestCallback callback;
+  } _OrthancPluginRestCallback;
+
+  /**
+   * @brief Register a REST callback.
+   *
+   * This function registers a REST callback against a regular
+   * expression for a URI. This function must be called during the
+   * initialization of the plugin, i.e. inside the
+   * OrthancPluginInitialize() public function.
+   *
+   * Each REST callback is guaranteed to run in mutual exclusion.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param pathRegularExpression Regular expression for the URI. May contain groups. 
+   * @param callback The callback function to handle the REST call.
+   * @see OrthancPluginRegisterRestCallbackNoLock()
+   *
+   * @note
+   * The regular expression is case sensitive and must follow the
+   * [Perl syntax](https://www.boost.org/doc/libs/1_67_0/libs/regex/doc/html/boost_regex/syntax/perl_syntax.html).
+   *
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterRestCallback(
+    OrthancPluginContext*     context,
+    const char*               pathRegularExpression,
+    OrthancPluginRestCallback callback)
+  {
+    _OrthancPluginRestCallback params;
+    params.pathRegularExpression = pathRegularExpression;
+    params.callback = callback;
+    context->InvokeService(context, _OrthancPluginService_RegisterRestCallback, &params);
+  }
+
+
+
+  /**
+   * @brief Register a REST callback, without locking.
+   *
+   * This function registers a REST callback against a regular
+   * expression for a URI. This function must be called during the
+   * initialization of the plugin, i.e. inside the
+   * OrthancPluginInitialize() public function.
+   *
+   * Contrarily to OrthancPluginRegisterRestCallback(), the callback
+   * will NOT be invoked in mutual exclusion. This can be useful for
+   * high-performance plugins that must handle concurrent requests
+   * (Orthanc uses a pool of threads, one thread being assigned to
+   * each incoming HTTP request). Of course, if using this function,
+   * it is up to the plugin to implement the required locking
+   * mechanisms.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param pathRegularExpression Regular expression for the URI. May contain groups.
+   * @param callback The callback function to handle the REST call.
+   * @see OrthancPluginRegisterRestCallback()
+   *
+   * @note
+   * The regular expression is case sensitive and must follow the
+   * [Perl syntax](https://www.boost.org/doc/libs/1_67_0/libs/regex/doc/html/boost_regex/syntax/perl_syntax.html).
+   *
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterRestCallbackNoLock(
+    OrthancPluginContext*     context,
+    const char*               pathRegularExpression,
+    OrthancPluginRestCallback callback)
+  {
+    _OrthancPluginRestCallback params;
+    params.pathRegularExpression = pathRegularExpression;
+    params.callback = callback;
+    context->InvokeService(context, _OrthancPluginService_RegisterRestCallbackNoLock, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginOnStoredInstanceCallback callback;
+  } _OrthancPluginOnStoredInstanceCallback;
+
+  /**
+   * @brief Register a callback for received instances.
+   *
+   * This function registers a callback function that is called
+   * whenever a new DICOM instance is stored into the Orthanc core.
+   *
+   * @warning Your callback function will be called synchronously with
+   * the core of Orthanc. This implies that deadlocks might emerge if
+   * you call other core primitives of Orthanc in your callback (such
+   * deadlocks are particularly visible in the presence of other plugins
+   * or Lua scripts). It is thus strongly advised to avoid any call to
+   * the REST API of Orthanc in the callback. If you have to call
+   * other primitives of Orthanc, you should make these calls in a
+   * separate thread, passing the pending events to be processed
+   * through a message queue.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param callback The callback function.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterOnStoredInstanceCallback(
+    OrthancPluginContext*                  context,
+    OrthancPluginOnStoredInstanceCallback  callback)
+  {
+    _OrthancPluginOnStoredInstanceCallback params;
+    params.callback = callback;
+
+    context->InvokeService(context, _OrthancPluginService_RegisterOnStoredInstanceCallback, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginRestOutput* output;
+    const void*              answer;
+    uint32_t                 answerSize;
+    const char*              mimeType;
+  } _OrthancPluginAnswerBuffer;
+
+  /**
+   * @brief Answer to a REST request.
+   *
+   * This function answers to a REST request with the content of a memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param answer Pointer to the memory buffer containing the answer.
+   * @param answerSize Number of bytes of the answer.
+   * @param mimeType The MIME type of the answer.
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginAnswerBuffer(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const void*              answer,
+    uint32_t                 answerSize,
+    const char*              mimeType)
+  {
+    _OrthancPluginAnswerBuffer params;
+    params.output = output;
+    params.answer = answer;
+    params.answerSize = answerSize;
+    params.mimeType = mimeType;
+    context->InvokeService(context, _OrthancPluginService_AnswerBuffer, &params);
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginRestOutput*  output;
+    OrthancPluginPixelFormat  format;
+    uint32_t                  width;
+    uint32_t                  height;
+    uint32_t                  pitch;
+    const void*               buffer;
+  } _OrthancPluginCompressAndAnswerPngImage;
+
+  typedef struct
+  {
+    OrthancPluginRestOutput*  output;
+    OrthancPluginImageFormat  imageFormat;
+    OrthancPluginPixelFormat  pixelFormat;
+    uint32_t                  width;
+    uint32_t                  height;
+    uint32_t                  pitch;
+    const void*               buffer;
+    uint8_t                   quality;
+  } _OrthancPluginCompressAndAnswerImage;
+
+
+  /**
+   * @brief Answer to a REST request with a PNG image.
+   *
+   * This function answers to a REST request with a PNG image. The
+   * parameters of this function describe a memory buffer that
+   * contains an uncompressed image. The image will be automatically compressed
+   * as a PNG image by the core system of Orthanc.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param format The memory layout of the uncompressed image.
+   * @param width The width of the image.
+   * @param height The height of the image.
+   * @param pitch The pitch of the image (i.e. the number of bytes
+   * between 2 successive lines of the image in the memory buffer).
+   * @param buffer The memory buffer containing the uncompressed image.
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginCompressAndAnswerPngImage(
+    OrthancPluginContext*     context,
+    OrthancPluginRestOutput*  output,
+    OrthancPluginPixelFormat  format,
+    uint32_t                  width,
+    uint32_t                  height,
+    uint32_t                  pitch,
+    const void*               buffer)
+  {
+    _OrthancPluginCompressAndAnswerImage params;
+    params.output = output;
+    params.imageFormat = OrthancPluginImageFormat_Png;
+    params.pixelFormat = format;
+    params.width = width;
+    params.height = height;
+    params.pitch = pitch;
+    params.buffer = buffer;
+    params.quality = 0;  /* No quality for PNG */
+    context->InvokeService(context, _OrthancPluginService_CompressAndAnswerImage, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*  target;
+    const char*                 instanceId;
+  } _OrthancPluginGetDicomForInstance;
+
+  /**
+   * @brief Retrieve a DICOM instance using its Orthanc identifier.
+   * 
+   * Retrieve a DICOM instance using its Orthanc identifier. The DICOM
+   * file is stored into a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param instanceId The Orthanc identifier of the DICOM instance of interest.
+   * @return 0 if success, or the error code if failure.
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginGetDicomForInstance(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 instanceId)
+  {
+    _OrthancPluginGetDicomForInstance params;
+    params.target = target;
+    params.instanceId = instanceId;
+    return context->InvokeService(context, _OrthancPluginService_GetDicomForInstance, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*  target;
+    const char*                 uri;
+  } _OrthancPluginRestApiGet;
+
+  /**
+   * @brief Make a GET call to the built-in Orthanc REST API.
+   * 
+   * Make a GET call to the built-in Orthanc REST API. The result to
+   * the query is stored into a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param uri The URI in the built-in Orthanc API.
+   * @return 0 if success, or the error code if failure.
+   * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource.
+   * @see OrthancPluginRestApiGetAfterPlugins
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiGet(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 uri)
+  {
+    _OrthancPluginRestApiGet params;
+    params.target = target;
+    params.uri = uri;
+    return context->InvokeService(context, _OrthancPluginService_RestApiGet, &params);
+  }
+
+
+
+  /**
+   * @brief Make a GET call to the REST API, as tainted by the plugins.
+   * 
+   * Make a GET call to the Orthanc REST API, after all the plugins
+   * are applied. In other words, if some plugin overrides or adds the
+   * called URI to the built-in Orthanc REST API, this call will
+   * return the result provided by this plugin. The result to the
+   * query is stored into a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param uri The URI in the built-in Orthanc API.
+   * @return 0 if success, or the error code if failure.
+   * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource.
+   * @see OrthancPluginRestApiGet
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiGetAfterPlugins(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 uri)
+  {
+    _OrthancPluginRestApiGet params;
+    params.target = target;
+    params.uri = uri;
+    return context->InvokeService(context, _OrthancPluginService_RestApiGetAfterPlugins, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*  target;
+    const char*                 uri;
+    const void*                 body;
+    uint32_t                    bodySize;
+  } _OrthancPluginRestApiPostPut;
+
+  /**
+   * @brief Make a POST call to the built-in Orthanc REST API.
+   * 
+   * Make a POST call to the built-in Orthanc REST API. The result to
+   * the query is stored into a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param uri The URI in the built-in Orthanc API.
+   * @param body The body of the POST request.
+   * @param bodySize The size of the body.
+   * @return 0 if success, or the error code if failure.
+   * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource.
+   * @see OrthancPluginRestApiPostAfterPlugins
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiPost(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 uri,
+    const void*                 body,
+    uint32_t                    bodySize)
+  {
+    _OrthancPluginRestApiPostPut params;
+    params.target = target;
+    params.uri = uri;
+    params.body = body;
+    params.bodySize = bodySize;
+    return context->InvokeService(context, _OrthancPluginService_RestApiPost, &params);
+  }
+
+
+  /**
+   * @brief Make a POST call to the REST API, as tainted by the plugins.
+   * 
+   * Make a POST call to the Orthanc REST API, after all the plugins
+   * are applied. In other words, if some plugin overrides or adds the
+   * called URI to the built-in Orthanc REST API, this call will
+   * return the result provided by this plugin. The result to the
+   * query is stored into a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param uri The URI in the built-in Orthanc API.
+   * @param body The body of the POST request.
+   * @param bodySize The size of the body.
+   * @return 0 if success, or the error code if failure.
+   * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource.
+   * @see OrthancPluginRestApiPost
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiPostAfterPlugins(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 uri,
+    const void*                 body,
+    uint32_t                    bodySize)
+  {
+    _OrthancPluginRestApiPostPut params;
+    params.target = target;
+    params.uri = uri;
+    params.body = body;
+    params.bodySize = bodySize;
+    return context->InvokeService(context, _OrthancPluginService_RestApiPostAfterPlugins, &params);
+  }
+
+
+
+  /**
+   * @brief Make a DELETE call to the built-in Orthanc REST API.
+   * 
+   * Make a DELETE call to the built-in Orthanc REST API.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param uri The URI to delete in the built-in Orthanc API.
+   * @return 0 if success, or the error code if failure.
+   * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource.
+   * @see OrthancPluginRestApiDeleteAfterPlugins
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiDelete(
+    OrthancPluginContext*       context,
+    const char*                 uri)
+  {
+    return context->InvokeService(context, _OrthancPluginService_RestApiDelete, uri);
+  }
+
+
+  /**
+   * @brief Make a DELETE call to the REST API, as tainted by the plugins.
+   * 
+   * Make a DELETE call to the Orthanc REST API, after all the plugins
+   * are applied. In other words, if some plugin overrides or adds the
+   * called URI to the built-in Orthanc REST API, this call will
+   * return the result provided by this plugin. 
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param uri The URI to delete in the built-in Orthanc API.
+   * @return 0 if success, or the error code if failure.
+   * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource.
+   * @see OrthancPluginRestApiDelete
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiDeleteAfterPlugins(
+    OrthancPluginContext*       context,
+    const char*                 uri)
+  {
+    return context->InvokeService(context, _OrthancPluginService_RestApiDeleteAfterPlugins, uri);
+  }
+
+
+
+  /**
+   * @brief Make a PUT call to the built-in Orthanc REST API.
+   * 
+   * Make a PUT call to the built-in Orthanc REST API. The result to
+   * the query is stored into a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param uri The URI in the built-in Orthanc API.
+   * @param body The body of the PUT request.
+   * @param bodySize The size of the body.
+   * @return 0 if success, or the error code if failure.
+   * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource.
+   * @see OrthancPluginRestApiPutAfterPlugins
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiPut(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 uri,
+    const void*                 body,
+    uint32_t                    bodySize)
+  {
+    _OrthancPluginRestApiPostPut params;
+    params.target = target;
+    params.uri = uri;
+    params.body = body;
+    params.bodySize = bodySize;
+    return context->InvokeService(context, _OrthancPluginService_RestApiPut, &params);
+  }
+
+
+
+  /**
+   * @brief Make a PUT call to the REST API, as tainted by the plugins.
+   * 
+   * Make a PUT call to the Orthanc REST API, after all the plugins
+   * are applied. In other words, if some plugin overrides or adds the
+   * called URI to the built-in Orthanc REST API, this call will
+   * return the result provided by this plugin. The result to the
+   * query is stored into a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param uri The URI in the built-in Orthanc API.
+   * @param body The body of the PUT request.
+   * @param bodySize The size of the body.
+   * @return 0 if success, or the error code if failure.
+   * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource.
+   * @see OrthancPluginRestApiPut
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiPutAfterPlugins(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 uri,
+    const void*                 body,
+    uint32_t                    bodySize)
+  {
+    _OrthancPluginRestApiPostPut params;
+    params.target = target;
+    params.uri = uri;
+    params.body = body;
+    params.bodySize = bodySize;
+    return context->InvokeService(context, _OrthancPluginService_RestApiPutAfterPlugins, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginRestOutput* output;
+    const char*              argument;
+  } _OrthancPluginOutputPlusArgument;
+
+  /**
+   * @brief Redirect a REST request.
+   *
+   * This function answers to a REST request by redirecting the user
+   * to another URI using HTTP status 301.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param redirection Where to redirect.
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginRedirect(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              redirection)
+  {
+    _OrthancPluginOutputPlusArgument params;
+    params.output = output;
+    params.argument = redirection;
+    context->InvokeService(context, _OrthancPluginService_Redirect, &params);
+  }
+
+
+
+  typedef struct
+  {
+    char**       result;
+    const char*  argument;
+  } _OrthancPluginRetrieveDynamicString;
+
+  /**
+   * @brief Look for a patient.
+   *
+   * Look for a patient stored in Orthanc, using its Patient ID tag (0x0010, 0x0020).
+   * This function uses the database index to run as fast as possible (it does not loop
+   * over all the stored patients).
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param patientID The Patient ID of interest.
+   * @return The NULL value if the patient is non-existent, or a string containing the 
+   * Orthanc ID of the patient. This string must be freed by OrthancPluginFreeString().
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupPatient(
+    OrthancPluginContext*  context,
+    const char*            patientID)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = patientID;
+
+    if (context->InvokeService(context, _OrthancPluginService_LookupPatient, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Look for a study.
+   *
+   * Look for a study stored in Orthanc, using its Study Instance UID tag (0x0020, 0x000d).
+   * This function uses the database index to run as fast as possible (it does not loop
+   * over all the stored studies).
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param studyUID The Study Instance UID of interest.
+   * @return The NULL value if the study is non-existent, or a string containing the 
+   * Orthanc ID of the study. This string must be freed by OrthancPluginFreeString().
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupStudy(
+    OrthancPluginContext*  context,
+    const char*            studyUID)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = studyUID;
+
+    if (context->InvokeService(context, _OrthancPluginService_LookupStudy, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Look for a study, using the accession number.
+   *
+   * Look for a study stored in Orthanc, using its Accession Number tag (0x0008, 0x0050).
+   * This function uses the database index to run as fast as possible (it does not loop
+   * over all the stored studies).
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param accessionNumber The Accession Number of interest.
+   * @return The NULL value if the study is non-existent, or a string containing the 
+   * Orthanc ID of the study. This string must be freed by OrthancPluginFreeString().
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupStudyWithAccessionNumber(
+    OrthancPluginContext*  context,
+    const char*            accessionNumber)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = accessionNumber;
+
+    if (context->InvokeService(context, _OrthancPluginService_LookupStudyWithAccessionNumber, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Look for a series.
+   *
+   * Look for a series stored in Orthanc, using its Series Instance UID tag (0x0020, 0x000e).
+   * This function uses the database index to run as fast as possible (it does not loop
+   * over all the stored series).
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param seriesUID The Series Instance UID of interest.
+   * @return The NULL value if the series is non-existent, or a string containing the 
+   * Orthanc ID of the series. This string must be freed by OrthancPluginFreeString().
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupSeries(
+    OrthancPluginContext*  context,
+    const char*            seriesUID)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = seriesUID;
+
+    if (context->InvokeService(context, _OrthancPluginService_LookupSeries, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Look for an instance.
+   *
+   * Look for an instance stored in Orthanc, using its SOP Instance UID tag (0x0008, 0x0018).
+   * This function uses the database index to run as fast as possible (it does not loop
+   * over all the stored instances).
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param sopInstanceUID The SOP Instance UID of interest.
+   * @return The NULL value if the instance is non-existent, or a string containing the 
+   * Orthanc ID of the instance. This string must be freed by OrthancPluginFreeString().
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupInstance(
+    OrthancPluginContext*  context,
+    const char*            sopInstanceUID)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = sopInstanceUID;
+
+    if (context->InvokeService(context, _OrthancPluginService_LookupInstance, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginRestOutput* output;
+    uint16_t                 status;
+  } _OrthancPluginSendHttpStatusCode;
+
+  /**
+   * @brief Send a HTTP status code.
+   *
+   * This function answers to a REST request by sending a HTTP status
+   * code (such as "400 - Bad Request"). Note that:
+   * - Successful requests (status 200) must use ::OrthancPluginAnswerBuffer().
+   * - Redirections (status 301) must use ::OrthancPluginRedirect().
+   * - Unauthorized access (status 401) must use ::OrthancPluginSendUnauthorized().
+   * - Methods not allowed (status 405) must use ::OrthancPluginSendMethodNotAllowed().
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param status The HTTP status code to be sent.
+   * @ingroup REST
+   * @see OrthancPluginSendHttpStatus()
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSendHttpStatusCode(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    uint16_t                 status)
+  {
+    _OrthancPluginSendHttpStatusCode params;
+    params.output = output;
+    params.status = status;
+    context->InvokeService(context, _OrthancPluginService_SendHttpStatusCode, &params);
+  }
+
+
+  /**
+   * @brief Signal that a REST request is not authorized.
+   *
+   * This function answers to a REST request by signaling that it is
+   * not authorized.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param realm The realm for the authorization process.
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSendUnauthorized(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              realm)
+  {
+    _OrthancPluginOutputPlusArgument params;
+    params.output = output;
+    params.argument = realm;
+    context->InvokeService(context, _OrthancPluginService_SendUnauthorized, &params);
+  }
+
+
+  /**
+   * @brief Signal that this URI does not support this HTTP method.
+   *
+   * This function answers to a REST request by signaling that the
+   * queried URI does not support this method.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param allowedMethods The allowed methods for this URI (e.g. "GET,POST" after a PUT or a POST request).
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSendMethodNotAllowed(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              allowedMethods)
+  {
+    _OrthancPluginOutputPlusArgument params;
+    params.output = output;
+    params.argument = allowedMethods;
+    context->InvokeService(context, _OrthancPluginService_SendMethodNotAllowed, &params);
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginRestOutput* output;
+    const char*              key;
+    const char*              value;
+  } _OrthancPluginSetHttpHeader;
+
+  /**
+   * @brief Set a cookie.
+   *
+   * This function sets a cookie in the HTTP client.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param cookie The cookie to be set.
+   * @param value The value of the cookie.
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSetCookie(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              cookie,
+    const char*              value)
+  {
+    _OrthancPluginSetHttpHeader params;
+    params.output = output;
+    params.key = cookie;
+    params.value = value;
+    context->InvokeService(context, _OrthancPluginService_SetCookie, &params);
+  }
+
+
+  /**
+   * @brief Set some HTTP header.
+   *
+   * This function sets a HTTP header in the HTTP answer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param key The HTTP header to be set.
+   * @param value The value of the HTTP header.
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSetHttpHeader(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              key,
+    const char*              value)
+  {
+    _OrthancPluginSetHttpHeader params;
+    params.output = output;
+    params.key = key;
+    params.value = value;
+    context->InvokeService(context, _OrthancPluginService_SetHttpHeader, &params);
+  }
+
+
+  typedef struct
+  {
+    char**                             resultStringToFree;
+    const char**                       resultString;
+    int64_t*                           resultInt64;
+    const char*                        key;
+    const OrthancPluginDicomInstance*  instance;
+    OrthancPluginInstanceOrigin*       resultOrigin;   /* New in Orthanc 0.9.5 SDK */
+  } _OrthancPluginAccessDicomInstance;
+
+
+  /**
+   * @brief Get the AET of a DICOM instance.
+   *
+   * This function returns the Application Entity Title (AET) of the
+   * DICOM modality from which a DICOM instance originates.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @return The AET if success, NULL if error.
+   * @ingroup DicomInstance
+   **/
+  ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceRemoteAet(
+    OrthancPluginContext*              context,
+    const OrthancPluginDicomInstance*  instance)
+  {
+    const char* result;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultString = &result;
+    params.instance = instance;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceRemoteAet, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Get the size of a DICOM file.
+   *
+   * This function returns the number of bytes of the given DICOM instance.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @return The size of the file, -1 in case of error.
+   * @ingroup DicomInstance
+   **/
+  ORTHANC_PLUGIN_INLINE int64_t OrthancPluginGetInstanceSize(
+    OrthancPluginContext*             context,
+    const OrthancPluginDicomInstance* instance)
+  {
+    int64_t size;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultInt64 = &size;
+    params.instance = instance;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceSize, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return -1;
+    }
+    else
+    {
+      return size;
+    }
+  }
+
+
+  /**
+   * @brief Get the data of a DICOM file.
+   *
+   * This function returns a pointer to the content of the given DICOM instance.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @return The pointer to the DICOM data, NULL in case of error.
+   * @ingroup DicomInstance
+   **/
+  ORTHANC_PLUGIN_INLINE const void* OrthancPluginGetInstanceData(
+    OrthancPluginContext*              context,
+    const OrthancPluginDicomInstance*  instance)
+  {
+    const char* result;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultString = &result;
+    params.instance = instance;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceData, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Get the DICOM tag hierarchy as a JSON file.
+   *
+   * This function returns a pointer to a newly created string
+   * containing a JSON file. This JSON file encodes the tag hierarchy
+   * of the given DICOM instance.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @return The NULL value in case of error, or a string containing the JSON file.
+   * This string must be freed by OrthancPluginFreeString().
+   * @ingroup DicomInstance
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceJson(
+    OrthancPluginContext*              context,
+    const OrthancPluginDicomInstance*  instance)
+  {
+    char* result;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultStringToFree = &result;
+    params.instance = instance;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceJson, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Get the DICOM tag hierarchy as a JSON file (with simplification).
+   *
+   * This function returns a pointer to a newly created string
+   * containing a JSON file. This JSON file encodes the tag hierarchy
+   * of the given DICOM instance. In contrast with
+   * ::OrthancPluginGetInstanceJson(), the returned JSON file is in
+   * its simplified version.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @return The NULL value in case of error, or a string containing the JSON file.
+   * This string must be freed by OrthancPluginFreeString().
+   * @ingroup DicomInstance
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceSimplifiedJson(
+    OrthancPluginContext*              context,
+    const OrthancPluginDicomInstance*  instance)
+  {
+    char* result;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultStringToFree = &result;
+    params.instance = instance;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceSimplifiedJson, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Check whether a DICOM instance is associated with some metadata.
+   *
+   * This function checks whether the DICOM instance of interest is
+   * associated with some metadata. As of Orthanc 0.8.1, in the
+   * callbacks registered by
+   * ::OrthancPluginRegisterOnStoredInstanceCallback(), the only
+   * possibly available metadata are "ReceptionDate", "RemoteAET" and
+   * "IndexInSeries".
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @param metadata The metadata of interest.
+   * @return 1 if the metadata is present, 0 if it is absent, -1 in case of error.
+   * @ingroup DicomInstance
+   **/
+  ORTHANC_PLUGIN_INLINE int  OrthancPluginHasInstanceMetadata(
+    OrthancPluginContext*              context,
+    const OrthancPluginDicomInstance*  instance,
+    const char*                        metadata)
+  {
+    int64_t result;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultInt64 = &result;
+    params.instance = instance;
+    params.key = metadata;
+
+    if (context->InvokeService(context, _OrthancPluginService_HasInstanceMetadata, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return -1;
+    }
+    else
+    {
+      return (result != 0);
+    }
+  }
+
+
+  /**
+   * @brief Get the value of some metadata associated with a given DICOM instance.
+   *
+   * This functions returns the value of some metadata that is associated with the DICOM instance of interest.
+   * Before calling this function, the existence of the metadata must have been checked with
+   * ::OrthancPluginHasInstanceMetadata().
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @param metadata The metadata of interest.
+   * @return The metadata value if success, NULL if error. Please note that the 
+   *         returned string belongs to the instance object and must NOT be 
+   *         deallocated. Please make a copy of the string if you wish to access 
+   *         it later.
+   * @ingroup DicomInstance
+   **/
+  ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceMetadata(
+    OrthancPluginContext*              context,
+    const OrthancPluginDicomInstance*  instance,
+    const char*                        metadata)
+  {
+    const char* result;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultString = &result;
+    params.instance = instance;
+    params.key = metadata;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceMetadata, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginStorageCreate  create;
+    OrthancPluginStorageRead    read;
+    OrthancPluginStorageRemove  remove;
+    OrthancPluginFree           free;
+  } _OrthancPluginRegisterStorageArea;
+
+  /**
+   * @brief Register a custom storage area.
+   *
+   * This function registers a custom storage area, to replace the
+   * built-in way Orthanc stores its files on the filesystem. This
+   * function must be called during the initialization of the plugin,
+   * i.e. inside the OrthancPluginInitialize() public function.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param create The callback function to store a file on the custom storage area.
+   * @param read The callback function to read a file from the custom storage area.
+   * @param remove The callback function to remove a file from the custom storage area.
+   * @ingroup Callbacks
+   * @deprecated Please use OrthancPluginRegisterStorageArea2()
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterStorageArea(
+    OrthancPluginContext*       context,
+    OrthancPluginStorageCreate  create,
+    OrthancPluginStorageRead    read,
+    OrthancPluginStorageRemove  remove)
+  {
+    _OrthancPluginRegisterStorageArea params;
+    params.create = create;
+    params.read = read;
+    params.remove = remove;
+
+#ifdef  __cplusplus
+    params.free = ::free;
+#else
+    params.free = free;
+#endif
+
+    context->InvokeService(context, _OrthancPluginService_RegisterStorageArea, &params);
+  }
+
+
+
+  /**
+   * @brief Return the path to the Orthanc executable.
+   *
+   * This function returns the path to the Orthanc executable.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return NULL in the case of an error, or a newly allocated string
+   * containing the path. This string must be freed by
+   * OrthancPluginFreeString().
+   **/
+  ORTHANC_PLUGIN_INLINE char *OrthancPluginGetOrthancPath(OrthancPluginContext* context)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = NULL;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetOrthancPath, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Return the directory containing the Orthanc.
+   *
+   * This function returns the path to the directory containing the Orthanc executable.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return NULL in the case of an error, or a newly allocated string
+   * containing the path. This string must be freed by
+   * OrthancPluginFreeString().
+   **/
+  ORTHANC_PLUGIN_INLINE char *OrthancPluginGetOrthancDirectory(OrthancPluginContext* context)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = NULL;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetOrthancDirectory, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Return the path to the configuration file(s).
+   *
+   * This function returns the path to the configuration file(s) that
+   * was specified when starting Orthanc. Since version 0.9.1, this
+   * path can refer to a folder that stores a set of configuration
+   * files. This function is deprecated in favor of
+   * OrthancPluginGetConfiguration().
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return NULL in the case of an error, or a newly allocated string
+   * containing the path. This string must be freed by
+   * OrthancPluginFreeString().
+   * @see OrthancPluginGetConfiguration()
+   **/
+  ORTHANC_PLUGIN_INLINE char *OrthancPluginGetConfigurationPath(OrthancPluginContext* context)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = NULL;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetConfigurationPath, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginOnChangeCallback callback;
+  } _OrthancPluginOnChangeCallback;
+
+  /**
+   * @brief Register a callback to monitor changes.
+   *
+   * This function registers a callback function that is called
+   * whenever a change happens to some DICOM resource.
+   *
+   * @warning Your callback function will be called synchronously with
+   * the core of Orthanc. This implies that deadlocks might emerge if
+   * you call other core primitives of Orthanc in your callback (such
+   * deadlocks are particularly visible in the presence of other plugins
+   * or Lua scripts). It is thus strongly advised to avoid any call to
+   * the REST API of Orthanc in the callback. If you have to call
+   * other primitives of Orthanc, you should make these calls in a
+   * separate thread, passing the pending events to be processed
+   * through a message queue.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param callback The callback function.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterOnChangeCallback(
+    OrthancPluginContext*          context,
+    OrthancPluginOnChangeCallback  callback)
+  {
+    _OrthancPluginOnChangeCallback params;
+    params.callback = callback;
+
+    context->InvokeService(context, _OrthancPluginService_RegisterOnChangeCallback, &params);
+  }
+
+
+
+  typedef struct
+  {
+    const char* plugin;
+    _OrthancPluginProperty property;
+    const char* value;
+  } _OrthancPluginSetPluginProperty;
+
+
+  /**
+   * @brief Set the URI where the plugin provides its Web interface.
+   *
+   * For plugins that come with a Web interface, this function
+   * declares the entry path where to find this interface. This
+   * information is notably used in the "Plugins" page of Orthanc
+   * Explorer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param uri The root URI for this plugin.
+   **/ 
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSetRootUri(
+    OrthancPluginContext*  context,
+    const char*            uri)
+  {
+    _OrthancPluginSetPluginProperty params;
+    params.plugin = OrthancPluginGetName();
+    params.property = _OrthancPluginProperty_RootUri;
+    params.value = uri;
+
+    context->InvokeService(context, _OrthancPluginService_SetPluginProperty, &params);
+  }
+
+
+  /**
+   * @brief Set a description for this plugin.
+   *
+   * Set a description for this plugin. It is displayed in the
+   * "Plugins" page of Orthanc Explorer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param description The description.
+   **/ 
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSetDescription(
+    OrthancPluginContext*  context,
+    const char*            description)
+  {
+    _OrthancPluginSetPluginProperty params;
+    params.plugin = OrthancPluginGetName();
+    params.property = _OrthancPluginProperty_Description;
+    params.value = description;
+
+    context->InvokeService(context, _OrthancPluginService_SetPluginProperty, &params);
+  }
+
+
+  /**
+   * @brief Extend the JavaScript code of Orthanc Explorer.
+   *
+   * Add JavaScript code to customize the default behavior of Orthanc
+   * Explorer. This can for instance be used to add new buttons.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param javascript The custom JavaScript code.
+   **/ 
+  ORTHANC_PLUGIN_INLINE void OrthancPluginExtendOrthancExplorer(
+    OrthancPluginContext*  context,
+    const char*            javascript)
+  {
+    _OrthancPluginSetPluginProperty params;
+    params.plugin = OrthancPluginGetName();
+    params.property = _OrthancPluginProperty_OrthancExplorer;
+    params.value = javascript;
+
+    context->InvokeService(context, _OrthancPluginService_SetPluginProperty, &params);
+  }
+
+
+  typedef struct
+  {
+    char**       result;
+    int32_t      property;
+    const char*  value;
+  } _OrthancPluginGlobalProperty;
+
+
+  /**
+   * @brief Get the value of a global property.
+   *
+   * Get the value of a global property that is stored in the Orthanc database. Global
+   * properties whose index is below 1024 are reserved by Orthanc.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param property The global property of interest.
+   * @param defaultValue The value to return, if the global property is unset.
+   * @return The value of the global property, or NULL in the case of an error. This
+   * string must be freed by OrthancPluginFreeString().
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginGetGlobalProperty(
+    OrthancPluginContext*  context,
+    int32_t                property,
+    const char*            defaultValue)
+  {
+    char* result;
+
+    _OrthancPluginGlobalProperty params;
+    params.result = &result;
+    params.property = property;
+    params.value = defaultValue;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetGlobalProperty, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Set the value of a global property.
+   *
+   * Set the value of a global property into the Orthanc
+   * database. Setting a global property can be used by plugins to
+   * save their internal parameters. Plugins are only allowed to set
+   * properties whose index are above or equal to 1024 (properties
+   * below 1024 are read-only and reserved by Orthanc).
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param property The global property of interest.
+   * @param value The value to be set in the global property.
+   * @return 0 if success, or the error code if failure.
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginSetGlobalProperty(
+    OrthancPluginContext*  context,
+    int32_t                property,
+    const char*            value)
+  {
+    _OrthancPluginGlobalProperty params;
+    params.result = NULL;
+    params.property = property;
+    params.value = value;
+
+    return context->InvokeService(context, _OrthancPluginService_SetGlobalProperty, &params);
+  }
+
+
+
+  typedef struct
+  {
+    int32_t   *resultInt32;
+    uint32_t  *resultUint32;
+    int64_t   *resultInt64;
+    uint64_t  *resultUint64;
+  } _OrthancPluginReturnSingleValue;
+
+  /**
+   * @brief Get the number of command-line arguments.
+   *
+   * Retrieve the number of command-line arguments that were used to launch Orthanc.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return The number of arguments.
+   **/
+  ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetCommandLineArgumentsCount(
+    OrthancPluginContext*  context)
+  {
+    uint32_t count = 0;
+
+    _OrthancPluginReturnSingleValue params;
+    memset(&params, 0, sizeof(params));
+    params.resultUint32 = &count;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetCommandLineArgumentsCount, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return 0;
+    }
+    else
+    {
+      return count;
+    }
+  }
+
+
+
+  /**
+   * @brief Get the value of a command-line argument.
+   *
+   * Get the value of one of the command-line arguments that were used
+   * to launch Orthanc. The number of available arguments can be
+   * retrieved by OrthancPluginGetCommandLineArgumentsCount().
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param argument The index of the argument.
+   * @return The value of the argument, or NULL in the case of an error. This
+   * string must be freed by OrthancPluginFreeString().
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginGetCommandLineArgument(
+    OrthancPluginContext*  context,
+    uint32_t               argument)
+  {
+    char* result;
+
+    _OrthancPluginGlobalProperty params;
+    params.result = &result;
+    params.property = (int32_t) argument;
+    params.value = NULL;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetCommandLineArgument, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Get the expected version of the database schema.
+   *
+   * Retrieve the expected version of the database schema.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return The version.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetExpectedDatabaseVersion(
+    OrthancPluginContext*  context)
+  {
+    uint32_t count = 0;
+
+    _OrthancPluginReturnSingleValue params;
+    memset(&params, 0, sizeof(params));
+    params.resultUint32 = &count;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetExpectedDatabaseVersion, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return 0;
+    }
+    else
+    {
+      return count;
+    }
+  }
+
+
+
+  /**
+   * @brief Return the content of the configuration file(s).
+   *
+   * This function returns the content of the configuration that is
+   * used by Orthanc, formatted as a JSON string.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return NULL in the case of an error, or a newly allocated string
+   * containing the configuration. This string must be freed by
+   * OrthancPluginFreeString().
+   **/
+  ORTHANC_PLUGIN_INLINE char *OrthancPluginGetConfiguration(OrthancPluginContext* context)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = NULL;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetConfiguration, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginRestOutput* output;
+    const char*              subType;
+    const char*              contentType;
+  } _OrthancPluginStartMultipartAnswer;
+
+  /**
+   * @brief Start an HTTP multipart answer.
+   *
+   * Initiates a HTTP multipart answer, as the result of a REST request.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param subType The sub-type of the multipart answer ("mixed" or "related").
+   * @param contentType The MIME type of the items in the multipart answer.
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginSendMultipartItem(), OrthancPluginSendMultipartItem2()
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginStartMultipartAnswer(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              subType,
+    const char*              contentType)
+  {
+    _OrthancPluginStartMultipartAnswer params;
+    params.output = output;
+    params.subType = subType;
+    params.contentType = contentType;
+    return context->InvokeService(context, _OrthancPluginService_StartMultipartAnswer, &params);
+  }
+
+
+  /**
+   * @brief Send an item as a part of some HTTP multipart answer.
+   *
+   * This function sends an item as a part of some HTTP multipart
+   * answer that was initiated by OrthancPluginStartMultipartAnswer().
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param answer Pointer to the memory buffer containing the item.
+   * @param answerSize Number of bytes of the item.
+   * @return 0 if success, or the error code if failure (this notably happens
+   * if the connection is closed by the client).
+   * @see OrthancPluginSendMultipartItem2()
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginSendMultipartItem(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const void*              answer,
+    uint32_t                 answerSize)
+  {
+    _OrthancPluginAnswerBuffer params;
+    params.output = output;
+    params.answer = answer;
+    params.answerSize = answerSize;
+    params.mimeType = NULL;
+    return context->InvokeService(context, _OrthancPluginService_SendMultipartItem, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*    target;
+    const void*                   source;
+    uint32_t                      size;
+    OrthancPluginCompressionType  compression;
+    uint8_t                       uncompress;
+  } _OrthancPluginBufferCompression;
+
+
+  /**
+   * @brief Compress or decompress a buffer.
+   *
+   * This function compresses or decompresses a buffer, using the
+   * version of the zlib library that is used by the Orthanc core.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param source The source buffer.
+   * @param size The size in bytes of the source buffer.
+   * @param compression The compression algorithm.
+   * @param uncompress If set to "0", the buffer must be compressed. 
+   * If set to "1", the buffer must be uncompressed.
+   * @return 0 if success, or the error code if failure.
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginBufferCompression(
+    OrthancPluginContext*         context,
+    OrthancPluginMemoryBuffer*    target,
+    const void*                   source,
+    uint32_t                      size,
+    OrthancPluginCompressionType  compression,
+    uint8_t                       uncompress)
+  {
+    _OrthancPluginBufferCompression params;
+    params.target = target;
+    params.source = source;
+    params.size = size;
+    params.compression = compression;
+    params.uncompress = uncompress;
+
+    return context->InvokeService(context, _OrthancPluginService_BufferCompression, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*  target;
+    const char*                 path;
+  } _OrthancPluginReadFile;
+
+  /**
+   * @brief Read a file.
+   * 
+   * Read the content of a file on the filesystem, and returns it into
+   * a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param path The path of the file to be read.
+   * @return 0 if success, or the error code if failure.
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginReadFile(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 path)
+  {
+    _OrthancPluginReadFile params;
+    params.target = target;
+    params.path = path;
+    return context->InvokeService(context, _OrthancPluginService_ReadFile, &params);
+  }
+
+
+
+  typedef struct
+  {
+    const char*  path;
+    const void*  data;
+    uint32_t     size;
+  } _OrthancPluginWriteFile;
+
+  /**
+   * @brief Write a file.
+   * 
+   * Write the content of a memory buffer to the filesystem.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param path The path of the file to be written.
+   * @param data The content of the memory buffer.
+   * @param size The size of the memory buffer.
+   * @return 0 if success, or the error code if failure.
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginWriteFile(
+    OrthancPluginContext*  context,
+    const char*            path,
+    const void*            data,
+    uint32_t               size)
+  {
+    _OrthancPluginWriteFile params;
+    params.path = path;
+    params.data = data;
+    params.size = size;
+    return context->InvokeService(context, _OrthancPluginService_WriteFile, &params);
+  }
+
+
+
+  typedef struct
+  {
+    const char**            target;
+    OrthancPluginErrorCode  error;
+  } _OrthancPluginGetErrorDescription;
+
+  /**
+   * @brief Get the description of a given error code.
+   *
+   * This function returns the description of a given error code.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param error The error code of interest.
+   * @return The error description. This is a statically-allocated
+   * string, do not free it.
+   **/
+  ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetErrorDescription(
+    OrthancPluginContext*    context,
+    OrthancPluginErrorCode   error)
+  {
+    const char* result = NULL;
+
+    _OrthancPluginGetErrorDescription params;
+    params.target = &result;
+    params.error = error;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetErrorDescription, &params) != OrthancPluginErrorCode_Success ||
+        result == NULL)
+    {
+      return "Unknown error code";
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginRestOutput* output;
+    uint16_t                 status;
+    const char*              body;
+    uint32_t                 bodySize;
+  } _OrthancPluginSendHttpStatus;
+
+  /**
+   * @brief Send a HTTP status, with a custom body.
+   *
+   * This function answers to a HTTP request by sending a HTTP status
+   * code (such as "400 - Bad Request"), together with a body
+   * describing the error. The body will only be returned if the
+   * configuration option "HttpDescribeErrors" of Orthanc is set to "true".
+   * 
+   * Note that:
+   * - Successful requests (status 200) must use ::OrthancPluginAnswerBuffer().
+   * - Redirections (status 301) must use ::OrthancPluginRedirect().
+   * - Unauthorized access (status 401) must use ::OrthancPluginSendUnauthorized().
+   * - Methods not allowed (status 405) must use ::OrthancPluginSendMethodNotAllowed().
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param status The HTTP status code to be sent.
+   * @param body The body of the answer.
+   * @param bodySize The size of the body.
+   * @see OrthancPluginSendHttpStatusCode()
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSendHttpStatus(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    uint16_t                 status,
+    const char*              body,
+    uint32_t                 bodySize)
+  {
+    _OrthancPluginSendHttpStatus params;
+    params.output = output;
+    params.status = status;
+    params.body = body;
+    params.bodySize = bodySize;
+    context->InvokeService(context, _OrthancPluginService_SendHttpStatus, &params);
+  }
+
+
+
+  typedef struct
+  {
+    const OrthancPluginImage*  image;
+    uint32_t*                  resultUint32;
+    OrthancPluginPixelFormat*  resultPixelFormat;
+    void**                     resultBuffer;
+  } _OrthancPluginGetImageInfo;
+
+
+  /**
+   * @brief Return the pixel format of an image.
+   *
+   * This function returns the type of memory layout for the pixels of the given image.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param image The image of interest.
+   * @return The pixel format.
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginPixelFormat  OrthancPluginGetImagePixelFormat(
+    OrthancPluginContext*      context,
+    const OrthancPluginImage*  image)
+  {
+    OrthancPluginPixelFormat target;
+    
+    _OrthancPluginGetImageInfo params;
+    memset(&params, 0, sizeof(params));
+    params.image = image;
+    params.resultPixelFormat = &target;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetImagePixelFormat, &params) != OrthancPluginErrorCode_Success)
+    {
+      return OrthancPluginPixelFormat_Unknown;
+    }
+    else
+    {
+      return (OrthancPluginPixelFormat) target;
+    }
+  }
+
+
+
+  /**
+   * @brief Return the width of an image.
+   *
+   * This function returns the width of the given image.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param image The image of interest.
+   * @return The width.
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE uint32_t  OrthancPluginGetImageWidth(
+    OrthancPluginContext*      context,
+    const OrthancPluginImage*  image)
+  {
+    uint32_t width;
+    
+    _OrthancPluginGetImageInfo params;
+    memset(&params, 0, sizeof(params));
+    params.image = image;
+    params.resultUint32 = &width;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetImageWidth, &params) != OrthancPluginErrorCode_Success)
+    {
+      return 0;
+    }
+    else
+    {
+      return width;
+    }
+  }
+
+
+
+  /**
+   * @brief Return the height of an image.
+   *
+   * This function returns the height of the given image.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param image The image of interest.
+   * @return The height.
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE uint32_t  OrthancPluginGetImageHeight(
+    OrthancPluginContext*      context,
+    const OrthancPluginImage*  image)
+  {
+    uint32_t height;
+    
+    _OrthancPluginGetImageInfo params;
+    memset(&params, 0, sizeof(params));
+    params.image = image;
+    params.resultUint32 = &height;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetImageHeight, &params) != OrthancPluginErrorCode_Success)
+    {
+      return 0;
+    }
+    else
+    {
+      return height;
+    }
+  }
+
+
+
+  /**
+   * @brief Return the pitch of an image.
+   *
+   * This function returns the pitch of the given image. The pitch is
+   * defined as the number of bytes between 2 successive lines of the
+   * image in the memory buffer.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param image The image of interest.
+   * @return The pitch.
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE uint32_t  OrthancPluginGetImagePitch(
+    OrthancPluginContext*      context,
+    const OrthancPluginImage*  image)
+  {
+    uint32_t pitch;
+    
+    _OrthancPluginGetImageInfo params;
+    memset(&params, 0, sizeof(params));
+    params.image = image;
+    params.resultUint32 = &pitch;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetImagePitch, &params) != OrthancPluginErrorCode_Success)
+    {
+      return 0;
+    }
+    else
+    {
+      return pitch;
+    }
+  }
+
+
+
+  /**
+   * @brief Return a pointer to the content of an image.
+   *
+   * This function returns a pointer to the memory buffer that
+   * contains the pixels of the image.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param image The image of interest.
+   * @return The pointer.
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE void*  OrthancPluginGetImageBuffer(
+    OrthancPluginContext*      context,
+    const OrthancPluginImage*  image)
+  {
+    void* target = NULL;
+
+    _OrthancPluginGetImageInfo params;
+    memset(&params, 0, sizeof(params));
+    params.resultBuffer = &target;
+    params.image = image;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetImageBuffer, &params) != OrthancPluginErrorCode_Success)
+    {
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginImage**       target;
+    const void*                data;
+    uint32_t                   size;
+    OrthancPluginImageFormat   format;
+  } _OrthancPluginUncompressImage;
+
+
+  /**
+   * @brief Decode a compressed image.
+   *
+   * This function decodes a compressed image from a memory buffer.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param data Pointer to a memory buffer containing the compressed image.
+   * @param size Size of the memory buffer containing the compressed image.
+   * @param format The file format of the compressed image.
+   * @return The uncompressed image. It must be freed with OrthancPluginFreeImage().
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginImage *OrthancPluginUncompressImage(
+    OrthancPluginContext*      context,
+    const void*                data,
+    uint32_t                   size,
+    OrthancPluginImageFormat   format)
+  {
+    OrthancPluginImage* target = NULL;
+
+    _OrthancPluginUncompressImage params;
+    memset(&params, 0, sizeof(params));
+    params.target = &target;
+    params.data = data;
+    params.size = size;
+    params.format = format;
+
+    if (context->InvokeService(context, _OrthancPluginService_UncompressImage, &params) != OrthancPluginErrorCode_Success)
+    {
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+
+
+
+  typedef struct
+  {
+    OrthancPluginImage*   image;
+  } _OrthancPluginFreeImage;
+
+  /**
+   * @brief Free an image.
+   *
+   * This function frees an image that was decoded with OrthancPluginUncompressImage().
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param image The image.
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE void  OrthancPluginFreeImage(
+    OrthancPluginContext* context, 
+    OrthancPluginImage*   image)
+  {
+    _OrthancPluginFreeImage params;
+    params.image = image;
+
+    context->InvokeService(context, _OrthancPluginService_FreeImage, &params);
+  }
+
+
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer* target;
+    OrthancPluginImageFormat   imageFormat;
+    OrthancPluginPixelFormat   pixelFormat;
+    uint32_t                   width;
+    uint32_t                   height;
+    uint32_t                   pitch;
+    const void*                buffer;
+    uint8_t                    quality;
+  } _OrthancPluginCompressImage;
+
+
+  /**
+   * @brief Encode a PNG image.
+   *
+   * This function compresses the given memory buffer containing an
+   * image using the PNG specification, and stores the result of the
+   * compression into a newly allocated memory buffer.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param format The memory layout of the uncompressed image.
+   * @param width The width of the image.
+   * @param height The height of the image.
+   * @param pitch The pitch of the image (i.e. the number of bytes
+   * between 2 successive lines of the image in the memory buffer).
+   * @param buffer The memory buffer containing the uncompressed image.
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginCompressAndAnswerPngImage()
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCompressPngImage(
+    OrthancPluginContext*         context,
+    OrthancPluginMemoryBuffer*    target,
+    OrthancPluginPixelFormat      format,
+    uint32_t                      width,
+    uint32_t                      height,
+    uint32_t                      pitch,
+    const void*                   buffer)
+  {
+    _OrthancPluginCompressImage params;
+    memset(&params, 0, sizeof(params));
+    params.target = target;
+    params.imageFormat = OrthancPluginImageFormat_Png;
+    params.pixelFormat = format;
+    params.width = width;
+    params.height = height;
+    params.pitch = pitch;
+    params.buffer = buffer;
+    params.quality = 0;  /* Unused for PNG */
+
+    return context->InvokeService(context, _OrthancPluginService_CompressImage, &params);
+  }
+
+
+  /**
+   * @brief Encode a JPEG image.
+   *
+   * This function compresses the given memory buffer containing an
+   * image using the JPEG specification, and stores the result of the
+   * compression into a newly allocated memory buffer.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param format The memory layout of the uncompressed image.
+   * @param width The width of the image.
+   * @param height The height of the image.
+   * @param pitch The pitch of the image (i.e. the number of bytes
+   * between 2 successive lines of the image in the memory buffer).
+   * @param buffer The memory buffer containing the uncompressed image.
+   * @param quality The quality of the JPEG encoding, between 1 (worst
+   * quality, best compression) and 100 (best quality, worst
+   * compression).
+   * @return 0 if success, or the error code if failure.
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCompressJpegImage(
+    OrthancPluginContext*         context,
+    OrthancPluginMemoryBuffer*    target,
+    OrthancPluginPixelFormat      format,
+    uint32_t                      width,
+    uint32_t                      height,
+    uint32_t                      pitch,
+    const void*                   buffer,
+    uint8_t                       quality)
+  {
+    _OrthancPluginCompressImage params;
+    memset(&params, 0, sizeof(params));
+    params.target = target;
+    params.imageFormat = OrthancPluginImageFormat_Jpeg;
+    params.pixelFormat = format;
+    params.width = width;
+    params.height = height;
+    params.pitch = pitch;
+    params.buffer = buffer;
+    params.quality = quality;
+
+    return context->InvokeService(context, _OrthancPluginService_CompressImage, &params);
+  }
+
+
+
+  /**
+   * @brief Answer to a REST request with a JPEG image.
+   *
+   * This function answers to a REST request with a JPEG image. The
+   * parameters of this function describe a memory buffer that
+   * contains an uncompressed image. The image will be automatically compressed
+   * as a JPEG image by the core system of Orthanc.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param format The memory layout of the uncompressed image.
+   * @param width The width of the image.
+   * @param height The height of the image.
+   * @param pitch The pitch of the image (i.e. the number of bytes
+   * between 2 successive lines of the image in the memory buffer).
+   * @param buffer The memory buffer containing the uncompressed image.
+   * @param quality The quality of the JPEG encoding, between 1 (worst
+   * quality, best compression) and 100 (best quality, worst
+   * compression).
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginCompressAndAnswerJpegImage(
+    OrthancPluginContext*     context,
+    OrthancPluginRestOutput*  output,
+    OrthancPluginPixelFormat  format,
+    uint32_t                  width,
+    uint32_t                  height,
+    uint32_t                  pitch,
+    const void*               buffer,
+    uint8_t                   quality)
+  {
+    _OrthancPluginCompressAndAnswerImage params;
+    params.output = output;
+    params.imageFormat = OrthancPluginImageFormat_Jpeg;
+    params.pixelFormat = format;
+    params.width = width;
+    params.height = height;
+    params.pitch = pitch;
+    params.buffer = buffer;
+    params.quality = quality;
+    context->InvokeService(context, _OrthancPluginService_CompressAndAnswerImage, &params);
+  }
+
+
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*  target;
+    OrthancPluginHttpMethod     method;
+    const char*                 url;
+    const char*                 username;
+    const char*                 password;
+    const void*                 body;
+    uint32_t                    bodySize;
+  } _OrthancPluginCallHttpClient;
+
+
+  /**
+   * @brief Issue a HTTP GET call.
+   * 
+   * Make a HTTP GET call to the given URL. The result to the query is
+   * stored into a newly allocated memory buffer. Favor
+   * OrthancPluginRestApiGet() if calling the built-in REST API of the
+   * Orthanc instance that hosts this plugin.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param url The URL of interest.
+   * @param username The username (can be <tt>NULL</tt> if no password protection).
+   * @param password The password (can be <tt>NULL</tt> if no password protection).
+   * @return 0 if success, or the error code if failure.
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginHttpGet(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 url,
+    const char*                 username,
+    const char*                 password)
+  {
+    _OrthancPluginCallHttpClient params;
+    memset(&params, 0, sizeof(params));
+
+    params.target = target;
+    params.method = OrthancPluginHttpMethod_Get;
+    params.url = url;
+    params.username = username;
+    params.password = password;
+
+    return context->InvokeService(context, _OrthancPluginService_CallHttpClient, &params);
+  }
+
+
+  /**
+   * @brief Issue a HTTP POST call.
+   * 
+   * Make a HTTP POST call to the given URL. The result to the query
+   * is stored into a newly allocated memory buffer. Favor
+   * OrthancPluginRestApiPost() if calling the built-in REST API of
+   * the Orthanc instance that hosts this plugin.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param url The URL of interest.
+   * @param body The content of the body of the request.
+   * @param bodySize The size of the body of the request.
+   * @param username The username (can be <tt>NULL</tt> if no password protection).
+   * @param password The password (can be <tt>NULL</tt> if no password protection).
+   * @return 0 if success, or the error code if failure.
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginHttpPost(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 url,
+    const void*                 body,
+    uint32_t                    bodySize,
+    const char*                 username,
+    const char*                 password)
+  {
+    _OrthancPluginCallHttpClient params;
+    memset(&params, 0, sizeof(params));
+
+    params.target = target;
+    params.method = OrthancPluginHttpMethod_Post;
+    params.url = url;
+    params.body = body;
+    params.bodySize = bodySize;
+    params.username = username;
+    params.password = password;
+
+    return context->InvokeService(context, _OrthancPluginService_CallHttpClient, &params);
+  }
+
+
+  /**
+   * @brief Issue a HTTP PUT call.
+   * 
+   * Make a HTTP PUT call to the given URL. The result to the query is
+   * stored into a newly allocated memory buffer. Favor
+   * OrthancPluginRestApiPut() if calling the built-in REST API of the
+   * Orthanc instance that hosts this plugin.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param url The URL of interest.
+   * @param body The content of the body of the request.
+   * @param bodySize The size of the body of the request.
+   * @param username The username (can be <tt>NULL</tt> if no password protection).
+   * @param password The password (can be <tt>NULL</tt> if no password protection).
+   * @return 0 if success, or the error code if failure.
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginHttpPut(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 url,
+    const void*                 body,
+    uint32_t                    bodySize,
+    const char*                 username,
+    const char*                 password)
+  {
+    _OrthancPluginCallHttpClient params;
+    memset(&params, 0, sizeof(params));
+
+    params.target = target;
+    params.method = OrthancPluginHttpMethod_Put;
+    params.url = url;
+    params.body = body;
+    params.bodySize = bodySize;
+    params.username = username;
+    params.password = password;
+
+    return context->InvokeService(context, _OrthancPluginService_CallHttpClient, &params);
+  }
+
+
+  /**
+   * @brief Issue a HTTP DELETE call.
+   * 
+   * Make a HTTP DELETE call to the given URL. Favor
+   * OrthancPluginRestApiDelete() if calling the built-in REST API of
+   * the Orthanc instance that hosts this plugin.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param url The URL of interest.
+   * @param username The username (can be <tt>NULL</tt> if no password protection).
+   * @param password The password (can be <tt>NULL</tt> if no password protection).
+   * @return 0 if success, or the error code if failure.
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginHttpDelete(
+    OrthancPluginContext*       context,
+    const char*                 url,
+    const char*                 username,
+    const char*                 password)
+  {
+    _OrthancPluginCallHttpClient params;
+    memset(&params, 0, sizeof(params));
+
+    params.method = OrthancPluginHttpMethod_Delete;
+    params.url = url;
+    params.username = username;
+    params.password = password;
+
+    return context->InvokeService(context, _OrthancPluginService_CallHttpClient, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginImage**       target;
+    const OrthancPluginImage*  source;
+    OrthancPluginPixelFormat   targetFormat;
+  } _OrthancPluginConvertPixelFormat;
+
+
+  /**
+   * @brief Change the pixel format of an image.
+   *
+   * This function creates a new image, changing the memory layout of the pixels.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param source The source image.
+   * @param targetFormat The target pixel format.
+   * @return The resulting image. It must be freed with OrthancPluginFreeImage().
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginImage *OrthancPluginConvertPixelFormat(
+    OrthancPluginContext*      context,
+    const OrthancPluginImage*  source,
+    OrthancPluginPixelFormat   targetFormat)
+  {
+    OrthancPluginImage* target = NULL;
+
+    _OrthancPluginConvertPixelFormat params;
+    params.target = &target;
+    params.source = source;
+    params.targetFormat = targetFormat;
+
+    if (context->InvokeService(context, _OrthancPluginService_ConvertPixelFormat, &params) != OrthancPluginErrorCode_Success)
+    {
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+
+
+  /**
+   * @brief Return the number of available fonts.
+   *
+   * This function returns the number of fonts that are built in the
+   * Orthanc core. These fonts can be used to draw texts on images
+   * through OrthancPluginDrawText().
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return The number of fonts.
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetFontsCount(
+    OrthancPluginContext*  context)
+  {
+    uint32_t count = 0;
+
+    _OrthancPluginReturnSingleValue params;
+    memset(&params, 0, sizeof(params));
+    params.resultUint32 = &count;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetFontsCount, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return 0;
+    }
+    else
+    {
+      return count;
+    }
+  }
+
+
+
+
+  typedef struct
+  {
+    uint32_t      fontIndex; /* in */
+    const char**  name; /* out */
+    uint32_t*     size; /* out */
+  } _OrthancPluginGetFontInfo;
+
+  /**
+   * @brief Return the name of a font.
+   *
+   * This function returns the name of a font that is built in the Orthanc core.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param fontIndex The index of the font. This value must be less than OrthancPluginGetFontsCount().
+   * @return The font name. This is a statically-allocated string, do not free it.
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetFontName(
+    OrthancPluginContext*  context,
+    uint32_t               fontIndex)
+  {
+    const char* result = NULL;
+
+    _OrthancPluginGetFontInfo params;
+    memset(&params, 0, sizeof(params));
+    params.name = &result;
+    params.fontIndex = fontIndex;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetFontInfo, &params) != OrthancPluginErrorCode_Success)
+    {
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Return the size of a font.
+   *
+   * This function returns the size of a font that is built in the Orthanc core.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param fontIndex The index of the font. This value must be less than OrthancPluginGetFontsCount().
+   * @return The font size.
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetFontSize(
+    OrthancPluginContext*  context,
+    uint32_t               fontIndex)
+  {
+    uint32_t result;
+
+    _OrthancPluginGetFontInfo params;
+    memset(&params, 0, sizeof(params));
+    params.size = &result;
+    params.fontIndex = fontIndex;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetFontInfo, &params) != OrthancPluginErrorCode_Success)
+    {
+      return 0;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginImage*   image;
+    uint32_t              fontIndex;
+    const char*           utf8Text;
+    int32_t               x;
+    int32_t               y;
+    uint8_t               r;
+    uint8_t               g;
+    uint8_t               b;
+  } _OrthancPluginDrawText;
+
+
+  /**
+   * @brief Draw text on an image.
+   *
+   * This function draws some text on some image.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param image The image upon which to draw the text.
+   * @param fontIndex The index of the font. This value must be less than OrthancPluginGetFontsCount().
+   * @param utf8Text The text to be drawn, encoded as an UTF-8 zero-terminated string.
+   * @param x The X position of the text over the image.
+   * @param y The Y position of the text over the image.
+   * @param r The value of the red color channel of the text.
+   * @param g The value of the green color channel of the text.
+   * @param b The value of the blue color channel of the text.
+   * @return 0 if success, other value if error.
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginDrawText(
+    OrthancPluginContext*  context,
+    OrthancPluginImage*    image,
+    uint32_t               fontIndex,
+    const char*            utf8Text,
+    int32_t                x,
+    int32_t                y,
+    uint8_t                r,
+    uint8_t                g,
+    uint8_t                b)
+  {
+    _OrthancPluginDrawText params;
+    memset(&params, 0, sizeof(params));
+    params.image = image;
+    params.fontIndex = fontIndex;
+    params.utf8Text = utf8Text;
+    params.x = x;
+    params.y = y;
+    params.r = r;
+    params.g = g;
+    params.b = b;
+
+    return context->InvokeService(context, _OrthancPluginService_DrawText, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginStorageArea*   storageArea;
+    const char*                 uuid;
+    const void*                 content;
+    uint64_t                    size;
+    OrthancPluginContentType    type;
+  } _OrthancPluginStorageAreaCreate;
+
+
+  /**
+   * @brief Create a file inside the storage area.
+   *
+   * This function creates a new file inside the storage area that is
+   * currently used by Orthanc.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param storageArea The storage area.
+   * @param uuid The identifier of the file to be created.
+   * @param content The content to store in the newly created file.
+   * @param size The size of the content.
+   * @param type The type of the file content.
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   * @deprecated This function should not be used anymore. Use "OrthancPluginRestApiPut()" on
+   * "/{patients|studies|series|instances}/{id}/attachments/{name}" instead.
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginStorageAreaCreate(
+    OrthancPluginContext*       context,
+    OrthancPluginStorageArea*   storageArea,
+    const char*                 uuid,
+    const void*                 content,
+    uint64_t                    size,
+    OrthancPluginContentType    type)
+  {
+    _OrthancPluginStorageAreaCreate params;
+    params.storageArea = storageArea;
+    params.uuid = uuid;
+    params.content = content;
+    params.size = size;
+    params.type = type;
+
+    return context->InvokeService(context, _OrthancPluginService_StorageAreaCreate, &params);
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*  target;
+    OrthancPluginStorageArea*   storageArea;
+    const char*                 uuid;
+    OrthancPluginContentType    type;
+  } _OrthancPluginStorageAreaRead;
+
+
+  /**
+   * @brief Read a file from the storage area.
+   *
+   * This function reads the content of a given file from the storage
+   * area that is currently used by Orthanc.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param storageArea The storage area.
+   * @param uuid The identifier of the file to be read.
+   * @param type The type of the file content.
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   * @deprecated This function should not be used anymore. Use "OrthancPluginRestApiGet()" on
+   * "/{patients|studies|series|instances}/{id}/attachments/{name}" instead.
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginStorageAreaRead(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    OrthancPluginStorageArea*   storageArea,
+    const char*                 uuid,
+    OrthancPluginContentType    type)
+  {
+    _OrthancPluginStorageAreaRead params;
+    params.target = target;
+    params.storageArea = storageArea;
+    params.uuid = uuid;
+    params.type = type;
+
+    return context->InvokeService(context, _OrthancPluginService_StorageAreaRead, &params);
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginStorageArea*   storageArea;
+    const char*                 uuid;
+    OrthancPluginContentType    type;
+  } _OrthancPluginStorageAreaRemove;
+
+  /**
+   * @brief Remove a file from the storage area.
+   *
+   * This function removes a given file from the storage area that is
+   * currently used by Orthanc.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param storageArea The storage area.
+   * @param uuid The identifier of the file to be removed.
+   * @param type The type of the file content.
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   * @deprecated This function should not be used anymore. Use "OrthancPluginRestApiDelete()" on
+   * "/{patients|studies|series|instances}/{id}/attachments/{name}" instead.
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginStorageAreaRemove(
+    OrthancPluginContext*       context,
+    OrthancPluginStorageArea*   storageArea,
+    const char*                 uuid,
+    OrthancPluginContentType    type)
+  {
+    _OrthancPluginStorageAreaRemove params;
+    params.storageArea = storageArea;
+    params.uuid = uuid;
+    params.type = type;
+
+    return context->InvokeService(context, _OrthancPluginService_StorageAreaRemove, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginErrorCode*  target;
+    int32_t                  code;
+    uint16_t                 httpStatus;
+    const char*              message;
+  } _OrthancPluginRegisterErrorCode;
+  
+  /**
+   * @brief Declare a custom error code for this plugin.
+   *
+   * This function declares a custom error code that can be generated
+   * by this plugin. This declaration is used to enrich the body of
+   * the HTTP answer in the case of an error, and to set the proper
+   * HTTP status code.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param code The error code that is internal to this plugin.
+   * @param httpStatus The HTTP status corresponding to this error.
+   * @param message The description of the error.
+   * @return The error code that has been assigned inside the Orthanc core.
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRegisterErrorCode(
+    OrthancPluginContext*    context,
+    int32_t                  code,
+    uint16_t                 httpStatus,
+    const char*              message)
+  {
+    OrthancPluginErrorCode target;
+
+    _OrthancPluginRegisterErrorCode params;
+    params.target = &target;
+    params.code = code;
+    params.httpStatus = httpStatus;
+    params.message = message;
+
+    if (context->InvokeService(context, _OrthancPluginService_RegisterErrorCode, &params) == OrthancPluginErrorCode_Success)
+    {
+      return target;
+    }
+    else
+    {
+      /* There was an error while assigned the error. Use a generic code. */
+      return OrthancPluginErrorCode_Plugin;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    uint16_t                          group;
+    uint16_t                          element;
+    OrthancPluginValueRepresentation  vr;
+    const char*                       name;
+    uint32_t                          minMultiplicity;
+    uint32_t                          maxMultiplicity;
+  } _OrthancPluginRegisterDictionaryTag;
+  
+  /**
+   * @brief Register a new tag into the DICOM dictionary.
+   *
+   * This function declares a new public tag in the dictionary of
+   * DICOM tags that are known to Orthanc. This function should be
+   * used in the OrthancPluginInitialize() callback.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param group The group of the tag.
+   * @param element The element of the tag.
+   * @param vr The value representation of the tag.
+   * @param name The nickname of the tag.
+   * @param minMultiplicity The minimum multiplicity of the tag (must be above 0).
+   * @param maxMultiplicity The maximum multiplicity of the tag. A value of 0 means
+   * an arbitrary multiplicity ("<tt>n</tt>").
+   * @return 0 if success, other value if error.
+   * @see OrthancPluginRegisterPrivateDictionaryTag()
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRegisterDictionaryTag(
+    OrthancPluginContext*             context,
+    uint16_t                          group,
+    uint16_t                          element,
+    OrthancPluginValueRepresentation  vr,
+    const char*                       name,
+    uint32_t                          minMultiplicity,
+    uint32_t                          maxMultiplicity)
+  {
+    _OrthancPluginRegisterDictionaryTag params;
+    params.group = group;
+    params.element = element;
+    params.vr = vr;
+    params.name = name;
+    params.minMultiplicity = minMultiplicity;
+    params.maxMultiplicity = maxMultiplicity;
+
+    return context->InvokeService(context, _OrthancPluginService_RegisterDictionaryTag, &params);
+  }
+
+
+
+  typedef struct
+  {
+    uint16_t                          group;
+    uint16_t                          element;
+    OrthancPluginValueRepresentation  vr;
+    const char*                       name;
+    uint32_t                          minMultiplicity;
+    uint32_t                          maxMultiplicity;
+    const char*                       privateCreator;
+  } _OrthancPluginRegisterPrivateDictionaryTag;
+  
+  /**
+   * @brief Register a new private tag into the DICOM dictionary.
+   *
+   * This function declares a new private tag in the dictionary of
+   * DICOM tags that are known to Orthanc. This function should be
+   * used in the OrthancPluginInitialize() callback.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param group The group of the tag.
+   * @param element The element of the tag.
+   * @param vr The value representation of the tag.
+   * @param name The nickname of the tag.
+   * @param minMultiplicity The minimum multiplicity of the tag (must be above 0).
+   * @param maxMultiplicity The maximum multiplicity of the tag. A value of 0 means
+   * an arbitrary multiplicity ("<tt>n</tt>").
+   * @param privateCreator The private creator of this private tag.
+   * @return 0 if success, other value if error.
+   * @see OrthancPluginRegisterDictionaryTag()
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRegisterPrivateDictionaryTag(
+    OrthancPluginContext*             context,
+    uint16_t                          group,
+    uint16_t                          element,
+    OrthancPluginValueRepresentation  vr,
+    const char*                       name,
+    uint32_t                          minMultiplicity,
+    uint32_t                          maxMultiplicity,
+    const char*                       privateCreator)
+  {
+    _OrthancPluginRegisterPrivateDictionaryTag params;
+    params.group = group;
+    params.element = element;
+    params.vr = vr;
+    params.name = name;
+    params.minMultiplicity = minMultiplicity;
+    params.maxMultiplicity = maxMultiplicity;
+    params.privateCreator = privateCreator;
+
+    return context->InvokeService(context, _OrthancPluginService_RegisterPrivateDictionaryTag, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginStorageArea*  storageArea;
+    OrthancPluginResourceType  level;
+  } _OrthancPluginReconstructMainDicomTags;
+
+  /**
+   * @brief Reconstruct the main DICOM tags.
+   *
+   * This function requests the Orthanc core to reconstruct the main
+   * DICOM tags of all the resources of the given type. This function
+   * can only be used as a part of the upgrade of a custom database
+   * back-end. A database transaction will be automatically setup.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param storageArea The storage area.
+   * @param level The type of the resources of interest.
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginReconstructMainDicomTags(
+    OrthancPluginContext*      context,
+    OrthancPluginStorageArea*  storageArea,
+    OrthancPluginResourceType  level)
+  {
+    _OrthancPluginReconstructMainDicomTags params;
+    params.level = level;
+    params.storageArea = storageArea;
+
+    return context->InvokeService(context, _OrthancPluginService_ReconstructMainDicomTags, &params);
+  }
+
+
+  typedef struct
+  {
+    char**                          result;
+    const char*                     instanceId;
+    const void*                     buffer;
+    uint32_t                        size;
+    OrthancPluginDicomToJsonFormat  format;
+    OrthancPluginDicomToJsonFlags   flags;
+    uint32_t                        maxStringLength;
+  } _OrthancPluginDicomToJson;
+
+
+  /**
+   * @brief Format a DICOM memory buffer as a JSON string.
+   *
+   * This function takes as input a memory buffer containing a DICOM
+   * file, and outputs a JSON string representing the tags of this
+   * DICOM file.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param buffer The memory buffer containing the DICOM file.
+   * @param size The size of the memory buffer.
+   * @param format The output format.
+   * @param flags Flags governing the output.
+   * @param maxStringLength The maximum length of a field. Too long fields will
+   * be output as "null". The 0 value means no maximum length.
+   * @return The NULL value if the case of an error, or the JSON
+   * string. This string must be freed by OrthancPluginFreeString().
+   * @ingroup Toolbox
+   * @see OrthancPluginDicomInstanceToJson
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginDicomBufferToJson(
+    OrthancPluginContext*           context,
+    const void*                     buffer,
+    uint32_t                        size,
+    OrthancPluginDicomToJsonFormat  format,
+    OrthancPluginDicomToJsonFlags   flags, 
+    uint32_t                        maxStringLength)
+  {
+    char* result;
+
+    _OrthancPluginDicomToJson params;
+    memset(&params, 0, sizeof(params));
+    params.result = &result;
+    params.buffer = buffer;
+    params.size = size;
+    params.format = format;
+    params.flags = flags;
+    params.maxStringLength = maxStringLength;
+
+    if (context->InvokeService(context, _OrthancPluginService_DicomBufferToJson, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Format a DICOM instance as a JSON string.
+   *
+   * This function formats a DICOM instance that is stored in Orthanc,
+   * and outputs a JSON string representing the tags of this DICOM
+   * instance.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instanceId The Orthanc identifier of the instance.
+   * @param format The output format.
+   * @param flags Flags governing the output.
+   * @param maxStringLength The maximum length of a field. Too long fields will
+   * be output as "null". The 0 value means no maximum length.
+   * @return The NULL value if the case of an error, or the JSON
+   * string. This string must be freed by OrthancPluginFreeString().
+   * @ingroup Toolbox
+   * @see OrthancPluginDicomInstanceToJson
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginDicomInstanceToJson(
+    OrthancPluginContext*           context,
+    const char*                     instanceId,
+    OrthancPluginDicomToJsonFormat  format,
+    OrthancPluginDicomToJsonFlags   flags, 
+    uint32_t                        maxStringLength)
+  {
+    char* result;
+
+    _OrthancPluginDicomToJson params;
+    memset(&params, 0, sizeof(params));
+    params.result = &result;
+    params.instanceId = instanceId;
+    params.format = format;
+    params.flags = flags;
+    params.maxStringLength = maxStringLength;
+
+    if (context->InvokeService(context, _OrthancPluginService_DicomInstanceToJson, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*  target;
+    const char*                 uri;
+    uint32_t                    headersCount;
+    const char* const*          headersKeys;
+    const char* const*          headersValues;
+    int32_t                     afterPlugins;
+  } _OrthancPluginRestApiGet2;
+
+  /**
+   * @brief Make a GET call to the Orthanc REST API, with custom HTTP headers.
+   * 
+   * Make a GET call to the Orthanc REST API with extended
+   * parameters. The result to the query is stored into a newly
+   * allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param uri The URI in the built-in Orthanc API.
+   * @param headersCount The number of HTTP headers.
+   * @param headersKeys Array containing the keys of the HTTP headers (can be <tt>NULL</tt> if no header).
+   * @param headersValues Array containing the values of the HTTP headers (can be <tt>NULL</tt> if no header).
+   * @param afterPlugins If 0, the built-in API of Orthanc is used.
+   * If 1, the API is tainted by the plugins.
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginRestApiGet, OrthancPluginRestApiGetAfterPlugins
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiGet2(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 uri,
+    uint32_t                    headersCount,
+    const char* const*          headersKeys,
+    const char* const*          headersValues,
+    int32_t                     afterPlugins)
+  {
+    _OrthancPluginRestApiGet2 params;
+    params.target = target;
+    params.uri = uri;
+    params.headersCount = headersCount;
+    params.headersKeys = headersKeys;
+    params.headersValues = headersValues;
+    params.afterPlugins = afterPlugins;
+
+    return context->InvokeService(context, _OrthancPluginService_RestApiGet2, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginWorklistCallback callback;
+  } _OrthancPluginWorklistCallback;
+
+  /**
+   * @brief Register a callback to handle modality worklists requests.
+   *
+   * This function registers a callback to handle C-Find SCP requests
+   * on modality worklists.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param callback The callback.
+   * @return 0 if success, other value if error.
+   * @ingroup DicomCallbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterWorklistCallback(
+    OrthancPluginContext*          context,
+    OrthancPluginWorklistCallback  callback)
+  {
+    _OrthancPluginWorklistCallback params;
+    params.callback = callback;
+
+    return context->InvokeService(context, _OrthancPluginService_RegisterWorklistCallback, &params);
+  }
+
+
+  
+  typedef struct
+  {
+    OrthancPluginWorklistAnswers*      answers;
+    const OrthancPluginWorklistQuery*  query;
+    const void*                        dicom;
+    uint32_t                           size;
+  } _OrthancPluginWorklistAnswersOperation;
+
+  /**
+   * @brief Add one answer to some modality worklist request.
+   *
+   * This function adds one worklist (encoded as a DICOM file) to the
+   * set of answers corresponding to some C-Find SCP request against
+   * modality worklists.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param answers The set of answers.
+   * @param query The worklist query, as received by the callback.
+   * @param dicom The worklist to answer, encoded as a DICOM file.
+   * @param size The size of the DICOM file.
+   * @return 0 if success, other value if error.
+   * @ingroup DicomCallbacks
+   * @see OrthancPluginCreateDicom()
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginWorklistAddAnswer(
+    OrthancPluginContext*             context,
+    OrthancPluginWorklistAnswers*     answers,
+    const OrthancPluginWorklistQuery* query,
+    const void*                       dicom,
+    uint32_t                          size)
+  {
+    _OrthancPluginWorklistAnswersOperation params;
+    params.answers = answers;
+    params.query = query;
+    params.dicom = dicom;
+    params.size = size;
+
+    return context->InvokeService(context, _OrthancPluginService_WorklistAddAnswer, &params);
+  }
+
+
+  /**
+   * @brief Mark the set of worklist answers as incomplete.
+   *
+   * This function marks as incomplete the set of answers
+   * corresponding to some C-Find SCP request against modality
+   * worklists. This must be used if canceling the handling of a
+   * request when too many answers are to be returned.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param answers The set of answers.
+   * @return 0 if success, other value if error.
+   * @ingroup DicomCallbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginWorklistMarkIncomplete(
+    OrthancPluginContext*          context,
+    OrthancPluginWorklistAnswers*  answers)
+  {
+    _OrthancPluginWorklistAnswersOperation params;
+    params.answers = answers;
+    params.query = NULL;
+    params.dicom = NULL;
+    params.size = 0;
+
+    return context->InvokeService(context, _OrthancPluginService_WorklistMarkIncomplete, &params);
+  }
+
+
+  typedef struct
+  {
+    const OrthancPluginWorklistQuery*  query;
+    const void*                        dicom;
+    uint32_t                           size;
+    int32_t*                           isMatch;
+    OrthancPluginMemoryBuffer*         target;
+  } _OrthancPluginWorklistQueryOperation;
+
+  /**
+   * @brief Test whether a worklist matches the query.
+   *
+   * This function checks whether one worklist (encoded as a DICOM
+   * file) matches the C-Find SCP query against modality
+   * worklists. This function must be called before adding the
+   * worklist as an answer through OrthancPluginWorklistAddAnswer().
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param query The worklist query, as received by the callback.
+   * @param dicom The worklist to answer, encoded as a DICOM file.
+   * @param size The size of the DICOM file.
+   * @return 1 if the worklist matches the query, 0 otherwise.
+   * @ingroup DicomCallbacks
+   **/
+  ORTHANC_PLUGIN_INLINE int32_t  OrthancPluginWorklistIsMatch(
+    OrthancPluginContext*              context,
+    const OrthancPluginWorklistQuery*  query,
+    const void*                        dicom,
+    uint32_t                           size)
+  {
+    int32_t isMatch = 0;
+
+    _OrthancPluginWorklistQueryOperation params;
+    params.query = query;
+    params.dicom = dicom;
+    params.size = size;
+    params.isMatch = &isMatch;
+    params.target = NULL;
+
+    if (context->InvokeService(context, _OrthancPluginService_WorklistIsMatch, &params) == OrthancPluginErrorCode_Success)
+    {
+      return isMatch;
+    }
+    else
+    {
+      /* Error: Assume non-match */
+      return 0;
+    }
+  }
+
+
+  /**
+   * @brief Retrieve the worklist query as a DICOM file.
+   *
+   * This function retrieves the DICOM file that underlies a C-Find
+   * SCP query against modality worklists.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target Memory buffer where to store the DICOM file. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param query The worklist query, as received by the callback.
+   * @return 0 if success, other value if error.
+   * @ingroup DicomCallbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginWorklistGetDicomQuery(
+    OrthancPluginContext*              context,
+    OrthancPluginMemoryBuffer*         target,
+    const OrthancPluginWorklistQuery*  query)
+  {
+    _OrthancPluginWorklistQueryOperation params;
+    params.query = query;
+    params.dicom = NULL;
+    params.size = 0;
+    params.isMatch = NULL;
+    params.target = target;
+
+    return context->InvokeService(context, _OrthancPluginService_WorklistGetDicomQuery, &params);
+  }
+
+
+  /**
+   * @brief Get the origin of a DICOM file.
+   *
+   * This function returns the origin of a DICOM instance that has been received by Orthanc.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @return The origin of the instance.
+   * @ingroup DicomInstance
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginInstanceOrigin OrthancPluginGetInstanceOrigin(
+    OrthancPluginContext*             context,
+    const OrthancPluginDicomInstance* instance)
+  {
+    OrthancPluginInstanceOrigin origin;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultOrigin = &origin;
+    params.instance = instance;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceOrigin, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return OrthancPluginInstanceOrigin_Unknown;
+    }
+    else
+    {
+      return origin;
+    }
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*     target;
+    const char*                    json;
+    const OrthancPluginImage*      pixelData;
+    OrthancPluginCreateDicomFlags  flags;
+  } _OrthancPluginCreateDicom;
+
+  /**
+   * @brief Create a DICOM instance from a JSON string and an image.
+   *
+   * This function takes as input a string containing a JSON file
+   * describing the content of a DICOM instance. As an output, it
+   * writes the corresponding DICOM instance to a newly allocated
+   * memory buffer. Additionally, an image to be encoded within the
+   * DICOM instance can also be provided.
+   *
+   * Private tags will be associated with the private creator whose
+   * value is specified in the "DefaultPrivateCreator" configuration
+   * option of Orthanc. The function OrthancPluginCreateDicom2() can
+   * be used if another private creator must be used to create this
+   * instance.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param json The input JSON file.
+   * @param pixelData The image. Can be NULL, if the pixel data is encoded inside the JSON with the data URI scheme.
+   * @param flags Flags governing the output.
+   * @return 0 if success, other value if error.
+   * @ingroup Toolbox
+   * @see OrthancPluginCreateDicom2
+   * @see OrthancPluginDicomBufferToJson
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCreateDicom(
+    OrthancPluginContext*          context,
+    OrthancPluginMemoryBuffer*     target,
+    const char*                    json,
+    const OrthancPluginImage*      pixelData,
+    OrthancPluginCreateDicomFlags  flags)
+  {
+    _OrthancPluginCreateDicom params;
+    params.target = target;
+    params.json = json;
+    params.pixelData = pixelData;
+    params.flags = flags;
+
+    return context->InvokeService(context, _OrthancPluginService_CreateDicom, &params);
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginDecodeImageCallback callback;
+  } _OrthancPluginDecodeImageCallback;
+
+  /**
+   * @brief Register a callback to handle the decoding of DICOM images.
+   *
+   * This function registers a custom callback to decode DICOM images,
+   * extending the built-in decoder of Orthanc that uses
+   * DCMTK. Starting with Orthanc 1.7.0, the exact behavior is
+   * affected by the configuration option
+   * "BuiltinDecoderTranscoderOrder" of Orthanc.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param callback The callback.
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterDecodeImageCallback(
+    OrthancPluginContext*             context,
+    OrthancPluginDecodeImageCallback  callback)
+  {
+    _OrthancPluginDecodeImageCallback params;
+    params.callback = callback;
+
+    return context->InvokeService(context, _OrthancPluginService_RegisterDecodeImageCallback, &params);
+  }
+  
+
+
+  typedef struct
+  {
+    OrthancPluginImage**       target;
+    OrthancPluginPixelFormat   format;
+    uint32_t                   width;
+    uint32_t                   height;
+    uint32_t                   pitch;
+    void*                      buffer;
+    const void*                constBuffer;
+    uint32_t                   bufferSize;
+    uint32_t                   frameIndex;
+  } _OrthancPluginCreateImage;
+
+
+  /**
+   * @brief Create an image.
+   *
+   * This function creates an image of given size and format.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param format The format of the pixels.
+   * @param width The width of the image.
+   * @param height The height of the image.
+   * @return The newly allocated image. It must be freed with OrthancPluginFreeImage().
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginCreateImage(
+    OrthancPluginContext*     context,
+    OrthancPluginPixelFormat  format,
+    uint32_t                  width,
+    uint32_t                  height)
+  {
+    OrthancPluginImage* target = NULL;
+
+    _OrthancPluginCreateImage params;
+    memset(&params, 0, sizeof(params));
+    params.target = &target;
+    params.format = format;
+    params.width = width;
+    params.height = height;
+
+    if (context->InvokeService(context, _OrthancPluginService_CreateImage, &params) != OrthancPluginErrorCode_Success)
+    {
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+
+  /**
+   * @brief Create an image pointing to a memory buffer.
+   *
+   * This function creates an image whose content points to a memory
+   * buffer managed by the plugin. Note that the buffer is directly
+   * accessed, no memory is allocated and no data is copied.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param format The format of the pixels.
+   * @param width The width of the image.
+   * @param height The height of the image.
+   * @param pitch The pitch of the image (i.e. the number of bytes
+   * between 2 successive lines of the image in the memory buffer).
+   * @param buffer The memory buffer.
+   * @return The newly allocated image. It must be freed with OrthancPluginFreeImage().
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginCreateImageAccessor(
+    OrthancPluginContext*     context,
+    OrthancPluginPixelFormat  format,
+    uint32_t                  width,
+    uint32_t                  height,
+    uint32_t                  pitch,
+    void*                     buffer)
+  {
+    OrthancPluginImage* target = NULL;
+
+    _OrthancPluginCreateImage params;
+    memset(&params, 0, sizeof(params));
+    params.target = &target;
+    params.format = format;
+    params.width = width;
+    params.height = height;
+    params.pitch = pitch;
+    params.buffer = buffer;
+
+    if (context->InvokeService(context, _OrthancPluginService_CreateImageAccessor, &params) != OrthancPluginErrorCode_Success)
+    {
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+
+
+  /**
+   * @brief Decode one frame from a DICOM instance.
+   *
+   * This function decodes one frame of a DICOM image that is stored
+   * in a memory buffer. This function will give the same result as
+   * OrthancPluginUncompressImage() for single-frame DICOM images.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param buffer Pointer to a memory buffer containing the DICOM image.
+   * @param bufferSize Size of the memory buffer containing the DICOM image.
+   * @param frameIndex The index of the frame of interest in a multi-frame image.
+   * @return The uncompressed image. It must be freed with OrthancPluginFreeImage().
+   * @ingroup Images
+   * @see OrthancPluginGetInstanceDecodedFrame()
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginDecodeDicomImage(
+    OrthancPluginContext*  context,
+    const void*            buffer,
+    uint32_t               bufferSize,
+    uint32_t               frameIndex)
+  {
+    OrthancPluginImage* target = NULL;
+
+    _OrthancPluginCreateImage params;
+    memset(&params, 0, sizeof(params));
+    params.target = &target;
+    params.constBuffer = buffer;
+    params.bufferSize = bufferSize;
+    params.frameIndex = frameIndex;
+
+    if (context->InvokeService(context, _OrthancPluginService_DecodeDicomImage, &params) != OrthancPluginErrorCode_Success)
+    {
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    char**       result;
+    const void*  buffer;
+    uint32_t     size;
+  } _OrthancPluginComputeHash;
+
+  /**
+   * @brief Compute an MD5 hash.
+   *
+   * This functions computes the MD5 cryptographic hash of the given memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param buffer The source memory buffer.
+   * @param size The size in bytes of the source buffer.
+   * @return The NULL value in case of error, or a string containing the cryptographic hash.
+   * This string must be freed by OrthancPluginFreeString().
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginComputeMd5(
+    OrthancPluginContext*  context,
+    const void*            buffer,
+    uint32_t               size)
+  {
+    char* result;
+
+    _OrthancPluginComputeHash params;
+    params.result = &result;
+    params.buffer = buffer;
+    params.size = size;
+
+    if (context->InvokeService(context, _OrthancPluginService_ComputeMd5, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Compute a SHA-1 hash.
+   *
+   * This functions computes the SHA-1 cryptographic hash of the given memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param buffer The source memory buffer.
+   * @param size The size in bytes of the source buffer.
+   * @return The NULL value in case of error, or a string containing the cryptographic hash.
+   * This string must be freed by OrthancPluginFreeString().
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginComputeSha1(
+    OrthancPluginContext*  context,
+    const void*            buffer,
+    uint32_t               size)
+  {
+    char* result;
+
+    _OrthancPluginComputeHash params;
+    params.result = &result;
+    params.buffer = buffer;
+    params.size = size;
+
+    if (context->InvokeService(context, _OrthancPluginService_ComputeSha1, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginDictionaryEntry* target;
+    const char*                   name;
+  } _OrthancPluginLookupDictionary;
+
+  /**
+   * @brief Get information about the given DICOM tag.
+   *
+   * This functions makes a lookup in the dictionary of DICOM tags
+   * that are known to Orthanc, and returns information about this
+   * tag. The tag can be specified using its human-readable name
+   * (e.g. "PatientName") or a set of two hexadecimal numbers
+   * (e.g. "0010-0020").
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target Where to store the information about the tag.
+   * @param name The name of the DICOM tag.
+   * @return 0 if success, other value if error.
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginLookupDictionary(
+    OrthancPluginContext*          context,
+    OrthancPluginDictionaryEntry*  target,
+    const char*                    name)
+  {
+    _OrthancPluginLookupDictionary params;
+    params.target = target;
+    params.name = name;
+    return context->InvokeService(context, _OrthancPluginService_LookupDictionary, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginRestOutput* output;
+    const void*              answer;
+    uint32_t                 answerSize;
+    uint32_t                 headersCount;
+    const char* const*       headersKeys;
+    const char* const*       headersValues;
+  } _OrthancPluginSendMultipartItem2;
+
+  /**
+   * @brief Send an item as a part of some HTTP multipart answer, with custom headers.
+   *
+   * This function sends an item as a part of some HTTP multipart
+   * answer that was initiated by OrthancPluginStartMultipartAnswer(). In addition to
+   * OrthancPluginSendMultipartItem(), this function will set HTTP header associated
+   * with the item.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param answer Pointer to the memory buffer containing the item.
+   * @param answerSize Number of bytes of the item.
+   * @param headersCount The number of HTTP headers.
+   * @param headersKeys Array containing the keys of the HTTP headers.
+   * @param headersValues Array containing the values of the HTTP headers.
+   * @return 0 if success, or the error code if failure (this notably happens
+   * if the connection is closed by the client).
+   * @see OrthancPluginSendMultipartItem()
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginSendMultipartItem2(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const void*              answer,
+    uint32_t                 answerSize,
+    uint32_t                 headersCount,
+    const char* const*       headersKeys,
+    const char* const*       headersValues)
+  {
+    _OrthancPluginSendMultipartItem2 params;
+    params.output = output;
+    params.answer = answer;
+    params.answerSize = answerSize;
+    params.headersCount = headersCount;
+    params.headersKeys = headersKeys;
+    params.headersValues = headersValues;    
+
+    return context->InvokeService(context, _OrthancPluginService_SendMultipartItem2, &params);
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginIncomingHttpRequestFilter callback;
+  } _OrthancPluginIncomingHttpRequestFilter;
+
+  /**
+   * @brief Register a callback to filter incoming HTTP requests.
+   *
+   * This function registers a custom callback to filter incoming HTTP/REST
+   * requests received by the HTTP server of Orthanc.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param callback The callback.
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   * @deprecated Please instead use OrthancPluginRegisterIncomingHttpRequestFilter2()
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterIncomingHttpRequestFilter(
+    OrthancPluginContext*                   context,
+    OrthancPluginIncomingHttpRequestFilter  callback)
+  {
+    _OrthancPluginIncomingHttpRequestFilter params;
+    params.callback = callback;
+
+    return context->InvokeService(context, _OrthancPluginService_RegisterIncomingHttpRequestFilter, &params);
+  }
+  
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*  answerBody;
+    OrthancPluginMemoryBuffer*  answerHeaders;
+    uint16_t*                   httpStatus;
+    OrthancPluginHttpMethod     method;
+    const char*                 url;
+    uint32_t                    headersCount;
+    const char* const*          headersKeys;
+    const char* const*          headersValues;
+    const void*                 body;
+    uint32_t                    bodySize;
+    const char*                 username;
+    const char*                 password;
+    uint32_t                    timeout;
+    const char*                 certificateFile;
+    const char*                 certificateKeyFile;
+    const char*                 certificateKeyPassword;
+    uint8_t                     pkcs11;
+  } _OrthancPluginCallHttpClient2;
+
+
+
+  /**
+   * @brief Issue a HTTP call with full flexibility.
+   * 
+   * Make a HTTP call to the given URL. The result to the query is
+   * stored into a newly allocated memory buffer. The HTTP request
+   * will be done accordingly to the global configuration of Orthanc
+   * (in particular, the options "HttpProxy", "HttpTimeout",
+   * "HttpsVerifyPeers", "HttpsCACertificates", and "Pkcs11" will be
+   * taken into account).
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param answerBody The target memory buffer (out argument).
+   *        It must be freed with OrthancPluginFreeMemoryBuffer().
+   *        The value of this argument is ignored if the HTTP method is DELETE.
+   * @param answerHeaders The target memory buffer for the HTTP headers in the answers (out argument). 
+   *        The answer headers are formatted as a JSON object (associative array).
+   *        The buffer must be freed with OrthancPluginFreeMemoryBuffer().
+   *        This argument can be set to NULL if the plugin has no interest in the HTTP headers.
+   * @param httpStatus The HTTP status after the execution of the request (out argument).
+   * @param method HTTP method to be used.
+   * @param url The URL of interest.
+   * @param headersCount The number of HTTP headers.
+   * @param headersKeys Array containing the keys of the HTTP headers (can be <tt>NULL</tt> if no header).
+   * @param headersValues Array containing the values of the HTTP headers (can be <tt>NULL</tt> if no header).
+   * @param username The username (can be <tt>NULL</tt> if no password protection).
+   * @param password The password (can be <tt>NULL</tt> if no password protection).
+   * @param body The HTTP body for a POST or PUT request.
+   * @param bodySize The size of the body.
+   * @param timeout Timeout in seconds (0 for default timeout).
+   * @param certificateFile Path to the client certificate for HTTPS, in PEM format
+   * (can be <tt>NULL</tt> if no client certificate or if not using HTTPS).
+   * @param certificateKeyFile Path to the key of the client certificate for HTTPS, in PEM format
+   * (can be <tt>NULL</tt> if no client certificate or if not using HTTPS).
+   * @param certificateKeyPassword Password to unlock the key of the client certificate 
+   * (can be <tt>NULL</tt> if no client certificate or if not using HTTPS).
+   * @param pkcs11 Enable PKCS#11 client authentication for hardware security modules and smart cards.
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginCallPeerApi()
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginHttpClient(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  answerBody,
+    OrthancPluginMemoryBuffer*  answerHeaders,
+    uint16_t*                   httpStatus,
+    OrthancPluginHttpMethod     method,
+    const char*                 url,
+    uint32_t                    headersCount,
+    const char* const*          headersKeys,
+    const char* const*          headersValues,
+    const void*                 body,
+    uint32_t                    bodySize,
+    const char*                 username,
+    const char*                 password,
+    uint32_t                    timeout,
+    const char*                 certificateFile,
+    const char*                 certificateKeyFile,
+    const char*                 certificateKeyPassword,
+    uint8_t                     pkcs11)
+  {
+    _OrthancPluginCallHttpClient2 params;
+    memset(&params, 0, sizeof(params));
+
+    params.answerBody = answerBody;
+    params.answerHeaders = answerHeaders;
+    params.httpStatus = httpStatus;
+    params.method = method;
+    params.url = url;
+    params.headersCount = headersCount;
+    params.headersKeys = headersKeys;
+    params.headersValues = headersValues;
+    params.body = body;
+    params.bodySize = bodySize;
+    params.username = username;
+    params.password = password;
+    params.timeout = timeout;
+    params.certificateFile = certificateFile;
+    params.certificateKeyFile = certificateKeyFile;
+    params.certificateKeyPassword = certificateKeyPassword;
+    params.pkcs11 = pkcs11;
+
+    return context->InvokeService(context, _OrthancPluginService_CallHttpClient2, &params);
+  }
+
+
+  /**
+   * @brief Generate an UUID.
+   *
+   * Generate a random GUID/UUID (globally unique identifier).
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return NULL in the case of an error, or a newly allocated string
+   * containing the UUID. This string must be freed by OrthancPluginFreeString().
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginGenerateUuid(
+    OrthancPluginContext*  context)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = NULL;
+
+    if (context->InvokeService(context, _OrthancPluginService_GenerateUuid, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+
+
+  typedef struct
+  {
+    OrthancPluginFindCallback callback;
+  } _OrthancPluginFindCallback;
+
+  /**
+   * @brief Register a callback to handle C-Find requests.
+   *
+   * This function registers a callback to handle C-Find SCP requests
+   * that are not related to modality worklists.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param callback The callback.
+   * @return 0 if success, other value if error.
+   * @ingroup DicomCallbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterFindCallback(
+    OrthancPluginContext*      context,
+    OrthancPluginFindCallback  callback)
+  {
+    _OrthancPluginFindCallback params;
+    params.callback = callback;
+
+    return context->InvokeService(context, _OrthancPluginService_RegisterFindCallback, &params);
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginFindAnswers      *answers;
+    const OrthancPluginFindQuery  *query;
+    const void                    *dicom;
+    uint32_t                       size;
+    uint32_t                       index;
+    uint32_t                      *resultUint32;
+    uint16_t                      *resultGroup;
+    uint16_t                      *resultElement;
+    char                         **resultString;
+  } _OrthancPluginFindOperation;
+
+  /**
+   * @brief Add one answer to some C-Find request.
+   *
+   * This function adds one answer (encoded as a DICOM file) to the
+   * set of answers corresponding to some C-Find SCP request that is
+   * not related to modality worklists.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param answers The set of answers.
+   * @param dicom The answer to be added, encoded as a DICOM file.
+   * @param size The size of the DICOM file.
+   * @return 0 if success, other value if error.
+   * @ingroup DicomCallbacks
+   * @see OrthancPluginCreateDicom()
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginFindAddAnswer(
+    OrthancPluginContext*      context,
+    OrthancPluginFindAnswers*  answers,
+    const void*                dicom,
+    uint32_t                   size)
+  {
+    _OrthancPluginFindOperation params;
+    memset(&params, 0, sizeof(params));
+    params.answers = answers;
+    params.dicom = dicom;
+    params.size = size;
+
+    return context->InvokeService(context, _OrthancPluginService_FindAddAnswer, &params);
+  }
+
+
+  /**
+   * @brief Mark the set of C-Find answers as incomplete.
+   *
+   * This function marks as incomplete the set of answers
+   * corresponding to some C-Find SCP request that is not related to
+   * modality worklists. This must be used if canceling the handling
+   * of a request when too many answers are to be returned.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param answers The set of answers.
+   * @return 0 if success, other value if error.
+   * @ingroup DicomCallbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginFindMarkIncomplete(
+    OrthancPluginContext*      context,
+    OrthancPluginFindAnswers*  answers)
+  {
+    _OrthancPluginFindOperation params;
+    memset(&params, 0, sizeof(params));
+    params.answers = answers;
+
+    return context->InvokeService(context, _OrthancPluginService_FindMarkIncomplete, &params);
+  }
+
+
+
+  /**
+   * @brief Get the number of tags in a C-Find query.
+   *
+   * This function returns the number of tags that are contained in
+   * the given C-Find query.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param query The C-Find query.
+   * @return The number of tags.
+   * @ingroup DicomCallbacks
+   **/
+  ORTHANC_PLUGIN_INLINE uint32_t  OrthancPluginGetFindQuerySize(
+    OrthancPluginContext*          context,
+    const OrthancPluginFindQuery*  query)
+  {
+    uint32_t count = 0;
+
+    _OrthancPluginFindOperation params;
+    memset(&params, 0, sizeof(params));
+    params.query = query;
+    params.resultUint32 = &count;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetFindQuerySize, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return 0;
+    }
+    else
+    {
+      return count;
+    }
+  }
+
+
+  /**
+   * @brief Get one tag in a C-Find query.
+   *
+   * This function returns the group and the element of one DICOM tag
+   * in the given C-Find query.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param group The group of the tag (output).
+   * @param element The element of the tag (output).
+   * @param query The C-Find query.
+   * @param index The index of the tag of interest.
+   * @return 0 if success, other value if error.
+   * @ingroup DicomCallbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginGetFindQueryTag(
+    OrthancPluginContext*          context,
+    uint16_t*                      group,
+    uint16_t*                      element,
+    const OrthancPluginFindQuery*  query,
+    uint32_t                       index)
+  {
+    _OrthancPluginFindOperation params;
+    memset(&params, 0, sizeof(params));
+    params.query = query;
+    params.index = index;
+    params.resultGroup = group;
+    params.resultElement = element;
+
+    return context->InvokeService(context, _OrthancPluginService_GetFindQueryTag, &params);
+  }
+
+
+  /**
+   * @brief Get the symbolic name of one tag in a C-Find query.
+   *
+   * This function returns the symbolic name of one DICOM tag in the
+   * given C-Find query.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param query The C-Find query.
+   * @param index The index of the tag of interest.
+   * @return The NULL value in case of error, or a string containing the name of the tag.
+   * @return 0 if success, other value if error.
+   * @ingroup DicomCallbacks
+   **/
+  ORTHANC_PLUGIN_INLINE char*  OrthancPluginGetFindQueryTagName(
+    OrthancPluginContext*          context,
+    const OrthancPluginFindQuery*  query,
+    uint32_t                       index)
+  {
+    char* result;
+
+    _OrthancPluginFindOperation params;
+    memset(&params, 0, sizeof(params));
+    params.query = query;
+    params.index = index;
+    params.resultString = &result;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetFindQueryTagName, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Get the value associated with one tag in a C-Find query.
+   *
+   * This function returns the value associated with one tag in the
+   * given C-Find query.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param query The C-Find query.
+   * @param index The index of the tag of interest.
+   * @return The NULL value in case of error, or a string containing the value of the tag.
+   * @return 0 if success, other value if error.
+   * @ingroup DicomCallbacks
+   **/
+  ORTHANC_PLUGIN_INLINE char*  OrthancPluginGetFindQueryValue(
+    OrthancPluginContext*          context,
+    const OrthancPluginFindQuery*  query,
+    uint32_t                       index)
+  {
+    char* result;
+
+    _OrthancPluginFindOperation params;
+    memset(&params, 0, sizeof(params));
+    params.query = query;
+    params.index = index;
+    params.resultString = &result;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetFindQueryValue, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+ 
+
+
+
+  typedef struct
+  {
+    OrthancPluginMoveCallback   callback;
+    OrthancPluginGetMoveSize    getMoveSize;
+    OrthancPluginApplyMove      applyMove;
+    OrthancPluginFreeMove       freeMove;
+  } _OrthancPluginMoveCallback;
+
+  /**
+   * @brief Register a callback to handle C-Move requests.
+   *
+   * This function registers a callback to handle C-Move SCP requests.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param callback The main callback.
+   * @param getMoveSize Callback to read the number of C-Move suboperations.
+   * @param applyMove Callback to apply one C-Move suboperation.
+   * @param freeMove Callback to free the C-Move driver.
+   * @return 0 if success, other value if error.
+   * @ingroup DicomCallbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterMoveCallback(
+    OrthancPluginContext*       context,
+    OrthancPluginMoveCallback   callback,
+    OrthancPluginGetMoveSize    getMoveSize,
+    OrthancPluginApplyMove      applyMove,
+    OrthancPluginFreeMove       freeMove)
+  {
+    _OrthancPluginMoveCallback params;
+    params.callback = callback;
+    params.getMoveSize = getMoveSize;
+    params.applyMove = applyMove;
+    params.freeMove = freeMove;
+
+    return context->InvokeService(context, _OrthancPluginService_RegisterMoveCallback, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginFindMatcher** target;
+    const void*                query;
+    uint32_t                   size;
+  } _OrthancPluginCreateFindMatcher;
+
+
+  /**
+   * @brief Create a C-Find matcher.
+   *
+   * This function creates a "matcher" object that can be used to
+   * check whether a DICOM instance matches a C-Find query. The C-Find
+   * query must be expressed as a DICOM buffer.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param query The C-Find DICOM query.
+   * @param size The size of the DICOM query.
+   * @return The newly allocated matcher. It must be freed with OrthancPluginFreeFindMatcher().
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginFindMatcher* OrthancPluginCreateFindMatcher(
+    OrthancPluginContext*  context,
+    const void*            query,
+    uint32_t               size)
+  {
+    OrthancPluginFindMatcher* target = NULL;
+
+    _OrthancPluginCreateFindMatcher params;
+    memset(&params, 0, sizeof(params));
+    params.target = &target;
+    params.query = query;
+    params.size = size;
+
+    if (context->InvokeService(context, _OrthancPluginService_CreateFindMatcher, &params) != OrthancPluginErrorCode_Success)
+    {
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginFindMatcher*   matcher;
+  } _OrthancPluginFreeFindMatcher;
+
+  /**
+   * @brief Free a C-Find matcher.
+   *
+   * This function frees a matcher that was created using OrthancPluginCreateFindMatcher().
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param matcher The matcher of interest.
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE void  OrthancPluginFreeFindMatcher(
+    OrthancPluginContext*     context, 
+    OrthancPluginFindMatcher* matcher)
+  {
+    _OrthancPluginFreeFindMatcher params;
+    params.matcher = matcher;
+
+    context->InvokeService(context, _OrthancPluginService_FreeFindMatcher, &params);
+  }
+
+
+  typedef struct
+  {
+    const OrthancPluginFindMatcher*  matcher;
+    const void*                      dicom;
+    uint32_t                         size;
+    int32_t*                         isMatch;
+  } _OrthancPluginFindMatcherIsMatch;
+
+  /**
+   * @brief Test whether a DICOM instance matches a C-Find query.
+   *
+   * This function checks whether one DICOM instance matches C-Find
+   * matcher that was previously allocated using
+   * OrthancPluginCreateFindMatcher().
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param matcher The matcher of interest.
+   * @param dicom The DICOM instance to be matched.
+   * @param size The size of the DICOM instance.
+   * @return 1 if the DICOM instance matches the query, 0 otherwise.
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE int32_t  OrthancPluginFindMatcherIsMatch(
+    OrthancPluginContext*            context,
+    const OrthancPluginFindMatcher*  matcher,
+    const void*                      dicom,
+    uint32_t                         size)
+  {
+    int32_t isMatch = 0;
+
+    _OrthancPluginFindMatcherIsMatch params;
+    params.matcher = matcher;
+    params.dicom = dicom;
+    params.size = size;
+    params.isMatch = &isMatch;
+
+    if (context->InvokeService(context, _OrthancPluginService_FindMatcherIsMatch, &params) == OrthancPluginErrorCode_Success)
+    {
+      return isMatch;
+    }
+    else
+    {
+      /* Error: Assume non-match */
+      return 0;
+    }
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginIncomingHttpRequestFilter2 callback;
+  } _OrthancPluginIncomingHttpRequestFilter2;
+
+  /**
+   * @brief Register a callback to filter incoming HTTP requests.
+   *
+   * This function registers a custom callback to filter incoming HTTP/REST
+   * requests received by the HTTP server of Orthanc.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param callback The callback.
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterIncomingHttpRequestFilter2(
+    OrthancPluginContext*                   context,
+    OrthancPluginIncomingHttpRequestFilter2 callback)
+  {
+    _OrthancPluginIncomingHttpRequestFilter2 params;
+    params.callback = callback;
+
+    return context->InvokeService(context, _OrthancPluginService_RegisterIncomingHttpRequestFilter2, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginPeers**  peers;
+  } _OrthancPluginGetPeers;
+
+  /**
+   * @brief Return the list of available Orthanc peers.
+   *
+   * This function returns the parameters of the Orthanc peers that are known to
+   * the Orthanc server hosting the plugin.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return NULL if error, or a newly allocated opaque data structure containing the peers.
+   * This structure must be freed with OrthancPluginFreePeers().
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginPeers* OrthancPluginGetPeers(
+    OrthancPluginContext*  context)
+  {
+    OrthancPluginPeers* peers = NULL;
+
+    _OrthancPluginGetPeers params;
+    memset(&params, 0, sizeof(params));
+    params.peers = &peers;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetPeers, &params) != OrthancPluginErrorCode_Success)
+    {
+      return NULL;
+    }
+    else
+    {
+      return peers;
+    }
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginPeers*   peers;
+  } _OrthancPluginFreePeers;
+
+  /**
+   * @brief Free the list of available Orthanc peers.
+   *
+   * This function frees the data structure returned by OrthancPluginGetPeers().
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param peers The data structure describing the Orthanc peers.
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE void  OrthancPluginFreePeers(
+    OrthancPluginContext*     context, 
+    OrthancPluginPeers* peers)
+  {
+    _OrthancPluginFreePeers params;
+    params.peers = peers;
+
+    context->InvokeService(context, _OrthancPluginService_FreePeers, &params);
+  }
+
+
+  typedef struct
+  {
+    uint32_t*                  target;
+    const OrthancPluginPeers*  peers;
+  } _OrthancPluginGetPeersCount;
+
+  /**
+   * @brief Get the number of Orthanc peers.
+   *
+   * This function returns the number of Orthanc peers.
+   *
+   * This function is thread-safe: Several threads sharing the same
+   * OrthancPluginPeers object can simultaneously call this function.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param peers The data structure describing the Orthanc peers.
+   * @result The number of peers. 
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetPeersCount(
+    OrthancPluginContext*      context,
+    const OrthancPluginPeers*  peers)
+  {
+    uint32_t target = 0;
+
+    _OrthancPluginGetPeersCount params;
+    memset(&params, 0, sizeof(params));
+    params.target = &target;
+    params.peers = peers;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetPeersCount, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return 0;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+
+  typedef struct
+  {
+    const char**               target;
+    const OrthancPluginPeers*  peers;
+    uint32_t                   peerIndex;
+    const char*                userProperty;
+  } _OrthancPluginGetPeerProperty;
+
+  /**
+   * @brief Get the symbolic name of an Orthanc peer.
+   *
+   * This function returns the symbolic name of the Orthanc peer,
+   * which corresponds to the key of the "OrthancPeers" configuration
+   * option of Orthanc.
+   *
+   * This function is thread-safe: Several threads sharing the same
+   * OrthancPluginPeers object can simultaneously call this function.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param peers The data structure describing the Orthanc peers.
+   * @param peerIndex The index of the peer of interest.
+   * This value must be lower than OrthancPluginGetPeersCount().
+   * @result The symbolic name, or NULL in the case of an error.
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetPeerName(
+    OrthancPluginContext*      context,
+    const OrthancPluginPeers*  peers,
+    uint32_t                   peerIndex)
+  {
+    const char* target = NULL;
+
+    _OrthancPluginGetPeerProperty params;
+    memset(&params, 0, sizeof(params));
+    params.target = &target;
+    params.peers = peers;
+    params.peerIndex = peerIndex;
+    params.userProperty = NULL;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetPeerName, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+
+  /**
+   * @brief Get the base URL of an Orthanc peer.
+   *
+   * This function returns the base URL to the REST API of some Orthanc peer.
+   *
+   * This function is thread-safe: Several threads sharing the same
+   * OrthancPluginPeers object can simultaneously call this function.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param peers The data structure describing the Orthanc peers.
+   * @param peerIndex The index of the peer of interest.
+   * This value must be lower than OrthancPluginGetPeersCount().
+   * @result The URL, or NULL in the case of an error.
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetPeerUrl(
+    OrthancPluginContext*      context,
+    const OrthancPluginPeers*  peers,
+    uint32_t                   peerIndex)
+  {
+    const char* target = NULL;
+
+    _OrthancPluginGetPeerProperty params;
+    memset(&params, 0, sizeof(params));
+    params.target = &target;
+    params.peers = peers;
+    params.peerIndex = peerIndex;
+    params.userProperty = NULL;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetPeerUrl, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+
+
+  /**
+   * @brief Get some user-defined property of an Orthanc peer.
+   *
+   * This function returns some user-defined property of some Orthanc
+   * peer. An user-defined property is a property that is associated
+   * with the peer in the Orthanc configuration file, but that is not
+   * recognized by the Orthanc core.
+   *
+   * This function is thread-safe: Several threads sharing the same
+   * OrthancPluginPeers object can simultaneously call this function.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param peers The data structure describing the Orthanc peers.
+   * @param peerIndex The index of the peer of interest.
+   * This value must be lower than OrthancPluginGetPeersCount().
+   * @param userProperty The user property of interest.
+   * @result The value of the user property, or NULL if it is not defined.
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetPeerUserProperty(
+    OrthancPluginContext*      context,
+    const OrthancPluginPeers*  peers,
+    uint32_t                   peerIndex,
+    const char*                userProperty)
+  {
+    const char* target = NULL;
+
+    _OrthancPluginGetPeerProperty params;
+    memset(&params, 0, sizeof(params));
+    params.target = &target;
+    params.peers = peers;
+    params.peerIndex = peerIndex;
+    params.userProperty = userProperty;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetPeerUserProperty, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* No such user property */
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*  answerBody;
+    OrthancPluginMemoryBuffer*  answerHeaders;
+    uint16_t*                   httpStatus;
+    const OrthancPluginPeers*   peers;
+    uint32_t                    peerIndex;
+    OrthancPluginHttpMethod     method;
+    const char*                 uri;
+    uint32_t                    additionalHeadersCount;
+    const char* const*          additionalHeadersKeys;
+    const char* const*          additionalHeadersValues;
+    const void*                 body;
+    uint32_t                    bodySize;
+    uint32_t                    timeout;
+  } _OrthancPluginCallPeerApi;
+
+  /**
+   * @brief Call the REST API of an Orthanc peer.
+   * 
+   * Make a REST call to the given URI in the REST API of a remote
+   * Orthanc peer. The result to the query is stored into a newly
+   * allocated memory buffer. The HTTP request will be done according
+   * to the "OrthancPeers" configuration option of Orthanc.
+   *
+   * This function is thread-safe: Several threads sharing the same
+   * OrthancPluginPeers object can simultaneously call this function.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param answerBody The target memory buffer (out argument).
+   *        It must be freed with OrthancPluginFreeMemoryBuffer().
+   *        The value of this argument is ignored if the HTTP method is DELETE.
+   * @param answerHeaders The target memory buffer for the HTTP headers in the answers (out argument). 
+   *        The answer headers are formatted as a JSON object (associative array).
+   *        The buffer must be freed with OrthancPluginFreeMemoryBuffer().
+   *        This argument can be set to NULL if the plugin has no interest in the HTTP headers.
+   * @param httpStatus The HTTP status after the execution of the request (out argument).
+   * @param peers The data structure describing the Orthanc peers.
+   * @param peerIndex The index of the peer of interest.
+   * This value must be lower than OrthancPluginGetPeersCount().
+   * @param method HTTP method to be used.
+   * @param uri The URI of interest in the REST API.
+   * @param additionalHeadersCount The number of HTTP headers to be added to the
+   * HTTP headers provided in the global configuration of Orthanc.
+   * @param additionalHeadersKeys Array containing the keys of the HTTP headers (can be <tt>NULL</tt> if no header).
+   * @param additionalHeadersValues Array containing the values of the HTTP headers (can be <tt>NULL</tt> if no header).
+   * @param body The HTTP body for a POST or PUT request.
+   * @param bodySize The size of the body.
+   * @param timeout Timeout in seconds (0 for default timeout).
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginHttpClient()
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginCallPeerApi(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  answerBody,
+    OrthancPluginMemoryBuffer*  answerHeaders,
+    uint16_t*                   httpStatus,
+    const OrthancPluginPeers*   peers,
+    uint32_t                    peerIndex,
+    OrthancPluginHttpMethod     method,
+    const char*                 uri,
+    uint32_t                    additionalHeadersCount,
+    const char* const*          additionalHeadersKeys,
+    const char* const*          additionalHeadersValues,
+    const void*                 body,
+    uint32_t                    bodySize,
+    uint32_t                    timeout)
+  {
+    _OrthancPluginCallPeerApi params;
+    memset(&params, 0, sizeof(params));
+
+    params.answerBody = answerBody;
+    params.answerHeaders = answerHeaders;
+    params.httpStatus = httpStatus;
+    params.peers = peers;
+    params.peerIndex = peerIndex;
+    params.method = method;
+    params.uri = uri;
+    params.additionalHeadersCount = additionalHeadersCount;
+    params.additionalHeadersKeys = additionalHeadersKeys;
+    params.additionalHeadersValues = additionalHeadersValues;
+    params.body = body;
+    params.bodySize = bodySize;
+    params.timeout = timeout;
+
+    return context->InvokeService(context, _OrthancPluginService_CallPeerApi, &params);
+  }
+
+
+
+
+
+  typedef struct
+  {
+    OrthancPluginJob**              target;
+    void                           *job;
+    OrthancPluginJobFinalize        finalize;
+    const char                     *type;
+    OrthancPluginJobGetProgress     getProgress;
+    OrthancPluginJobGetContent      getContent;
+    OrthancPluginJobGetSerialized   getSerialized;
+    OrthancPluginJobStep            step;
+    OrthancPluginJobStop            stop;
+    OrthancPluginJobReset           reset;
+  } _OrthancPluginCreateJob;
+
+  /**
+   * @brief Create a custom job.
+   *
+   * This function creates a custom job to be run by the jobs engine
+   * of Orthanc.
+   * 
+   * Orthanc starts one dedicated thread per custom job that is
+   * running. It is guaranteed that all the callbacks will only be
+   * called from this single dedicated thread, in mutual exclusion: As
+   * a consequence, it is *not* mandatory to protect the various
+   * callbacks by mutexes.
+   * 
+   * The custom job can nonetheless launch its own processing threads
+   * on the first call to the "step()" callback, and stop them once
+   * the "stop()" callback is called.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param job The job to be executed.
+   * @param finalize The finalization callback.
+   * @param type The type of the job, provided to the job unserializer. 
+   * See OrthancPluginRegisterJobsUnserializer().
+   * @param getProgress The progress callback.
+   * @param getContent The content callback.
+   * @param getSerialized The serialization callback.
+   * @param step The callback to execute the individual steps of the job.
+   * @param stop The callback that is invoked once the job leaves the "running" state.
+   * @param reset The callback that is invoked if a stopped job is started again.
+   * @return The newly allocated job. It must be freed with OrthancPluginFreeJob(),
+   * as long as it is not submitted with OrthancPluginSubmitJob().
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginJob *OrthancPluginCreateJob(
+    OrthancPluginContext           *context,
+    void                           *job,
+    OrthancPluginJobFinalize        finalize,
+    const char                     *type,
+    OrthancPluginJobGetProgress     getProgress,
+    OrthancPluginJobGetContent      getContent,
+    OrthancPluginJobGetSerialized   getSerialized,
+    OrthancPluginJobStep            step,
+    OrthancPluginJobStop            stop,
+    OrthancPluginJobReset           reset)
+  {
+    OrthancPluginJob* target = NULL;
+
+    _OrthancPluginCreateJob params;
+    memset(&params, 0, sizeof(params));
+
+    params.target = &target;
+    params.job = job;
+    params.finalize = finalize;
+    params.type = type;
+    params.getProgress = getProgress;
+    params.getContent = getContent;
+    params.getSerialized = getSerialized;
+    params.step = step;
+    params.stop = stop;
+    params.reset = reset;
+
+    if (context->InvokeService(context, _OrthancPluginService_CreateJob, &params) != OrthancPluginErrorCode_Success ||
+        target == NULL)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginJob*   job;
+  } _OrthancPluginFreeJob;
+
+  /**
+   * @brief Free a custom job.
+   *
+   * This function frees an image that was created with OrthancPluginCreateJob().
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param job The job.
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE void  OrthancPluginFreeJob(
+    OrthancPluginContext* context, 
+    OrthancPluginJob*     job)
+  {
+    _OrthancPluginFreeJob params;
+    params.job = job;
+
+    context->InvokeService(context, _OrthancPluginService_FreeJob, &params);
+  }
+
+
+  
+  typedef struct
+  {
+    char**             resultId;
+    OrthancPluginJob  *job;
+    int                priority;
+  } _OrthancPluginSubmitJob;
+
+  /**
+   * @brief Submit a new job to the jobs engine of Orthanc.
+   *
+   * This function adds the given job to the pending jobs of
+   * Orthanc. Orthanc will take take of freeing it by invoking the
+   * finalization callback provided to OrthancPluginCreateJob().
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param job The job, as received by OrthancPluginCreateJob().
+   * @param priority The priority of the job.
+   * @return ID of the newly-submitted job. This string must be freed by OrthancPluginFreeString().
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE char *OrthancPluginSubmitJob(
+    OrthancPluginContext   *context,
+    OrthancPluginJob       *job,
+    int                     priority)
+  {
+    char* resultId = NULL;
+
+    _OrthancPluginSubmitJob params;
+    memset(&params, 0, sizeof(params));
+
+    params.resultId = &resultId;
+    params.job = job;
+    params.priority = priority;
+
+    if (context->InvokeService(context, _OrthancPluginService_SubmitJob, &params) != OrthancPluginErrorCode_Success ||
+        resultId == NULL)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return resultId;
+    }
+  }
+  
+
+
+  typedef struct
+  {
+    OrthancPluginJobsUnserializer unserializer;
+  } _OrthancPluginJobsUnserializer;
+
+  /**
+   * @brief Register an unserializer for custom jobs.
+   *
+   * This function registers an unserializer that decodes custom jobs
+   * from a JSON string. This callback is invoked when the jobs engine
+   * of Orthanc is started (on Orthanc initialization), for each job
+   * that is stored in the Orthanc database.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param unserializer The job unserializer.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterJobsUnserializer(
+    OrthancPluginContext*          context,
+    OrthancPluginJobsUnserializer  unserializer)
+  {
+    _OrthancPluginJobsUnserializer params;
+    params.unserializer = unserializer;
+
+    context->InvokeService(context, _OrthancPluginService_RegisterJobsUnserializer, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginRestOutput* output;
+    const char*              details;
+    uint8_t                  log;
+  } _OrthancPluginSetHttpErrorDetails;
+
+  /**
+   * @brief Provide a detailed description for an HTTP error.
+   *
+   * This function sets the detailed description associated with an
+   * HTTP error. This description will be displayed in the "Details"
+   * field of the JSON body of the HTTP answer. It is only taken into
+   * consideration if the REST callback returns an error code that is
+   * different from "OrthancPluginErrorCode_Success", and if the
+   * "HttpDescribeErrors" configuration option of Orthanc is set to
+   * "true".
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param details The details of the error message.
+   * @param log Whether to also write the detailed error to the Orthanc logs.
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSetHttpErrorDetails(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              details,
+    uint8_t                  log)
+  {
+    _OrthancPluginSetHttpErrorDetails params;
+    params.output = output;
+    params.details = details;
+    params.log = log;
+    context->InvokeService(context, _OrthancPluginService_SetHttpErrorDetails, &params);
+  }
+
+
+
+  typedef struct
+  {
+    const char** result;
+    const char*  argument;
+  } _OrthancPluginRetrieveStaticString;
+
+  /**
+   * @brief Detect the MIME type of a file.
+   *
+   * This function returns the MIME type of a file by inspecting its extension.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param path Path to the file.
+   * @return The MIME type. This is a statically-allocated
+   * string, do not free it.
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE const char* OrthancPluginAutodetectMimeType(
+    OrthancPluginContext*  context,
+    const char*            path)
+  {
+    const char* result = NULL;
+
+    _OrthancPluginRetrieveStaticString params;
+    params.result = &result;
+    params.argument = path;
+
+    if (context->InvokeService(context, _OrthancPluginService_AutodetectMimeType, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    const char*               name;
+    float                     value;
+    OrthancPluginMetricsType  type;
+  } _OrthancPluginSetMetricsValue;
+
+  /**
+   * @brief Set the value of a metrics.
+   *
+   * This function sets the value of a metrics to monitor the behavior
+   * of the plugin through tools such as Prometheus. The values of all
+   * the metrics are stored within the Orthanc context.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param name The name of the metrics to be set.
+   * @param value The value of the metrics.
+   * @param type The type of the metrics. This parameter is only taken into consideration
+   * the first time this metrics is set.
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSetMetricsValue(
+    OrthancPluginContext*     context,
+    const char*               name,
+    float                     value,
+    OrthancPluginMetricsType  type)
+  {
+    _OrthancPluginSetMetricsValue params;
+    params.name = name;
+    params.value = value;
+    params.type = type;
+    context->InvokeService(context, _OrthancPluginService_SetMetricsValue, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginRefreshMetricsCallback  callback;
+  } _OrthancPluginRegisterRefreshMetricsCallback;
+
+  /**
+   * @brief Register a callback to refresh the metrics.
+   *
+   * This function registers a callback to refresh the metrics. The
+   * callback must make calls to OrthancPluginSetMetricsValue().
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param callback The callback function to handle the refresh.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterRefreshMetricsCallback(
+    OrthancPluginContext*               context,
+    OrthancPluginRefreshMetricsCallback callback)
+  {
+    _OrthancPluginRegisterRefreshMetricsCallback params;
+    params.callback = callback;
+    context->InvokeService(context, _OrthancPluginService_RegisterRefreshMetricsCallback, &params);
+  }
+
+
+
+
+  typedef struct
+  {
+    char**                               target;
+    const void*                          dicom;
+    uint32_t                             dicomSize;
+    OrthancPluginDicomWebBinaryCallback  callback;
+  } _OrthancPluginEncodeDicomWeb;
+
+  /**
+   * @brief Convert a DICOM instance to DICOMweb JSON.
+   *
+   * This function converts a memory buffer containing a DICOM instance,
+   * into its DICOMweb JSON representation.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param dicom Pointer to the DICOM instance.
+   * @param dicomSize Size of the DICOM instance.
+   * @param callback Callback to set the value of the binary tags.
+   * @see OrthancPluginCreateDicom()
+   * @return The NULL value in case of error, or the JSON document. This string must
+   * be freed by OrthancPluginFreeString().
+   * @deprecated OrthancPluginEncodeDicomWebJson2()
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginEncodeDicomWebJson(
+    OrthancPluginContext*                context,
+    const void*                          dicom,
+    uint32_t                             dicomSize,
+    OrthancPluginDicomWebBinaryCallback  callback)
+  {
+    char* target = NULL;
+    
+    _OrthancPluginEncodeDicomWeb params;
+    params.target = &target;
+    params.dicom = dicom;
+    params.dicomSize = dicomSize;
+    params.callback = callback;
+
+    if (context->InvokeService(context, _OrthancPluginService_EncodeDicomWebJson, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+
+  /**
+   * @brief Convert a DICOM instance to DICOMweb XML.
+   *
+   * This function converts a memory buffer containing a DICOM instance,
+   * into its DICOMweb XML representation.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param dicom Pointer to the DICOM instance.
+   * @param dicomSize Size of the DICOM instance.
+   * @param callback Callback to set the value of the binary tags.
+   * @return The NULL value in case of error, or the XML document. This string must
+   * be freed by OrthancPluginFreeString().
+   * @see OrthancPluginCreateDicom()
+   * @deprecated OrthancPluginEncodeDicomWebXml2()
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginEncodeDicomWebXml(
+    OrthancPluginContext*                context,
+    const void*                          dicom,
+    uint32_t                             dicomSize,
+    OrthancPluginDicomWebBinaryCallback  callback)
+  {
+    char* target = NULL;
+    
+    _OrthancPluginEncodeDicomWeb params;
+    params.target = &target;
+    params.dicom = dicom;
+    params.dicomSize = dicomSize;
+    params.callback = callback;
+
+    if (context->InvokeService(context, _OrthancPluginService_EncodeDicomWebXml, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+  
+
+
+  typedef struct
+  {
+    char**                                target;
+    const void*                           dicom;
+    uint32_t                              dicomSize;
+    OrthancPluginDicomWebBinaryCallback2  callback;
+    void*                                 payload;
+  } _OrthancPluginEncodeDicomWeb2;
+
+  /**
+   * @brief Convert a DICOM instance to DICOMweb JSON.
+   *
+   * This function converts a memory buffer containing a DICOM instance,
+   * into its DICOMweb JSON representation.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param dicom Pointer to the DICOM instance.
+   * @param dicomSize Size of the DICOM instance.
+   * @param callback Callback to set the value of the binary tags.
+   * @param payload User payload.
+   * @return The NULL value in case of error, or the JSON document. This string must
+   * be freed by OrthancPluginFreeString().
+   * @see OrthancPluginCreateDicom()
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginEncodeDicomWebJson2(
+    OrthancPluginContext*                 context,
+    const void*                           dicom,
+    uint32_t                              dicomSize,
+    OrthancPluginDicomWebBinaryCallback2  callback,
+    void*                                 payload)
+  {
+    char* target = NULL;
+    
+    _OrthancPluginEncodeDicomWeb2 params;
+    params.target = &target;
+    params.dicom = dicom;
+    params.dicomSize = dicomSize;
+    params.callback = callback;
+    params.payload = payload;
+
+    if (context->InvokeService(context, _OrthancPluginService_EncodeDicomWebJson2, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+
+  /**
+   * @brief Convert a DICOM instance to DICOMweb XML.
+   *
+   * This function converts a memory buffer containing a DICOM instance,
+   * into its DICOMweb XML representation.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param dicom Pointer to the DICOM instance.
+   * @param dicomSize Size of the DICOM instance.
+   * @param callback Callback to set the value of the binary tags.
+   * @param payload User payload.
+   * @return The NULL value in case of error, or the XML document. This string must
+   * be freed by OrthancPluginFreeString().
+   * @see OrthancPluginCreateDicom()
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginEncodeDicomWebXml2(
+    OrthancPluginContext*                 context,
+    const void*                           dicom,
+    uint32_t                              dicomSize,
+    OrthancPluginDicomWebBinaryCallback2  callback,
+    void*                                 payload)
+  {
+    char* target = NULL;
+    
+    _OrthancPluginEncodeDicomWeb2 params;
+    params.target = &target;
+    params.dicom = dicom;
+    params.dicomSize = dicomSize;
+    params.callback = callback;
+    params.payload = payload;
+
+    if (context->InvokeService(context, _OrthancPluginService_EncodeDicomWebXml2, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+  
+
+
+  /**
+   * @brief Callback executed when a HTTP header is received during a chunked transfer.
+   *
+   * Signature of a callback function that is called by Orthanc acting
+   * as a HTTP client during a chunked HTTP transfer, as soon as it
+   * receives one HTTP header from the answer of the remote HTTP
+   * server.
+   *
+   * @see OrthancPluginChunkedHttpClient()
+   * @param answer The user payload, as provided by the calling plugin.
+   * @param key The key of the HTTP header.
+   * @param value The value of the HTTP header.
+   * @return 0 if success, or the error code if failure.
+   * @ingroup Toolbox
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginChunkedClientAnswerAddHeader) (
+    void* answer,
+    const char* key,
+    const char* value);
+
+
+  /**
+   * @brief Callback executed when an answer chunk is received during a chunked transfer.
+   *
+   * Signature of a callback function that is called by Orthanc acting
+   * as a HTTP client during a chunked HTTP transfer, as soon as it
+   * receives one data chunk from the answer of the remote HTTP
+   * server.
+   *
+   * @see OrthancPluginChunkedHttpClient()
+   * @param answer The user payload, as provided by the calling plugin.
+   * @param data The content of the data chunk.
+   * @param size The size of the data chunk.
+   * @return 0 if success, or the error code if failure.
+   * @ingroup Toolbox
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginChunkedClientAnswerAddChunk) (
+    void* answer,
+    const void* data,
+    uint32_t size);
+  
+
+  /**
+   * @brief Callback to know whether the request body is entirely read during a chunked transfer 
+   *
+   * Signature of a callback function that is called by Orthanc acting
+   * as a HTTP client during a chunked HTTP transfer, while reading
+   * the body of a POST or PUT request. The plugin must answer "1" as
+   * soon as the body is entirely read: The "request" data structure
+   * must act as an iterator.
+   *
+   * @see OrthancPluginChunkedHttpClient()
+   * @param request The user payload, as provided by the calling plugin.
+   * @return "1" if the body is over, or "0" if there is still data to be read.
+   * @ingroup Toolbox
+   **/
+  typedef uint8_t (*OrthancPluginChunkedClientRequestIsDone) (void* request);
+
+
+  /**
+   * @brief Callback to advance in the request body during a chunked transfer 
+   *
+   * Signature of a callback function that is called by Orthanc acting
+   * as a HTTP client during a chunked HTTP transfer, while reading
+   * the body of a POST or PUT request. This function asks the plugin
+   * to advance to the next chunk of data of the request body: The
+   * "request" data structure must act as an iterator.
+   *
+   * @see OrthancPluginChunkedHttpClient()
+   * @param request The user payload, as provided by the calling plugin.
+   * @return 0 if success, or the error code if failure.
+   * @ingroup Toolbox
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginChunkedClientRequestNext) (void* request);
+
+
+  /**
+   * @brief Callback to read the current chunk of the request body during a chunked transfer 
+   *
+   * Signature of a callback function that is called by Orthanc acting
+   * as a HTTP client during a chunked HTTP transfer, while reading
+   * the body of a POST or PUT request. The plugin must provide the
+   * content of the current chunk of data of the request body.
+   *
+   * @see OrthancPluginChunkedHttpClient()
+   * @param request The user payload, as provided by the calling plugin.
+   * @return The content of the current request chunk.
+   * @ingroup Toolbox
+   **/
+  typedef const void* (*OrthancPluginChunkedClientRequestGetChunkData) (void* request);
+
+
+  /**
+   * @brief Callback to read the size of the current request chunk during a chunked transfer 
+   *
+   * Signature of a callback function that is called by Orthanc acting
+   * as a HTTP client during a chunked HTTP transfer, while reading
+   * the body of a POST or PUT request. The plugin must provide the
+   * size of the current chunk of data of the request body.
+   *
+   * @see OrthancPluginChunkedHttpClient()
+   * @param request The user payload, as provided by the calling plugin.
+   * @return The size of the current request chunk.
+   * @ingroup Toolbox
+   **/
+  typedef uint32_t (*OrthancPluginChunkedClientRequestGetChunkSize) (void* request);
+
+  
+  typedef struct
+  {
+    void*                                          answer;
+    OrthancPluginChunkedClientAnswerAddChunk       answerAddChunk;
+    OrthancPluginChunkedClientAnswerAddHeader      answerAddHeader;
+    uint16_t*                                      httpStatus;
+    OrthancPluginHttpMethod                        method;
+    const char*                                    url;
+    uint32_t                                       headersCount;
+    const char* const*                             headersKeys;
+    const char* const*                             headersValues;
+    void*                                          request;
+    OrthancPluginChunkedClientRequestIsDone        requestIsDone;
+    OrthancPluginChunkedClientRequestGetChunkData  requestChunkData;
+    OrthancPluginChunkedClientRequestGetChunkSize  requestChunkSize;
+    OrthancPluginChunkedClientRequestNext          requestNext;
+    const char*                                    username;
+    const char*                                    password;
+    uint32_t                                       timeout;
+    const char*                                    certificateFile;
+    const char*                                    certificateKeyFile;
+    const char*                                    certificateKeyPassword;
+    uint8_t                                        pkcs11;
+  } _OrthancPluginChunkedHttpClient;
+
+  
+  /**
+   * @brief Issue a HTTP call, using chunked HTTP transfers.
+   * 
+   * Make a HTTP call to the given URL using chunked HTTP
+   * transfers. The request body is provided as an iterator over data
+   * chunks. The answer is provided as a sequence of function calls
+   * with the individual HTTP headers and answer chunks.
+   * 
+   * Contrarily to OrthancPluginHttpClient() that entirely stores the
+   * request body and the answer body in memory buffers, this function
+   * uses chunked HTTP transfers. This results in a lower memory
+   * consumption. Pay attention to the fact that Orthanc servers with
+   * version <= 1.5.6 do not support chunked transfers: You must use
+   * OrthancPluginHttpClient() if contacting such older servers.
+   *
+   * The HTTP request will be done accordingly to the global
+   * configuration of Orthanc (in particular, the options "HttpProxy",
+   * "HttpTimeout", "HttpsVerifyPeers", "HttpsCACertificates", and
+   * "Pkcs11" will be taken into account).
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param answer The user payload for the answer body. It will be provided to the callbacks for the answer.
+   * @param answerAddChunk Callback function to report a data chunk from the answer body.
+   * @param answerAddHeader Callback function to report an HTTP header sent by the remote server.
+   * @param httpStatus The HTTP status after the execution of the request (out argument).
+   * @param method HTTP method to be used.
+   * @param url The URL of interest.
+   * @param headersCount The number of HTTP headers.
+   * @param headersKeys Array containing the keys of the HTTP headers (can be <tt>NULL</tt> if no header).
+   * @param headersValues Array containing the values of the HTTP headers (can be <tt>NULL</tt> if no header).
+   * @param request The user payload containing the request body, and acting as an iterator.
+   * It will be provided to the callbacks for the request.
+   * @param requestIsDone Callback function to tell whether the request body is entirely read.
+   * @param requestChunkData Callback function to get the content of the current data chunk of the request body.
+   * @param requestChunkSize Callback function to get the size of the current data chunk of the request body.
+   * @param requestNext Callback function to advance to the next data chunk of the request body.
+   * @param username The username (can be <tt>NULL</tt> if no password protection).
+   * @param password The password (can be <tt>NULL</tt> if no password protection).
+   * @param timeout Timeout in seconds (0 for default timeout).
+   * @param certificateFile Path to the client certificate for HTTPS, in PEM format
+   * (can be <tt>NULL</tt> if no client certificate or if not using HTTPS).
+   * @param certificateKeyFile Path to the key of the client certificate for HTTPS, in PEM format
+   * (can be <tt>NULL</tt> if no client certificate or if not using HTTPS).
+   * @param certificateKeyPassword Password to unlock the key of the client certificate 
+   * (can be <tt>NULL</tt> if no client certificate or if not using HTTPS).
+   * @param pkcs11 Enable PKCS#11 client authentication for hardware security modules and smart cards.
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginHttpClient()
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginChunkedHttpClient(
+    OrthancPluginContext*                          context,
+    void*                                          answer,
+    OrthancPluginChunkedClientAnswerAddChunk       answerAddChunk,
+    OrthancPluginChunkedClientAnswerAddHeader      answerAddHeader,
+    uint16_t*                                      httpStatus,
+    OrthancPluginHttpMethod                        method,
+    const char*                                    url,
+    uint32_t                                       headersCount,
+    const char* const*                             headersKeys,
+    const char* const*                             headersValues,
+    void*                                          request,
+    OrthancPluginChunkedClientRequestIsDone        requestIsDone,
+    OrthancPluginChunkedClientRequestGetChunkData  requestChunkData,
+    OrthancPluginChunkedClientRequestGetChunkSize  requestChunkSize,
+    OrthancPluginChunkedClientRequestNext          requestNext,
+    const char*                                    username,
+    const char*                                    password,
+    uint32_t                                       timeout,
+    const char*                                    certificateFile,
+    const char*                                    certificateKeyFile,
+    const char*                                    certificateKeyPassword,
+    uint8_t                                        pkcs11)
+  {
+    _OrthancPluginChunkedHttpClient params;
+    memset(&params, 0, sizeof(params));
+
+    /* In common with OrthancPluginHttpClient() */
+    params.httpStatus = httpStatus;
+    params.method = method;
+    params.url = url;
+    params.headersCount = headersCount;
+    params.headersKeys = headersKeys;
+    params.headersValues = headersValues;
+    params.username = username;
+    params.password = password;
+    params.timeout = timeout;
+    params.certificateFile = certificateFile;
+    params.certificateKeyFile = certificateKeyFile;
+    params.certificateKeyPassword = certificateKeyPassword;
+    params.pkcs11 = pkcs11;
+
+    /* For chunked body/answer */
+    params.answer = answer;
+    params.answerAddChunk = answerAddChunk;
+    params.answerAddHeader = answerAddHeader;
+    params.request = request;
+    params.requestIsDone = requestIsDone;
+    params.requestChunkData = requestChunkData;
+    params.requestChunkSize = requestChunkSize;
+    params.requestNext = requestNext;
+
+    return context->InvokeService(context, _OrthancPluginService_ChunkedHttpClient, &params);
+  }
+
+
+
+  /**
+   * @brief Opaque structure that reads the content of a HTTP request body during a chunked HTTP transfer.
+   * @ingroup Callbacks
+   **/
+  typedef struct _OrthancPluginServerChunkedRequestReader_t OrthancPluginServerChunkedRequestReader;
+
+
+
+  /**
+   * @brief Callback to create a reader to handle incoming chunked HTTP transfers.
+   *
+   * Signature of a callback function that is called by Orthanc acting
+   * as a HTTP server that supports chunked HTTP transfers. This
+   * callback is only invoked if the HTTP method is POST or PUT. The
+   * callback must create an user-specific "reader" object that will
+   * be fed with the body of the incoming body.
+   * 
+   * @see OrthancPluginRegisterChunkedRestCallback()
+   * @param reader Memory location that must be filled with the newly-created reader.
+   * @param url The URI that is accessed.
+   * @param request The body of the HTTP request. Note that "body" and "bodySize" are not used.
+   * @return 0 if success, or the error code if failure.
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginServerChunkedRequestReaderFactory) (
+    OrthancPluginServerChunkedRequestReader**  reader,
+    const char*                                url,
+    const OrthancPluginHttpRequest*            request);
+
+  
+  /**
+   * @brief Callback invoked whenever a new data chunk is available during a chunked transfer.
+   *
+   * Signature of a callback function that is called by Orthanc acting
+   * as a HTTP server that supports chunked HTTP transfers. This callback
+   * is invoked as soon as a new data chunk is available for the request body.
+   * 
+   * @see OrthancPluginRegisterChunkedRestCallback()
+   * @param reader The user payload, as created by the OrthancPluginServerChunkedRequestReaderFactory() callback.
+   * @param data The content of the data chunk.
+   * @param size The size of the data chunk.
+   * @return 0 if success, or the error code if failure.
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginServerChunkedRequestReaderAddChunk) (
+    OrthancPluginServerChunkedRequestReader* reader,
+    const void*                              data,
+    uint32_t                                 size);
+    
+
+  /**
+   * @brief Callback invoked whenever the request body is entirely received.
+   *
+   * Signature of a callback function that is called by Orthanc acting
+   * as a HTTP server that supports chunked HTTP transfers. This
+   * callback is invoked as soon as the full body of the HTTP request
+   * is available. The plugin can then send its answer thanks to the
+   * provided "output" object.
+   * 
+   * @see OrthancPluginRegisterChunkedRestCallback()
+   * @param reader The user payload, as created by the OrthancPluginServerChunkedRequestReaderFactory() callback.
+   * @param output The HTTP connection to the client application.
+   * @return 0 if success, or the error code if failure.
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginServerChunkedRequestReaderExecute) (
+    OrthancPluginServerChunkedRequestReader* reader,
+    OrthancPluginRestOutput*                 output);
+    
+
+  /**
+   * @brief Callback invoked to release the resources associated with an incoming HTTP chunked transfer.
+   *
+   * Signature of a callback function that is called by Orthanc acting
+   * as a HTTP server that supports chunked HTTP transfers. This
+   * callback is invoked to release all the resources allocated by the
+   * given reader. Note that this function might be invoked even if
+   * the entire body was not read, to deal with client error or
+   * disconnection.
+   * 
+   * @see OrthancPluginRegisterChunkedRestCallback()
+   * @param reader The user payload, as created by the OrthancPluginServerChunkedRequestReaderFactory() callback.
+   **/
+  typedef void (*OrthancPluginServerChunkedRequestReaderFinalize) (
+    OrthancPluginServerChunkedRequestReader* reader);
+  
+  typedef struct
+  {
+    const char*                                      pathRegularExpression;
+    OrthancPluginRestCallback                        getHandler;
+    OrthancPluginServerChunkedRequestReaderFactory   postHandler;
+    OrthancPluginRestCallback                        deleteHandler;
+    OrthancPluginServerChunkedRequestReaderFactory   putHandler;
+    OrthancPluginServerChunkedRequestReaderAddChunk  addChunk;
+    OrthancPluginServerChunkedRequestReaderExecute   execute;
+    OrthancPluginServerChunkedRequestReaderFinalize  finalize;
+  } _OrthancPluginChunkedRestCallback;
+
+
+  /**
+   * @brief Register a REST callback to handle chunked HTTP transfers.
+   *
+   * This function registers a REST callback against a regular
+   * expression for a URI. This function must be called during the
+   * initialization of the plugin, i.e. inside the
+   * OrthancPluginInitialize() public function.
+   *
+   * Contrarily to OrthancPluginRegisterRestCallback(), the callbacks
+   * will NOT be invoked in mutual exclusion, so it is up to the
+   * plugin to implement the required locking mechanisms.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param pathRegularExpression Regular expression for the URI. May contain groups. 
+   * @param getHandler The callback function to handle REST calls using the GET HTTP method.
+   * @param postHandler The callback function to handle REST calls using the POST HTTP method.
+   * @param deleteHandler The callback function to handle REST calls using the DELETE HTTP method.
+   * @param putHandler The callback function to handle REST calls using the PUT HTTP method.
+   * @param addChunk The callback invoked when a new chunk is available for the request body of a POST or PUT call.
+   * @param execute The callback invoked once the entire body of a POST or PUT call is read.
+   * @param finalize The callback invoked to release the resources associated with a POST or PUT call.
+   * @see OrthancPluginRegisterRestCallbackNoLock()
+   *
+   * @note
+   * The regular expression is case sensitive and must follow the
+   * [Perl syntax](https://www.boost.org/doc/libs/1_67_0/libs/regex/doc/html/boost_regex/syntax/perl_syntax.html).
+   *
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterChunkedRestCallback(
+    OrthancPluginContext*                            context,
+    const char*                                      pathRegularExpression,
+    OrthancPluginRestCallback                        getHandler,
+    OrthancPluginServerChunkedRequestReaderFactory   postHandler,
+    OrthancPluginRestCallback                        deleteHandler,
+    OrthancPluginServerChunkedRequestReaderFactory   putHandler,
+    OrthancPluginServerChunkedRequestReaderAddChunk  addChunk,
+    OrthancPluginServerChunkedRequestReaderExecute   execute,
+    OrthancPluginServerChunkedRequestReaderFinalize  finalize)
+  {
+    _OrthancPluginChunkedRestCallback params;
+    params.pathRegularExpression = pathRegularExpression;
+    params.getHandler = getHandler;
+    params.postHandler = postHandler;
+    params.deleteHandler = deleteHandler;
+    params.putHandler = putHandler;
+    params.addChunk = addChunk;
+    params.execute = execute;
+    params.finalize = finalize;
+
+    context->InvokeService(context, _OrthancPluginService_RegisterChunkedRestCallback, &params);
+  }
+
+
+
+
+
+  typedef struct
+  {
+    char**       result;
+    uint16_t     group;
+    uint16_t     element;
+    const char*  privateCreator;
+  } _OrthancPluginGetTagName;
+
+  /**
+   * @brief Returns the symbolic name of a DICOM tag.
+   *
+   * This function makes a lookup to the dictionary of DICOM tags that
+   * are known to Orthanc, and returns the symbolic name of a DICOM tag.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param group The group of the tag.
+   * @param element The element of the tag.
+   * @param privateCreator For private tags, the name of the private creator (can be NULL).
+   * @return NULL in the case of an error, or a newly allocated string
+   * containing the path. This string must be freed by
+   * OrthancPluginFreeString().
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginGetTagName(
+    OrthancPluginContext*  context,
+    uint16_t               group,
+    uint16_t               element,
+    const char*            privateCreator)
+  {
+    char* result;
+
+    _OrthancPluginGetTagName params;
+    params.result = &result;
+    params.group = group;
+    params.element = element;
+    params.privateCreator = privateCreator;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetTagName, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+
+  /**
+   * @brief Callback executed by the storage commitment SCP.
+   *
+   * Signature of a factory function that creates an object to handle
+   * one incoming storage commitment request.
+   *
+   * @remark The factory receives the list of the SOP class/instance
+   * UIDs of interest to the remote storage commitment SCU. This gives
+   * the factory the possibility to start some prefetch process
+   * upfront in the background, before the handler object is actually
+   * queried about the status of these DICOM instances.
+   *
+   * @param handler Output variable where the factory puts the handler object it created.
+   * @param jobId ID of the Orthanc job that is responsible for handling 
+   * the storage commitment request. This job will successively look for the
+   * status of all the individual queried DICOM instances.
+   * @param transactionUid UID of the storage commitment transaction
+   * provided by the storage commitment SCU. It contains the value of the
+   * (0008,1195) DICOM tag.
+   * @param sopClassUids Array of the SOP class UIDs (0008,0016) that are queried by the SCU.
+   * @param sopInstanceUids Array of the SOP instance UIDs (0008,0018) that are queried by the SCU.
+   * @param countInstances Number of DICOM instances that are queried. This is the size
+   * of the `sopClassUids` and `sopInstanceUids` arrays.
+   * @param remoteAet The AET of the storage commitment SCU.
+   * @param calledAet The AET used by the SCU to contact the storage commitment SCP (i.e. Orthanc).
+   * @return 0 if success, other value if error.
+   * @ingroup DicomCallbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginStorageCommitmentFactory) (
+    void**              handler /* out */,
+    const char*         jobId,
+    const char*         transactionUid,
+    const char* const*  sopClassUids,
+    const char* const*  sopInstanceUids,
+    uint32_t            countInstances,
+    const char*         remoteAet,
+    const char*         calledAet);
+
+  
+  /**
+   * @brief Callback to free one storage commitment SCP handler.
+   * 
+   * Signature of a callback function that releases the resources
+   * allocated by the factory of the storage commitment SCP. The
+   * handler is the return value of a previous call to the
+   * OrthancPluginStorageCommitmentFactory() callback.
+   *
+   * @param handler The handler object to be destructed.
+   * @ingroup DicomCallbacks
+   **/
+  typedef void (*OrthancPluginStorageCommitmentDestructor) (void* handler);
+
+
+  /**
+   * @brief Callback to get the status of one DICOM instance in the
+   * storage commitment SCP.
+   *
+   * Signature of a callback function that is successively invoked for
+   * each DICOM instance that is queried by the remote storage
+   * commitment SCU.  The function must be tought of as a method of
+   * the handler object that was created by a previous call to the
+   * OrthancPluginStorageCommitmentFactory() callback. After each call
+   * to this method, the progress of the associated Orthanc job is
+   * updated.
+   * 
+   * @param target Output variable where to put the status for the queried instance.
+   * @param handler The handler object associated with this storage commitment request.
+   * @param sopClassUid The SOP class UID (0008,0016) of interest.
+   * @param sopInstanceUid The SOP instance UID (0008,0018) of interest.
+   * @ingroup DicomCallbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginStorageCommitmentLookup) (
+    OrthancPluginStorageCommitmentFailureReason* target,
+    void* handler,
+    const char* sopClassUid,
+    const char* sopInstanceUid);
+    
+    
+  typedef struct
+  {
+    OrthancPluginStorageCommitmentFactory     factory;
+    OrthancPluginStorageCommitmentDestructor  destructor;
+    OrthancPluginStorageCommitmentLookup      lookup;
+  } _OrthancPluginRegisterStorageCommitmentScpCallback;
+
+  /**
+   * @brief Register a callback to handle incoming requests to the storage commitment SCP.
+   *
+   * This function registers a callback to handle storage commitment SCP requests.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param factory Factory function that creates the handler object
+   * for incoming storage commitment requests.
+   * @param destructor Destructor function to destroy the handler object.
+   * @param lookup Callback function to get the status of one DICOM instance.
+   * @return 0 if success, other value if error.
+   * @ingroup DicomCallbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterStorageCommitmentScpCallback(
+    OrthancPluginContext*                     context,
+    OrthancPluginStorageCommitmentFactory     factory,
+    OrthancPluginStorageCommitmentDestructor  destructor,
+    OrthancPluginStorageCommitmentLookup      lookup)
+  {
+    _OrthancPluginRegisterStorageCommitmentScpCallback params;
+    params.factory = factory;
+    params.destructor = destructor;
+    params.lookup = lookup;
+    return context->InvokeService(context, _OrthancPluginService_RegisterStorageCommitmentScpCallback, &params);
+  }
+  
+
+
+  /**
+   * @brief Callback to filter incoming DICOM instances received by Orthanc.
+   *
+   * Signature of a callback function that is triggered whenever
+   * Orthanc receives a new DICOM instance (e.g. through REST API or
+   * DICOM protocol), and that answers whether this DICOM instance
+   * should be accepted or discarded by Orthanc.
+   *
+   * Note that the metadata information is not available
+   * (i.e. GetInstanceMetadata() should not be used on "instance").
+   *
+   * @warning Your callback function will be called synchronously with
+   * the core of Orthanc. This implies that deadlocks might emerge if
+   * you call other core primitives of Orthanc in your callback (such
+   * deadlocks are particularly visible in the presence of other plugins
+   * or Lua scripts). It is thus strongly advised to avoid any call to
+   * the REST API of Orthanc in the callback. If you have to call
+   * other primitives of Orthanc, you should make these calls in a
+   * separate thread, passing the pending events to be processed
+   * through a message queue.
+   * 
+   * @param instance The received DICOM instance.
+   * @return 0 to discard the instance, 1 to store the instance, -1 if error.
+   * @ingroup Callbacks
+   **/
+  typedef int32_t (*OrthancPluginIncomingDicomInstanceFilter) (
+    const OrthancPluginDicomInstance* instance);
+
+
+  typedef struct
+  {
+    OrthancPluginIncomingDicomInstanceFilter callback;
+  } _OrthancPluginIncomingDicomInstanceFilter;
+
+  /**
+   * @brief Register a callback to filter incoming DICOM instances.
+   *
+   * This function registers a custom callback to filter incoming
+   * DICOM instances received by Orthanc (either through the REST API
+   * or through the DICOM protocol).
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param callback The callback.
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterIncomingDicomInstanceFilter(
+    OrthancPluginContext*                     context,
+    OrthancPluginIncomingDicomInstanceFilter  callback)
+  {
+    _OrthancPluginIncomingDicomInstanceFilter params;
+    params.callback = callback;
+
+    return context->InvokeService(context, _OrthancPluginService_RegisterIncomingDicomInstanceFilter, &params);
+  }
+
+
+  /**
+   * @brief Callback to filter incoming DICOM instances received by 
+   * Orthanc through C-STORE.
+   *
+   * Signature of a callback function that is triggered whenever
+   * Orthanc receives a new DICOM instance using DICOM C-STORE, and
+   * that answers whether this DICOM instance should be accepted or
+   * discarded by Orthanc. If the instance is discarded, the callback
+   * can specify the DIMSE error code answered by the Orthanc C-STORE
+   * SCP.
+   *
+   * Note that the metadata information is not available
+   * (i.e. GetInstanceMetadata() should not be used on "instance").
+   *
+   * @warning Your callback function will be called synchronously with
+   * the core of Orthanc. This implies that deadlocks might emerge if
+   * you call other core primitives of Orthanc in your callback (such
+   * deadlocks are particularly visible in the presence of other plugins
+   * or Lua scripts). It is thus strongly advised to avoid any call to
+   * the REST API of Orthanc in the callback. If you have to call
+   * other primitives of Orthanc, you should make these calls in a
+   * separate thread, passing the pending events to be processed
+   * through a message queue.
+   *
+   * @param dimseStatus If the DICOM instance is discarded, 
+   * DIMSE status to be sent by the C-STORE SCP of Orthanc
+   * @param instance The received DICOM instance.
+   * @return 0 to discard the instance, 1 to store the instance, -1 if error.
+   * @ingroup Callbacks
+   **/
+  typedef int32_t (*OrthancPluginIncomingCStoreInstanceFilter) (
+    uint16_t* dimseStatus /* out */,
+    const OrthancPluginDicomInstance* instance);
+
+
+  typedef struct
+  {
+    OrthancPluginIncomingCStoreInstanceFilter callback;
+  } _OrthancPluginIncomingCStoreInstanceFilter;
+
+  /**
+   * @brief Register a callback to filter incoming DICOM instances
+   * received by Orthanc through C-STORE.
+   *
+   * This function registers a custom callback to filter incoming
+   * DICOM instances received by Orthanc through the DICOM protocol.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param callback The callback.
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterIncomingCStoreInstanceFilter(
+    OrthancPluginContext*                      context,
+    OrthancPluginIncomingCStoreInstanceFilter  callback)
+  {
+    _OrthancPluginIncomingCStoreInstanceFilter params;
+    params.callback = callback;
+
+    return context->InvokeService(context, _OrthancPluginService_RegisterIncomingCStoreInstanceFilter, &params);
+  }
+
+  /**
+   * @brief Callback to keep/discard/modify a DICOM instance received
+   * by Orthanc from any source (C-STORE or REST API)
+   *
+   * Signature of a callback function that is triggered whenever
+   * Orthanc receives a new DICOM instance (through DICOM protocol or
+   * REST API), and that specifies an action to be applied to this
+   * newly received instance. The instance can be kept as it is, can
+   * be modified by the plugin, or can be discarded.
+   *
+   * This callback is invoked immediately after reception, i.e. before
+   * transcoding and before filtering
+   * (cf. OrthancPluginRegisterIncomingDicomInstanceFilter()).
+   *
+   * @warning Your callback function will be called synchronously with
+   * the core of Orthanc. This implies that deadlocks might emerge if
+   * you call other core primitives of Orthanc in your callback (such
+   * deadlocks are particularly visible in the presence of other plugins
+   * or Lua scripts). It is thus strongly advised to avoid any call to
+   * the REST API of Orthanc in the callback. If you have to call
+   * other primitives of Orthanc, you should make these calls in a
+   * separate thread, passing the pending events to be processed
+   * through a message queue.
+   *
+   * @param modifiedDicomBuffer A buffer containing the modified DICOM (output).
+   * This buffer must be allocated using OrthancPluginCreateMemoryBuffer64()
+   * and will be freed by the Orthanc core.
+   * @param receivedDicomBuffer A buffer containing the received DICOM (input).
+   * @param receivedDicomBufferSize The size of the received DICOM (input).
+   * @param origin The origin of the DICOM instance (input).
+   * @return `OrthancPluginReceivedInstanceAction_KeepAsIs` to accept the instance as is,<br/>
+   *         `OrthancPluginReceivedInstanceAction_Modify` to store the modified DICOM contained in `modifiedDicomBuffer`,<br/>
+   *         `OrthancPluginReceivedInstanceAction_Discard` to tell Orthanc to discard the instance.
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginReceivedInstanceAction (*OrthancPluginReceivedInstanceCallback) (
+    OrthancPluginMemoryBuffer64* modifiedDicomBuffer,
+    const void* receivedDicomBuffer,
+    uint64_t receivedDicomBufferSize,
+    OrthancPluginInstanceOrigin origin);
+
+
+  typedef struct
+  {
+    OrthancPluginReceivedInstanceCallback callback;
+  } _OrthancPluginReceivedInstanceCallback;
+
+  /**
+   * @brief Register a callback to keep/discard/modify a DICOM instance received
+   * by Orthanc from any source (C-STORE or REST API)
+   *
+   * This function registers a custom callback to keep/discard/modify
+   * incoming DICOM instances received by Orthanc from any source
+   * (C-STORE or REST API).
+   * 
+   * @warning Contrarily to
+   * OrthancPluginRegisterIncomingCStoreInstanceFilter() and
+   * OrthancPluginRegisterIncomingDicomInstanceFilter() that can be
+   * called by multiple plugins,
+   * OrthancPluginRegisterReceivedInstanceCallback() can only be used
+   * by one single plugin.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param callback The callback.
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterReceivedInstanceCallback(
+    OrthancPluginContext*                     context,
+    OrthancPluginReceivedInstanceCallback     callback)
+  {
+    _OrthancPluginReceivedInstanceCallback params;
+    params.callback = callback;
+
+    return context->InvokeService(context, _OrthancPluginService_RegisterReceivedInstanceCallback, &params);
+  }
+
+  /**
+   * @brief Get the transfer syntax of a DICOM file.
+   *
+   * This function returns a pointer to a newly created string that
+   * contains the transfer syntax UID of the DICOM instance. The empty
+   * string might be returned if this information is unknown.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @return The NULL value in case of error, or a string containing the
+   * transfer syntax UID. This string must be freed by OrthancPluginFreeString().
+   * @ingroup DicomInstance
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceTransferSyntaxUid(
+    OrthancPluginContext*              context,
+    const OrthancPluginDicomInstance*  instance)
+  {
+    char* result;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultStringToFree = &result;
+    params.instance = instance;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceTransferSyntaxUid, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Check whether the DICOM file has pixel data.
+   *
+   * This function returns a Boolean value indicating whether the
+   * DICOM instance contains the pixel data (7FE0,0010) tag.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @return "1" if the DICOM instance contains pixel data, or "0" if
+   * the tag is missing, or "-1" in the case of an error.
+   * @ingroup DicomInstance
+   **/
+  ORTHANC_PLUGIN_INLINE int32_t OrthancPluginHasInstancePixelData(
+    OrthancPluginContext*             context,
+    const OrthancPluginDicomInstance* instance)
+  {
+    int64_t hasPixelData;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultInt64 = &hasPixelData;
+    params.instance = instance;
+
+    if (context->InvokeService(context, _OrthancPluginService_HasInstancePixelData, &params) != OrthancPluginErrorCode_Success ||
+        hasPixelData < 0 ||
+        hasPixelData > 1)
+    {
+      /* Error */
+      return -1;
+    }
+    else
+    {
+      return (hasPixelData != 0);
+    }
+  }
+
+
+
+
+
+
+  typedef struct
+  {
+    OrthancPluginDicomInstance**  target;
+    const void*                   buffer;
+    uint32_t                      size;
+    const char*                   transferSyntax;
+  } _OrthancPluginCreateDicomInstance;
+
+  /**
+   * @brief Parse a DICOM instance.
+   *
+   * This function parses a memory buffer that contains a DICOM
+   * file. The function returns a new pointer to a data structure that
+   * is managed by the Orthanc core.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param buffer The memory buffer containing the DICOM instance.
+   * @param size The size of the memory buffer.
+   * @return The newly allocated DICOM instance. It must be freed with OrthancPluginFreeDicomInstance().
+   * @ingroup DicomInstance
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginDicomInstance* OrthancPluginCreateDicomInstance(
+    OrthancPluginContext*  context,
+    const void*            buffer,
+    uint32_t               size)
+  {
+    OrthancPluginDicomInstance* target = NULL;
+
+    _OrthancPluginCreateDicomInstance params;
+    params.target = &target;
+    params.buffer = buffer;
+    params.size = size;
+
+    if (context->InvokeService(context, _OrthancPluginService_CreateDicomInstance, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+  typedef struct
+  {
+    OrthancPluginDicomInstance*   dicom;
+  } _OrthancPluginFreeDicomInstance;
+
+  /**
+   * @brief Free a DICOM instance.
+   *
+   * This function frees a DICOM instance that was parsed using
+   * OrthancPluginCreateDicomInstance().
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param dicom The DICOM instance.
+   * @ingroup DicomInstance
+   **/
+  ORTHANC_PLUGIN_INLINE void  OrthancPluginFreeDicomInstance(
+    OrthancPluginContext*        context, 
+    OrthancPluginDicomInstance*  dicom)
+  {
+    _OrthancPluginFreeDicomInstance params;
+    params.dicom = dicom;
+
+    context->InvokeService(context, _OrthancPluginService_FreeDicomInstance, &params);
+  }
+
+
+  typedef struct
+  {
+    uint32_t*                             targetUint32;
+    OrthancPluginMemoryBuffer*            targetBuffer;
+    OrthancPluginImage**                  targetImage;
+    char**                                targetStringToFree;
+    const OrthancPluginDicomInstance*     instance;
+    uint32_t                              frameIndex;
+    OrthancPluginDicomToJsonFormat        format;
+    OrthancPluginDicomToJsonFlags         flags;
+    uint32_t                              maxStringLength;
+    OrthancPluginDicomWebBinaryCallback2  dicomWebCallback;
+    void*                                 dicomWebPayload;
+  } _OrthancPluginAccessDicomInstance2;
+
+  /**
+   * @brief Get the number of frames in a DICOM instance.
+   *
+   * This function returns the number of frames that are part of a
+   * DICOM image managed by the Orthanc core.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @return The number of frames (will be zero in the case of an error).
+   * @ingroup DicomInstance
+   **/
+  ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetInstanceFramesCount(
+    OrthancPluginContext*             context,
+    const OrthancPluginDicomInstance* instance)
+  {
+    uint32_t count;
+
+    _OrthancPluginAccessDicomInstance2 params;
+    memset(&params, 0, sizeof(params));
+    params.targetUint32 = &count;
+    params.instance = instance;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceFramesCount, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return 0;
+    }
+    else
+    {
+      return count;
+    }
+  }
+
+
+  /**
+   * @brief Get the raw content of a frame in a DICOM instance.
+   *
+   * This function returns a memory buffer containing the raw content
+   * of a frame in a DICOM instance that is managed by the Orthanc
+   * core. This is notably useful for compressed transfer syntaxes, as
+   * it gives access to the embedded files (such as JPEG, JPEG-LS or
+   * JPEG2k). The Orthanc core transparently reassembles the fragments
+   * to extract the raw frame.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param instance The instance of interest.
+   * @param frameIndex The index of the frame of interest.
+   * @return 0 if success, or the error code if failure.
+   * @ingroup DicomInstance
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginGetInstanceRawFrame(
+    OrthancPluginContext*             context,
+    OrthancPluginMemoryBuffer*        target,
+    const OrthancPluginDicomInstance* instance,
+    uint32_t                          frameIndex)
+  {
+    _OrthancPluginAccessDicomInstance2 params;
+    memset(&params, 0, sizeof(params));
+    params.targetBuffer = target;
+    params.instance = instance;
+    params.frameIndex = frameIndex;
+
+    return context->InvokeService(context, _OrthancPluginService_GetInstanceRawFrame, &params);
+  }
+
+
+  /**
+   * @brief Decode one frame from a DICOM instance.
+   *
+   * This function decodes one frame of a DICOM image that is managed
+   * by the Orthanc core.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @param frameIndex The index of the frame of interest.
+   * @return The uncompressed image. It must be freed with OrthancPluginFreeImage().
+   * @ingroup DicomInstance
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginGetInstanceDecodedFrame(
+    OrthancPluginContext*             context,
+    const OrthancPluginDicomInstance* instance,
+    uint32_t                          frameIndex)
+  {
+    OrthancPluginImage* target = NULL;
+
+    _OrthancPluginAccessDicomInstance2 params;
+    memset(&params, 0, sizeof(params));
+    params.targetImage = &target;
+    params.instance = instance;
+    params.frameIndex = frameIndex;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceDecodedFrame, &params) != OrthancPluginErrorCode_Success)
+    {
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+  
+  /**
+   * @brief Parse and transcode a DICOM instance.
+   *
+   * This function parses a memory buffer that contains a DICOM file,
+   * then transcodes it to the given transfer syntax. The function
+   * returns a new pointer to a data structure that is managed by the
+   * Orthanc core.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param buffer The memory buffer containing the DICOM instance.
+   * @param size The size of the memory buffer.
+   * @param transferSyntax The transfer syntax UID for the transcoding.
+   * @return The newly allocated DICOM instance. It must be freed with OrthancPluginFreeDicomInstance().
+   * @ingroup DicomInstance
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginDicomInstance* OrthancPluginTranscodeDicomInstance(
+    OrthancPluginContext*  context,
+    const void*            buffer,
+    uint32_t               size,
+    const char*            transferSyntax)
+  {
+    OrthancPluginDicomInstance* target = NULL;
+
+    _OrthancPluginCreateDicomInstance params;
+    params.target = &target;
+    params.buffer = buffer;
+    params.size = size;
+    params.transferSyntax = transferSyntax;
+
+    if (context->InvokeService(context, _OrthancPluginService_TranscodeDicomInstance, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+  /**
+   * @brief Writes a DICOM instance to a memory buffer.
+   *
+   * This function returns a memory buffer containing the
+   * serialization of a DICOM instance that is managed by the Orthanc
+   * core.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param instance The instance of interest.
+   * @return 0 if success, or the error code if failure.
+   * @ingroup DicomInstance
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginSerializeDicomInstance(
+    OrthancPluginContext*             context,
+    OrthancPluginMemoryBuffer*        target,
+    const OrthancPluginDicomInstance* instance)
+  {
+    _OrthancPluginAccessDicomInstance2 params;
+    memset(&params, 0, sizeof(params));
+    params.targetBuffer = target;
+    params.instance = instance;
+
+    return context->InvokeService(context, _OrthancPluginService_SerializeDicomInstance, &params);
+  }
+  
+
+  /**
+   * @brief Format a DICOM memory buffer as a JSON string.
+   *
+   * This function takes as DICOM instance managed by the Orthanc
+   * core, and outputs a JSON string representing the tags of this
+   * DICOM file.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The DICOM instance of interest.
+   * @param format The output format.
+   * @param flags Flags governing the output.
+   * @param maxStringLength The maximum length of a field. Too long fields will
+   * be output as "null". The 0 value means no maximum length.
+   * @return The NULL value if the case of an error, or the JSON
+   * string. This string must be freed by OrthancPluginFreeString().
+   * @ingroup DicomInstance
+   * @see OrthancPluginDicomBufferToJson
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceAdvancedJson(
+    OrthancPluginContext*              context,
+    const OrthancPluginDicomInstance*  instance,
+    OrthancPluginDicomToJsonFormat     format,
+    OrthancPluginDicomToJsonFlags      flags, 
+    uint32_t                           maxStringLength)
+  {
+    char* result = NULL;
+
+    _OrthancPluginAccessDicomInstance2 params;
+    memset(&params, 0, sizeof(params));
+    params.targetStringToFree = &result;
+    params.instance = instance;
+    params.format = format;
+    params.flags = flags;
+    params.maxStringLength = maxStringLength;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceAdvancedJson, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Convert a DICOM instance to DICOMweb JSON.
+   *
+   * This function converts a DICOM instance that is managed by the
+   * Orthanc core, into its DICOMweb JSON representation.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The DICOM instance of interest.
+   * @param callback Callback to set the value of the binary tags.
+   * @param payload User payload.
+   * @return The NULL value in case of error, or the JSON document. This string must
+   * be freed by OrthancPluginFreeString().
+   * @ingroup DicomInstance
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceDicomWebJson(
+    OrthancPluginContext*                 context,
+    const OrthancPluginDicomInstance*     instance,
+    OrthancPluginDicomWebBinaryCallback2  callback,
+    void*                                 payload)
+  {
+    char* target = NULL;
+    
+    _OrthancPluginAccessDicomInstance2 params;
+    params.targetStringToFree = &target;
+    params.instance = instance;
+    params.dicomWebCallback = callback;
+    params.dicomWebPayload = payload;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceDicomWebJson, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+  
+
+  /**
+   * @brief Convert a DICOM instance to DICOMweb XML.
+   *
+   * This function converts a DICOM instance that is managed by the
+   * Orthanc core, into its DICOMweb XML representation.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The DICOM instance of interest.
+   * @param callback Callback to set the value of the binary tags.
+   * @param payload User payload.
+   * @return The NULL value in case of error, or the XML document. This string must
+   * be freed by OrthancPluginFreeString().
+   * @ingroup DicomInstance
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceDicomWebXml(
+    OrthancPluginContext*                 context,
+    const OrthancPluginDicomInstance*     instance,
+    OrthancPluginDicomWebBinaryCallback2  callback,
+    void*                                 payload)
+  {
+    char* target = NULL;
+    
+    _OrthancPluginAccessDicomInstance2 params;
+    params.targetStringToFree = &target;
+    params.instance = instance;
+    params.dicomWebCallback = callback;
+    params.dicomWebPayload = payload;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceDicomWebXml, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+
+
+  /**
+   * @brief Signature of a callback function to transcode a DICOM instance.
+   * @param transcoded Target memory buffer. It must be allocated by the
+   * plugin using OrthancPluginCreateMemoryBuffer().
+   * @param buffer Memory buffer containing the source DICOM instance.
+   * @param size Size of the source memory buffer.
+   * @param allowedSyntaxes A C array of possible transfer syntaxes UIDs for the
+   * result of the transcoding. The plugin must choose by itself the 
+   * transfer syntax that will be used for the resulting DICOM image.
+   * @param countSyntaxes The number of transfer syntaxes that are contained
+   * in the "allowedSyntaxes" array.
+   * @param allowNewSopInstanceUid Whether the transcoding plugin can select
+   * a transfer syntax that will change the SOP instance UID (or, in other 
+   * terms, whether the plugin can transcode using lossy compression).
+   * @return 0 if success (i.e. image successfully transcoded and stored into
+   * "transcoded"), or the error code if failure.
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginTranscoderCallback) (
+    OrthancPluginMemoryBuffer* transcoded /* out */,
+    const void*                buffer,
+    uint64_t                   size,
+    const char* const*         allowedSyntaxes,
+    uint32_t                   countSyntaxes,
+    uint8_t                    allowNewSopInstanceUid);
+
+
+  typedef struct
+  {
+    OrthancPluginTranscoderCallback callback;
+  } _OrthancPluginTranscoderCallback;
+
+  /**
+   * @brief Register a callback to handle the transcoding of DICOM images.
+   *
+   * This function registers a custom callback to transcode DICOM
+   * images, extending the built-in transcoder of Orthanc that uses
+   * DCMTK. The exact behavior is affected by the configuration option
+   * "BuiltinDecoderTranscoderOrder" of Orthanc.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param callback The callback.
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterTranscoderCallback(
+    OrthancPluginContext*            context,
+    OrthancPluginTranscoderCallback  callback)
+  {
+    _OrthancPluginTranscoderCallback params;
+    params.callback = callback;
+
+    return context->InvokeService(context, _OrthancPluginService_RegisterTranscoderCallback, &params);
+  }
+  
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*  target;
+    uint32_t                    size;
+  } _OrthancPluginCreateMemoryBuffer;
+
+  /**
+   * @brief Create a 32-bit memory buffer.
+   *
+   * This function creates a memory buffer that is managed by the
+   * Orthanc core. The main use case of this function is for plugins
+   * that act as DICOM transcoders.
+   * 
+   * Your plugin should never call "free()" on the resulting memory
+   * buffer, as the C library that is used by the plugin is in general
+   * not the same as the one used by the Orthanc core.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param size Size of the memory buffer to be created.
+   * @return 0 if success, or the error code if failure.
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCreateMemoryBuffer(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    uint32_t                    size)
+  {
+    _OrthancPluginCreateMemoryBuffer params;
+    params.target = target;
+    params.size = size;
+
+    return context->InvokeService(context, _OrthancPluginService_CreateMemoryBuffer, &params);
+  }
+  
+
+  /**
+   * @brief Generate a token to grant full access to the REST API of Orthanc.
+   *
+   * This function generates a token that can be set in the HTTP
+   * header "Authorization" so as to grant full access to the REST API
+   * of Orthanc using an external HTTP client. Using this function
+   * avoids the need of adding a separate user in the
+   * "RegisteredUsers" configuration of Orthanc, which eases
+   * deployments.
+   *
+   * This feature is notably useful in multiprocess scenarios, where a
+   * subprocess created by a plugin has no access to the
+   * "OrthancPluginContext", and thus cannot call
+   * "OrthancPluginRestApi[Get|Post|Put|Delete]()".
+   *
+   * This situation is frequently encountered in Python plugins, where
+   * the "multiprocessing" package can be used to bypass the Global
+   * Interpreter Lock (GIL) and thus to improve performance and
+   * concurrency.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return The authorization token, or NULL value in the case of an error.
+   * This string must be freed by OrthancPluginFreeString().
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginGenerateRestApiAuthorizationToken(
+    OrthancPluginContext*  context)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = NULL;
+
+    if (context->InvokeService(context, _OrthancPluginService_GenerateRestApiAuthorizationToken,
+                               &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer64*  target;
+    uint64_t                      size;
+  } _OrthancPluginCreateMemoryBuffer64;
+
+  /**
+   * @brief Create a 64-bit memory buffer.
+   *
+   * This function creates a 64-bit memory buffer that is managed by
+   * the Orthanc core. The main use case of this function is for
+   * plugins that read files from the storage area.
+   * 
+   * Your plugin should never call "free()" on the resulting memory
+   * buffer, as the C library that is used by the plugin is in general
+   * not the same as the one used by the Orthanc core.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param size Size of the memory buffer to be created.
+   * @return 0 if success, or the error code if failure.
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCreateMemoryBuffer64(
+    OrthancPluginContext*         context,
+    OrthancPluginMemoryBuffer64*  target,
+    uint64_t                      size)
+  {
+    _OrthancPluginCreateMemoryBuffer64 params;
+    params.target = target;
+    params.size = size;
+
+    return context->InvokeService(context, _OrthancPluginService_CreateMemoryBuffer64, &params);
+  }
+  
+
+  typedef struct
+  {
+    OrthancPluginStorageCreate    create;
+    OrthancPluginStorageReadWhole readWhole;
+    OrthancPluginStorageReadRange readRange;
+    OrthancPluginStorageRemove    remove;
+  } _OrthancPluginRegisterStorageArea2;
+
+  /**
+   * @brief Register a custom storage area, with support for range request.
+   *
+   * This function registers a custom storage area, to replace the
+   * built-in way Orthanc stores its files on the filesystem. This
+   * function must be called during the initialization of the plugin,
+   * i.e. inside the OrthancPluginInitialize() public function.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param create The callback function to store a file on the custom storage area.
+   * @param readWhole The callback function to read a whole file from the custom storage area.
+   * @param readRange The callback function to read some range of a file from the custom storage area.
+   * If this feature is not supported by the plugin, this value can be set to NULL.
+   * @param remove The callback function to remove a file from the custom storage area.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterStorageArea2(
+    OrthancPluginContext*          context,
+    OrthancPluginStorageCreate     create,
+    OrthancPluginStorageReadWhole  readWhole,
+    OrthancPluginStorageReadRange  readRange,
+    OrthancPluginStorageRemove     remove)
+  {
+    _OrthancPluginRegisterStorageArea2 params;
+    params.create = create;
+    params.readWhole = readWhole;
+    params.readRange = readRange;
+    params.remove = remove;
+    context->InvokeService(context, _OrthancPluginService_RegisterStorageArea2, &params);
+  }
+
+
+
+  typedef struct
+  {
+    _OrthancPluginCreateDicom  createDicom;
+    const char*                privateCreator;
+  } _OrthancPluginCreateDicom2;
+
+  /**
+   * @brief Create a DICOM instance from a JSON string and an image, with a private creator.
+   *
+   * This function takes as input a string containing a JSON file
+   * describing the content of a DICOM instance. As an output, it
+   * writes the corresponding DICOM instance to a newly allocated
+   * memory buffer. Additionally, an image to be encoded within the
+   * DICOM instance can also be provided.
+   *
+   * Contrarily to the function OrthancPluginCreateDicom(), this
+   * function can be explicitly provided with a private creator.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param json The input JSON file.
+   * @param pixelData The image. Can be NULL, if the pixel data is encoded inside the JSON with the data URI scheme.
+   * @param flags Flags governing the output.
+   * @param privateCreator The private creator to be used for the private DICOM tags.
+   * Check out the global configuration option "Dictionary" of Orthanc.
+   * @return 0 if success, other value if error.
+   * @ingroup Toolbox
+   * @see OrthancPluginCreateDicom
+   * @see OrthancPluginDicomBufferToJson
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCreateDicom2(
+    OrthancPluginContext*          context,
+    OrthancPluginMemoryBuffer*     target,
+    const char*                    json,
+    const OrthancPluginImage*      pixelData,
+    OrthancPluginCreateDicomFlags  flags,
+    const char*                    privateCreator)
+  {
+    _OrthancPluginCreateDicom2 params;
+    params.createDicom.target = target;
+    params.createDicom.json = json;
+    params.createDicom.pixelData = pixelData;
+    params.createDicom.flags = flags;
+    params.privateCreator = privateCreator;
+
+    return context->InvokeService(context, _OrthancPluginService_CreateDicom2, &params);
+  }
+
+
+
+
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*  answerBody;
+    OrthancPluginMemoryBuffer*  answerHeaders;
+    uint16_t*                   httpStatus;
+    OrthancPluginHttpMethod     method;
+    const char*                 uri;
+    uint32_t                    headersCount;
+    const char* const*          headersKeys;
+    const char* const*          headersValues;
+    const void*                 body;
+    uint32_t                    bodySize;
+    uint8_t                     afterPlugins;
+  } _OrthancPluginCallRestApi;
+
+  /**
+   * @brief Call the REST API of Orthanc with full flexibility.
+   * 
+   * Make a call to the given URI in the REST API of Orthanc. The
+   * result to the query is stored into a newly allocated memory
+   * buffer. This function is always granted full access to the REST
+   * API (no credentials, nor security token is needed).
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param answerBody The target memory buffer (out argument).
+   *        It must be freed with OrthancPluginFreeMemoryBuffer().
+   *        The value of this argument is ignored if the HTTP method is DELETE.
+   * @param answerHeaders The target memory buffer for the HTTP headers in the answer (out argument). 
+   *        The answer headers are formatted as a JSON object (associative array).
+   *        The buffer must be freed with OrthancPluginFreeMemoryBuffer().
+   *        This argument can be set to NULL if the plugin has no interest in the answer HTTP headers.
+   * @param httpStatus The HTTP status after the execution of the request (out argument).
+   * @param method HTTP method to be used.
+   * @param uri The URI of interest.
+   * @param headersCount The number of HTTP headers.
+   * @param headersKeys Array containing the keys of the HTTP headers (can be <tt>NULL</tt> if no header).
+   * @param headersValues Array containing the values of the HTTP headers (can be <tt>NULL</tt> if no header).
+   * @param body The HTTP body for a POST or PUT request.
+   * @param bodySize The size of the body.
+   * @param afterPlugins If 0, the built-in API of Orthanc is used.
+   * If 1, the API is tainted by the plugins.
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginRestApiGet2, OrthancPluginRestApiPost, OrthancPluginRestApiPut, OrthancPluginRestApiDelete
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginCallRestApi(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  answerBody,
+    OrthancPluginMemoryBuffer*  answerHeaders,
+    uint16_t*                   httpStatus,
+    OrthancPluginHttpMethod     method,
+    const char*                 uri,
+    uint32_t                    headersCount,
+    const char* const*          headersKeys,
+    const char* const*          headersValues,
+    const void*                 body,
+    uint32_t                    bodySize,
+    uint8_t                     afterPlugins)
+  {
+    _OrthancPluginCallRestApi params;
+    memset(&params, 0, sizeof(params));
+
+    params.answerBody = answerBody;
+    params.answerHeaders = answerHeaders;
+    params.httpStatus = httpStatus;
+    params.method = method;
+    params.uri = uri;
+    params.headersCount = headersCount;
+    params.headersKeys = headersKeys;
+    params.headersValues = headersValues;
+    params.body = body;
+    params.bodySize = bodySize;
+    params.afterPlugins = afterPlugins;
+
+    return context->InvokeService(context, _OrthancPluginService_CallRestApi, &params);
+  }
+
+
+
+  /**
+   * @brief Opaque structure that represents a WebDAV collection.
+   * @ingroup Callbacks
+   **/
+  typedef struct _OrthancPluginWebDavCollection_t OrthancPluginWebDavCollection;
+
+
+  /**
+   * @brief Declare a file while returning the content of a folder.
+   *
+   * This function declares a file while returning the content of a
+   * WebDAV folder.
+   *
+   * @param collection Context of the collection.
+   * @param name Base name of the file.
+   * @param dateTime The date and time of creation of the file.
+   * Check out the documentation of OrthancPluginWebDavRetrieveFile() for more information.
+   * @param size Size of the file.
+   * @param mimeType The MIME type of the file. If empty or set to `NULL`,
+   * Orthanc will do a best guess depending on the file extension.
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginWebDavAddFile) (
+    OrthancPluginWebDavCollection*  collection,
+    const char*                     name,
+    uint64_t                        size,
+    const char*                     mimeType,
+    const char*                     dateTime);
+
+  
+  /**
+   * @brief Declare a subfolder while returning the content of a folder.
+   *
+   * This function declares a subfolder while returning the content of a
+   * WebDAV folder.
+   *
+   * @param collection Context of the collection.
+   * @param name Base name of the subfolder.
+   * @param dateTime The date and time of creation of the subfolder.
+   * Check out the documentation of OrthancPluginWebDavRetrieveFile() for more information.
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginWebDavAddFolder) (
+    OrthancPluginWebDavCollection*  collection,
+    const char*                     name,
+    const char*                     dateTime);
+
+
+  /**
+   * @brief Retrieve the content of a file.
+   *
+   * This function is used to forward the content of a file from a
+   * WebDAV collection, to the core of Orthanc.
+   *
+   * @param collection Context of the collection.
+   * @param data Content of the file.
+   * @param size Size of the file.
+   * @param mimeType The MIME type of the file. If empty or set to `NULL`,
+   * Orthanc will do a best guess depending on the file extension.
+   * @param dateTime The date and time of creation of the file.
+   * It must be formatted as an ISO string of form
+   * `YYYYMMDDTHHMMSS,fffffffff` where T is the date-time
+   * separator. It must be expressed in UTC (it is the responsibility
+   * of the plugin to do the possible timezone
+   * conversions). Internally, this string will be parsed using
+   * `boost::posix_time::from_iso_string()`.
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginWebDavRetrieveFile) (
+    OrthancPluginWebDavCollection*  collection,
+    const void*                     data,
+    uint64_t                        size,
+    const char*                     mimeType,
+    const char*                     dateTime);
+
+  
+  /**
+   * @brief Callback for testing the existence of a folder.
+   *
+   * Signature of a callback function that tests whether the given
+   * path in the WebDAV collection exists and corresponds to a folder.
+   *
+   * @param isExisting Pointer to a Boolean that must be set to `1` if the folder exists, or `0` otherwise.
+   * @param pathSize Number of levels in the path.
+   * @param pathItems Items making the path.
+   * @param payload The user payload.
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginWebDavIsExistingFolderCallback) (
+    uint8_t*                        isExisting, /* out */
+    uint32_t                        pathSize,
+    const char* const*              pathItems,
+    void*                           payload);
+
+  
+  /**
+   * @brief Callback for listing the content of a folder.
+   *
+   * Signature of a callback function that lists the content of a
+   * folder in the WebDAV collection. The callback must call the
+   * provided `addFile()` and `addFolder()` functions to emit the
+   * content of the folder.
+   *
+   * @param isExisting Pointer to a Boolean that must be set to `1` if the folder exists, or `0` otherwise.
+   * @param collection Context to be provided to `addFile()` and `addFolder()` functions.
+   * @param addFile Function to add a file to the list.
+   * @param addFolder Function to add a folder to the list.
+   * @param pathSize Number of levels in the path.
+   * @param pathItems Items making the path.
+   * @param payload The user payload.
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginWebDavListFolderCallback) (
+    uint8_t*                        isExisting, /* out */
+    OrthancPluginWebDavCollection*  collection,
+    OrthancPluginWebDavAddFile      addFile,
+    OrthancPluginWebDavAddFolder    addFolder,
+    uint32_t                        pathSize,
+    const char* const*              pathItems,
+    void*                           payload);
+
+  
+  /**
+   * @brief Callback for retrieving the content of a file.
+   *
+   * Signature of a callback function that retrieves the content of a
+   * file in the WebDAV collection. The callback must call the
+   * provided `retrieveFile()` function to emit the actual content of
+   * the file.
+   *
+   * @param collection Context to be provided to `retrieveFile()` function.
+   * @param retrieveFile Function to return the content of the file.
+   * @param pathSize Number of levels in the path.
+   * @param pathItems Items making the path.
+   * @param payload The user payload.
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginWebDavRetrieveFileCallback) (
+    OrthancPluginWebDavCollection*  collection,
+    OrthancPluginWebDavRetrieveFile retrieveFile,
+    uint32_t                        pathSize,
+    const char* const*              pathItems,
+    void*                           payload);
+
+  
+  /**
+   * @brief Callback to store a file.
+   *
+   * Signature of a callback function that stores a file into the
+   * WebDAV collection.
+   *
+   * @param isReadOnly Pointer to a Boolean that must be set to `1` if the collection is read-only, or `0` otherwise.
+   * @param pathSize Number of levels in the path.
+   * @param pathItems Items making the path.
+   * @param data Content of the file to be stored.
+   * @param size Size of the file to be stored.
+   * @param payload The user payload.
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginWebDavStoreFileCallback) (
+    uint8_t*                        isReadOnly, /* out */
+    uint32_t                        pathSize,
+    const char* const*              pathItems,
+    const void*                     data,
+    uint64_t                        size,
+    void*                           payload);
+
+  
+  /**
+   * @brief Callback to create a folder.
+   *
+   * Signature of a callback function that creates a folder in the
+   * WebDAV collection.
+   *
+   * @param isReadOnly Pointer to a Boolean that must be set to `1` if the collection is read-only, or `0` otherwise.
+   * @param pathSize Number of levels in the path.
+   * @param pathItems Items making the path.
+   * @param payload The user payload.
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginWebDavCreateFolderCallback) (
+    uint8_t*                        isReadOnly, /* out */
+    uint32_t                        pathSize,
+    const char* const*              pathItems,
+    void*                           payload);
+
+  
+  /**
+   * @brief Callback to remove a file or a folder.
+   *
+   * Signature of a callback function that removes a file or a folder
+   * from the WebDAV collection.
+   *
+   * @param isReadOnly Pointer to a Boolean that must be set to `1` if the collection is read-only, or `0` otherwise.
+   * @param pathSize Number of levels in the path.
+   * @param pathItems Items making the path.
+   * @param payload The user payload.
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginWebDavDeleteItemCallback) (
+    uint8_t*                        isReadOnly, /* out */
+    uint32_t                        pathSize,
+    const char* const*              pathItems,
+    void*                           payload);
+
+  
+  typedef struct
+  {
+    const char*                                  uri;
+    OrthancPluginWebDavIsExistingFolderCallback  isExistingFolder;
+    OrthancPluginWebDavListFolderCallback        listFolder;
+    OrthancPluginWebDavRetrieveFileCallback      retrieveFile;
+    OrthancPluginWebDavStoreFileCallback         storeFile;
+    OrthancPluginWebDavCreateFolderCallback      createFolder;
+    OrthancPluginWebDavDeleteItemCallback        deleteItem;
+    void*                                        payload;
+  } _OrthancPluginRegisterWebDavCollection;
+
+  /**
+   * @brief Register a WebDAV virtual filesystem.
+   *
+   * This function maps a WebDAV collection onto the given URI in the
+   * REST API of Orthanc. This function must be called during the
+   * initialization of the plugin, i.e. inside the
+   * OrthancPluginInitialize() public function.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param uri URI where to map the WebDAV collection (must start with a `/` character).
+   * @param isExistingFolder Callback method to test for the existence of a folder.
+   * @param listFolder Callback method to list the content of a folder.
+   * @param retrieveFile Callback method to retrieve the content of a file.
+   * @param storeFile Callback method to store a file into the WebDAV collection.
+   * @param createFolder Callback method to create a folder.
+   * @param deleteItem Callback method to delete a file or a folder.
+   * @param payload The user payload.
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterWebDavCollection(
+    OrthancPluginContext*                        context,
+    const char*                                  uri,
+    OrthancPluginWebDavIsExistingFolderCallback  isExistingFolder,
+    OrthancPluginWebDavListFolderCallback        listFolder,
+    OrthancPluginWebDavRetrieveFileCallback      retrieveFile,
+    OrthancPluginWebDavStoreFileCallback         storeFile,
+    OrthancPluginWebDavCreateFolderCallback      createFolder,
+    OrthancPluginWebDavDeleteItemCallback        deleteItem,
+    void*                                        payload)
+  {
+    _OrthancPluginRegisterWebDavCollection params;
+    params.uri = uri;
+    params.isExistingFolder = isExistingFolder;
+    params.listFolder = listFolder;
+    params.retrieveFile = retrieveFile;
+    params.storeFile = storeFile;
+    params.createFolder = createFolder;
+    params.deleteItem = deleteItem;
+    params.payload = payload;
+
+    return context->InvokeService(context, _OrthancPluginService_RegisterWebDavCollection, &params);
+  }
+  
+
+#ifdef  __cplusplus
+}
+#endif
+
+
+/** @} */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Toolchains/LinuxStandardBaseToolchain.cmake	Fri Apr 22 17:58:59 2022 +0200
@@ -0,0 +1,100 @@
+# Orthanc - A Lightweight, RESTful DICOM Store
+# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+# Department, University Hospital of Liege, Belgium
+# Copyright (C) 2017-2022 Osimis S.A., Belgium
+# Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+#
+# This program is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public License
+# as published by the Free Software Foundation, either version 3 of
+# the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this program. If not, see
+# <http://www.gnu.org/licenses/>.
+
+
+#
+# Full build, as used on the BuildBot CIS:
+#
+#   $ LSB_CC=gcc-4.8 LSB_CXX=g++-4.8 cmake ../OrthancServer/ -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TOOLCHAIN_FILE=../OrthancFramework/Resources/Toolchains/LinuxStandardBaseToolchain.cmake -DUSE_LEGACY_JSONCPP=ON -DUSE_LEGACY_LIBICU=ON -DBOOST_LOCALE_BACKEND=icu -DENABLE_PKCS11=ON -G Ninja
+#
+# Or, more lightweight version (without libp11 and ICU):
+#
+#   $ LSB_CC=gcc-4.8 LSB_CXX=g++-4.8 cmake ../OrthancServer/ -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TOOLCHAIN_FILE=../OrthancFramework/Resources/Toolchains/LinuxStandardBaseToolchain.cmake -DUSE_LEGACY_JSONCPP=ON -G Ninja
+#
+
+INCLUDE(CMakeForceCompiler)
+
+SET(LSB_PATH $ENV{LSB_PATH} CACHE STRING "")
+SET(LSB_CC $ENV{LSB_CC} CACHE STRING "")
+SET(LSB_CXX $ENV{LSB_CXX} CACHE STRING "")
+SET(LSB_TARGET_VERSION "4.0" CACHE STRING "")
+
+IF ("${LSB_PATH}" STREQUAL "")
+  SET(LSB_PATH "/opt/lsb")
+ENDIF()
+
+IF (EXISTS ${LSB_PATH}/lib64)
+  SET(LSB_TARGET_PROCESSOR "x86_64")
+  SET(LSB_LIBPATH ${LSB_PATH}/lib64-${LSB_TARGET_VERSION})
+ELSEIF (EXISTS ${LSB_PATH}/lib)
+  SET(LSB_TARGET_PROCESSOR "x86")
+  SET(LSB_LIBPATH ${LSB_PATH}/lib-${LSB_TARGET_VERSION})
+ELSE()
+  MESSAGE(FATAL_ERROR "Unable to detect the target processor architecture. Check the LSB_PATH environment variable.")
+ENDIF()
+
+SET(LSB_CPPPATH ${LSB_PATH}/include)
+SET(PKG_CONFIG_PATH ${LSB_LIBPATH}/pkgconfig/)
+
+# the name of the target operating system
+SET(CMAKE_SYSTEM_NAME Linux)
+SET(CMAKE_SYSTEM_VERSION LinuxStandardBase)
+SET(CMAKE_SYSTEM_PROCESSOR ${LSB_TARGET_PROCESSOR})
+
+# which compilers to use for C and C++
+SET(CMAKE_C_COMPILER ${LSB_PATH}/bin/lsbcc)
+
+if (${CMAKE_VERSION} VERSION_LESS "3.6.0") 
+  CMAKE_FORCE_CXX_COMPILER(${LSB_PATH}/bin/lsbc++ GNU)
+else()
+  SET(CMAKE_CXX_COMPILER ${LSB_PATH}/bin/lsbc++)
+endif()
+
+# here is the target environment located
+SET(CMAKE_FIND_ROOT_PATH ${LSB_PATH})
+
+# adjust the default behaviour of the FIND_XXX() commands:
+# search headers and libraries in the target environment, search 
+# programs in the host environment
+SET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
+SET(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY NEVER)
+SET(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE NEVER)
+
+SET(CMAKE_CROSSCOMPILING OFF)
+
+
+SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} --lsb-target-version=${LSB_TARGET_VERSION} -I${LSB_PATH}/include" CACHE INTERNAL "" FORCE)
+SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --lsb-target-version=${LSB_TARGET_VERSION} -nostdinc++ -I${LSB_PATH}/include -I${LSB_PATH}/include/c++ -I${LSB_PATH}/include/c++/backward" CACHE INTERNAL "" FORCE)
+SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} --lsb-target-version=${LSB_TARGET_VERSION} -L${LSB_LIBPATH} --lsb-besteffort" CACHE INTERNAL "" FORCE)
+SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --lsb-target-version=${LSB_TARGET_VERSION} -L${LSB_LIBPATH} --lsb-besteffort" CACHE INTERNAL "" FORCE)
+
+if (NOT "${LSB_CXX}" STREQUAL "")
+  SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --lsb-cxx=${LSB_CXX}")
+  SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} --lsb-cxx=${LSB_CXX}")
+  SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --lsb-cxx=${LSB_CXX}")
+endif()
+
+if (NOT "${LSB_CC}" STREQUAL "")
+  SET(CMAKE_C_FLAGS "${CMAKE_CC_FLAGS} --lsb-cc=${LSB_CC}")
+  SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --lsb-cc=${LSB_CC}")
+  SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} --lsb-cc=${LSB_CC}")
+  SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --lsb-cc=${LSB_CC}")
+endif()
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Toolchains/MinGW-W64-Toolchain32.cmake	Fri Apr 22 17:58:59 2022 +0200
@@ -0,0 +1,38 @@
+# Orthanc - A Lightweight, RESTful DICOM Store
+# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+# Department, University Hospital of Liege, Belgium
+# Copyright (C) 2017-2022 Osimis S.A., Belgium
+# Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+#
+# This program is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public License
+# as published by the Free Software Foundation, either version 3 of
+# the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this program. If not, see
+# <http://www.gnu.org/licenses/>.
+
+
+# the name of the target operating system
+set(CMAKE_SYSTEM_NAME Windows)
+
+# which compilers to use for C and C++
+set(CMAKE_C_COMPILER i686-w64-mingw32-gcc)
+set(CMAKE_CXX_COMPILER i686-w64-mingw32-g++)
+set(CMAKE_RC_COMPILER i686-w64-mingw32-windres)
+
+# here is the target environment located
+set(CMAKE_FIND_ROOT_PATH /usr/i686-w64-mingw32)
+
+# adjust the default behaviour of the FIND_XXX() commands:
+# search headers and libraries in the target environment, search 
+# programs in the host environment
+set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
+set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
+set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Toolchains/MinGW-W64-Toolchain64.cmake	Fri Apr 22 17:58:59 2022 +0200
@@ -0,0 +1,38 @@
+# Orthanc - A Lightweight, RESTful DICOM Store
+# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+# Department, University Hospital of Liege, Belgium
+# Copyright (C) 2017-2022 Osimis S.A., Belgium
+# Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+#
+# This program is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public License
+# as published by the Free Software Foundation, either version 3 of
+# the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this program. If not, see
+# <http://www.gnu.org/licenses/>.
+
+
+# the name of the target operating system
+set(CMAKE_SYSTEM_NAME Windows)
+
+# which compilers to use for C and C++
+set(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc)
+set(CMAKE_CXX_COMPILER x86_64-w64-mingw32-g++)
+set(CMAKE_RC_COMPILER x86_64-w64-mingw32-windres)
+
+# here is the target environment located
+set(CMAKE_FIND_ROOT_PATH /usr/i686-w64-mingw32)
+
+# adjust the default behaviour of the FIND_XXX() commands:
+# search headers and libraries in the target environment, search 
+# programs in the host environment
+set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
+set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
+set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Toolchains/MinGWToolchain.cmake	Fri Apr 22 17:58:59 2022 +0200
@@ -0,0 +1,41 @@
+# Orthanc - A Lightweight, RESTful DICOM Store
+# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+# Department, University Hospital of Liege, Belgium
+# Copyright (C) 2017-2022 Osimis S.A., Belgium
+# Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+#
+# This program is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public License
+# as published by the Free Software Foundation, either version 3 of
+# the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this program. If not, see
+# <http://www.gnu.org/licenses/>.
+
+
+# the name of the target operating system
+set(CMAKE_SYSTEM_NAME Windows)
+
+# which compilers to use for C and C++
+set(CMAKE_C_COMPILER i586-mingw32msvc-gcc)
+set(CMAKE_CXX_COMPILER i586-mingw32msvc-g++)
+set(CMAKE_RC_COMPILER i586-mingw32msvc-windres)
+
+# here is the target environment located
+set(CMAKE_FIND_ROOT_PATH /usr/i586-mingw32msvc)
+
+# adjust the default behaviour of the FIND_XXX() commands:
+# search headers and libraries in the target environment, search 
+# programs in the host environment
+set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
+set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
+set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
+
+set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DSTACK_SIZE_PARAM_IS_A_RESERVATION=0x10000" CACHE INTERNAL "" FORCE)
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DSTACK_SIZE_PARAM_IS_A_RESERVATION=0x10000" CACHE INTERNAL "" FORCE)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/SyncOrthancFolder.py	Fri Apr 22 17:58:59 2022 +0200
@@ -0,0 +1,76 @@
+#!/usr/bin/python
+
+#
+# This maintenance script updates the content of the "Orthanc" folder
+# to match the latest version of the Orthanc source code.
+#
+
+import multiprocessing
+import os
+import stat
+import urllib2
+
+TARGET = os.path.join(os.path.dirname(__file__), 'Orthanc')
+PLUGIN_SDK_VERSION = '1.10.1'
+REPOSITORY = 'https://hg.orthanc-server.com/orthanc/raw-file'
+
+FILES = [
+    ('OrthancFramework/Resources/CMake/AutoGeneratedCode.cmake', 'CMake'),
+    ('OrthancFramework/Resources/CMake/Compiler.cmake', 'CMake'),
+    ('OrthancFramework/Resources/CMake/DownloadOrthancFramework.cmake', 'CMake'),
+    ('OrthancFramework/Resources/CMake/DownloadPackage.cmake', 'CMake'),
+    ('OrthancFramework/Resources/CMake/GoogleTestConfiguration.cmake', 'CMake'),
+    ('OrthancFramework/Resources/EmbedResources.py', 'CMake'),
+    ('OrthancFramework/Resources/Toolchains/LinuxStandardBaseToolchain.cmake', 'Toolchains'),
+    ('OrthancFramework/Resources/Toolchains/MinGW-W64-Toolchain32.cmake', 'Toolchains'),
+    ('OrthancFramework/Resources/Toolchains/MinGW-W64-Toolchain64.cmake', 'Toolchains'),
+    ('OrthancFramework/Resources/Toolchains/MinGWToolchain.cmake', 'Toolchains'),
+    ('OrthancFramework/Resources/WindowsResources.py', 'CMake'),
+    ('OrthancFramework/Resources/WindowsResources.rc', 'CMake'),
+    ('OrthancServer/Plugins/Samples/Common/ExportedSymbolsPlugins.list', 'Plugins'),
+    ('OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp', 'Plugins'),
+    ('OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.h', 'Plugins'),
+    ('OrthancServer/Plugins/Samples/Common/OrthancPluginException.h', 'Plugins'),
+    ('OrthancServer/Plugins/Samples/Common/OrthancPluginsExports.cmake', 'Plugins'),
+    ('OrthancServer/Plugins/Samples/Common/VersionScriptPlugins.map', 'Plugins'),
+]
+
+SDK = [
+    'orthanc/OrthancCPlugin.h',
+]
+
+
+def Download(x):
+    branch = x[0]
+    source = x[1]
+    target = os.path.join(TARGET, x[2])
+    print target
+
+    try:
+        os.makedirs(os.path.dirname(target))
+    except:
+        pass
+
+    url = '%s/%s/%s' % (REPOSITORY, branch, source)
+
+    with open(target, 'w') as f:
+        f.write(urllib2.urlopen(url).read())
+
+
+commands = []
+
+for f in FILES:
+    commands.append([ 'default',
+                      f[0],
+                      os.path.join(f[1], os.path.basename(f[0])) ])
+
+for f in SDK:
+    commands.append([
+        'Orthanc-%s' % PLUGIN_SDK_VERSION, 
+        'OrthancServer/Plugins/Include/%s' % f,
+        'Sdk-%s/%s' % (PLUGIN_SDK_VERSION, f) 
+    ])
+
+
+pool = multiprocessing.Pool(10)  # simultaneous downloads
+pool.map(Download, commands)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Sources/Framework/BufferReader.cpp	Fri Apr 22 17:58:59 2022 +0200
@@ -0,0 +1,130 @@
+/**
+ * Neuroimaging plugin for Orthanc
+ * Copyright (C) 2021-2022 Sebastien Jodogne, UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "BufferReader.h"
+
+#include <OrthancException.h>
+
+#include <string.h>
+
+
+namespace Neuro
+{
+  void BufferReader::ReadBlock(void* target,
+                               size_t size)
+  {
+    if (size > 0)
+    {
+      if (pos_ + size > size_)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+      }
+      else
+      {
+        memcpy(target, data_ + pos_, size);
+        pos_ += size;
+      }
+    }
+  }
+
+
+  void BufferReader::Setup(const void* data,
+                           size_t size)
+  {
+    if (size != 0 &&
+        data == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+    }
+    else
+    {
+      data_ = reinterpret_cast<const uint8_t*>(data);
+      size_ = size;
+      pos_ = 0;
+    }
+  }
+  
+
+  BufferReader::BufferReader(const std::string& buffer)
+  {
+    Setup(buffer.empty() ? NULL : buffer.c_str(), buffer.size());
+  }
+  
+
+  std::string BufferReader::ReadNullTerminatedString()
+  {
+    for (size_t i = pos_; i < size_; i++)
+    {
+      if (data_[i] == '\0')
+      {
+        size_t length = i - pos_;
+        size_t start = pos_;
+        pos_ = i + 1;
+        return std::string(reinterpret_cast<const char*>(data_ + start), length);
+      }
+    }
+
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+  }
+
+
+  std::string BufferReader::ReadBlock(size_t size)
+  {
+    std::string s;
+    s.resize(size);
+
+    if (size > 0)
+    {
+      ReadBlock(&s[0], size);
+    }
+
+    return s;
+  }
+
+
+  void BufferReader::Skip(size_t bytes)
+  {
+    if (pos_ + bytes <= size_)
+    {
+      pos_ += bytes;
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+  }
+
+
+  uint32_t BufferReader::ReadUInt32()
+  {
+    if (pos_ + 4 <= size_)
+    {
+      uint32_t value = ((static_cast<uint32_t>(data_[pos_ + 3]) << 24) |
+                        (static_cast<uint32_t>(data_[pos_ + 2]) << 16) |
+                        (static_cast<uint32_t>(data_[pos_ + 1]) << 8) |
+                        static_cast<uint32_t>(data_[pos_]));
+      pos_ += 4;
+      return value;
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Sources/Framework/BufferReader.h	Fri Apr 22 17:58:59 2022 +0200
@@ -0,0 +1,63 @@
+/**
+ * Neuroimaging plugin for Orthanc
+ * Copyright (C) 2021-2022 Sebastien Jodogne, UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include <boost/noncopyable.hpp>
+#include <stdint.h>
+#include <string>
+
+namespace Neuro
+{
+  class BufferReader : public boost::noncopyable
+  {
+  private:
+    const uint8_t*  data_;
+    size_t          size_;
+    size_t          pos_;
+
+    void ReadBlock(void* target,
+                   size_t size);
+
+    void Setup(const void* data,
+               size_t size);
+
+  public:
+    explicit BufferReader(const std::string& buffer);
+  
+    BufferReader(void* data,
+                 size_t size)
+    {
+      Setup(data, size);
+    }
+
+    std::string ReadNullTerminatedString();
+
+    std::string ReadBlock(size_t size);
+
+    void Skip(size_t bytes);
+
+    uint32_t ReadUInt32();
+
+    size_t GetPosition() const
+    {
+      return pos_;
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Sources/Framework/CSAHeader.cpp	Fri Apr 22 17:58:59 2022 +0200
@@ -0,0 +1,217 @@
+/**
+ * Neuroimaging plugin for Orthanc
+ * Copyright (C) 2021-2022 Sebastien Jodogne, UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "CSAHeader.h"
+
+#include "BufferReader.h"
+
+#include <OrthancException.h>
+
+#include <cassert>
+
+
+namespace Neuro
+{
+  void CSAHeader::Clear()
+  {
+    for (Content::iterator it = content_.begin(); it != content_.end(); ++it)
+    {
+      assert(it->second != NULL);
+      delete it->second;
+    }
+  }
+
+
+  void CSAHeader::Load(const std::string& tag)
+  {
+    // https://nipy.org/nibabel/dicom/siemens_csa.html
+
+    Clear();
+  
+    BufferReader reader(tag);
+    
+    if (reader.ReadUInt32() != 0x30315653)  // This is the "SV10" header
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+
+    reader.ReadUInt32();  // Unused, often equals to 0x01020304
+
+    const uint32_t n_tags = reader.ReadUInt32();
+    if (n_tags == 0 ||
+        n_tags > 128)
+    {
+      // This should in the range 1..128
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+
+    if (reader.ReadUInt32() != 77)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+  
+    for (uint32_t i = 0; i < n_tags; i++)
+    {
+      const std::string name = reader.ReadNullTerminatedString();
+
+      if (name.size() >= 63)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+      }
+
+      reader.Skip(64 - name.size() - 1);
+
+      const uint32_t vm = reader.ReadUInt32();
+
+      const std::string vr = reader.ReadNullTerminatedString();
+      if (vr.size() >= 4)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+      }
+
+      reader.Skip(4 - vr.size() - 1);
+    
+      reader.ReadUInt32();  // "syngodt" = syngo.via data type
+      const uint32_t nitems = reader.ReadUInt32();
+      const uint32_t sync = reader.ReadUInt32();
+
+      if (sync != 77 &&
+          sync != 205)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+      }
+
+      std::unique_ptr<CSATag> tag2(new CSATag(vr));
+
+      for (uint32_t j = 0; j < nitems; j++)
+      {
+        reader.ReadUInt32();
+        const uint32_t item_len = reader.ReadUInt32();
+        reader.ReadUInt32();
+        reader.ReadUInt32();
+
+        if (vm == 0 ||
+            j < vm)
+        {
+          tag2->AddValue(reader.ReadBlock(item_len));
+        }
+        else
+        {
+          reader.Skip(item_len);
+        }
+
+        // Set the stream position to the next 4 byte boundary
+        if (reader.GetPosition() % 4 != 0)
+        {
+          reader.Skip(4 - reader.GetPosition() % 4);
+        }
+      }
+
+      if (content_.find(name) != content_.end())
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
+                                        "Tag is repeated in CSA header: " + name);
+      }
+      else
+      {
+        content_[name] = tag2.release();
+      }
+    }
+  }
+
+
+  const CSATag& CSAHeader::GetTag(const std::string& name) const
+  {
+    Content::const_iterator found = content_.find(name);
+
+    if (found == content_.end())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentItem);
+    }
+    else
+    {
+      assert(found->second != NULL);
+      return *found->second;
+    }
+  }
+
+
+  void CSAHeader::ListTags(std::list<std::string>& tags) const
+  {
+    for (Content::const_iterator it = content_.begin(); it != content_.end(); ++it)
+    {
+      assert(it->second != NULL);
+      tags.push_back(it->first);
+    }
+  }
+
+
+  bool CSAHeader::ParseUnsignedInteger32(uint32_t& target,
+                                         const std::string& tagName) const
+  {
+    Content::const_iterator found = content_.find(tagName);
+
+    if (found == content_.end())
+    {
+      return false;
+    }
+    else if (found->second->GetSize() != 1)
+    {
+      return false;
+    }
+    else
+    {
+      return found->second->ParseUnsignedInteger32(target, 0);
+    }
+  }
+
+
+  CSATag& CSAHeader::AddTag(const std::string& name,
+                            const std::string& vr)
+  {
+    if (content_.find(name) != content_.end())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange,
+                                      "Tag already exists: " + name);
+    }
+    else
+    {
+      CSATag* tag = new CSATag(vr);
+      content_[name] = tag;
+      return *tag;
+    }
+  }
+
+
+  void CSAHeader::AddValue(const std::string& tagName,
+                           const std::string& value)
+  {
+    Content::iterator found = content_.find(tagName);
+
+    if (found == content_.end())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentItem);
+    }
+    else
+    {
+      assert(found->second != NULL);
+      found->second->AddValue(value);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Sources/Framework/CSAHeader.h	Fri Apr 22 17:58:59 2022 +0200
@@ -0,0 +1,65 @@
+/**
+ * Neuroimaging plugin for Orthanc
+ * Copyright (C) 2021-2022 Sebastien Jodogne, UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "CSATag.h"
+
+#include <list>
+#include <map>
+
+
+namespace Neuro
+{
+  class CSAHeader : public boost::noncopyable
+  {
+  private:
+    typedef std::map<std::string, CSATag*>  Content;
+
+    Content  content_;
+
+    void Clear();
+  
+  public:
+    ~CSAHeader()
+    {
+      Clear();
+    }
+
+    void Load(const std::string& tag);
+
+    bool HasTag(const std::string& name) const
+    {
+      return (content_.find(name) != content_.end());
+    }
+
+    const CSATag& GetTag(const std::string& name) const;
+  
+    void ListTags(std::list<std::string>& tags) const;
+
+    bool ParseUnsignedInteger32(uint32_t& target,
+                                const std::string& tagName) const;
+
+    CSATag& AddTag(const std::string& name,
+                   const std::string& vr);
+
+    void AddValue(const std::string& tagName,
+                  const std::string& value);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Sources/Framework/CSATag.cpp	Fri Apr 22 17:58:59 2022 +0200
@@ -0,0 +1,93 @@
+/**
+ * Neuroimaging plugin for Orthanc
+ * Copyright (C) 2021-2022 Sebastien Jodogne, UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "CSATag.h"
+
+#include <OrthancException.h>
+#include <SerializationToolbox.h>
+
+
+namespace Neuro
+{
+  CSATag& CSATag::AddValue(const std::string& value)
+  {
+    values_.push_back(value);
+    return *this;
+  }
+
+
+  const std::string& CSATag::GetBinaryValue(size_t index) const
+  {
+    if (index >= values_.size())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      return values_[index];
+    }
+  }
+
+
+  std::string CSATag::GetStringValue(size_t index) const
+  {
+    const std::string& s = GetBinaryValue(index);
+
+    // Crop the string at the first encountered '\0' value
+    size_t pos = s.find('\0');
+    if (pos == std::string::npos)
+    {
+      return s;
+    }
+    else
+    {
+      return s.substr(0, pos);
+    }
+  }
+
+
+  bool CSATag::ParseUnsignedInteger32(uint32_t& target,
+                                      size_t index) const
+  {
+    return Orthanc::SerializationToolbox::ParseUnsignedInteger32(target, GetStringValue(index));
+  }
+
+
+  bool CSATag::ParseDouble(double& target,
+                           size_t index) const
+  {
+    return Orthanc::SerializationToolbox::ParseDouble(target, GetStringValue(index));
+  }
+
+
+  bool CSATag::ParseVector(std::vector<double>& target) const
+  {
+    target.resize(values_.size());
+
+    for (size_t i = 0; i < values_.size(); i++)
+    {
+      if (!ParseDouble(target[i], i))
+      {
+        return false;
+      }
+    }
+
+    return true;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Sources/Framework/CSATag.h	Fri Apr 22 17:58:59 2022 +0200
@@ -0,0 +1,65 @@
+/**
+ * Neuroimaging plugin for Orthanc
+ * Copyright (C) 2021-2022 Sebastien Jodogne, UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include <boost/noncopyable.hpp>
+#include <string>
+#include <vector>
+
+
+namespace Neuro
+{
+  class CSATag : public boost::noncopyable
+  {
+  private:
+    std::string               vr_;
+    std::vector<std::string>  values_;
+
+  public:
+    explicit CSATag(const std::string& vr) :
+      vr_(vr)
+    {
+    }
+
+    CSATag& AddValue(const std::string& value);
+
+    const std::string& GetVR() const
+    {
+      return vr_;
+    }
+
+    const size_t GetSize() const
+    {
+      return values_.size();
+    }
+
+    const std::string& GetBinaryValue(size_t index) const;
+
+    std::string GetStringValue(size_t index) const;
+
+    bool ParseUnsignedInteger32(uint32_t& target,
+                                size_t index) const;
+
+    bool ParseDouble(double& target,
+                     size_t index) const;
+
+    bool ParseVector(std::vector<double>& target) const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Sources/Framework/DicomInstancesCollection.cpp	Fri Apr 22 17:58:59 2022 +0200
@@ -0,0 +1,540 @@
+/**
+ * Neuroimaging plugin for Orthanc
+ * Copyright (C) 2021-2022 Sebastien Jodogne, UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "DicomInstancesCollection.h"
+
+#include "NeuroToolbox.h"
+
+#include <OrthancException.h>
+#include <SerializationToolbox.h>
+
+#include <boost/lexical_cast.hpp>
+
+static const std::string CSA_PHASE_ENCODING_DIRECTION_POSITIVE = "PhaseEncodingDirectionPositive";
+
+
+namespace Neuro
+{
+  namespace
+  {
+    struct SliceComparator
+    {
+      bool operator() (const Slice& a,
+                       const Slice& b) const
+      {
+        if (a.GetProjectionAlongNormal() < b.GetProjectionAlongNormal())
+        {
+          return true;
+        }
+        else if (a.GetProjectionAlongNormal() > b.GetProjectionAlongNormal())
+        {
+          return false;
+        }
+        else
+        {
+          return a.GetInstanceNumber() < b.GetInstanceNumber();
+        }
+      }
+    };
+  }
+
+
+  namespace
+  {
+    class DescriptionWriter : public boost::noncopyable
+    {
+    private:
+      std::list<std::string>  content_;
+      std::set<std::string>   index_;
+
+    public:
+      void AddString(const std::string& key,
+                     const std::string& value)
+      {
+        if (index_.find(key) == index_.end())
+        {
+          content_.push_back(key + "=" + value);
+          index_.insert(key);
+        }
+        else
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls,
+                                          "The description already has this key: " + key);
+        }
+      }
+
+      void AddDouble(const std::string& key,
+                     double value,
+                     const std::string& format)
+      {
+        char buf[64];
+        sprintf(buf, format.c_str(), value);
+        AddString(key, buf);
+      }
+
+      void Write(nifti_image& nifti) const
+      {
+        std::string s;
+
+        for (std::list<std::string>::const_iterator it = content_.begin(); it != content_.end(); ++it)
+        {
+          if (!s.empty())
+          {
+            s += ';';
+          }
+
+          s += *it;
+        }
+
+        strncpy(nifti.descrip, s.c_str(), sizeof(nifti.descrip) - 1);
+      }
+    };
+  }
+  
+
+  void DicomInstancesCollection::ExtractSlices(std::list<Slice>& slices) const
+  {
+    for (size_t i = 0; i < GetSize(); i++)
+    {
+      GetInstance(i).ExtractSlices(slices, i);
+    }
+  }
+
+
+  static void Compute3DOrientation(nifti_image& nifti,
+                                   PhaseEncodingDirection phaseEncoding)
+  {
+    nifti.sto_xyz.m[3][0] = 0;
+    nifti.sto_xyz.m[3][1] = 0;
+    nifti.sto_xyz.m[3][2] = 0;
+    nifti.sto_xyz.m[3][3] = 1;
+
+    float qb, qc, qd, qx, qy, qz, dx, dy, dz, qfac;
+    nifti_mat44_to_quatern(nifti.sto_xyz, &qb, &qc, &qd, &qx, &qy, &qz, &dx, &dy, &dz, &qfac);
+
+    // Normalize the quaternion to positive components
+    if (qb <= std::numeric_limits<double>::epsilon() &&
+        qc <= std::numeric_limits<double>::epsilon() &&
+        qd <= std::numeric_limits<double>::epsilon())
+    {
+      qb = -qb;
+      qc = -qc;
+      qd = -qd;
+    }
+    
+    nifti.quatern_b = qb;
+    nifti.quatern_c = qc;
+    nifti.quatern_d = qd;
+    nifti.qoffset_x = qx;
+    nifti.qoffset_y = qy;
+    nifti.qoffset_z = qz;
+    nifti.qfac = qfac;
+    nifti.dx = dx;
+    nifti.dy = dy;
+    nifti.dz = dz;
+    nifti.pixdim[0] = qfac;
+    nifti.pixdim[1] = dx;
+    nifti.pixdim[2] = dy;
+    nifti.pixdim[3] = dz;
+
+    // https://github.com/rordenlab/dcm2niix/blob/master/console/nii_dicom.cpp
+    // Function "headerDcm2Nii2()"
+    switch (phaseEncoding)
+    {
+      case PhaseEncodingDirection_Row:
+        nifti.phase_dim = 1;
+        nifti.freq_dim = 2;
+        nifti.slice_dim = 3;
+        break;
+        
+      case PhaseEncodingDirection_Column:
+        nifti.phase_dim = 2;
+        nifti.freq_dim = 1;
+        nifti.slice_dim = 3;
+        break;
+
+      case PhaseEncodingDirection_None:
+        nifti.phase_dim = 0;
+        nifti.freq_dim = 0;
+        nifti.slice_dim = 0;
+        break;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+  }
+
+
+  static void ConvertDicomToNiftiOrientation(nifti_image& nifti)
+  {
+    for (int c = 0; c < 2; c++)
+    {
+      for (int r = 0; r < 4; r++)
+      {
+        nifti.sto_xyz.m[c][r] = -nifti.sto_xyz.m[c][r];
+      }
+    }
+    
+    // "nii_flipY()" in dcm2niix
+    
+    nifti.sto_xyz.m[0][3] = nifti.sto_xyz.m[0][1] * static_cast<double>(nifti.ny - 1) + nifti.sto_xyz.m[0][3];
+    nifti.sto_xyz.m[1][3] = nifti.sto_xyz.m[1][1] * static_cast<double>(nifti.ny - 1) + nifti.sto_xyz.m[1][3];
+    nifti.sto_xyz.m[2][3] = nifti.sto_xyz.m[2][1] * static_cast<double>(nifti.ny - 1) + nifti.sto_xyz.m[2][3];
+
+    for (int r = 0; r < 3; r++)
+    {
+      nifti.sto_xyz.m[r][1] = -nifti.sto_xyz.m[r][1];
+    }
+  }
+  
+
+  static void InitializeNiftiHeader(nifti_image& nifti,
+                                    const InputDicomInstance& instance)
+  {
+    memset(&nifti, 0, sizeof(nifti));
+    nifti.scl_slope = instance.GetRescaleSlope();
+    nifti.scl_inter = instance.GetRescaleIntercept();
+    nifti.xyz_units = NIFTI_UNITS_MM;
+    nifti.time_units = NIFTI_UNITS_SEC;
+    nifti.nifti_type = 1;  // NIFTI-1 (1 file)
+    nifti.qform_code = NIFTI_XFORM_SCANNER_ANAT;
+    nifti.sform_code = NIFTI_XFORM_SCANNER_ANAT;
+
+    Orthanc::PixelFormat format;
+    if (!instance.GetImageInformation().ExtractPixelFormat(format, false))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+    
+    switch (format)
+    {
+      case Orthanc::PixelFormat_Grayscale16:
+        // In this situation, dcm2niix uses "NIFTI_TYPE_INT16", which is wrong
+        nifti.datatype = NIFTI_TYPE_UINT16;
+        nifti.nbyper = 2;
+        break;
+
+      case Orthanc::PixelFormat_SignedGrayscale16:
+        nifti.datatype = NIFTI_TYPE_INT16;
+        nifti.nbyper = 2;
+        break;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+    }
+  }
+  
+  
+  unsigned int DicomInstancesCollection::GetMultiBandFactor() const
+  {
+    unsigned int mb = 0;
+    
+    for (size_t i = 0; i < GetSize(); i++)
+    {
+      mb = std::max(mb, GetInstance(i).GetMultiBandFactor());
+    }
+
+    return mb;
+  }
+  
+
+  void DicomInstancesCollection::WriteDescription(nifti_image& nifti,
+                                                  const std::vector<Slice>& sortedSlices) const
+  {
+    bool hasAcquisitionTime = false;
+    double lowestAcquisitionTime, highestAcquisitionTime;
+
+    for (size_t i = 0; i < sortedSlices.size(); i++)
+    {
+      if (sortedSlices[i].HasAcquisitionTime())
+      {
+        double t = sortedSlices[i].GetAcquisitionTime();
+        
+        if (hasAcquisitionTime)
+        {
+          lowestAcquisitionTime = std::min(lowestAcquisitionTime, t);
+          highestAcquisitionTime = std::max(highestAcquisitionTime, t);
+        }
+        else
+        {
+          hasAcquisitionTime = true;
+          lowestAcquisitionTime = highestAcquisitionTime = t;
+        }
+      }
+    }
+    
+    DescriptionWriter description;
+
+    const InputDicomInstance& firstInstance = GetInstance(sortedSlices[0].GetInstanceIndexInCollection());
+    
+    if (firstInstance.HasEchoTime())
+    {
+      description.AddDouble("TE", firstInstance.GetEchoTime(), "%.2g");
+    }
+
+    if (hasAcquisitionTime)
+    {
+      if (firstInstance.GetModality() == Modality_PET)
+      {
+        description.AddDouble("Time", highestAcquisitionTime, "%.3f");
+      }
+      else
+      {
+        description.AddDouble("Time", lowestAcquisitionTime, "%.3f");
+      }
+    }
+
+    uint32_t phaseEncodingDirectionPositive;
+    if (firstInstance.GetCSAHeader().ParseUnsignedInteger32(phaseEncodingDirectionPositive, CSA_PHASE_ENCODING_DIRECTION_POSITIVE))
+    {
+      description.AddString("phase", boost::lexical_cast<std::string>(phaseEncodingDirectionPositive));
+    }
+      
+    const unsigned int multiBandFactor = GetMultiBandFactor();
+    if (multiBandFactor > 1)
+    {
+      description.AddString("mb", boost::lexical_cast<std::string>(multiBandFactor));
+    }
+
+    description.Write(nifti);
+  }
+     
+  
+  DicomInstancesCollection::~DicomInstancesCollection()
+  {
+    for (size_t i = 0; i < instances_.size(); i++)
+    {
+      assert(instances_[i] != NULL);
+      delete instances_[i];
+    }
+  }
+  
+
+  void DicomInstancesCollection::AddInstance(InputDicomInstance* instance,
+                                             const std::string& orthancId)
+  {
+    if (instance == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+    }
+    else
+    {
+      instances_.push_back(instance);
+      orthancIds_.push_back(orthancId);
+    }
+  }
+    
+
+  const InputDicomInstance& DicomInstancesCollection::GetInstance(size_t index) const
+  {
+    assert(orthancIds_.size() == instances_.size());
+    if (index >= instances_.size())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      assert(instances_[index] != NULL);
+      return *instances_[index];
+    }    
+  }
+
+
+  const std::string& DicomInstancesCollection::GetOrthancId(size_t index) const
+  {
+    assert(orthancIds_.size() == instances_.size());
+    if (index >= instances_.size())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      return orthancIds_[index];
+    }    
+  }
+  
+
+  void DicomInstancesCollection::CreateNiftiHeader(nifti_image& nifti /* out */,
+                                                   std::vector<Slice>& slices /* out */) const
+  {
+    // TODO: Sanity check - Verify that all the instances have the
+    // same pixel spacing, the same sizes, the same modality, are parallel
+      
+    std::list<Slice> unsortedSlices;
+    ExtractSlices(unsortedSlices);
+
+    std::vector<Slice> sortedSlices;
+    sortedSlices.reserve(unsortedSlices.size());
+    std::copy(std::begin(unsortedSlices), std::end(unsortedSlices), std::back_inserter(sortedSlices));
+
+    SliceComparator comparator;
+    std::sort(sortedSlices.begin(), sortedSlices.end(), comparator);
+
+    size_t numberOfAcquisitions = 1;
+    while (numberOfAcquisitions < sortedSlices.size() &&
+           NeuroToolbox::IsNear(sortedSlices[0].GetProjectionAlongNormal(),
+                                sortedSlices[numberOfAcquisitions].GetProjectionAlongNormal(), 0.0001))
+    {
+      numberOfAcquisitions++;
+    }
+
+    if (sortedSlices.size() % numberOfAcquisitions != 0)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange, "Inconsistent number of acquisitions");
+    }
+
+    size_t acquisitionLength = sortedSlices.size() / numberOfAcquisitions;
+
+    for (size_t i = 1; i < acquisitionLength; i++)
+    {
+      if (NeuroToolbox::IsNear(sortedSlices[(i - 1) * numberOfAcquisitions].GetProjectionAlongNormal(),
+                               sortedSlices[i * numberOfAcquisitions].GetProjectionAlongNormal(), 0.0001))
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange, "Ambiguity in the 3D locations");
+      }
+    }
+      
+    for (size_t i = 0; i < acquisitionLength; i++)
+    {
+      for (size_t j = 1; j < numberOfAcquisitions; j++)
+      {
+        if (sortedSlices[i * numberOfAcquisitions].GetInstanceNumber() ==
+            sortedSlices[i * numberOfAcquisitions + j].GetInstanceNumber())
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange, "Ambiguity in the instance numbers");
+        }
+
+        if (!NeuroToolbox::IsNear(sortedSlices[i * numberOfAcquisitions].GetProjectionAlongNormal(),
+                                  sortedSlices[i * numberOfAcquisitions + j].GetProjectionAlongNormal(), 0.0001))
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange, "Ambiguity in the 3D locations");
+        }
+      }
+    }
+
+    const InputDicomInstance& firstInstance = GetInstance(sortedSlices[0].GetInstanceIndexInCollection());
+    
+    InitializeNiftiHeader(nifti, firstInstance);
+
+    nifti.dim[1] = nifti.nx = sortedSlices[0].GetWidth();
+    nifti.dim[2] = nifti.ny = sortedSlices[0].GetHeight();
+
+    nifti.pixdim[1] = nifti.dx = firstInstance.GetPixelSpacingX();
+    nifti.pixdim[2] = nifti.dy = firstInstance.GetPixelSpacingY();
+
+    if (numberOfAcquisitions >= sortedSlices.size())
+    {
+      nifti.pixdim[3] = nifti.dz = firstInstance.GetVoxelSpacingZ();
+    }
+    else
+    {
+      nifti.pixdim[3] = nifti.dz = (sortedSlices[numberOfAcquisitions].GetProjectionAlongNormal() -
+                                    sortedSlices[0].GetProjectionAlongNormal());
+    }
+
+    assert(nifti.dz > 0);
+      
+    if (acquisitionLength == 1 ||
+        numberOfAcquisitions == 1)
+    {
+      nifti.dim[0] = nifti.ndim = 3;
+      nifti.dim[3] = nifti.nz = std::max(numberOfAcquisitions, acquisitionLength);
+    }
+    else
+    {
+      nifti.dim[0] = nifti.ndim = 4;
+      nifti.dim[3] = nifti.nz = acquisitionLength;
+      nifti.dim[4] = nifti.nt = numberOfAcquisitions;
+
+      bool hasDt = false;
+        
+      if (firstInstance.GetManufacturer() == Manufacturer_Philips &&
+          sortedSlices[0].HasAcquisitionTime())
+      {
+        // Check out "trDiff0" in "nii_dicom_batch.cpp"
+        double a = NeuroToolbox::FixDicomTime(sortedSlices[0].GetAcquisitionTime());
+        double maxTimeDifference = 0;
+          
+        for (size_t i = 1; i < sortedSlices.size(); i++)
+        {
+          if (sortedSlices[i].HasAcquisitionTime())
+          {
+            double b = NeuroToolbox::FixDicomTime(sortedSlices[i].GetAcquisitionTime());
+            maxTimeDifference = std::max(maxTimeDifference, b - a);
+          }
+        }
+
+        if (!NeuroToolbox::IsNear(maxTimeDifference, 0))
+        {
+          hasDt = true;
+          nifti.pixdim[4] = nifti.dt = static_cast<float>(maxTimeDifference / (nifti.nt - 1.0));
+        }
+      }
+
+      if (!hasDt)
+      {
+        double repetitionTime;
+        if (firstInstance.LookupRepetitionTime(repetitionTime))
+        {
+          float r = static_cast<float>(repetitionTime / 1000.0);  // Conversion to seconds
+          nifti.pixdim[4] = nifti.dt = r;
+          hasDt = true;
+        }
+      }
+
+      if (!hasDt)
+      {
+        nifti.pixdim[4] = nifti.dt = 1;
+      }
+    }
+      
+    nifti.nvox = 1;
+    for (int i = 0; i < nifti.dim[0]; i++)
+    {
+      nifti.nvox *= nifti.dim[i + 1];
+    }
+    
+    nifti.slice_code = firstInstance.DetectSiemensSliceCode();
+      
+    for (uint8_t i = 0; i < 3; i++)
+    {
+      nifti.sto_xyz.m[i][0] = firstInstance.GetAxisX(i) * nifti.dx;
+      nifti.sto_xyz.m[i][1] = firstInstance.GetAxisY(i) * nifti.dy;
+      nifti.sto_xyz.m[i][2] = sortedSlices[0].GetNormal(i) * nifti.dz;
+      nifti.sto_xyz.m[i][3] = sortedSlices[0].GetOrigin(i);
+    }
+
+    ConvertDicomToNiftiOrientation(nifti);
+
+    Compute3DOrientation(nifti, firstInstance.GetPhaseEncodingDirection());
+
+    WriteDescription(nifti, sortedSlices);
+
+    slices.reserve(sortedSlices.size());
+    for (size_t j = 0; j < numberOfAcquisitions; j++)
+    {
+      for (size_t i = 0; i < acquisitionLength; i++)
+      {
+        slices.push_back(sortedSlices[i * numberOfAcquisitions + j]);
+      }
+    }
+
+    assert(slices.size() == sortedSlices.size());
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Sources/Framework/DicomInstancesCollection.h	Fri Apr 22 17:58:59 2022 +0200
@@ -0,0 +1,60 @@
+/**
+ * Neuroimaging plugin for Orthanc
+ * Copyright (C) 2021-2022 Sebastien Jodogne, UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "InputDicomInstance.h"
+
+#include <nifti1_io.h>
+
+
+namespace Neuro
+{
+  class DicomInstancesCollection : public boost::noncopyable
+  {
+  private:
+    std::vector<InputDicomInstance*>  instances_;
+    std::vector<std::string>          orthancIds_;
+
+    unsigned int GetMultiBandFactor() const;
+
+    void WriteDescription(nifti_image& nifti,
+                          const std::vector<Slice>& sortedSlices) const;
+
+  public:
+    ~DicomInstancesCollection();
+
+    void AddInstance(InputDicomInstance* instance,  // Takes ownership
+                     const std::string& orthancId);
+    
+    size_t GetSize() const
+    {
+      return instances_.size();
+    }
+
+    const InputDicomInstance& GetInstance(size_t index) const;
+
+    const std::string& GetOrthancId(size_t index) const;
+
+    void ExtractSlices(std::list<Slice>& slices) const;
+
+    void CreateNiftiHeader(nifti_image& nifti /* out */,
+                           std::vector<Slice>& slices /* out */) const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Sources/Framework/IDicomFrameDecoder.cpp	Fri Apr 22 17:58:59 2022 +0200
@@ -0,0 +1,88 @@
+/**
+ * Neuroimaging plugin for Orthanc
+ * Copyright (C) 2021-2022 Sebastien Jodogne, UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "IDicomFrameDecoder.h"
+
+#include <OrthancException.h>
+
+
+namespace Neuro
+{
+  void IDicomFrameDecoder::Apply(NiftiWriter& writer,
+                                 IDicomFrameDecoder& decoder,
+                                 const std::vector<Slice>& slices)
+  {
+    for (size_t i = 1; i < slices.size(); i++)
+    {
+      if (slices[0].GetWidth() != slices[i].GetWidth() ||
+          slices[0].GetHeight() != slices[i].GetHeight())
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented,
+                                        "The slices have varying dimensions");
+      }
+    }
+      
+    size_t currentInstanceIndex;
+    unsigned int currentFrameNumber;
+    std::unique_ptr<IDecodedFrame> currentFrame;
+
+    bool first = true;
+    Orthanc::PixelFormat format;
+
+    for (size_t i = 0; i < slices.size(); i++)
+    {
+      if (currentFrame.get() == NULL ||
+          currentInstanceIndex != slices[i].GetInstanceIndexInCollection() ||
+          currentFrameNumber != slices[i].GetFrameNumber())
+      {
+        currentFrame.reset(decoder.DecodeFrame(slices[i]));
+        if (currentFrame.get() == NULL)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+        }
+          
+        currentInstanceIndex = slices[i].GetInstanceIndexInCollection();
+        currentFrameNumber = slices[i].GetFrameNumber();
+      }
+
+      Orthanc::ImageAccessor region;
+      currentFrame->GetRegion(region, slices[i].GetX(), slices[i].GetY(), slices[i].GetWidth(), slices[i].GetHeight());
+
+      if (region.GetWidth() != slices[i].GetWidth() ||
+          region.GetHeight() != slices[i].GetHeight())
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+        
+      if (first)
+      {
+        first = false;
+        format = region.GetFormat();
+      }
+
+      if (region.GetFormat() != format)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat,
+                                        "The slices have varying pixel formats");
+      }
+
+      writer.AddSlice(region);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Sources/Framework/IDicomFrameDecoder.h	Fri Apr 22 17:58:59 2022 +0200
@@ -0,0 +1,56 @@
+/**
+ * Neuroimaging plugin for Orthanc
+ * Copyright (C) 2021-2022 Sebastien Jodogne, UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "NiftiWriter.h"
+#include "Slice.h"
+
+#include <vector>
+
+namespace Neuro
+{
+  class IDicomFrameDecoder : public boost::noncopyable
+  {
+  public:
+    class IDecodedFrame : public boost::noncopyable
+    {
+    public:
+      virtual ~IDecodedFrame()
+      {
+      }
+
+      virtual void GetRegion(Orthanc::ImageAccessor& region,
+                             unsigned int x,
+                             unsigned int y,
+                             unsigned int width,
+                             unsigned int height) = 0;
+    };
+    
+    virtual ~IDicomFrameDecoder()
+    {
+    }
+
+    virtual IDecodedFrame* DecodeFrame(const Slice& slice) = 0;
+
+    static void Apply(NiftiWriter& writer /* output */,
+                      IDicomFrameDecoder& decoder,
+                      const std::vector<Slice>& slices);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Sources/Framework/InputDicomInstance.cpp	Fri Apr 22 17:58:59 2022 +0200
@@ -0,0 +1,840 @@
+/**
+ * Neuroimaging plugin for Orthanc
+ * Copyright (C) 2021-2022 Sebastien Jodogne, UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "InputDicomInstance.h"
+
+#include "NeuroToolbox.h"
+
+#include <Logging.h>
+#include <OrthancException.h>
+#include <SerializationToolbox.h>
+#include <Toolbox.h>
+
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/lexical_cast.hpp>
+
+#if ORTHANC_ENABLE_DCMTK == 1
+#  include <DicomParsing/FromDcmtkBridge.h>
+#endif
+
+#include <nifti1_io.h>
+
+static const Orthanc::DicomTag DICOM_TAG_ECHO_TIME(0x0018, 0x0081);
+static const Orthanc::DicomTag DICOM_TAG_IN_PLANE_PHASE_ENCODING_DIRECTION(0x0018, 0x1312);
+static const Orthanc::DicomTag DICOM_TAG_REPETITION_TIME(0x0018, 0x0080);
+static const Orthanc::DicomTag DICOM_TAG_SLICE_SLOPE_PHILIPS(0x2005, 0x100e);
+static const Orthanc::DicomTag DICOM_TAG_SLICE_TIMING_SIEMENS(0x0019, 0x1029);
+static const Orthanc::DicomTag DICOM_TAG_SPACING_BETWEEN_SLICES(0x0018, 0x0088);
+
+static const std::string CSA_NUMBER_OF_IMAGES_IN_MOSAIC = "NumberOfImagesInMosaic";
+static const std::string CSA_SLICE_NORMAL_VECTOR = "SliceNormalVector";
+
+
+namespace Neuro
+{
+  static Manufacturer GetManufacturer(const Orthanc::DicomMap& dicom)
+  {
+    std::string manufacturer = dicom.GetStringValue(Orthanc::DICOM_TAG_MANUFACTURER, "", false);
+    Orthanc::Toolbox::ToUpperCase(manufacturer);
+      
+    if (boost::algorithm::starts_with(manufacturer, "SI"))
+    {
+      return Manufacturer_Siemens;
+    }
+    else if (boost::algorithm::starts_with(manufacturer, "GE"))
+    {
+      return Manufacturer_GE;
+    }
+    else if (boost::algorithm::starts_with(manufacturer, "HI"))
+    {
+      return Manufacturer_Hitachi;
+    }
+    else if (boost::algorithm::starts_with(manufacturer, "ME"))
+    {
+      return Manufacturer_Mediso;
+    }
+    else if (boost::algorithm::starts_with(manufacturer, "PH"))
+    {
+      return Manufacturer_Philips;
+    }
+    else if (boost::algorithm::starts_with(manufacturer, "TO"))
+    {
+      return Manufacturer_Toshiba;
+    }
+    else if (boost::algorithm::starts_with(manufacturer, "CA"))
+    {
+      return Manufacturer_Canon;
+    }
+    else if (boost::algorithm::starts_with(manufacturer, "UI"))
+    {
+      return Manufacturer_UIH;
+    }
+    else if (boost::algorithm::starts_with(manufacturer, "BR"))
+    {
+      return Manufacturer_Bruker;
+    }
+    else
+    {
+      return Manufacturer_Unknown;
+    }
+  }
+  
+
+  static Modality GetModality(const Orthanc::DicomMap& dicom)
+  {
+    std::string modality = dicom.GetStringValue(Orthanc::DICOM_TAG_MODALITY, "", false);
+    Orthanc::Toolbox::ToUpperCase(modality);
+      
+    if (boost::algorithm::starts_with(modality, "MR"))
+    {
+      return Modality_MR;
+    }
+    else if (boost::algorithm::starts_with(modality, "PT"))
+    {
+      return Modality_PET;
+    }
+    else if (boost::algorithm::starts_with(modality, "CT"))
+    {
+      return Modality_CT;
+    }
+    else
+    {
+      return Modality_Unknown;
+    }
+  }
+  
+
+  void InputDicomInstance::ParseImagePositionPatient()
+  {
+    if (NeuroToolbox::ParseVector(imagePositionPatient_, *tags_, Orthanc::DICOM_TAG_IMAGE_POSITION_PATIENT))
+    {
+      if (imagePositionPatient_.size() != 3)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+      }
+    }
+    else
+    {
+      imagePositionPatient_.resize(3);
+      imagePositionPatient_[0] = 0;
+      imagePositionPatient_[1] = 0;
+      imagePositionPatient_[2] = 0;
+    }
+  }
+
+
+  void InputDicomInstance::ParseImageOrientationPatient()
+  {
+    if (NeuroToolbox::ParseVector(imageOrientationPatient_, *tags_, Orthanc::DICOM_TAG_IMAGE_ORIENTATION_PATIENT))
+    {
+      if (imageOrientationPatient_.size() != 6)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+      }
+    }
+    else
+    {
+      // Set the canonical orientation
+      imageOrientationPatient_.resize(6);
+      imageOrientationPatient_[0] = 1;
+      imageOrientationPatient_[1] = 0;
+      imageOrientationPatient_[2] = 0;
+      imageOrientationPatient_[3] = 0;
+      imageOrientationPatient_[4] = 1;
+      imageOrientationPatient_[5] = 0;
+    }
+
+    std::vector<double> axisX, axisY;
+
+    axisX.resize(3);
+    axisX[0] = imageOrientationPatient_[0];
+    axisX[1] = imageOrientationPatient_[1];
+    axisX[2] = imageOrientationPatient_[2];
+      
+    axisY.resize(3);
+    axisY[0] = imageOrientationPatient_[3];
+    axisY[1] = imageOrientationPatient_[4];
+    axisY[2] = imageOrientationPatient_[5];
+      
+    NeuroToolbox::CrossProduct(normal_, axisX, axisY);
+  }
+
+
+  void InputDicomInstance::ParsePixelSpacing()
+  {
+    std::vector<double> pixelSpacing;
+    if (NeuroToolbox::ParseVector(pixelSpacing, *tags_, Orthanc::DICOM_TAG_PIXEL_SPACING))
+    {
+      if (pixelSpacing.size() != 2)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+      }
+      else
+      {
+        pixelSpacingX_ = pixelSpacing[0];
+        pixelSpacingY_ = pixelSpacing[1];
+      }
+    }
+    else
+    {
+      pixelSpacingX_ = 1;
+      pixelSpacingY_ = 1;
+    }
+  }
+
+
+  void InputDicomInstance::ParseVoxelSpacingZ()
+  {
+    std::vector<double> v;
+    if (NeuroToolbox::ParseVector(v, *tags_, DICOM_TAG_SPACING_BETWEEN_SLICES))
+    {
+      if (v.size() != 1)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+      }
+      else
+      {
+        voxelSpacingZ_ = v[0];
+      }
+    }
+    else if (NeuroToolbox::ParseVector(v, *tags_, Orthanc::DICOM_TAG_SLICE_THICKNESS))
+    {
+      if (v.size() != 1)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+      }
+      else
+      {
+        voxelSpacingZ_ = v[0];
+      }
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, "Unable to determine spacing between slices");
+    }      
+  }
+
+
+  void InputDicomInstance::ParseRescale()
+  {
+    std::vector<double> v;
+      
+    if (NeuroToolbox::ParseVector(v, *tags_, Orthanc::DICOM_TAG_RESCALE_SLOPE))
+    {
+      if (v.size() == 1)
+      {
+        rescaleSlope_ = v[0];
+      }
+      else
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+      }
+    }
+    else
+    {
+      rescaleSlope_ = 1;
+    }
+
+    if (manufacturer_ == Manufacturer_Philips &&
+        NeuroToolbox::ParseVector(v, *tags_, DICOM_TAG_SLICE_SLOPE_PHILIPS))
+    {
+      if (v.size() == 1 &&
+          !NeuroToolbox::IsNear(v[0], 0))
+      {
+        rescaleSlope_ /= v[0];  // cf. PMC3998685
+      }
+      else
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+      }
+    }
+
+    if (NeuroToolbox::ParseVector(v, *tags_, Orthanc::DICOM_TAG_RESCALE_INTERCEPT))
+    {
+      if (v.size() == 1)
+      {
+        rescaleIntercept_ = v[0];
+      }
+      else
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+      }
+    }
+    else
+    {
+      rescaleIntercept_ = 0;
+    }
+  }
+
+
+  void InputDicomInstance::ParsePhaseEncodingDirection()
+  {
+    const std::string s = tags_->GetStringValue(DICOM_TAG_IN_PLANE_PHASE_ENCODING_DIRECTION, "", false);
+
+    if (s == "ROW")
+    {
+      phaseEncodingDirection_ = PhaseEncodingDirection_Row;
+    }
+    else if (s == "COL")
+    {
+      phaseEncodingDirection_ = PhaseEncodingDirection_Column;
+    }
+    else if (s.empty())
+    {
+      phaseEncodingDirection_ = PhaseEncodingDirection_None;
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+  }
+
+
+  void InputDicomInstance::ParseSliceTimingSiemens()
+  {
+    if (!NeuroToolbox::ParseVector(sliceTimingSiemens_, *tags_, DICOM_TAG_SLICE_TIMING_SIEMENS))
+    {
+      sliceTimingSiemens_.clear();
+    }
+  }
+
+
+  void InputDicomInstance::Setup()
+  {
+    assert(tags_.get() != NULL);
+
+    info_.reset(new Orthanc::DicomImageInformation(*tags_));
+
+    if (!tags_->ParseInteger32(instanceNumber_, Orthanc::DICOM_TAG_INSTANCE_NUMBER))
+    {
+      LOG(WARNING) << "DICOM instance without an instance number";
+    }
+    
+    manufacturer_ = ::Neuro::GetManufacturer(*tags_);
+    modality_ = ::Neuro::GetModality(*tags_);
+    hasEchoTime_ = tags_->ParseDouble(echoTime_, DICOM_TAG_ECHO_TIME);
+    hasAcquisitionTime_ = tags_->ParseDouble(acquisitionTime_, Orthanc::DICOM_TAG_ACQUISITION_TIME);
+    
+    ParseImagePositionPatient();
+    ParseImageOrientationPatient();
+    ParsePixelSpacing();
+    ParseVoxelSpacingZ();
+    ParseRescale();
+    ParseSliceTimingSiemens();
+    ParsePhaseEncodingDirection();
+  }
+  
+
+  double InputDicomInstance::GetImageOrientationPatient(unsigned int index) const
+  {
+    assert(imageOrientationPatient_.size() == 6);
+      
+    if (index >= 6)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      return imageOrientationPatient_[index];
+    }
+  }
+
+
+#if ORTHANC_ENABLE_DCMTK == 1
+  void InputDicomInstance::LoadDicom(const Orthanc::ParsedDicomFile& dicom)
+  {
+    tags_.reset(new Orthanc::DicomMap);
+    dicom.ExtractDicomSummary(*tags_, 0);
+
+    std::string csa;
+    if (dicom.GetTagValue(csa, DICOM_TAG_SIEMENS_CSA_HEADER))
+    {
+      csa_.Load(csa);
+    }
+
+    DcmSequenceOfItems *sequence = NULL;
+    if (const_cast<Orthanc::ParsedDicomFile&>(dicom).GetDcmtkObject().getDataset()->findAndGetSequence(
+          DcmTagKey(DICOM_TAG_UIH_MR_VFRAME_SEQUENCE.GetGroup(),
+                    DICOM_TAG_UIH_MR_VFRAME_SEQUENCE.GetElement()), sequence).good() &&
+        sequence != NULL)
+    {
+      for (unsigned long i = 0; i < sequence->card(); i++)
+      {
+        Orthanc::DicomMap m;
+        std::set<Orthanc::DicomTag> none;
+        Orthanc::FromDcmtkBridge::ExtractDicomSummary(m, *sequence->getItem(i), 0, none);
+        AddUIHFrameSequenceItem(m);
+      }
+    }
+
+    Setup();
+  }
+#endif
+
+
+  InputDicomInstance::~InputDicomInstance()
+  {
+    for (size_t i = 0; i < uihFrameSequence_.size(); i++)
+    {
+      assert(uihFrameSequence_[i] != NULL);
+      delete uihFrameSequence_[i];
+    }
+  }
+
+
+  const Orthanc::DicomMap& InputDicomInstance::GetUIHFrameSequenceItem(size_t index) const
+  {
+    if (index >= uihFrameSequence_.size())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      assert(uihFrameSequence_[index] != NULL);
+      return *uihFrameSequence_[index];
+    }
+  }
+
+
+  double InputDicomInstance::GetEchoTime() const
+  {
+    if (hasEchoTime_)
+    {
+      return echoTime_;
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+  }
+
+
+  double InputDicomInstance::GetAcquisitionTime() const
+  {
+    if (hasAcquisitionTime_)
+    {
+      return acquisitionTime_;
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+  }
+
+    
+  double InputDicomInstance::GetImagePositionPatient(unsigned int index) const
+  {
+    assert(imagePositionPatient_.size() == 3);
+      
+    if (index >= 3)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      return imagePositionPatient_[index];
+    }
+  }
+
+
+  double InputDicomInstance::GetAxisX(unsigned int index) const
+  {
+    if (index >= 3)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      return GetImageOrientationPatient(index);
+    }
+  }
+
+  
+  double InputDicomInstance::GetAxisY(unsigned int index) const
+  {
+    if (index >= 3)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      return GetImageOrientationPatient(3 + index);
+    }
+  }
+
+  
+  double InputDicomInstance::GetNormal(unsigned int index) const
+  {
+    assert(normal_.size() == 3);
+      
+    if (index >= 3)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      return normal_[index];
+    }
+  }
+
+  
+  unsigned int InputDicomInstance::GetMultiBandFactor() const
+  {
+    std::vector<double> v;
+    if (NeuroToolbox::ParseVector(v, *tags_, DICOM_TAG_SLICE_TIMING_SIEMENS))
+    {
+      unsigned int count = 0;
+
+      for (size_t i = 0; i < v.size(); i++)
+      {
+        if (NeuroToolbox::IsNear(v[i], v[0]))
+        {
+          count++;
+        }
+      }
+
+      return count;
+    }
+    else
+    {
+      return 0;
+    }
+  }
+
+
+  int InputDicomInstance::DetectSiemensSliceCode() const
+  {
+    size_t countZeros = 0;
+    for (size_t i = 0; i < sliceTimingSiemens_.size(); i++)
+    {
+      if (NeuroToolbox::IsNear(sliceTimingSiemens_[i], 0.0))
+      {
+        countZeros++;
+      }
+    }
+
+    const size_t minTimeIndex = std::distance(sliceTimingSiemens_.begin(), std::min_element(sliceTimingSiemens_.begin(), sliceTimingSiemens_.end()));
+
+    if (countZeros < 2)
+    {
+      const size_t size = sliceTimingSiemens_.size();  // corresponds to "itemsOK"
+
+      if (minTimeIndex == 1)
+      {
+        return NIFTI_SLICE_ALT_INC2; // e.g. 3,1,4,2
+      }
+      else if (minTimeIndex == size - 2)
+      {
+        return NIFTI_SLICE_ALT_DEC2; // e.g. 2,4,1,3 or 5,2,4,1,3
+      }
+      else if (size >= 3 &&
+               minTimeIndex == 0 &&
+               sliceTimingSiemens_[1] < sliceTimingSiemens_[2])
+      {
+        return NIFTI_SLICE_SEQ_INC; // e.g. 1,2,3,4
+      }
+      else if (size >= 3 &&
+               minTimeIndex == 0 &&
+               sliceTimingSiemens_[1] > sliceTimingSiemens_[2])
+      {
+        return NIFTI_SLICE_ALT_INC; //e.g. 1,3,2,4
+      }
+      else if (size >= 4 &&
+               minTimeIndex == size - 1 &&
+               sliceTimingSiemens_[size - 3] > sliceTimingSiemens_[size - 2])
+      {
+        return NIFTI_SLICE_SEQ_DEC; //e.g. 4,3,2,1 or 5,4,3,2,1
+      }
+      else if (size >= 4 &&
+               minTimeIndex == (size - 1) &&
+               sliceTimingSiemens_[size - 3] < sliceTimingSiemens_[size - 2])
+      {
+        return NIFTI_SLICE_ALT_DEC;
+      }
+      else
+      {
+        return NIFTI_SLICE_UNKNOWN;
+      }
+    }
+    else
+    {
+      return NIFTI_SLICE_UNKNOWN;
+    }
+  }
+
+
+  bool InputDicomInstance::LookupRepetitionTime(double& value) const
+  {
+    std::vector<double> repetitionTime;
+    if (NeuroToolbox::ParseVector(repetitionTime, *tags_, DICOM_TAG_REPETITION_TIME))
+    {
+      if (repetitionTime.size() == 1)
+      {
+        value = repetitionTime[0];
+        return true;
+      }
+      else
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+      }
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  void InputDicomInstance::ExtractSiemensMosaicSlices(std::list<Slice>& slices,
+                                                      size_t instanceIndexInCollection) const
+  {
+    // https://github.com/malaterre/GDCM/blob/master/Source/MediaStorageAndFileFormat/gdcmSplitMosaicFilter.cxx
+
+    uint32_t numberOfImagesInMosaic;
+    if (GetImageInformation().GetNumberOfFrames() != 1 ||
+        !GetCSAHeader().ParseUnsignedInteger32(numberOfImagesInMosaic, CSA_NUMBER_OF_IMAGES_IN_MOSAIC) ||
+        numberOfImagesInMosaic == 0)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+  
+    const unsigned int countPerAxis = static_cast<unsigned int>(std::ceil(sqrtf(numberOfImagesInMosaic)));
+
+    if (GetImageInformation().GetWidth() % countPerAxis != 0 ||
+        GetImageInformation().GetHeight() % countPerAxis != 0 ||
+        numberOfImagesInMosaic > (countPerAxis * countPerAxis))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+
+    // https://nipy.org/nibabel/dicom/dicom_mosaic.html#dicom-orientation-for-mosaic
+
+    const unsigned int width = GetImageInformation().GetWidth() / countPerAxis;
+    const unsigned int height = GetImageInformation().GetHeight() / countPerAxis;
+
+    mat44 sto_xyz;
+    
+    for (uint8_t i = 0; i < 3; i++)
+    {
+      sto_xyz.m[i][0] = GetAxisX(i) * GetPixelSpacingX();
+      sto_xyz.m[i][1] = GetAxisY(i) * GetPixelSpacingY();
+      sto_xyz.m[i][2] = GetNormal(i) * GetVoxelSpacingZ();
+      sto_xyz.m[i][3] = GetImagePositionPatient(i);
+    }
+
+    {
+      const double mc = static_cast<double>(GetImageInformation().GetWidth());
+      const double mr = static_cast<double>(GetImageInformation().GetHeight());
+      const double nc = static_cast<double>(width);
+      const double nr = static_cast<double>(height);
+      const double dc = (mc - nc) / 2.0;
+      const double dr = (mr - nr) / 2.0;
+      sto_xyz.m[0][3] = GetImagePositionPatient(0) + sto_xyz.m[0][0] * dc + sto_xyz.m[0][1] * dr;
+      sto_xyz.m[1][3] = GetImagePositionPatient(1) + sto_xyz.m[1][0] * dc + sto_xyz.m[1][1] * dr;
+      sto_xyz.m[2][3] = GetImagePositionPatient(2) + sto_xyz.m[2][0] * dc + sto_xyz.m[2][1] * dr;
+    }
+
+    std::vector<double> sliceNormalVector;
+    if (!GetCSAHeader().GetTag(CSA_SLICE_NORMAL_VECTOR).ParseVector(sliceNormalVector) ||
+        sliceNormalVector.size() != 3)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+    
+    {
+      unsigned int pos = 0;
+      for (unsigned int y = 0; y < countPerAxis; y++)
+      {
+        for (unsigned int x = 0; x < countPerAxis; x++, pos++)
+        {
+          if (pos < numberOfImagesInMosaic)
+          {
+            double z = GetVoxelSpacingZ() * static_cast<double>(pos);
+            
+            slices.push_back(Slice(instanceIndexInCollection, 0 /* frame index */, GetInstanceNumber(),
+                                   x * width, y * height, width, height,
+                                   sto_xyz.m[0][3] + z * sliceNormalVector[0],
+                                   sto_xyz.m[1][3] + z * sliceNormalVector[1],
+                                   sto_xyz.m[2][3] + z * sliceNormalVector[2],
+                                   sliceNormalVector[0], sliceNormalVector[1], sliceNormalVector[2]));
+
+            if (HasAcquisitionTime())
+            {
+              slices.back().SetAcquisitionTime(GetAcquisitionTime());
+            }
+          }
+        }
+      }
+    }
+  }
+
+
+  void InputDicomInstance::ExtractUIHSlices(std::list<Slice>& slices,
+                                            size_t instanceIndexInCollection) const
+  {
+    // https://github.com/rordenlab/dcm2niix/issues/225#issuecomment-422645183
+    const double total = static_cast<double>(GetUIHFrameSequenceSize());
+    const double dcols = std::ceil(sqrt(total));
+    if (dcols <= 0 ||
+        GetImageInformation().GetNumberOfFrames() != 1)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+
+    const unsigned int cols = static_cast<unsigned int>(dcols);
+    if (GetImageInformation().GetWidth() % cols != 0 ||
+        GetUIHFrameSequenceSize() % cols != 0)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+
+    const unsigned int rows = static_cast<unsigned int>(GetUIHFrameSequenceSize() / cols);
+    assert(cols * rows == GetUIHFrameSequenceSize());
+
+    if (GetImageInformation().GetHeight() % rows != 0)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+
+    unsigned int width = GetImageInformation().GetWidth() / cols;
+    unsigned int height = GetImageInformation().GetHeight() / rows;
+
+    unsigned int pos = 0;
+    for (unsigned int y = 0; y < rows; y++)
+    {
+      for (unsigned int x = 0; x < cols; x++, pos++)
+      {
+        std::vector<double> origin;
+        std::vector<double> acquisitionTime;
+        if (!NeuroToolbox::ParseVector(origin, GetUIHFrameSequenceItem(pos), Orthanc::DICOM_TAG_IMAGE_POSITION_PATIENT) ||
+            !NeuroToolbox::ParseVector(acquisitionTime, GetUIHFrameSequenceItem(pos), Orthanc::DICOM_TAG_ACQUISITION_TIME) ||
+            origin.size() != 3 ||
+            acquisitionTime.size() != 1)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+        }
+        else
+        {
+          slices.push_back(Slice(instanceIndexInCollection, 0 /* frame index */, GetInstanceNumber(),
+                                 x * width, y * height, width, height,
+                                 origin[0], origin[1], origin[2],
+                                 GetNormal(0), GetNormal(1), GetNormal(2)));
+
+          slices.back().SetAcquisitionTime(acquisitionTime[0]);
+        }
+      }
+    }
+  }
+  
+  
+  void InputDicomInstance::ExtractGenericSlices(std::list<Slice>& slices,
+                                                size_t instanceIndexInCollection) const
+  {
+    unsigned int numberOfFrames = GetImageInformation().GetNumberOfFrames();
+    
+    if (numberOfFrames != 1)
+    {
+      // This is the case of RT-DOSE
+      std::vector<double> frameOffset;
+      if (!NeuroToolbox::ParseVector(frameOffset, GetTags(), Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR) ||
+          frameOffset.size() != numberOfFrames)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented,
+                                        "Cannot detect the 3D coordinates in a multiframe instance");
+      }
+      else
+      {
+        for (unsigned int frame = 0; frame < numberOfFrames; frame++)
+        {
+          double z = frameOffset[frame];
+          slices.push_back(Slice(instanceIndexInCollection, frame, GetInstanceNumber(),
+                                 0, 0, GetImageInformation().GetWidth(),
+                                 GetImageInformation().GetHeight(),
+                                 GetImagePositionPatient(0) + z * GetNormal(0),
+                                 GetImagePositionPatient(1) + z * GetNormal(1),
+                                 GetImagePositionPatient(2) + z * GetNormal(2),
+                                 GetNormal(0), GetNormal(1), GetNormal(2)));
+
+          if (HasAcquisitionTime())
+          {
+            slices.back().SetAcquisitionTime(GetAcquisitionTime());
+          }
+        }
+      }
+    }
+    else
+    {
+      slices.push_back(Slice(instanceIndexInCollection, 0 /* single frame */, GetInstanceNumber(),
+                             0, 0, GetImageInformation().GetWidth(),
+                             GetImageInformation().GetHeight(),
+                             GetImagePositionPatient(0),
+                             GetImagePositionPatient(1),
+                             GetImagePositionPatient(2),
+                             GetNormal(0), GetNormal(1), GetNormal(2)));
+
+      if (HasAcquisitionTime())
+      {
+        slices.back().SetAcquisitionTime(GetAcquisitionTime());
+      }
+    }
+  }
+
+
+  void InputDicomInstance::ExtractSlices(std::list<Slice>& slices,
+                                         size_t instanceIndexInCollection) const
+  {
+    if (GetManufacturer() == Manufacturer_Siemens &&
+        GetCSAHeader().HasTag(CSA_NUMBER_OF_IMAGES_IN_MOSAIC))
+    {
+      ExtractSiemensMosaicSlices(slices, instanceIndexInCollection);
+    }
+    else if (GetManufacturer() == Manufacturer_UIH &&
+             GetUIHFrameSequenceSize() > 0)
+    {
+      ExtractUIHSlices(slices, instanceIndexInCollection);
+    }
+    else
+    {
+      ExtractGenericSlices(slices, instanceIndexInCollection);
+    }
+  }
+
+
+  size_t InputDicomInstance::ComputeInstanceNiftiBodySize() const
+  {
+    Orthanc::PixelFormat format;
+    if (!GetImageInformation().ExtractPixelFormat(format, true))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+
+    size_t bytesPerPixel = Orthanc::GetBytesPerPixel(format);
+
+    std::list<Neuro::Slice> slices;
+    ExtractSlices(slices, 0 /* unused */);
+
+    unsigned int niftiBodySize = 0;
+    for (std::list<Neuro::Slice>::const_iterator it = slices.begin(); it != slices.end(); ++it)
+    {
+      niftiBodySize += bytesPerPixel * it->GetWidth() * it->GetHeight();
+    }
+
+    return niftiBodySize;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Sources/Framework/InputDicomInstance.h	Fri Apr 22 17:58:59 2022 +0200
@@ -0,0 +1,227 @@
+/**
+ * Neuroimaging plugin for Orthanc
+ * Copyright (C) 2021-2022 Sebastien Jodogne, UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#if !defined(ORTHANC_ENABLE_DCMTK)
+#  error The macro ORTHANC_ENABLE_DCMTK must be defined
+#endif
+
+#include "CSAHeader.h"
+#include "NeuroEnumerations.h"
+#include "Slice.h"
+
+#include <DicomFormat/DicomImageInformation.h>
+#include <DicomFormat/DicomMap.h>
+
+#if ORTHANC_ENABLE_DCMTK == 1
+#  include <DicomParsing/ParsedDicomFile.h>
+#endif
+
+
+namespace Neuro
+{
+  class InputDicomInstance : public boost::noncopyable
+  {
+  private:
+    // Inputs
+    std::unique_ptr<Orthanc::DicomMap>  tags_;
+    CSAHeader                           csa_;
+    std::vector<Orthanc::DicomMap*>     uihFrameSequence_;
+
+    // Extracted values
+    std::unique_ptr<Orthanc::DicomImageInformation>  info_;    
+    int32_t                             instanceNumber_;
+    Manufacturer                        manufacturer_;
+    Modality                            modality_;
+    bool                                hasEchoTime_;
+    double                              echoTime_;
+    bool                                hasAcquisitionTime_;
+    double                              acquisitionTime_;
+
+    // Parsed values
+    std::vector<double>                 imagePositionPatient_;
+    std::vector<double>                 imageOrientationPatient_;
+    std::vector<double>                 normal_;
+    double                              pixelSpacingX_;
+    double                              pixelSpacingY_;
+    double                              voxelSpacingZ_;
+    double                              rescaleSlope_;
+    double                              rescaleIntercept_;
+    PhaseEncodingDirection              phaseEncodingDirection_;
+    std::vector<double>                 sliceTimingSiemens_;
+
+    void ParseImagePositionPatient();
+    
+    void ParseImageOrientationPatient();
+    
+    void ParsePixelSpacing();
+    
+    void ParseVoxelSpacingZ();
+    
+    void ParseRescale();
+
+    void ParsePhaseEncodingDirection();
+    
+    void ParseSliceTimingSiemens();
+
+    void Setup();
+
+    double GetImageOrientationPatient(unsigned int index) const;
+    
+#if ORTHANC_ENABLE_DCMTK == 1
+    void LoadDicom(const Orthanc::ParsedDicomFile& dicom);
+#endif
+
+    void ExtractSiemensMosaicSlices(std::list<Slice>& slices,
+                                    size_t instanceIndexInCollection) const;
+
+    void ExtractUIHSlices(std::list<Slice>& slices,
+                          size_t instanceIndexInCollection) const;
+
+    void ExtractGenericSlices(std::list<Slice>& slices,
+                              size_t instanceIndexInCollection) const;
+
+  public:
+    explicit InputDicomInstance(const Orthanc::DicomMap& tags) :
+      tags_(tags.Clone())
+    {
+      Setup();
+    }
+
+#if ORTHANC_ENABLE_DCMTK == 1
+    explicit InputDicomInstance(const Orthanc::ParsedDicomFile& dicom)
+    {
+      LoadDicom(dicom);
+    }
+#endif
+
+    ~InputDicomInstance();
+
+    const Orthanc::DicomMap& GetTags() const
+    {
+      return *tags_;
+    }
+
+    const CSAHeader& GetCSAHeader() const
+    {
+      return csa_;
+    }
+    
+    CSAHeader& GetCSAHeader()
+    {
+      return csa_;
+    }
+
+    void AddUIHFrameSequenceItem(const Orthanc::DicomMap& item)
+    {
+      uihFrameSequence_.push_back(item.Clone());
+    }
+
+    size_t GetUIHFrameSequenceSize() const
+    {
+      return uihFrameSequence_.size();
+    }
+
+    const Orthanc::DicomMap& GetUIHFrameSequenceItem(size_t index) const;
+
+    const Orthanc::DicomImageInformation& GetImageInformation() const
+    {
+      return *info_;
+    }
+
+    int32_t GetInstanceNumber() const
+    {
+      return instanceNumber_;
+    }
+
+    Manufacturer GetManufacturer() const
+    {
+      return manufacturer_;
+    }
+
+    Modality GetModality() const
+    {
+      return modality_;
+    }
+
+    bool HasEchoTime() const
+    {
+      return hasEchoTime_;
+    }
+
+    double GetEchoTime() const;
+
+    bool HasAcquisitionTime() const
+    {
+      return hasAcquisitionTime_;
+    }
+
+    double GetAcquisitionTime() const;
+
+    double GetImagePositionPatient(unsigned int index) const;
+    
+    double GetAxisX(unsigned int index) const;
+
+    double GetAxisY(unsigned int index) const;
+
+    double GetNormal(unsigned int index) const;
+
+    double GetPixelSpacingX() const
+    {
+      return pixelSpacingX_;
+    }
+
+    double GetPixelSpacingY() const
+    {
+      return pixelSpacingY_;
+    }
+
+    double GetVoxelSpacingZ() const
+    {
+      return voxelSpacingZ_;
+    }
+
+    double GetRescaleSlope() const
+    {
+      return rescaleSlope_;
+    }
+
+    double GetRescaleIntercept() const
+    {
+      return rescaleIntercept_;
+    }
+    
+    PhaseEncodingDirection GetPhaseEncodingDirection() const
+    {
+      return phaseEncodingDirection_;
+    }
+    
+    unsigned int GetMultiBandFactor() const;
+    
+    int DetectSiemensSliceCode() const;
+
+    bool LookupRepetitionTime(double& value) const;
+
+    void ExtractSlices(std::list<Slice>& slices,
+                       size_t instanceIndexInCollection) const;
+
+    size_t ComputeInstanceNiftiBodySize() const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Sources/Framework/NeuroEnumerations.h	Fri Apr 22 17:58:59 2022 +0200
@@ -0,0 +1,53 @@
+/**
+ * Neuroimaging plugin for Orthanc
+ * Copyright (C) 2021-2022 Sebastien Jodogne, UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+
+namespace Neuro
+{
+  enum PhaseEncodingDirection
+  {
+    PhaseEncodingDirection_None,
+    PhaseEncodingDirection_Column,
+    PhaseEncodingDirection_Row
+  };
+
+  enum Modality
+  {
+    Modality_Unknown,
+    Modality_MR,
+    Modality_PET,
+    Modality_CT
+  };
+
+  enum Manufacturer
+  {
+    Manufacturer_Unknown,
+    Manufacturer_Siemens,
+    Manufacturer_GE,
+    Manufacturer_Hitachi,
+    Manufacturer_Mediso,
+    Manufacturer_Philips,
+    Manufacturer_Toshiba,
+    Manufacturer_Canon,
+    Manufacturer_UIH,
+    Manufacturer_Bruker
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Sources/Framework/NeuroToolbox.cpp	Fri Apr 22 17:58:59 2022 +0200
@@ -0,0 +1,115 @@
+/**
+ * Neuroimaging plugin for Orthanc
+ * Copyright (C) 2021-2022 Sebastien Jodogne, UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "NeuroToolbox.h"
+
+#include <OrthancException.h>
+#include <SerializationToolbox.h>
+#include <Toolbox.h>
+
+#include <boost/lexical_cast.hpp>
+
+
+namespace Neuro
+{
+  double NeuroToolbox::FixDicomTime(double t)
+  {
+    // Switch from the "HHMMSS.frac" format of DICOM to the number of
+    // seconds since midnight
+    const double frac = t - std::floor(t);
+    const unsigned int integral = static_cast<unsigned int>(std::floor(t));
+    const unsigned int seconds = integral % 100;
+    const unsigned int minutes = (integral / 100) % 100;
+    const unsigned int hours = (integral / 10000);
+
+    if (seconds >= 60 ||
+        minutes >= 60 ||
+        hours >= 24)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, "Badly formatted DICOM time: " +
+                                      boost::lexical_cast<std::string>(t));
+    }
+    else
+    {
+      return static_cast<double>(hours * 3600 + minutes * 60 + seconds) + frac;
+    }
+  }
+
+
+  bool NeuroToolbox::IsNear(double a,
+                            double b,
+                            double threshold)
+  {
+    return fabs(a - b) <= threshold;
+  }
+
+
+  bool NeuroToolbox::IsNear(double a,
+                            double b)
+  {
+    return IsNear(a, b, std::numeric_limits<float>::epsilon());
+  }
+
+
+  bool NeuroToolbox::ParseVector(std::vector<double>& target,
+                                 const Orthanc::DicomMap& dicom,
+                                 const Orthanc::DicomTag& tag)
+  {
+    std::string value;
+    if (dicom.LookupStringValue(value, tag, false))
+    {
+      std::vector<std::string> tokens;
+      Orthanc::Toolbox::TokenizeString(tokens, value, '\\');
+
+      target.resize(tokens.size());
+      for (size_t i = 0; i < tokens.size(); i++)
+      {
+        if (!Orthanc::SerializationToolbox::ParseDouble(target[i], tokens[i]))
+        {
+          return false;
+        }
+      }
+
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  void NeuroToolbox::CrossProduct(std::vector<double>& target,
+                                  const std::vector<double>& u,
+                                  const std::vector<double>& v)
+  {
+    if (u.size() != 3 ||
+        v.size() != 3)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      target.resize(3);
+      target[0] = u[1] * v[2] - u[2] * v[1];
+      target[1] = u[2] * v[0] - u[0] * v[2];
+      target[2] = u[0] * v[1] - u[1] * v[0];
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Sources/Framework/NeuroToolbox.h	Fri Apr 22 17:58:59 2022 +0200
@@ -0,0 +1,55 @@
+/**
+ * Neuroimaging plugin for Orthanc
+ * Copyright (C) 2021-2022 Sebastien Jodogne, UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include <DicomFormat/DicomMap.h>
+
+
+namespace Neuro
+{
+  static const Orthanc::DicomTag DICOM_TAG_SIEMENS_CSA_HEADER(0x0029, 0x1010);
+  static const Orthanc::DicomTag DICOM_TAG_UIH_MR_VFRAME_SEQUENCE(0x0065, 0x1051); // https://github.com/rordenlab/dcm2niix/issues/225
+
+  class NeuroToolbox
+  {
+  private:
+    NeuroToolbox()  // This is a pure static class
+    {
+    }
+    
+  public:
+    static double FixDicomTime(double t);
+
+    static bool IsNear(double a,
+                       double b,
+                       double threshold);
+
+    static bool IsNear(double a,
+                       double b);
+    
+    static bool ParseVector(std::vector<double>& target,
+                            const Orthanc::DicomMap& dicom,
+                            const Orthanc::DicomTag& tag);
+    
+    static void CrossProduct(std::vector<double>& target,
+                             const std::vector<double>& u,
+                             const std::vector<double>& v);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Sources/Framework/NiftiWriter.cpp	Fri Apr 22 17:58:59 2022 +0200
@@ -0,0 +1,119 @@
+/**
+ * Neuroimaging plugin for Orthanc
+ * Copyright (C) 2021-2022 Sebastien Jodogne, UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "NiftiWriter.h"
+
+#include <Compression/GzipCompressor.h>
+#include <Images/Image.h>
+#include <OrthancException.h>
+
+#include <cassert>
+
+
+namespace Neuro
+{
+  void NiftiWriter::WriteHeader(const nifti_image& header)
+  {
+    if (hasHeader_)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      nifti_image fixed;
+      memcpy(&fixed, &header, sizeof(nifti_image));
+
+      std::string empty(1, '\0');
+      fixed.fname = &empty[0];
+      fixed.iname = NULL;
+      fixed.num_ext = 0;  // no extension
+    
+      nifti_set_iname_offset(&fixed);
+
+      if (fixed.nifti_type != NIFTI_FTYPE_NIFTI1_1)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+      }
+    
+      nifti_1_header serialized = nifti_convert_nim2nhdr(&fixed);
+      serialized.vox_offset = (348 + 4);  // (*)
+
+      static const uint8_t nope[4] = { 0, 0, 0, 0 };
+
+      assert(sizeof(serialized) == 348);
+      buffer_.AddChunk(&serialized, sizeof(serialized));
+
+      assert(sizeof(nope) == 4);
+      buffer_.AddChunk(&nope, sizeof(nope));  // because of (*)
+
+      hasHeader_ = true;
+    }
+  }
+
+
+  void NiftiWriter::AddSlice(const Orthanc::ImageAccessor& slice)
+  {
+    if (!hasHeader_)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    else if (slice.GetWidth() != 0 &&
+             slice.GetHeight() != 0)
+    {
+      Orthanc::Image image(slice.GetFormat(), slice.GetWidth(), slice.GetHeight(),
+                           true /* force minimal pitch, as no pitch is allowed in NIfTI */);
+
+      const size_t rowSize = GetBytesPerPixel(image.GetFormat()) * image.GetWidth();
+      if (rowSize != image.GetPitch())
+      {
+        // Should never happen because of minimal pitch
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+
+      assert(image.GetPitch() <= slice.GetPitch());
+
+      for (unsigned int y = 0; y < slice.GetHeight(); y++)
+      {
+        const uint8_t *source = reinterpret_cast<const uint8_t*>(slice.GetConstRow(y));
+        uint8_t *target = reinterpret_cast<uint8_t*>(image.GetRow(image.GetHeight() - 1 - y));
+        memcpy(target, source, rowSize);
+      }
+
+      buffer_.AddChunk(image.GetConstBuffer(), rowSize * image.GetHeight());
+    }
+  }
+
+
+  void NiftiWriter::Flatten(std::string& target,
+                            bool compress)
+  {
+    if (compress)
+    {
+      std::string uncompressed;
+      buffer_.Flatten(uncompressed);
+
+      Orthanc::GzipCompressor compressor;
+      Orthanc::IBufferCompressor::Compress(target, compressor, uncompressed);
+    }
+    else
+    {
+      buffer_.Flatten(target);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Sources/Framework/NiftiWriter.h	Fri Apr 22 17:58:59 2022 +0200
@@ -0,0 +1,49 @@
+/**
+ * Neuroimaging plugin for Orthanc
+ * Copyright (C) 2021-2022 Sebastien Jodogne, UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include <ChunkedBuffer.h>
+#include <Images/ImageAccessor.h>
+
+#include <nifti1_io.h>
+
+
+namespace Neuro
+{
+  class NiftiWriter : public boost::noncopyable
+  {
+  private:
+    bool                    hasHeader_;
+    Orthanc::ChunkedBuffer  buffer_;
+
+  public:
+    NiftiWriter() :
+      hasHeader_(false)
+    {
+    }
+  
+    void WriteHeader(const nifti_image& header);
+
+    void AddSlice(const Orthanc::ImageAccessor& slice);
+
+    void Flatten(std::string& target,
+                 bool compress);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Sources/Framework/Slice.cpp	Fri Apr 22 17:58:59 2022 +0200
@@ -0,0 +1,116 @@
+/**
+ * Neuroimaging plugin for Orthanc
+ * Copyright (C) 2021-2022 Sebastien Jodogne, UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "Slice.h"
+
+#include <OrthancException.h>
+
+
+namespace Neuro
+{
+  Slice::Slice(size_t instanceIndexInCollection,
+               unsigned int frameNumber,
+               int32_t instanceNumber,
+               unsigned int x,
+               unsigned int y,
+               unsigned int width,
+               unsigned int height,
+               double originX,
+               double originY,
+               double originZ,
+               double normalX,
+               double normalY,
+               double normalZ) :
+    instanceIndexInCollection_(instanceIndexInCollection),
+    frameNumber_(frameNumber),
+    instanceNumber_(instanceNumber),
+    x_(x),
+    y_(y),
+    width_(width),
+    height_(height),
+    originX_(originX),
+    originY_(originY),
+    originZ_(originZ),
+    normalX_(normalX),
+    normalY_(normalY),
+    normalZ_(normalZ),
+    hasAcquisitionTime_(false),
+    acquisitionTime_(0)  // dummy value
+  {
+    projectionAlongNormal_ = (originX * normalX + originY * normalY + originZ * normalZ);
+  }
+
+
+  double Slice::GetNormal(unsigned int i) const
+  {
+    switch (i)
+    {
+      case 0:
+        return normalX_;
+
+      case 1:
+        return normalY_;
+
+      case 2:
+        return normalZ_;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+  }
+  
+
+  double Slice::GetOrigin(unsigned int i) const
+  {
+    switch (i)
+    {
+      case 0:
+        return originX_;
+
+      case 1:
+        return originY_;
+
+      case 2:
+        return originZ_;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  void Slice::SetAcquisitionTime(double t)
+  {
+    hasAcquisitionTime_ = true;
+    acquisitionTime_ = t;
+  }
+
+
+  double Slice::GetAcquisitionTime() const
+  {
+    if (hasAcquisitionTime_)
+    {
+      return acquisitionTime_;
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Sources/Framework/Slice.h	Fri Apr 22 17:58:59 2022 +0200
@@ -0,0 +1,116 @@
+/**
+ * Neuroimaging plugin for Orthanc
+ * Copyright (C) 2021-2022 Sebastien Jodogne, UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include <stddef.h>
+#include <stdint.h>
+
+
+namespace Neuro
+{
+  class Slice
+  {
+  private:
+    size_t        instanceIndexInCollection_;
+    unsigned int  frameNumber_;
+    int32_t       instanceNumber_;
+    unsigned int  x_;
+    unsigned int  y_;
+    unsigned int  width_;
+    unsigned int  height_;
+    double        originX_;
+    double        originY_;
+    double        originZ_;
+    double        normalX_;
+    double        normalY_;
+    double        normalZ_;
+    bool          hasAcquisitionTime_;
+    double        acquisitionTime_;
+    double        projectionAlongNormal_;
+
+  public:
+    Slice(size_t instanceIndexInCollection,
+          unsigned int frameNumber,
+          int32_t instanceNumber,
+          unsigned int x,
+          unsigned int y,
+          unsigned int width,
+          unsigned int height,
+          double originX,
+          double originY,
+          double originZ,
+          double normalX,
+          double normalY,
+          double normalZ);
+
+    size_t GetInstanceIndexInCollection() const
+    {
+      return instanceIndexInCollection_;
+    }
+
+    unsigned int GetFrameNumber() const
+    {
+      return frameNumber_;
+    }
+
+    int32_t GetInstanceNumber() const
+    {
+      return instanceNumber_;
+    }
+
+    unsigned int GetX() const
+    {
+      return x_;
+    }
+
+    unsigned int GetY() const
+    {
+      return y_;
+    }
+
+    unsigned int GetWidth() const
+    {
+      return width_;
+    }
+
+    unsigned int GetHeight() const
+    {
+      return height_;
+    }
+
+    double GetNormal(unsigned int i) const;
+
+    double GetOrigin(unsigned int i) const;
+
+    double GetProjectionAlongNormal() const
+    {
+      return projectionAlongNormal_;
+    }
+
+    void SetAcquisitionTime(double t);
+
+    bool HasAcquisitionTime() const
+    {
+      return hasAcquisitionTime_;
+    }
+
+    double GetAcquisitionTime() const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Sources/Plugin/OrthancExplorer.js	Fri Apr 22 17:58:59 2022 +0200
@@ -0,0 +1,53 @@
+/**
+ * Neuroimaging plugin for Orthanc
+ * Copyright (C) 2021-2022 Sebastien Jodogne, UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+$('#series').live('pagebeforecreate', function() {
+  var b = $('<a>')
+      .attr('data-role', 'button')
+      .attr('href', '#')
+      .attr('data-icon', 'search')
+      .attr('data-theme', 'e')
+      .text('Export to NIfTI');
+
+  b.insertBefore($('#series-delete').parent().parent());
+  b.click(function(e) {
+    if ($.mobile.pageData) {
+      e.preventDefault();  // stop the browser from following
+      window.location.href = '../series/' + $.mobile.pageData.uuid + '/nifti';
+    }
+  });
+});
+
+
+$('#instance').live('pagebeforecreate', function() {
+  var b = $('<a>')
+      .attr('data-role', 'button')
+      .attr('href', '#')
+      .attr('data-icon', 'search')
+      .attr('data-theme', 'e')
+      .text('Export to NIfTI');
+
+  b.insertBefore($('#instance-delete').parent().parent());
+  b.click(function(e) {
+    if ($.mobile.pageData) {
+      e.preventDefault();  // stop the browser from following
+      window.location.href = '../instances/' + $.mobile.pageData.uuid + '/nifti';
+    }
+  });
+});
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Sources/Plugin/Plugin.cpp	Fri Apr 22 17:58:59 2022 +0200
@@ -0,0 +1,286 @@
+/**
+ * Neuroimaging plugin for Orthanc
+ * Copyright (C) 2021-2022 Sebastien Jodogne, UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "PluginFrameDecoder.h"
+
+#include "../Framework/NeuroToolbox.h"
+#include "../Framework/NiftiWriter.h"
+
+#include <EmbeddedResources.h>
+
+#include <Logging.h>
+#include <SystemToolbox.h>
+
+
+static void CreateNifti(std::string& target,
+                        const Neuro::DicomInstancesCollection& collection,
+                        bool compress)
+{
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  collection.CreateNiftiHeader(nifti, slices);
+
+  Neuro::NiftiWriter writer;
+  writer.WriteHeader(nifti);
+
+  Neuro::PluginFrameDecoder decoder(collection);
+  Neuro::IDicomFrameDecoder::Apply(writer, decoder, slices);
+  
+  writer.Flatten(target, compress);
+}
+
+
+static Neuro::InputDicomInstance* AcquireInstance(const std::string& instanceId)
+{
+#if 0
+  /**
+   * This version uses DCMTK. It should be avoided for performance, as
+   * it requires reading the entire DICOM files from the disk, whereas
+   * "OrthancPluginDicomInstanceToJson()" will only read the DICOM
+   * files up to pixel data, and will take advantage of the caching
+   * mechanisms implemented inside the Orthanc core.
+   **/
+  OrthancPlugins::MemoryBuffer dicom;
+  dicom.GetDicomInstance(instanceId);
+
+  Orthanc::ParsedDicomFile parsed(dicom.GetData(), dicom.GetSize());
+  return new Neuro::InputDicomInstance(parsed);
+  
+#else
+  Orthanc::DicomMap tags;
+
+  {
+    OrthancPlugins::OrthancString s;
+    s.Assign(OrthancPluginDicomInstanceToJson(
+               OrthancPlugins::GetGlobalContext(), instanceId.c_str(), OrthancPluginDicomToJsonFormat_Full,
+               static_cast<OrthancPluginDicomToJsonFlags>(OrthancPluginDicomToJsonFlags_IncludePrivateTags |
+                                                          OrthancPluginDicomToJsonFlags_IncludeUnknownTags |
+                                                          OrthancPluginDicomToJsonFlags_StopAfterPixelData |
+                                                          OrthancPluginDicomToJsonFlags_SkipGroupLengths), 0));
+
+    Json::Value json;
+    if (s.GetContent() == NULL ||
+        !OrthancPlugins::ReadJson(json, s.GetContent()))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentItem, "Missing instance: " + instanceId);
+    }
+    
+    tags.FromDicomAsJson(json);
+  }
+
+  std::unique_ptr<Neuro::InputDicomInstance> instance(new Neuro::InputDicomInstance(tags));
+  
+  switch (instance->GetManufacturer())
+  {
+    case Neuro::Manufacturer_Siemens:
+    {
+      std::string csa;
+      if (OrthancPlugins::RestApiGetString(csa, "/instances/" + instanceId + "/content/" +
+                                           Neuro::DICOM_TAG_SIEMENS_CSA_HEADER.Format(), false))
+      {
+        instance->GetCSAHeader().Load(csa);
+      }
+      break;
+    }
+
+    case Neuro::Manufacturer_UIH:
+    {
+      const std::string uri = "/instances/" + instanceId + "/content/" + Neuro::DICOM_TAG_UIH_MR_VFRAME_SEQUENCE.Format();
+      
+      Json::Value uih;
+      if (OrthancPlugins::RestApiGet(uih, uri, false) &&
+          uih.type() == Json::arrayValue)
+      {
+        for (Json::Value::ArrayIndex i = 0; i < uih.size(); i++)
+        {
+          Json::Value tags2;
+
+          if (uih[i].type() != Json::stringValue ||
+              !OrthancPlugins::RestApiGet(tags2, uri + "/" + uih[i].asString(), false) ||
+              tags2.type() != Json::arrayValue)
+          {
+            throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+          }
+
+          Orthanc::DicomMap m;
+
+          for (Json::Value::ArrayIndex j = 0; j < tags2.size(); j++)
+          {
+            Orthanc::DicomTag tag(0, 0);
+            std::string value;
+            
+            if (tags2[j].type() != Json::stringValue ||
+                !Orthanc::DicomTag::ParseHexadecimal(tag, tags2[j].asCString()) ||
+                !OrthancPlugins::RestApiGetString(value, uri + "/" + uih[i].asString() + "/" + tags2[j].asString(), false))
+            {
+              throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+            }
+            else
+            {
+              m.SetValue(tag, value, false);
+            }
+          }
+
+          instance->AddUIHFrameSequenceItem(m);
+        }
+      }
+      break;
+    }
+
+    default:
+      break;
+  }
+
+  return instance.release();
+#endif
+}  
+
+
+void SeriesToNifti(OrthancPluginRestOutput* output,
+                   const char* url,
+                   const OrthancPluginHttpRequest* request)
+{
+  static const char* const KEY_INSTANCES = "Instances";
+
+  OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
+  
+  if (request->method != OrthancPluginHttpMethod_Get)
+  {
+    OrthancPluginSendMethodNotAllowed(context, output, "GET");
+  }
+  else
+  {
+    const std::string seriesId(request->groups[0]);
+
+    Json::Value series;
+    if (!OrthancPlugins::RestApiGet(series, "/series/" + seriesId, false))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentItem, "Missing series: " + seriesId);
+    }
+
+    if (series.type() != Json::objectValue ||
+        !series.isMember(KEY_INSTANCES) ||
+        series[KEY_INSTANCES].type() != Json::arrayValue)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+
+    Neuro::DicomInstancesCollection collection;
+
+    for (Json::Value::ArrayIndex i = 0; i < series[KEY_INSTANCES].size(); i++)
+    {
+      if (series[KEY_INSTANCES][i].type() != Json::stringValue)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+      else
+      {
+        const std::string id = series[KEY_INSTANCES][i].asString();
+        collection.AddInstance(AcquireInstance(id), id);
+      }
+    }
+
+    std::string nifti;
+    CreateNifti(nifti, collection, false /* todo - compress */);
+
+    const std::string contentDisposition = "filename=\"" + seriesId + ".nii\"";
+    OrthancPluginSetHttpHeader(context, output, "Content-Disposition", contentDisposition.c_str());
+  
+    OrthancPluginAnswerBuffer(context, output, nifti.c_str(), nifti.size(), "application/octet-stream");
+  }
+}
+
+
+void InstanceToNifti(OrthancPluginRestOutput* output,
+                     const char* url,
+                     const OrthancPluginHttpRequest* request)
+{
+  OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
+  
+  if (request->method != OrthancPluginHttpMethod_Get)
+  {
+    OrthancPluginSendMethodNotAllowed(context, output, "GET");
+  }
+  else
+  {
+    const std::string instanceId(request->groups[0]);
+
+    Neuro::DicomInstancesCollection collection;
+    collection.AddInstance(AcquireInstance(instanceId), instanceId);
+
+    std::string nifti;
+    CreateNifti(nifti, collection, false /* todo - compress */);
+
+    const std::string contentDisposition = "filename=\"" + instanceId + ".nii\"";
+    OrthancPluginSetHttpHeader(context, output, "Content-Disposition", contentDisposition.c_str());
+  
+    OrthancPluginAnswerBuffer(context, output, nifti.c_str(), nifti.size(), "application/octet-stream");
+  }
+}
+
+
+extern "C"
+{
+  ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* context)
+  {
+    OrthancPlugins::SetGlobalContext(context);
+    Orthanc::Logging::InitializePluginContext(context);
+    Orthanc::Logging::EnableInfoLevel(true);
+
+    /* Check the version of the Orthanc core */
+    if (OrthancPluginCheckVersion(context) == 0)
+    {
+      OrthancPlugins::ReportMinimalOrthancVersion(ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER,
+                                                  ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER,
+                                                  ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER);
+      return -1;
+    }
+
+    OrthancPluginSetDescription(context, "Add support for NIfTI in Orthanc.");
+
+    OrthancPlugins::RegisterRestCallback<SeriesToNifti>("/series/(.*)/nifti", true /* thread safe */);
+    OrthancPlugins::RegisterRestCallback<InstanceToNifti>("/instances/(.*)/nifti", true /* thread safe */);
+
+    {
+      std::string explorer;
+      Orthanc::EmbeddedResources::GetFileResource(
+        explorer, Orthanc::EmbeddedResources::ORTHANC_EXPLORER);
+      OrthancPluginExtendOrthancExplorer(context, explorer.c_str());
+    }
+ 
+    return 0;
+  }
+
+
+  ORTHANC_PLUGINS_API void OrthancPluginFinalize()
+  {
+  }
+
+
+  ORTHANC_PLUGINS_API const char* OrthancPluginGetName()
+  {
+    return "neuro";
+  }
+
+
+  ORTHANC_PLUGINS_API const char* OrthancPluginGetVersion()
+  {
+    return ORTHANC_PLUGIN_VERSION;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Sources/Plugin/PluginFrameDecoder.cpp	Fri Apr 22 17:58:59 2022 +0200
@@ -0,0 +1,87 @@
+/**
+ * Neuroimaging plugin for Orthanc
+ * Copyright (C) 2021-2022 Sebastien Jodogne, UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "PluginFrameDecoder.h"
+
+
+namespace Neuro
+{
+  static Orthanc::PixelFormat Convert(OrthancPluginPixelFormat format)
+  {
+    switch (format)
+    {
+      case OrthancPluginPixelFormat_Grayscale16:
+        return Orthanc::PixelFormat_Grayscale16;
+
+      case OrthancPluginPixelFormat_SignedGrayscale16:
+        return Orthanc::PixelFormat_SignedGrayscale16;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  class PluginFrameDecoder::DecodedFrame : public IDecodedFrame
+  {
+  private:
+    std::unique_ptr<OrthancPlugins::OrthancImage>  frame_;
+      
+  public:
+    explicit DecodedFrame(OrthancPlugins::OrthancImage* frame) :
+      frame_(frame)
+    {
+      if (frame == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+      }
+    }
+      
+    virtual void GetRegion(Orthanc::ImageAccessor& region,
+                           unsigned int x,
+                           unsigned int y,
+                           unsigned int width,
+                           unsigned int height)
+    {
+      Orthanc::ImageAccessor f;
+      f.AssignReadOnly(Convert(frame_->GetPixelFormat()), frame_->GetWidth(),
+                       frame_->GetHeight(), frame_->GetPitch(), frame_->GetBuffer());
+
+      f.GetRegion(region, x, y, width, height);
+    }
+  };
+
+    
+  PluginFrameDecoder::IDecodedFrame* PluginFrameDecoder::DecodeFrame(const Slice& slice)
+  {
+    const std::string id = collection_.GetOrthancId(slice.GetInstanceIndexInCollection());
+
+    if (id != currentInstanceId_)
+    {
+      OrthancPlugins::MemoryBuffer dicom;
+      dicom.GetDicomInstance(id);
+        
+      currentInstance_.reset(new OrthancPlugins::DicomInstance(dicom.GetData(), dicom.GetSize()));
+      currentInstanceId_ = id;
+    }
+
+    assert(currentInstance_.get() != NULL);
+    return new DecodedFrame(currentInstance_->GetDecodedFrame(slice.GetFrameNumber()));
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Sources/Plugin/PluginFrameDecoder.h	Fri Apr 22 17:58:59 2022 +0200
@@ -0,0 +1,47 @@
+/**
+ * Neuroimaging plugin for Orthanc
+ * Copyright (C) 2021-2022 Sebastien Jodogne, UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../Framework/IDicomFrameDecoder.h"
+#include "../Framework/DicomInstancesCollection.h"
+
+#include "../../Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h"
+
+
+namespace Neuro
+{
+  class PluginFrameDecoder : public IDicomFrameDecoder
+  {
+  private:
+    class DecodedFrame;
+
+    const DicomInstancesCollection&                collection_;
+    std::string                                    currentInstanceId_;
+    std::unique_ptr<OrthancPlugins::DicomInstance> currentInstance_;
+    
+  public:
+    explicit PluginFrameDecoder(const DicomInstancesCollection& collection) :
+      collection_(collection)
+    {
+    }
+    
+    virtual IDecodedFrame* DecodeFrame(const Slice& slice) ORTHANC_OVERRIDE;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Sources/UnitTestsSources/NiftiTests.cpp	Fri Apr 22 17:58:59 2022 +0200
@@ -0,0 +1,13663 @@
+/**
+ * Neuroimaging plugin for Orthanc
+ * Copyright (C) 2021-2022 Sebastien Jodogne, UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+// AUTOGENERATED FILE, DON'T MODIFY BY HAND
+
+#include <gtest/gtest.h>
+
+#include "../Framework/DicomInstancesCollection.h"
+
+#include <DicomFormat/DicomMap.h>
+
+static void CheckConsistency(const nifti_image& nifti)
+{
+  ASSERT_EQ(nifti.nx, nifti.dim[1]);
+  ASSERT_EQ(nifti.ny, nifti.dim[2]);
+  ASSERT_EQ(nifti.nz, nifti.dim[3]);
+  ASSERT_EQ(nifti.nt, nifti.dim[4]);
+  ASSERT_EQ(nifti.nu, nifti.dim[5]);
+  ASSERT_EQ(nifti.nv, nifti.dim[6]);
+  ASSERT_EQ(nifti.nw, nifti.dim[7]);
+  ASSERT_EQ(nifti.dx, nifti.pixdim[1]);
+  ASSERT_EQ(nifti.dy, nifti.pixdim[2]);
+  ASSERT_EQ(nifti.dz, nifti.pixdim[3]);
+  ASSERT_EQ(nifti.dt, nifti.pixdim[4]);
+  ASSERT_EQ(nifti.du, nifti.pixdim[5]);
+  ASSERT_EQ(nifti.dv, nifti.pixdim[6]);
+  ASSERT_EQ(nifti.dw, nifti.pixdim[7]);
+  ASSERT_FLOAT_EQ(nifti.qoffset_x, nifti.sto_xyz.m[0][3]);
+  ASSERT_FLOAT_EQ(nifti.qoffset_y, nifti.sto_xyz.m[1][3]);
+  ASSERT_FLOAT_EQ(nifti.qoffset_z, nifti.sto_xyz.m[2][3]);
+  ASSERT_FLOAT_EQ(0.0, nifti.sto_xyz.m[3][0]);
+  ASSERT_FLOAT_EQ(0.0, nifti.sto_xyz.m[3][1]);
+  ASSERT_FLOAT_EQ(0.0, nifti.sto_xyz.m[3][2]);
+  ASSERT_FLOAT_EQ(1.0, nifti.sto_xyz.m[3][3]);
+  ASSERT_EQ(nifti.xyz_units, NIFTI_UNITS_MM);
+  ASSERT_EQ(nifti.time_units, NIFTI_UNITS_SEC);
+}
+
+static void LoadInstance_0(Neuro::DicomInstancesCollection& target)
+{
+  // ('brainix', 'SOUS - 702/IM-0001-0001.dcm')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "Philips Medical Systems", false);
+  tags.SetValue(0x0020, 0x0013, "1", false);
+  tags.SetValue(0x0018, 0x0080, "449.998992919921", false);
+  tags.SetValue(0x0018, 0x0081, "10.0", false);
+  tags.SetValue(0x0018, 0x1312, "ROW", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "512", false);
+  tags.SetValue(0x0028, 0x0011, "512", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "12", false);
+  tags.SetValue(0x0028, 0x0102, "11", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "143958.890000", false);
+  tags.SetValue(0x0018, 0x0050, "5.0", false);
+  tags.SetValue(0x0018, 0x0088, "6.0", false);
+  tags.SetValue(0x0020, 0x0032, "-123.48501954041\\-123.85898304080\\83.2371420264244", false);
+  tags.SetValue(0x0020, 0x0037, "0.99971222877502\\7.8810308973E-12\\0.02398800104856\\-0.0017278126906\\0.99740260839462\\0.07200747728347", false);
+  tags.SetValue(0x0028, 0x0030, "0.46875\\0.46875", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_0)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_0(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 512);
+  ASSERT_NEAR(nifti.pixdim[1], 0.468750, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 512);
+  ASSERT_NEAR(nifti.pixdim[2], 0.468750, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 1);
+  ASSERT_NEAR(nifti.pixdim[3], 6.000000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 262144u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(54));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(54));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(54));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -0.468615, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], -0.000810, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], 0.143554, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 123.898880, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 0.467532, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], 0.432169, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -115.050117, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.011244, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], -0.033754, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 5.982694, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], 100.485184, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000432, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, -0.999279, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.036035, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=10;Time=143958.890");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_1(Neuro::DicomInstancesCollection& target)
+{
+  // ('vix', 'IM-0001-0001.dcm')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "CT", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "1", false);
+  tags.SetValue(0x0028, 0x1052, "-1024", false);
+  tags.SetValue(0x0028, 0x1053, "1", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.2", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "512", false);
+  tags.SetValue(0x0028, 0x0011, "512", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "12", false);
+  tags.SetValue(0x0028, 0x0102, "11", false);
+  tags.SetValue(0x0028, 0x0103, "1", false);
+  tags.SetValue(0x0008, 0x0032, "101812.967223", false);
+  tags.SetValue(0x0018, 0x0050, "1", false);
+  tags.SetValue(0x0020, 0x0032, "-126.798828125\\-210.798828125\\-41.5", false);
+  tags.SetValue(0x0020, 0x0037, "1\\0\\0\\0\\1\\0", false);
+  tags.SetValue(0x0028, 0x0030, "0.40234375\\0.40234375", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_1)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_1(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 512);
+  ASSERT_NEAR(nifti.pixdim[1], 0.402344, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 512);
+  ASSERT_NEAR(nifti.pixdim[2], 0.402344, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 1);
+  ASSERT_NEAR(nifti.pixdim[3], 1.000000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 262144u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 4);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(0));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(0));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(0));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, -1024.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -0.402344, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 126.798828, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 0.402344, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], 5.201172, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 1.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -41.500000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 1.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.000000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "Time=101812.967");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_2(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa', 'In/Orientation/ax/axasc35/MR.1.3.12.2.1107.5.2.32.35131.2014031012493950715786673')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "1", false);
+  tags.SetValue(0x0018, 0x0080, "3000", false);
+  tags.SetValue(0x0018, 0x0081, "30", false);
+  tags.SetValue(0x0018, 0x1312, "COL", false);
+  tags.SetValue(0x0019, 0x1029, "0.0\\70.00000001\\142.50000002\\215.0\\285.0\\357.50000001\\429.99999999\\500.0\\572.50000001\\645.00000002\\715.0\\787.50000001\\860.00000002\\932.5\\1002.5\\1075.00000001\\1147.50000002\\1217.5\\1290.00000001\\1362.50000002\\1432.5\\1505.0\\1577.50000001\\1647.50000002\\1720.0\\1792.50000001\\1862.50000002\\1935.0\\2007.50000001\\2077.50000001\\2149.99999999\\2222.5\\2295.00000001\\2365.00000002\\2437.5", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "384", false);
+  tags.SetValue(0x0028, 0x0011, "384", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "12", false);
+  tags.SetValue(0x0028, 0x0102, "11", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "134935.305000", false);
+  tags.SetValue(0x0018, 0x0050, "3", false);
+  tags.SetValue(0x0018, 0x0088, "3.6000000030835", false);
+  tags.SetValue(0x0020, 0x0032, "-624\\-661.82658862211\\-6.5255017698948", false);
+  tags.SetValue(0x0020, 0x0037, "1\\-1e-016\\0\\1e-016\\0.99415096409965\\-0.1079993545339", false);
+  tags.SetValue(0x0028, 0x0030, "3.25\\3.25", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("35");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("1");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("0.0").AddValue("0.10799944").AddValue("0.99415095");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_2)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_2(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 35);
+  ASSERT_NEAR(nifti.pixdim[3], 3.600000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 143360u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 1);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 104.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 3.230991, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.388798, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -58.684311, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.350998, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 3.578943, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -84.798035, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 0.998537, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.054079, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=134935.305;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_3(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa', 'In/Orientation/ax/axasc35/MR.1.3.12.2.1107.5.2.32.35131.2014031012494230872886774')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "2", false);
+  tags.SetValue(0x0018, 0x0080, "3000", false);
+  tags.SetValue(0x0018, 0x0081, "30", false);
+  tags.SetValue(0x0018, 0x1312, "COL", false);
+  tags.SetValue(0x0019, 0x1029, "0.0\\72.50000001\\144.99999999\\217.5\\287.50000001\\359.99999999\\432.5\\502.5\\575.00000001\\647.49999999\\717.5\\790.00000001\\862.49999999\\932.5\\1005.0\\1077.49999998\\1147.49999999\\1220.0\\1292.50000001\\1362.49999999\\1435.0\\1507.50000001\\1579.99999999\\1649.99999999\\1722.5\\1794.99999998\\1864.99999999\\1937.5\\2010.00000001\\2079.99999999\\2152.5\\2225.00000001\\2294.99999998\\2367.49999999\\2440.0", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "384", false);
+  tags.SetValue(0x0028, 0x0011, "384", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "12", false);
+  tags.SetValue(0x0028, 0x0102, "11", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "134938.315000", false);
+  tags.SetValue(0x0018, 0x0050, "3", false);
+  tags.SetValue(0x0018, 0x0088, "3.6000000030835", false);
+  tags.SetValue(0x0020, 0x0032, "-624\\-661.82658862211\\-6.5255017698948", false);
+  tags.SetValue(0x0020, 0x0037, "1\\-1e-016\\0\\1e-016\\0.99415096409965\\-0.1079993545339", false);
+  tags.SetValue(0x0028, 0x0030, "3.25\\3.25", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("35");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("1");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("0.0").AddValue("0.10799944").AddValue("0.99415095");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_3)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_3(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 35);
+  ASSERT_NEAR(nifti.pixdim[3], 3.600000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 143360u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 1);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 104.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 3.230991, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.388798, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -58.684311, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.350998, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 3.578943, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -84.798035, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 0.998537, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.054079, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=134938.315;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_4(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa', 'In/Orientation/ax/axasc36/MR.1.3.12.2.1107.5.2.32.35131.2014031012525641770887330')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "1", false);
+  tags.SetValue(0x0018, 0x0080, "3000", false);
+  tags.SetValue(0x0018, 0x0081, "30", false);
+  tags.SetValue(0x0018, 0x1312, "COL", false);
+  tags.SetValue(0x0019, 0x1029, "0.0\\70.00000001\\139.99999998\\209.99999999\\280.0\\350.00000001\\417.49999998\\487.49999999\\557.5\\627.5\\697.49999998\\767.49999999\\837.49999999\\907.5\\977.49999998\\1044.99999998\\1114.99999999\\1185.0\\1255.0\\1324.99999998\\1394.99999999\\1465.0\\1535.0\\1604.99999998\\1672.49999999\\1742.49999999\\1812.5\\1882.50000001\\1952.49999998\\2022.49999999\\2092.5\\2162.50000001\\2232.49999998\\2299.99999999\\2370.0\\2440.0", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "384", false);
+  tags.SetValue(0x0028, 0x0011, "384", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "12", false);
+  tags.SetValue(0x0028, 0x0102, "11", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "135252.445000", false);
+  tags.SetValue(0x0018, 0x0050, "3", false);
+  tags.SetValue(0x0018, 0x0088, "3.6000000571183", false);
+  tags.SetValue(0x0020, 0x0032, "-624\\-662.02098940946\\-8.3149762571995", false);
+  tags.SetValue(0x0020, 0x0037, "1\\-1e-016\\0\\1e-016\\0.99415096409965\\-0.1079993545339", false);
+  tags.SetValue(0x0028, 0x0030, "3.25\\3.25", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("36");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("1");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("0.0").AddValue("0.10799921").AddValue("0.99415098");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_4)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_4(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 36);
+  ASSERT_NEAR(nifti.pixdim[3], 3.600000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 147456u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 1);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 104.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 3.230991, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.388798, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -58.489914, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.350998, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 3.578943, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -86.587509, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 0.998537, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.054079, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=135252.445;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_5(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa', 'In/Orientation/ax/axasc36/MR.1.3.12.2.1107.5.2.32.35131.2014031012525922908387440')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "2", false);
+  tags.SetValue(0x0018, 0x0080, "3000", false);
+  tags.SetValue(0x0018, 0x0081, "30", false);
+  tags.SetValue(0x0018, 0x1312, "COL", false);
+  tags.SetValue(0x0019, 0x1029, "0.0\\70.00000001\\140.00000001\\210.00000002\\277.5\\347.5\\417.50000001\\487.50000002\\557.50000002\\627.5\\697.50000001\\767.50000002\\837.50000002\\905.0\\975.00000001\\1045.00000001\\1115.00000002\\1185.0\\1255.0\\1325.00000001\\1395.00000002\\1465.0\\1532.5\\1602.50000001\\1672.50000002\\1742.50000002\\1812.5\\1882.50000001\\1952.50000001\\2022.50000002\\2092.5\\2160.0\\2230.00000001\\2300.00000002\\2370.00000002\\2440.0", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "384", false);
+  tags.SetValue(0x0028, 0x0011, "384", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "12", false);
+  tags.SetValue(0x0028, 0x0102, "11", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "135255.457500", false);
+  tags.SetValue(0x0018, 0x0050, "3", false);
+  tags.SetValue(0x0018, 0x0088, "3.6000000571183", false);
+  tags.SetValue(0x0020, 0x0032, "-624\\-662.02098940946\\-8.3149762571995", false);
+  tags.SetValue(0x0020, 0x0037, "1\\-1e-016\\0\\1e-016\\0.99415096409965\\-0.1079993545339", false);
+  tags.SetValue(0x0028, 0x0030, "3.25\\3.25", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("36");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("1");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("0.0").AddValue("0.10799921").AddValue("0.99415098");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_5)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_5(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 36);
+  ASSERT_NEAR(nifti.pixdim[3], 3.600000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 147456u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 1);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 104.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 3.230991, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.388798, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -58.489914, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.350998, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 3.578943, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -86.587509, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 0.998537, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.054079, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=135255.457;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_6(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa', 'In/Orientation/ax/axasc36b/MR.1.3.12.2.1107.5.2.32.35131.2014031012542072126387788')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "1", false);
+  tags.SetValue(0x0018, 0x0080, "3000", false);
+  tags.SetValue(0x0018, 0x0081, "30", false);
+  tags.SetValue(0x0018, 0x1312, "COL", false);
+  tags.SetValue(0x0019, 0x1029, "0.0\\70.00000001\\139.99999998\\209.99999999\\280.0\\350.00000001\\419.99999998\\487.49999999\\557.5\\627.5\\697.49999998\\767.49999999\\837.49999999\\907.5\\977.50000001\\1047.49999999\\1114.99999999\\1185.0\\1255.0\\1324.99999998\\1394.99999999\\1465.0\\1535.0\\1604.99999998\\1672.49999999\\1742.49999999\\1812.5\\1882.50000001\\1952.49999998\\2022.49999999\\2092.5\\2162.50000001\\2232.49999998\\2299.99999999\\2370.0\\2440.0", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "384", false);
+  tags.SetValue(0x0028, 0x0011, "384", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "12", false);
+  tags.SetValue(0x0028, 0x0102, "11", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "135416.225000", false);
+  tags.SetValue(0x0018, 0x0050, "3", false);
+  tags.SetValue(0x0018, 0x0088, "3.6000000571183", false);
+  tags.SetValue(0x0020, 0x0032, "-624\\-662.02098940946\\-8.3149762571995", false);
+  tags.SetValue(0x0020, 0x0037, "1\\-1e-016\\0\\1e-016\\0.99415096409965\\-0.1079993545339", false);
+  tags.SetValue(0x0028, 0x0030, "3.25\\3.25", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("36");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("1");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("0.0").AddValue("0.10799921").AddValue("0.99415098");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_6)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_6(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 36);
+  ASSERT_NEAR(nifti.pixdim[3], 3.600000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 147456u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 1);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 104.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 3.230991, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.388798, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -58.489914, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.350998, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 3.578943, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -86.587509, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 0.998537, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.054079, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=135416.225;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_7(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa', 'In/Orientation/ax/axasc36b/MR.1.3.12.2.1107.5.2.32.35131.2014031012542352754587892')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "2", false);
+  tags.SetValue(0x0018, 0x0080, "3000", false);
+  tags.SetValue(0x0018, 0x0081, "30", false);
+  tags.SetValue(0x0018, 0x1312, "COL", false);
+  tags.SetValue(0x0019, 0x1029, "0.0\\70.00000001\\140.00000001\\210.00000002\\280.0\\347.5\\417.50000001\\487.50000002\\557.50000002\\627.5\\697.50000001\\767.50000002\\837.50000002\\907.5\\975.00000001\\1045.00000001\\1115.00000002\\1185.0\\1255.0\\1325.00000001\\1395.00000002\\1465.00000003\\1535.0\\1602.50000001\\1672.50000002\\1742.50000002\\1812.5\\1882.50000001\\1952.50000001\\2022.50000002\\2092.5\\2162.50000001\\2230.00000001\\2300.00000002\\2370.00000002\\2440.0", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "384", false);
+  tags.SetValue(0x0028, 0x0011, "384", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "12", false);
+  tags.SetValue(0x0028, 0x0102, "11", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "135419.237500", false);
+  tags.SetValue(0x0018, 0x0050, "3", false);
+  tags.SetValue(0x0018, 0x0088, "3.6000000571183", false);
+  tags.SetValue(0x0020, 0x0032, "-624\\-662.02098940946\\-8.3149762571995", false);
+  tags.SetValue(0x0020, 0x0037, "1\\-1e-016\\0\\1e-016\\0.99415096409965\\-0.1079993545339", false);
+  tags.SetValue(0x0028, 0x0030, "3.25\\3.25", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("36");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("1");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("0.0").AddValue("0.10799921").AddValue("0.99415098");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_7)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_7(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 36);
+  ASSERT_NEAR(nifti.pixdim[3], 3.600000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 147456u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 1);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 104.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 3.230991, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.388798, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -58.489914, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.350998, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 3.578943, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -86.587509, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 0.998537, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.054079, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=135419.237;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_8(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa', 'In/Orientation/ax/axdesc35/MR.1.3.12.2.1107.5.2.32.35131.2014031012504272932486891')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "1", false);
+  tags.SetValue(0x0018, 0x0080, "3000", false);
+  tags.SetValue(0x0018, 0x0081, "30", false);
+  tags.SetValue(0x0018, 0x1312, "COL", false);
+  tags.SetValue(0x0019, 0x1029, "2437.5\\2364.99999999\\2295.00000001\\2222.5\\2149.99999999\\2080.00000002\\2007.50000001\\1935.0\\1864.99999999\\1792.50000001\\1720.0\\1649.99999999\\1577.50000001\\1505.0\\1435.0\\1362.50000002\\1290.00000001\\1220.0\\1147.49999999\\1075.00000001\\1002.5\\932.5\\860.00000002\\787.50000001\\717.5\\644.99999999\\572.50000001\\502.5\\429.99999999\\357.50000001\\287.50000001\\215.0\\142.50000002\\72.50000001\\0.0", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "384", false);
+  tags.SetValue(0x0028, 0x0011, "384", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "12", false);
+  tags.SetValue(0x0028, 0x0102, "11", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "135041.527500", false);
+  tags.SetValue(0x0018, 0x0050, "3", false);
+  tags.SetValue(0x0018, 0x0088, "3.6000000030835", false);
+  tags.SetValue(0x0020, 0x0032, "-624\\-661.82658862211\\-6.5255017698948", false);
+  tags.SetValue(0x0020, 0x0037, "1\\-1e-016\\0\\1e-016\\0.99415096409965\\-0.1079993545339", false);
+  tags.SetValue(0x0028, 0x0030, "3.25\\3.25", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("35");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("1");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("0.0").AddValue("0.10799944").AddValue("0.99415095");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_8)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_8(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 35);
+  ASSERT_NEAR(nifti.pixdim[3], 3.600000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 143360u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 2);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 104.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 3.230991, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.388798, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -58.684311, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.350998, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 3.578943, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -84.798035, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 0.998537, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.054079, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=135041.527;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_9(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa', 'In/Orientation/ax/axdesc35/MR.1.3.12.2.1107.5.2.32.35131.2014031012504554260286994')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "2", false);
+  tags.SetValue(0x0018, 0x0080, "3000", false);
+  tags.SetValue(0x0018, 0x0081, "30", false);
+  tags.SetValue(0x0018, 0x1312, "COL", false);
+  tags.SetValue(0x0019, 0x1029, "2437.5\\2364.99999999\\2294.99999998\\2222.5\\2149.99999999\\2079.99999999\\2007.49999998\\1935.0\\1862.49999999\\1792.49999998\\1720.0\\1647.49999999\\1577.49999998\\1505.0\\1432.5\\1362.49999999\\1289.99999998\\1217.5\\1147.49999999\\1074.99999998\\1002.5\\932.5\\859.99999999\\787.50000001\\717.5\\644.99999999\\572.49999998\\502.5\\429.99999999\\357.49999998\\285.0\\215.0\\142.49999999\\69.99999998\\0.0", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "384", false);
+  tags.SetValue(0x0028, 0x0011, "384", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "12", false);
+  tags.SetValue(0x0028, 0x0102, "11", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "135044.540000", false);
+  tags.SetValue(0x0018, 0x0050, "3", false);
+  tags.SetValue(0x0018, 0x0088, "3.6000000030835", false);
+  tags.SetValue(0x0020, 0x0032, "-624\\-661.82658862211\\-6.5255017698948", false);
+  tags.SetValue(0x0020, 0x0037, "1\\-1e-016\\0\\1e-016\\0.99415096409965\\-0.1079993545339", false);
+  tags.SetValue(0x0028, 0x0030, "3.25\\3.25", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("35");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("1");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("0.0").AddValue("0.10799944").AddValue("0.99415095");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_9)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_9(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 35);
+  ASSERT_NEAR(nifti.pixdim[3], 3.600000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 143360u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 2);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 104.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 3.230991, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.388798, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -58.684311, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.350998, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 3.578943, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -84.798035, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 0.998537, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.054079, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=135044.540;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_10(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa', 'In/Orientation/ax/axdesc36/MR.1.3.12.2.1107.5.2.32.35131.2014031012533345318387559')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "1", false);
+  tags.SetValue(0x0018, 0x0080, "3000", false);
+  tags.SetValue(0x0018, 0x0081, "30", false);
+  tags.SetValue(0x0018, 0x1312, "COL", false);
+  tags.SetValue(0x0019, 0x1029, "2439.99999997\\2372.5\\2302.49999999\\2232.49999998\\2162.49999998\\2092.5\\2022.49999999\\1952.49999998\\1882.49999998\\1812.5\\1745.0\\1674.99999999\\1604.99999998\\1534.99999997\\1465.0\\1394.99999999\\1324.99999998\\1254.99999998\\1185.0\\1117.49999999\\1047.49999999\\977.49999998\\907.5\\837.49999999\\767.49999999\\697.49999998\\627.49999997\\557.5\\489.99999999\\419.99999998\\349.99999998\\280.0\\209.99999999\\139.99999998\\69.99999998\\0.0", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "384", false);
+  tags.SetValue(0x0028, 0x0011, "384", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "12", false);
+  tags.SetValue(0x0028, 0x0102, "11", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "135332.235000", false);
+  tags.SetValue(0x0018, 0x0050, "3", false);
+  tags.SetValue(0x0018, 0x0088, "3.6000000571183", false);
+  tags.SetValue(0x0020, 0x0032, "-624\\-662.02098940946\\-8.3149762571995", false);
+  tags.SetValue(0x0020, 0x0037, "1\\-1e-016\\0\\1e-016\\0.99415096409965\\-0.1079993545339", false);
+  tags.SetValue(0x0028, 0x0030, "3.25\\3.25", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("36");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("1");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("0.0").AddValue("0.10799921").AddValue("0.99415098");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_10)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_10(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 36);
+  ASSERT_NEAR(nifti.pixdim[3], 3.600000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 147456u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 2);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 104.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 3.230991, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.388798, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -58.489914, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.350998, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 3.578943, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -86.587509, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 0.998537, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.054079, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=135332.235;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_11(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa', 'In/Orientation/ax/axdesc36/MR.1.3.12.2.1107.5.2.32.35131.2014031012533626317387666')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "2", false);
+  tags.SetValue(0x0018, 0x0080, "3000", false);
+  tags.SetValue(0x0018, 0x0081, "30", false);
+  tags.SetValue(0x0018, 0x1312, "COL", false);
+  tags.SetValue(0x0019, 0x1029, "2440.0\\2370.0\\2300.00000002\\2232.50000001\\2162.50000001\\2092.5\\2022.49999999\\1952.50000001\\1882.50000001\\1812.5\\1742.49999999\\1672.50000002\\1605.00000001\\1535.0\\1465.0\\1395.00000002\\1325.00000001\\1255.0\\1185.0\\1114.99999999\\1045.00000001\\977.50000001\\907.5\\837.49999999\\767.50000002\\697.50000001\\627.5\\557.5\\487.50000002\\417.50000001\\350.00000001\\280.0\\209.99999999\\140.00000001\\70.00000001\\0.0", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "384", false);
+  tags.SetValue(0x0028, 0x0011, "384", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "12", false);
+  tags.SetValue(0x0028, 0x0102, "11", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "135335.247500", false);
+  tags.SetValue(0x0018, 0x0050, "3", false);
+  tags.SetValue(0x0018, 0x0088, "3.6000000571183", false);
+  tags.SetValue(0x0020, 0x0032, "-624\\-662.02098940946\\-8.3149762571995", false);
+  tags.SetValue(0x0020, 0x0037, "1\\-1e-016\\0\\1e-016\\0.99415096409965\\-0.1079993545339", false);
+  tags.SetValue(0x0028, 0x0030, "3.25\\3.25", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("36");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("1");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("0.0").AddValue("0.10799921").AddValue("0.99415098");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_11)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_11(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 36);
+  ASSERT_NEAR(nifti.pixdim[3], 3.600000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 147456u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 2);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 104.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 3.230991, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.388798, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -58.489914, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.350998, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 3.578943, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -86.587509, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 0.998537, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.054079, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=135335.247;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_12(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa', 'In/Orientation/ax/axint35/MR.1.3.12.2.1107.5.2.32.35131.2014031012511448225287110')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "1", false);
+  tags.SetValue(0x0018, 0x0080, "3000", false);
+  tags.SetValue(0x0018, 0x0081, "30", false);
+  tags.SetValue(0x0018, 0x1312, "COL", false);
+  tags.SetValue(0x0019, 0x1029, "0.0\\1292.50000001\\72.50000001\\1362.50000002\\145.00000002\\1435.0\\215.0\\1507.50000001\\287.50000001\\1577.50000001\\360.00000002\\1649.99999999\\429.99999999\\1722.5\\502.5\\1792.50000001\\575.00000001\\1864.99999999\\645.00000002\\1937.5\\717.5\\2007.50000001\\790.00000001\\2080.00000002\\860.00000002\\2152.5\\932.5\\2222.5\\1005.0\\2295.00000001\\1075.00000001\\2367.49999999\\1147.49999999\\2437.5\\1220.0", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "384", false);
+  tags.SetValue(0x0028, 0x0011, "384", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "12", false);
+  tags.SetValue(0x0028, 0x0102, "11", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "135110.870000", false);
+  tags.SetValue(0x0018, 0x0050, "3", false);
+  tags.SetValue(0x0018, 0x0088, "3.6000000030835", false);
+  tags.SetValue(0x0020, 0x0032, "-624\\-661.82658862211\\-6.5255017698948", false);
+  tags.SetValue(0x0020, 0x0037, "1\\-1e-016\\0\\1e-016\\0.99415096409965\\-0.1079993545339", false);
+  tags.SetValue(0x0028, 0x0030, "3.25\\3.25", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("35");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("1");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("0.0").AddValue("0.10799944").AddValue("0.99415095");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_12)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_12(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 35);
+  ASSERT_NEAR(nifti.pixdim[3], 3.600000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 143360u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 3);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 104.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 3.230991, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.388798, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -58.684311, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.350998, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 3.578943, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -84.798035, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 0.998537, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.054079, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=135110.870;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_13(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa', 'In/Orientation/ax/axint35/MR.1.3.12.2.1107.5.2.32.35131.2014031012511729322987214')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "2", false);
+  tags.SetValue(0x0018, 0x0080, "3000", false);
+  tags.SetValue(0x0018, 0x0081, "30", false);
+  tags.SetValue(0x0018, 0x1312, "COL", false);
+  tags.SetValue(0x0019, 0x1029, "0.0\\1289.99999998\\72.49999998\\1362.49999999\\142.49999999\\1435.0\\215.0\\1505.0\\287.50000001\\1577.49999998\\357.49999998\\1649.99999999\\429.99999999\\1720.0\\502.5\\1792.49999998\\574.99999998\\1864.99999999\\644.99999999\\1935.0\\717.5\\2007.50000001\\789.99999998\\2079.99999999\\859.99999999\\2152.5\\932.5\\2222.5\\1005.0\\2294.99999998\\1074.99999998\\2367.49999999\\1147.49999999\\2437.5\\1220.0", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "384", false);
+  tags.SetValue(0x0028, 0x0011, "384", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "12", false);
+  tags.SetValue(0x0028, 0x0102, "11", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "135113.882500", false);
+  tags.SetValue(0x0018, 0x0050, "3", false);
+  tags.SetValue(0x0018, 0x0088, "3.6000000030835", false);
+  tags.SetValue(0x0020, 0x0032, "-624\\-661.82658862211\\-6.5255017698948", false);
+  tags.SetValue(0x0020, 0x0037, "1\\-1e-016\\0\\1e-016\\0.99415096409965\\-0.1079993545339", false);
+  tags.SetValue(0x0028, 0x0030, "3.25\\3.25", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("35");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("1");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("0.0").AddValue("0.10799944").AddValue("0.99415095");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_13)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_13(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 35);
+  ASSERT_NEAR(nifti.pixdim[3], 3.600000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 143360u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 3);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 104.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 3.230991, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.388798, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -58.684311, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.350998, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 3.578943, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -84.798035, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 0.998537, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.054079, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=135113.883;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_14(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa', 'In/Orientation/ax/axint36/MR.1.3.12.2.1107.5.2.32.35131.2014031012544786586988013')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "1", false);
+  tags.SetValue(0x0018, 0x0080, "3000", false);
+  tags.SetValue(0x0018, 0x0081, "30", false);
+  tags.SetValue(0x0018, 0x1312, "COL", false);
+  tags.SetValue(0x0019, 0x1029, "1255.0\\0.0\\1325.00000001\\70.00000001\\1394.99999999\\140.00000001\\1465.0\\209.99999999\\1535.0\\280.0\\1605.00000001\\350.00000001\\1672.50000002\\417.50000001\\1742.49999999\\487.49999999\\1812.5\\557.5\\1882.50000001\\627.5\\1952.50000001\\697.50000001\\2022.49999999\\767.49999999\\2092.5\\837.49999999\\2162.50000001\\907.5\\2232.50000001\\977.50000001\\2299.99999999\\1045.00000001\\2370.0\\1114.99999999\\2440.0\\1185.0", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "384", false);
+  tags.SetValue(0x0028, 0x0011, "384", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "12", false);
+  tags.SetValue(0x0028, 0x0102, "11", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "135444.722500", false);
+  tags.SetValue(0x0018, 0x0050, "3", false);
+  tags.SetValue(0x0018, 0x0088, "3.6000000571183", false);
+  tags.SetValue(0x0020, 0x0032, "-624\\-662.02098940946\\-8.3149762571995", false);
+  tags.SetValue(0x0020, 0x0037, "1\\-1e-016\\0\\1e-016\\0.99415096409965\\-0.1079993545339", false);
+  tags.SetValue(0x0028, 0x0030, "3.25\\3.25", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("36");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("1");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("0.0").AddValue("0.10799921").AddValue("0.99415098");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_14)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_14(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 36);
+  ASSERT_NEAR(nifti.pixdim[3], 3.600000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 147456u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 5);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 104.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 3.230991, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.388798, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -58.489914, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.350998, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 3.578943, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -86.587509, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 0.998537, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.054079, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=135444.723;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_15(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa', 'In/Orientation/ax/axint36/MR.1.3.12.2.1107.5.2.32.35131.2014031012545067891488118')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "2", false);
+  tags.SetValue(0x0018, 0x0080, "3000", false);
+  tags.SetValue(0x0018, 0x0081, "30", false);
+  tags.SetValue(0x0018, 0x1312, "COL", false);
+  tags.SetValue(0x0019, 0x1029, "1254.99999998\\0.0\\1324.99999998\\69.99999998\\1394.99999999\\139.99999998\\1465.0\\209.99999999\\1532.5\\280.0\\1602.49999998\\347.5\\1672.49999999\\417.49999998\\1742.49999999\\487.49999999\\1812.5\\557.5\\1882.49999998\\627.5\\1952.49999998\\697.49999998\\2022.49999999\\767.49999999\\2092.5\\837.49999999\\2160.0\\905.0\\2229.99999998\\974.99999998\\2299.99999999\\1044.99999998\\2370.0\\1114.99999999\\2440.0\\1185.0", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "384", false);
+  tags.SetValue(0x0028, 0x0011, "384", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "12", false);
+  tags.SetValue(0x0028, 0x0102, "11", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "135447.735000", false);
+  tags.SetValue(0x0018, 0x0050, "3", false);
+  tags.SetValue(0x0018, 0x0088, "3.6000000571183", false);
+  tags.SetValue(0x0020, 0x0032, "-624\\-662.02098940946\\-8.3149762571995", false);
+  tags.SetValue(0x0020, 0x0037, "1\\-1e-016\\0\\1e-016\\0.99415096409965\\-0.1079993545339", false);
+  tags.SetValue(0x0028, 0x0030, "3.25\\3.25", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("36");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("1");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("0.0").AddValue("0.10799921").AddValue("0.99415098");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_15)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_15(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 36);
+  ASSERT_NEAR(nifti.pixdim[3], 3.600000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 147456u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 5);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 104.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 3.230991, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.388798, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -58.489914, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.350998, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 3.578943, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -86.587509, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 0.998537, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.054079, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=135447.735;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_16(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa', 'In/Orientation/axmb/AxAsc36mb2a/jpg1.dcm')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "1", false);
+  tags.SetValue(0x0018, 0x0080, "3000", false);
+  tags.SetValue(0x0018, 0x0081, "34", false);
+  tags.SetValue(0x0018, 0x1312, "COL", false);
+  tags.SetValue(0x0019, 0x1029, "0.0\\137.49999998\\277.5\\417.49999998\\557.5\\694.99999998\\834.99999999\\974.99999998\\1114.99999999\\1254.99999998\\1392.49999999\\1532.5\\1672.49999999\\1812.5\\1949.99999998\\2090.0\\2229.99999998\\2370.0\\2509.99999998\\2647.49999999\\2787.49999998\\2927.49999999\\3067.49999998\\3204.99999999\\3345.0\\3484.99999999\\3625.0\\3764.99999998\\3902.5\\4042.49999998\\4182.5\\4322.49999998\\4459.99999999\\4599.99999998\\4739.99999999\\4879.99999998", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "516", false);
+  tags.SetValue(0x0028, 0x0011, "516", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "12", false);
+  tags.SetValue(0x0028, 0x0102, "11", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "140149.417500", false);
+  tags.SetValue(0x0018, 0x0050, "3", false);
+  tags.SetValue(0x0018, 0x0088, "3.599999959714", false);
+  tags.SetValue(0x0020, 0x0032, "-696.0000243187\\-737.45290897958\\51.565712817043", false);
+  tags.SetValue(0x0020, 0x0037, "1\\2e-016\\0\\-2e-016\\0.98388503666185\\-0.178802222114", false);
+  tags.SetValue(0x0028, 0x0030, "2.6976745128632\\2.6976745128632", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("36");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("1");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("0.0").AddValue("0.17880235").AddValue("0.98388501");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_16)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_16(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 86);
+  ASSERT_NEAR(nifti.pixdim[1], 2.697675, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 86);
+  ASSERT_NEAR(nifti.pixdim[2], 2.697675, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 36);
+  ASSERT_NEAR(nifti.pixdim[3], 3.600000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 266256u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 1);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -2.697675, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 115.999977, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 2.654202, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.643688, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -58.807571, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.482350, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 3.541986, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -93.139343, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 0.995963, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.089763, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=34;Time=140149.418;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_17(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa', 'In/Orientation/axmb/AxAsc36mb2a/jpg2.dcm')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "2", false);
+  tags.SetValue(0x0018, 0x0080, "3000", false);
+  tags.SetValue(0x0018, 0x0081, "34", false);
+  tags.SetValue(0x0018, 0x1312, "COL", false);
+  tags.SetValue(0x0019, 0x1029, "0.0\\137.49999998\\277.5\\417.49999998\\557.5\\697.49999998\\834.99999999\\975.00000001\\1114.99999999\\1255.0\\1392.49999999\\1532.5\\1672.49999999\\1812.5\\1952.49999998\\2090.0\\2229.99999998\\2370.0\\0.0\\137.49999998\\277.5\\417.49999998\\557.5\\697.49999998\\834.99999999\\975.00000001\\1114.99999999\\1255.0\\1392.49999999\\1532.5\\1672.49999999\\1812.5\\1952.49999998\\2090.0\\2229.99999998\\2370.0", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "516", false);
+  tags.SetValue(0x0028, 0x0011, "516", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "12", false);
+  tags.SetValue(0x0028, 0x0102, "11", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "140203.975000", false);
+  tags.SetValue(0x0018, 0x0050, "3", false);
+  tags.SetValue(0x0018, 0x0088, "3.599999959714", false);
+  tags.SetValue(0x0020, 0x0032, "-696.0000243187\\-737.45290897958\\51.565712817043", false);
+  tags.SetValue(0x0020, 0x0037, "1\\2e-016\\0\\-2e-016\\0.98388503666185\\-0.178802222114", false);
+  tags.SetValue(0x0028, 0x0030, "2.6976745128632\\2.6976745128632", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("36");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("1");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("0.0").AddValue("0.17880235").AddValue("0.98388501");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_17)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_17(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 86);
+  ASSERT_NEAR(nifti.pixdim[1], 2.697675, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 86);
+  ASSERT_NEAR(nifti.pixdim[2], 2.697675, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 36);
+  ASSERT_NEAR(nifti.pixdim[3], 3.600000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 266256u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -2.697675, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 115.999977, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 2.654202, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.643688, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -58.807571, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.482350, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 3.541986, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -93.139343, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 0.995963, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.089763, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=34;Time=140203.975;phase=1;mb=2");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_18(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa', 'In/Orientation/cor/corasc35/MR.1.3.12.2.1107.5.2.32.35131.2014031012570555283988916')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "1", false);
+  tags.SetValue(0x0018, 0x0080, "3000", false);
+  tags.SetValue(0x0018, 0x0081, "30", false);
+  tags.SetValue(0x0018, 0x1312, "ROW", false);
+  tags.SetValue(0x0019, 0x1029, "0.0\\72.50000001\\144.99999999\\215.0\\287.50000001\\359.99999999\\429.99999999\\502.5\\575.00000001\\644.99999999\\717.5\\790.00000001\\859.99999999\\932.5\\1005.0\\1075.00000001\\1147.49999999\\1220.0\\1290.00000001\\1362.49999999\\1435.0\\1507.50000001\\1577.49999998\\1649.99999999\\1722.5\\1792.50000001\\1864.99999999\\1937.5\\2007.50000001\\2079.99999999\\2152.5\\2222.5\\2295.00000001\\2367.49999999\\2437.5", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "384", false);
+  tags.SetValue(0x0028, 0x0011, "384", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "12", false);
+  tags.SetValue(0x0028, 0x0102, "11", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "135701.907500", false);
+  tags.SetValue(0x0018, 0x0050, "3", false);
+  tags.SetValue(0x0018, 0x0088, "3.6000000917836", false);
+  tags.SetValue(0x0020, 0x0032, "-624\\-37.655651722011\\623.83806791559", false);
+  tags.SetValue(0x0020, 0x0037, "1\\0\\0\\0\\-0.1529858224513\\-0.988228383588", false);
+  tags.SetValue(0x0028, 0x0030, "3.25\\3.25", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("35");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("1");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("0.0").AddValue("0.98822844").AddValue("-0.15298548");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_18)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_18(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 35);
+  ASSERT_NEAR(nifti.pixdim[3], 3.600000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 143360u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(54));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(54));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(54));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 1);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 104.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], -0.497204, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -3.557622, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], 148.532135, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 3.211742, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], -0.550749, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -92.380424, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 0.650774, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.759271, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=135701.908;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_19(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa', 'In/Orientation/cor/corasc35/MR.1.3.12.2.1107.5.2.32.35131.2014031012570836467089021')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "2", false);
+  tags.SetValue(0x0018, 0x0080, "3000", false);
+  tags.SetValue(0x0018, 0x0081, "30", false);
+  tags.SetValue(0x0018, 0x1312, "ROW", false);
+  tags.SetValue(0x0019, 0x1029, "0.0\\72.49999998\\142.49999999\\215.0\\287.49999998\\357.49999998\\429.99999999\\502.49999997\\572.49999998\\644.99999999\\717.5\\789.99999998\\859.99999999\\932.5\\1004.99999998\\1074.99999998\\1147.49999999\\1220.0\\1289.99999998\\1362.49999999\\1435.0\\1504.99999998\\1577.49999998\\1649.99999999\\1720.0\\1792.49999998\\1864.99999999\\1935.0\\2007.49999998\\2079.99999999\\2152.5\\2222.49999997\\2294.99999998\\2367.49999999\\2437.5", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "384", false);
+  tags.SetValue(0x0028, 0x0011, "384", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "12", false);
+  tags.SetValue(0x0028, 0x0102, "11", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "135704.920000", false);
+  tags.SetValue(0x0018, 0x0050, "3", false);
+  tags.SetValue(0x0018, 0x0088, "3.6000000917836", false);
+  tags.SetValue(0x0020, 0x0032, "-624\\-37.655651722011\\623.83806791559", false);
+  tags.SetValue(0x0020, 0x0037, "1\\0\\0\\0\\-0.1529858224513\\-0.988228383588", false);
+  tags.SetValue(0x0028, 0x0030, "3.25\\3.25", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("35");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("1");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("0.0").AddValue("0.98822844").AddValue("-0.15298548");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_19)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_19(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 35);
+  ASSERT_NEAR(nifti.pixdim[3], 3.600000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 143360u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(54));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(54));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(54));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 1);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 104.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], -0.497204, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -3.557622, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], 148.532135, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 3.211742, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], -0.550749, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -92.380424, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 0.650774, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.759271, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=135704.920;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_20(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa', 'In/Orientation/cor/corasc36/MR.1.3.12.2.1107.5.2.32.35131.2014031012553857441288245')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "1", false);
+  tags.SetValue(0x0018, 0x0080, "3000", false);
+  tags.SetValue(0x0018, 0x0081, "30", false);
+  tags.SetValue(0x0018, 0x1312, "ROW", false);
+  tags.SetValue(0x0019, 0x1029, "0.0\\67.5\\137.50000001\\207.50000002\\277.5\\347.5\\417.50000001\\487.50000002\\557.5\\627.5\\695.00000001\\765.00000001\\834.99999999\\905.0\\975.00000001\\1045.00000001\\1114.99999999\\1185.0\\1255.0\\1322.50000001\\1392.50000002\\1462.49999999\\1532.5\\1602.50000001\\1672.50000002\\1742.49999999\\1812.5\\1882.50000001\\1950.00000001\\2020.00000002\\2090.0\\2160.0\\2230.00000001\\2300.00000002\\2370.0\\2440.0", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "384", false);
+  tags.SetValue(0x0028, 0x0011, "384", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "12", false);
+  tags.SetValue(0x0028, 0x0102, "11", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "135534.370000", false);
+  tags.SetValue(0x0018, 0x0050, "3", false);
+  tags.SetValue(0x0018, 0x0088, "3.6000000014324", false);
+  tags.SetValue(0x0020, 0x0032, "-624\\-39.434460315761\\624.11344232813", false);
+  tags.SetValue(0x0020, 0x0037, "1\\0\\0\\0\\-0.1529858224513\\-0.988228383588", false);
+  tags.SetValue(0x0028, 0x0030, "3.25\\3.25", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("36");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("1");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("0.0").AddValue("0.98822836").AddValue("-0.152986");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_20)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_20(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 36);
+  ASSERT_NEAR(nifti.pixdim[3], 3.600000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 147456u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(54));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(54));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(54));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 1);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 104.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], -0.497204, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -3.557622, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], 150.310944, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 3.211742, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], -0.550749, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -92.105034, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 0.650774, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.759271, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=135534.370;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_21(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa', 'In/Orientation/cor/corasc36/MR.1.3.12.2.1107.5.2.32.35131.2014031012554137824288349')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "2", false);
+  tags.SetValue(0x0018, 0x0080, "3000", false);
+  tags.SetValue(0x0018, 0x0081, "30", false);
+  tags.SetValue(0x0018, 0x1312, "ROW", false);
+  tags.SetValue(0x0019, 0x1029, "0.0\\70.00000001\\139.99999998\\209.99999999\\280.0\\350.00000001\\419.99999998\\489.99999999\\560.0\\627.5\\697.50000001\\767.49999999\\837.49999999\\907.5\\977.50000001\\1047.49999999\\1117.49999999\\1185.0\\1255.0\\1324.99999998\\1394.99999999\\1465.0\\1535.0\\1604.99999998\\1674.99999999\\1745.0\\1812.5\\1882.50000001\\1952.49999998\\2022.49999999\\2092.5\\2162.50000001\\2232.49999998\\2302.49999999\\2372.5\\2440.0", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "384", false);
+  tags.SetValue(0x0028, 0x0011, "384", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "12", false);
+  tags.SetValue(0x0028, 0x0102, "11", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "135537.380000", false);
+  tags.SetValue(0x0018, 0x0050, "3", false);
+  tags.SetValue(0x0018, 0x0088, "3.6000000014324", false);
+  tags.SetValue(0x0020, 0x0032, "-624\\-39.434460315761\\624.11344232813", false);
+  tags.SetValue(0x0020, 0x0037, "1\\0\\0\\0\\-0.1529858224513\\-0.988228383588", false);
+  tags.SetValue(0x0028, 0x0030, "3.25\\3.25", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("36");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("1");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("0.0").AddValue("0.98822836").AddValue("-0.152986");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_21)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_21(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 36);
+  ASSERT_NEAR(nifti.pixdim[3], 3.600000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 147456u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(54));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(54));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(54));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 1);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 104.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], -0.497204, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -3.557622, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], 150.310944, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 3.211742, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], -0.550749, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -92.105034, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 0.650774, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.759271, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=135537.380;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_22(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa', 'In/Orientation/cor/cordesc35/MR.1.3.12.2.1107.5.2.32.35131.2014031012573612281789140')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "1", false);
+  tags.SetValue(0x0018, 0x0080, "3000", false);
+  tags.SetValue(0x0018, 0x0081, "30", false);
+  tags.SetValue(0x0018, 0x1312, "ROW", false);
+  tags.SetValue(0x0019, 0x1029, "2437.5\\2367.50000002\\2295.00000001\\2222.5\\2152.50000003\\2080.00000002\\2007.50000001\\1937.5\\1865.00000002\\1792.50000001\\1722.5\\1650.00000002\\1577.50000001\\1507.50000001\\1435.00000003\\1362.50000002\\1292.50000001\\1220.0\\1147.50000002\\1077.50000001\\1005.0\\932.50000002\\860.00000002\\790.00000001\\717.50000003\\645.00000002\\575.00000001\\502.5\\430.00000002\\360.00000002\\287.50000001\\215.00000003\\145.00000002\\72.50000001\\0.0", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "384", false);
+  tags.SetValue(0x0028, 0x0011, "384", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "12", false);
+  tags.SetValue(0x0028, 0x0102, "11", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "135734.797500", false);
+  tags.SetValue(0x0018, 0x0050, "3", false);
+  tags.SetValue(0x0018, 0x0088, "3.6000000917836", false);
+  tags.SetValue(0x0020, 0x0032, "-624\\-37.655651722011\\623.83806791559", false);
+  tags.SetValue(0x0020, 0x0037, "1\\0\\0\\0\\-0.1529858224513\\-0.988228383588", false);
+  tags.SetValue(0x0028, 0x0030, "3.25\\3.25", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("35");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("1");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("0.0").AddValue("0.98822844").AddValue("-0.15298548");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_22)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_22(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 35);
+  ASSERT_NEAR(nifti.pixdim[3], 3.600000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 143360u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(54));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(54));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(54));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 2);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 104.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], -0.497204, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -3.557622, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], 148.532135, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 3.211742, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], -0.550749, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -92.380424, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 0.650774, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.759271, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=135734.797;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_23(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa', 'In/Orientation/cor/cordesc35/MR.1.3.12.2.1107.5.2.32.35131.2014031012573893053289241')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "2", false);
+  tags.SetValue(0x0018, 0x0080, "3000", false);
+  tags.SetValue(0x0018, 0x0081, "30", false);
+  tags.SetValue(0x0018, 0x1312, "ROW", false);
+  tags.SetValue(0x0019, 0x1029, "2437.5\\2367.49999999\\2295.00000001\\2222.5\\2152.5\\2080.00000002\\2007.50000001\\1937.5\\1864.99999999\\1792.50000001\\1720.0\\1649.99999999\\1577.50000001\\1505.0\\1435.0\\1362.49999999\\1290.00000001\\1220.0\\1147.49999999\\1075.00000001\\1005.0\\932.5\\860.00000002\\790.00000001\\717.5\\644.99999999\\575.00000001\\502.5\\429.99999999\\357.50000001\\287.50000001\\215.0\\142.50000002\\72.50000001\\0.0", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "384", false);
+  tags.SetValue(0x0028, 0x0011, "384", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "12", false);
+  tags.SetValue(0x0028, 0x0102, "11", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "135737.810000", false);
+  tags.SetValue(0x0018, 0x0050, "3", false);
+  tags.SetValue(0x0018, 0x0088, "3.6000000917836", false);
+  tags.SetValue(0x0020, 0x0032, "-624\\-37.655651722011\\623.83806791559", false);
+  tags.SetValue(0x0020, 0x0037, "1\\0\\0\\0\\-0.1529858224513\\-0.988228383588", false);
+  tags.SetValue(0x0028, 0x0030, "3.25\\3.25", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("35");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("1");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("0.0").AddValue("0.98822844").AddValue("-0.15298548");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_23)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_23(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 35);
+  ASSERT_NEAR(nifti.pixdim[3], 3.600000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 143360u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(54));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(54));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(54));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 2);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 104.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], -0.497204, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -3.557622, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], 148.532135, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 3.211742, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], -0.550749, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -92.380424, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 0.650774, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.759271, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=135737.810;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_24(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa', 'In/Orientation/cor/cordesc36/MR.1.3.12.2.1107.5.2.32.35131.2014031012560686746788470')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "1", false);
+  tags.SetValue(0x0018, 0x0080, "3000", false);
+  tags.SetValue(0x0018, 0x0081, "30", false);
+  tags.SetValue(0x0018, 0x1312, "ROW", false);
+  tags.SetValue(0x0019, 0x1029, "2440.0\\2370.0\\2302.49999999\\2232.50000001\\2162.50000001\\2092.5\\2022.49999999\\1952.50000001\\1882.50000001\\1812.5\\1742.49999999\\1675.00000002\\1605.00000001\\1535.0\\1465.0\\1394.99999999\\1325.00000001\\1255.0\\1185.0\\1117.49999999\\1047.50000002\\977.50000001\\907.5\\837.49999999\\767.50000002\\697.50000001\\627.5\\557.5\\489.99999999\\420.00000001\\350.00000001\\280.0\\209.99999999\\140.00000001\\70.00000001\\0.0", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "384", false);
+  tags.SetValue(0x0028, 0x0011, "384", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "12", false);
+  tags.SetValue(0x0028, 0x0102, "11", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "135604.842500", false);
+  tags.SetValue(0x0018, 0x0050, "3", false);
+  tags.SetValue(0x0018, 0x0088, "3.6000000014324", false);
+  tags.SetValue(0x0020, 0x0032, "-624\\-39.434460315761\\624.11344232813", false);
+  tags.SetValue(0x0020, 0x0037, "1\\0\\0\\0\\-0.1529858224513\\-0.988228383588", false);
+  tags.SetValue(0x0028, 0x0030, "3.25\\3.25", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("36");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("1");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("0.0").AddValue("0.98822836").AddValue("-0.152986");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_24)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_24(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 36);
+  ASSERT_NEAR(nifti.pixdim[3], 3.600000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 147456u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(54));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(54));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(54));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 2);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 104.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], -0.497204, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -3.557622, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], 150.310944, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 3.211742, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], -0.550749, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -92.105034, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 0.650774, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.759271, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=135604.842;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_25(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa', 'In/Orientation/cor/cordesc36/MR.1.3.12.2.1107.5.2.32.35131.2014031012560968177888575')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "2", false);
+  tags.SetValue(0x0018, 0x0080, "3000", false);
+  tags.SetValue(0x0018, 0x0081, "30", false);
+  tags.SetValue(0x0018, 0x1312, "ROW", false);
+  tags.SetValue(0x0019, 0x1029, "2440.0\\2370.0\\2299.99999999\\2232.49999998\\2162.50000001\\2092.5\\2022.49999999\\1952.49999998\\1882.49999998\\1812.5\\1742.49999999\\1672.49999999\\1604.99999998\\1535.0\\1465.0\\1394.99999999\\1324.99999998\\1255.0\\1185.0\\1114.99999999\\1044.99999998\\977.49999998\\907.5\\837.49999999\\767.49999999\\697.49999998\\627.5\\557.5\\487.49999999\\417.49999998\\350.00000001\\280.0\\209.99999999\\139.99999998\\69.99999998\\0.0", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "384", false);
+  tags.SetValue(0x0028, 0x0011, "384", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "12", false);
+  tags.SetValue(0x0028, 0x0102, "11", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "135607.855000", false);
+  tags.SetValue(0x0018, 0x0050, "3", false);
+  tags.SetValue(0x0018, 0x0088, "3.6000000014324", false);
+  tags.SetValue(0x0020, 0x0032, "-624\\-39.434460315761\\624.11344232813", false);
+  tags.SetValue(0x0020, 0x0037, "1\\0\\0\\0\\-0.1529858224513\\-0.988228383588", false);
+  tags.SetValue(0x0028, 0x0030, "3.25\\3.25", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("36");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("1");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("0.0").AddValue("0.98822836").AddValue("-0.152986");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_25)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_25(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 36);
+  ASSERT_NEAR(nifti.pixdim[3], 3.600000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 147456u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(54));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(54));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(54));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 2);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 104.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], -0.497204, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -3.557622, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], 150.310944, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 3.211742, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], -0.550749, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -92.105034, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 0.650774, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.759271, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=135607.855;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_26(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa', 'In/Orientation/cor/corint35/MR.1.3.12.2.1107.5.2.32.35131.2014031012580590504189357')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "1", false);
+  tags.SetValue(0x0018, 0x0080, "3000", false);
+  tags.SetValue(0x0018, 0x0081, "30", false);
+  tags.SetValue(0x0018, 0x1312, "ROW", false);
+  tags.SetValue(0x0019, 0x1029, "0.0\\1290.00000001\\70.00000001\\1362.50000002\\142.50000002\\1432.50000002\\215.00000003\\1505.0\\285.0\\1577.50000001\\357.50000001\\1647.50000002\\430.00000002\\1720.0\\502.5\\1792.50000001\\572.50000001\\1865.00000002\\645.00000002\\1935.00000003\\717.50000003\\2007.50000001\\787.50000001\\2080.00000002\\860.00000002\\2150.00000002\\932.50000002\\2222.5\\1002.5\\2295.00000001\\1075.00000001\\2365.00000002\\1147.50000002\\2437.5\\1217.50000003", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "384", false);
+  tags.SetValue(0x0028, 0x0011, "384", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "12", false);
+  tags.SetValue(0x0028, 0x0102, "11", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "135801.485000", false);
+  tags.SetValue(0x0018, 0x0050, "3", false);
+  tags.SetValue(0x0018, 0x0088, "3.6000000917836", false);
+  tags.SetValue(0x0020, 0x0032, "-624\\-37.655651722011\\623.83806791559", false);
+  tags.SetValue(0x0020, 0x0037, "1\\0\\0\\0\\-0.1529858224513\\-0.988228383588", false);
+  tags.SetValue(0x0028, 0x0030, "3.25\\3.25", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("35");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("1");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("0.0").AddValue("0.98822844").AddValue("-0.15298548");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_26)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_26(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 35);
+  ASSERT_NEAR(nifti.pixdim[3], 3.600000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 143360u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(54));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(54));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(54));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 3);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 104.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], -0.497204, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -3.557622, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], 148.532135, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 3.211742, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], -0.550749, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -92.380424, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 0.650774, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.759271, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=135801.485;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_27(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa', 'In/Orientation/cor/corint35/MR.1.3.12.2.1107.5.2.32.35131.2014031012580871608689461')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "2", false);
+  tags.SetValue(0x0018, 0x0080, "3000", false);
+  tags.SetValue(0x0018, 0x0081, "30", false);
+  tags.SetValue(0x0018, 0x1312, "ROW", false);
+  tags.SetValue(0x0019, 0x1029, "0.0\\1290.00000001\\70.00000001\\1362.49999999\\142.50000002\\1432.5\\215.0\\1505.0\\285.0\\1577.50000001\\357.50000001\\1647.49999999\\429.99999999\\1720.0\\500.0\\1792.50000001\\572.50000001\\1862.49999999\\644.99999999\\1935.0\\715.0\\2007.50000001\\787.50000001\\2077.50000001\\860.00000002\\2149.99999999\\929.99999999\\2222.5\\1002.5\\2292.50000001\\1075.00000001\\2364.99999999\\1147.49999999\\2437.5\\1217.5", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "384", false);
+  tags.SetValue(0x0028, 0x0011, "384", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "12", false);
+  tags.SetValue(0x0028, 0x0102, "11", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "135804.497500", false);
+  tags.SetValue(0x0018, 0x0050, "3", false);
+  tags.SetValue(0x0018, 0x0088, "3.6000000917836", false);
+  tags.SetValue(0x0020, 0x0032, "-624\\-37.655651722011\\623.83806791559", false);
+  tags.SetValue(0x0020, 0x0037, "1\\0\\0\\0\\-0.1529858224513\\-0.988228383588", false);
+  tags.SetValue(0x0028, 0x0030, "3.25\\3.25", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("35");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("1");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("0.0").AddValue("0.98822844").AddValue("-0.15298548");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_27)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_27(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 35);
+  ASSERT_NEAR(nifti.pixdim[3], 3.600000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 143360u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(54));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(54));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(54));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 3);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 104.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], -0.497204, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -3.557622, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], 148.532135, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 3.211742, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], -0.550749, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -92.380424, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 0.650774, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.759271, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=135804.497;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_28(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa', 'In/Orientation/cor/corint36/MR.1.3.12.2.1107.5.2.32.35131.2014031012563584014288694')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "1", false);
+  tags.SetValue(0x0018, 0x0080, "3000", false);
+  tags.SetValue(0x0018, 0x0081, "30", false);
+  tags.SetValue(0x0018, 0x1312, "ROW", false);
+  tags.SetValue(0x0019, 0x1029, "1255.0\\0.0\\1325.00000001\\70.00000001\\1394.99999999\\139.99999998\\1465.0\\209.99999999\\1535.0\\280.0\\1602.50000001\\347.5\\1672.49999999\\417.50000001\\1742.49999999\\487.49999999\\1812.5\\557.5\\1882.50000001\\627.5\\1952.49999998\\697.50000001\\2022.49999999\\767.49999999\\2092.5\\837.49999999\\2160.0\\907.5\\2230.00000001\\975.00000001\\2299.99999999\\1044.99999998\\2370.0\\1114.99999999\\2440.0\\1185.0", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "384", false);
+  tags.SetValue(0x0028, 0x0011, "384", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "12", false);
+  tags.SetValue(0x0028, 0x0102, "11", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "135633.507500", false);
+  tags.SetValue(0x0018, 0x0050, "3", false);
+  tags.SetValue(0x0018, 0x0088, "3.6000000014324", false);
+  tags.SetValue(0x0020, 0x0032, "-624\\-39.434460315761\\624.11344232813", false);
+  tags.SetValue(0x0020, 0x0037, "1\\0\\0\\0\\-0.1529858224513\\-0.988228383588", false);
+  tags.SetValue(0x0028, 0x0030, "3.25\\3.25", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("36");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("1");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("0.0").AddValue("0.98822836").AddValue("-0.152986");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_28)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_28(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 36);
+  ASSERT_NEAR(nifti.pixdim[3], 3.600000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 147456u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(54));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(54));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(54));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 5);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 104.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], -0.497204, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -3.557622, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], 150.310944, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 3.211742, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], -0.550749, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -92.105034, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 0.650774, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.759271, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=135633.508;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_29(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa', 'In/Orientation/cor/corint36/MR.1.3.12.2.1107.5.2.32.35131.2014031012563864907288801')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "2", false);
+  tags.SetValue(0x0018, 0x0080, "3000", false);
+  tags.SetValue(0x0018, 0x0081, "30", false);
+  tags.SetValue(0x0018, 0x1312, "ROW", false);
+  tags.SetValue(0x0019, 0x1029, "1254.99999998\\0.0\\1324.99999998\\69.99999998\\1394.99999999\\139.99999998\\1462.49999999\\207.49999999\\1532.49999997\\277.5\\1602.49999998\\347.49999997\\1672.49999999\\417.49999998\\1742.49999999\\487.49999999\\1812.5\\557.5\\1882.49999998\\627.49999997\\1952.49999998\\697.49999998\\2022.49999999\\767.49999999\\2090.0\\834.99999999\\2159.99999997\\905.0\\2229.99999998\\974.99999998\\2299.99999999\\1044.99999998\\2370.0\\1114.99999999\\2439.99999997\\1185.0", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "384", false);
+  tags.SetValue(0x0028, 0x0011, "384", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "12", false);
+  tags.SetValue(0x0028, 0x0102, "11", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "135636.520000", false);
+  tags.SetValue(0x0018, 0x0050, "3", false);
+  tags.SetValue(0x0018, 0x0088, "3.6000000014324", false);
+  tags.SetValue(0x0020, 0x0032, "-624\\-39.434460315761\\624.11344232813", false);
+  tags.SetValue(0x0020, 0x0037, "1\\0\\0\\0\\-0.1529858224513\\-0.988228383588", false);
+  tags.SetValue(0x0028, 0x0030, "3.25\\3.25", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("36");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("1");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("0.0").AddValue("0.98822836").AddValue("-0.152986");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_29)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_29(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 36);
+  ASSERT_NEAR(nifti.pixdim[3], 3.600000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 147456u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(54));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(54));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(54));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 5);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 104.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], -0.497204, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -3.557622, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], 150.310944, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 3.211742, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], -0.550749, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -92.105034, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 0.650774, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.759271, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=135636.520;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_30(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa', 'In/Orientation/sag/sagasc35/MR.1.3.12.2.1107.5.2.32.35131.2014031013000537156690252')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "1", false);
+  tags.SetValue(0x0018, 0x0080, "3000", false);
+  tags.SetValue(0x0018, 0x0081, "30", false);
+  tags.SetValue(0x0018, 0x1312, "ROW", false);
+  tags.SetValue(0x0019, 0x1029, "0.0\\72.50000001\\142.50000002\\215.0\\287.50000001\\357.50000001\\430.00000002\\502.5\\575.00000001\\645.00000002\\717.5\\790.00000001\\860.00000002\\932.5\\1005.0\\1075.00000001\\1147.50000002\\1220.0\\1290.00000001\\1362.50000002\\1435.0\\1505.0\\1577.50000001\\1650.00000002\\1720.0\\1792.50000001\\1865.00000002\\1935.0\\2007.50000001\\2080.00000002\\2152.5\\2222.5\\2295.00000001\\2367.50000002\\2437.5", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "384", false);
+  tags.SetValue(0x0028, 0x0011, "384", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "12", false);
+  tags.SetValue(0x0028, 0x0102, "11", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "140000.990000", false);
+  tags.SetValue(0x0018, 0x0050, "3", false);
+  tags.SetValue(0x0018, 0x0088, "3.6000000448788", false);
+  tags.SetValue(0x0020, 0x0032, "-61.200000762939\\-660.3196144104\\598.57627105713", false);
+  tags.SetValue(0x0020, 0x0037, "0\\1\\0\\0\\0\\-1", false);
+  tags.SetValue(0x0028, 0x0030, "3.25\\3.25", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("35");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("1");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("1.0").AddValue("0.0").AddValue("0.0");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_30)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_30(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 35);
+  ASSERT_NEAR(nifti.pixdim[3], 3.600000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 143360u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(54));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(54));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(54));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 1);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -3.600000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 61.200001, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], 140.319641, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -126.173706, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.500000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, -0.500000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, -0.500000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=140000.990;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_31(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa', 'In/Orientation/sag/sagasc35/MR.1.3.12.2.1107.5.2.32.35131.2014031013000818402490359')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "2", false);
+  tags.SetValue(0x0018, 0x0080, "3000", false);
+  tags.SetValue(0x0018, 0x0081, "30", false);
+  tags.SetValue(0x0018, 0x1312, "ROW", false);
+  tags.SetValue(0x0019, 0x1029, "0.0\\72.50000001\\142.49999999\\215.0\\287.50000001\\357.49999998\\429.99999999\\502.5\\572.50000001\\644.99999999\\717.5\\787.50000001\\859.99999999\\932.5\\1002.5\\1075.00000001\\1147.49999999\\1217.5\\1290.00000001\\1362.49999999\\1435.0\\1505.0\\1577.49999998\\1649.99999999\\1720.0\\1792.50000001\\1864.99999999\\1935.0\\2007.50000001\\2079.99999999\\2149.99999999\\2222.5\\2294.99999998\\2364.99999999\\2437.5", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "384", false);
+  tags.SetValue(0x0028, 0x0011, "384", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "12", false);
+  tags.SetValue(0x0028, 0x0102, "11", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "140004.002500", false);
+  tags.SetValue(0x0018, 0x0050, "3", false);
+  tags.SetValue(0x0018, 0x0088, "3.6000000448788", false);
+  tags.SetValue(0x0020, 0x0032, "-61.200000762939\\-660.3196144104\\598.57627105713", false);
+  tags.SetValue(0x0020, 0x0037, "0\\1\\0\\0\\0\\-1", false);
+  tags.SetValue(0x0028, 0x0030, "3.25\\3.25", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("35");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("1");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("1.0").AddValue("0.0").AddValue("0.0");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_31)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_31(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 35);
+  ASSERT_NEAR(nifti.pixdim[3], 3.600000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 143360u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(54));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(54));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(54));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 1);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -3.600000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 61.200001, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], 140.319641, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -126.173706, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.500000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, -0.500000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, -0.500000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=140004.003;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_32(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa', 'In/Orientation/sag/sagasc36/MR.1.3.12.2.1107.5.2.32.35131.2014031012583942473089577')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "1", false);
+  tags.SetValue(0x0018, 0x0080, "3000", false);
+  tags.SetValue(0x0018, 0x0081, "30", false);
+  tags.SetValue(0x0018, 0x1312, "ROW", false);
+  tags.SetValue(0x0019, 0x1029, "0.0\\69.99999998\\139.99999998\\209.99999999\\277.5\\347.5\\417.49999998\\487.49999999\\557.5\\627.5\\697.49999998\\767.49999999\\837.49999999\\905.0\\974.99999998\\1044.99999998\\1114.99999999\\1185.0\\1255.0\\1324.99999998\\1394.99999999\\1465.0\\1532.5\\1602.49999998\\1672.49999999\\1742.49999999\\1812.5\\1882.49999998\\1952.49999998\\2022.49999999\\2090.0\\2160.0\\2229.99999998\\2299.99999999\\2370.0\\2440.0", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "384", false);
+  tags.SetValue(0x0028, 0x0011, "384", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "12", false);
+  tags.SetValue(0x0028, 0x0102, "11", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "135835.760000", false);
+  tags.SetValue(0x0018, 0x0050, "3", false);
+  tags.SetValue(0x0018, 0x0088, "3.6", false);
+  tags.SetValue(0x0020, 0x0032, "-63\\-660.3196144104\\598.57627105713", false);
+  tags.SetValue(0x0020, 0x0037, "0\\1\\0\\0\\0\\-1", false);
+  tags.SetValue(0x0028, 0x0030, "3.25\\3.25", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("36");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("1");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("1.0").AddValue("0.0").AddValue("0.0");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_32)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_32(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 36);
+  ASSERT_NEAR(nifti.pixdim[3], 3.600000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 147456u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(54));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(54));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(54));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 1);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -3.600000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 63.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], 140.319641, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -126.173706, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.500000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, -0.500000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, -0.500000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=135835.760;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_33(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa', 'In/Orientation/sag/sagasc36/MR.1.3.12.2.1107.5.2.32.35131.2014031012584223181889687')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "2", false);
+  tags.SetValue(0x0018, 0x0080, "3000", false);
+  tags.SetValue(0x0018, 0x0081, "30", false);
+  tags.SetValue(0x0018, 0x1312, "ROW", false);
+  tags.SetValue(0x0019, 0x1029, "0.0\\70.00000001\\137.50000001\\207.50000002\\277.5\\347.5\\417.50000001\\487.50000002\\557.5\\627.5\\697.50000001\\765.00000001\\835.00000002\\905.0\\975.00000001\\1045.00000001\\1115.00000002\\1185.0\\1255.0\\1325.00000001\\1392.50000002\\1462.49999999\\1532.5\\1602.50000001\\1672.50000002\\1742.50000002\\1812.5\\1882.50000001\\1952.50000001\\2020.00000002\\2090.0\\2160.0\\2230.00000001\\2300.00000002\\2370.0\\2440.0", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "384", false);
+  tags.SetValue(0x0028, 0x0011, "384", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "12", false);
+  tags.SetValue(0x0028, 0x0102, "11", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "135838.772500", false);
+  tags.SetValue(0x0018, 0x0050, "3", false);
+  tags.SetValue(0x0018, 0x0088, "3.6", false);
+  tags.SetValue(0x0020, 0x0032, "-63\\-660.3196144104\\598.57627105713", false);
+  tags.SetValue(0x0020, 0x0037, "0\\1\\0\\0\\0\\-1", false);
+  tags.SetValue(0x0028, 0x0030, "3.25\\3.25", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("36");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("1");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("1.0").AddValue("0.0").AddValue("0.0");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_33)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_33(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 36);
+  ASSERT_NEAR(nifti.pixdim[3], 3.600000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 147456u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(54));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(54));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(54));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 1);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -3.600000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 63.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], 140.319641, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -126.173706, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.500000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, -0.500000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, -0.500000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=135838.772;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_34(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa', 'In/Orientation/sag/sagdesc35/MR.1.3.12.2.1107.5.2.32.35131.2014031013003590507090477')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "1", false);
+  tags.SetValue(0x0018, 0x0080, "3000", false);
+  tags.SetValue(0x0018, 0x0081, "30", false);
+  tags.SetValue(0x0018, 0x1312, "ROW", false);
+  tags.SetValue(0x0019, 0x1029, "2437.5\\2365.00000002\\2295.00000001\\2222.5\\2149.99999999\\2080.00000002\\2007.50000001\\1935.0\\1862.50000002\\1792.50000001\\1720.0\\1647.50000002\\1577.50000001\\1505.0\\1432.5\\1362.50000002\\1290.00000001\\1217.5\\1147.50000002\\1075.00000001\\1002.5\\932.5\\860.00000002\\787.50000001\\717.5\\645.00000002\\572.50000001\\502.5\\430.00000002\\357.50000001\\285.0\\215.0\\142.50000002\\70.00000001\\0.0", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "384", false);
+  tags.SetValue(0x0028, 0x0011, "384", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "12", false);
+  tags.SetValue(0x0028, 0x0102, "11", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "140034.397500", false);
+  tags.SetValue(0x0018, 0x0050, "3", false);
+  tags.SetValue(0x0018, 0x0088, "3.6000000448788", false);
+  tags.SetValue(0x0020, 0x0032, "-61.200000762939\\-660.3196144104\\598.57627105713", false);
+  tags.SetValue(0x0020, 0x0037, "0\\1\\0\\0\\0\\-1", false);
+  tags.SetValue(0x0028, 0x0030, "3.25\\3.25", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("35");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("1");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("1.0").AddValue("0.0").AddValue("0.0");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_34)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_34(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 35);
+  ASSERT_NEAR(nifti.pixdim[3], 3.600000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 143360u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(54));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(54));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(54));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 2);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -3.600000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 61.200001, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], 140.319641, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -126.173706, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.500000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, -0.500000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, -0.500000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=140034.397;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_35(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa', 'In/Orientation/sag/sagdesc35/MR.1.3.12.2.1107.5.2.32.35131.2014031013003871821190579')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "2", false);
+  tags.SetValue(0x0018, 0x0080, "3000", false);
+  tags.SetValue(0x0018, 0x0081, "30", false);
+  tags.SetValue(0x0018, 0x1312, "ROW", false);
+  tags.SetValue(0x0019, 0x1029, "2437.5\\2364.99999999\\2292.50000001\\2222.5\\2149.99999999\\2077.49999998\\2007.50000001\\1935.0\\1862.49999999\\1792.50000001\\1720.0\\1647.49999999\\1577.49999998\\1505.0\\1432.5\\1362.49999999\\1290.00000001\\1217.5\\1144.99999999\\1074.99999998\\1002.5\\929.99999999\\859.99999999\\787.50000001\\715.0\\644.99999999\\572.50000001\\500.0\\429.99999999\\357.49999998\\285.0\\215.0\\142.49999999\\70.00000001\\0.0", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "384", false);
+  tags.SetValue(0x0028, 0x0011, "384", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "12", false);
+  tags.SetValue(0x0028, 0x0102, "11", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "140037.410000", false);
+  tags.SetValue(0x0018, 0x0050, "3", false);
+  tags.SetValue(0x0018, 0x0088, "3.6000000448788", false);
+  tags.SetValue(0x0020, 0x0032, "-61.200000762939\\-660.3196144104\\598.57627105713", false);
+  tags.SetValue(0x0020, 0x0037, "0\\1\\0\\0\\0\\-1", false);
+  tags.SetValue(0x0028, 0x0030, "3.25\\3.25", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("35");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("1");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("1.0").AddValue("0.0").AddValue("0.0");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_35)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_35(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 35);
+  ASSERT_NEAR(nifti.pixdim[3], 3.600000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 143360u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(54));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(54));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(54));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 2);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -3.600000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 61.200001, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], 140.319641, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -126.173706, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.500000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, -0.500000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, -0.500000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=140037.410;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_36(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa', 'In/Orientation/sag/sagdesc36/MR.1.3.12.2.1107.5.2.32.35131.2014031012590727219489809')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "1", false);
+  tags.SetValue(0x0018, 0x0080, "3000", false);
+  tags.SetValue(0x0018, 0x0081, "30", false);
+  tags.SetValue(0x0018, 0x1312, "ROW", false);
+  tags.SetValue(0x0019, 0x1029, "2440.0\\2370.00000002\\2300.00000002\\2230.00000001\\2162.50000001\\2092.5\\2022.50000002\\1952.50000001\\1882.50000001\\1812.5\\1742.50000002\\1672.50000002\\1602.50000001\\1535.0\\1465.0\\1395.00000002\\1325.00000001\\1255.0\\1185.0\\1115.00000002\\1045.00000001\\975.00000001\\907.5\\837.50000002\\767.50000002\\697.50000001\\627.5\\557.50000002\\487.50000002\\417.50000001\\347.5\\280.0\\210.00000002\\140.00000001\\70.00000001\\0.0", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "384", false);
+  tags.SetValue(0x0028, 0x0011, "384", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "12", false);
+  tags.SetValue(0x0028, 0x0102, "11", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "135905.272500", false);
+  tags.SetValue(0x0018, 0x0050, "3", false);
+  tags.SetValue(0x0018, 0x0088, "3.6", false);
+  tags.SetValue(0x0020, 0x0032, "-63\\-660.3196144104\\598.57627105713", false);
+  tags.SetValue(0x0020, 0x0037, "0\\1\\0\\0\\0\\-1", false);
+  tags.SetValue(0x0028, 0x0030, "3.25\\3.25", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("36");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("1");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("1.0").AddValue("0.0").AddValue("0.0");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_36)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_36(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 36);
+  ASSERT_NEAR(nifti.pixdim[3], 3.600000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 147456u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(54));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(54));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(54));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 2);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -3.600000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 63.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], 140.319641, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -126.173706, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.500000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, -0.500000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, -0.500000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=135905.272;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_37(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa', 'In/Orientation/sag/sagdesc36/MR.1.3.12.2.1107.5.2.32.35131.201403101259108388189913')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "2", false);
+  tags.SetValue(0x0018, 0x0080, "3000", false);
+  tags.SetValue(0x0018, 0x0081, "30", false);
+  tags.SetValue(0x0018, 0x1312, "ROW", false);
+  tags.SetValue(0x0019, 0x1029, "2440.0\\2370.0\\2299.99999999\\2230.00000001\\2160.0\\2090.0\\2022.49999999\\1952.49999998\\1882.50000001\\1812.5\\1742.49999999\\1672.49999999\\1602.50000001\\1532.5\\1462.49999999\\1394.99999999\\1325.00000001\\1255.0\\1185.0\\1114.99999999\\1045.00000001\\975.00000001\\905.0\\834.99999999\\767.49999999\\697.50000001\\627.5\\557.5\\487.49999999\\417.50000001\\347.5\\277.5\\207.49999999\\139.99999998\\70.00000001\\0.0", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "384", false);
+  tags.SetValue(0x0028, 0x0011, "384", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "12", false);
+  tags.SetValue(0x0028, 0x0102, "11", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "135908.285000", false);
+  tags.SetValue(0x0018, 0x0050, "3", false);
+  tags.SetValue(0x0018, 0x0088, "3.6", false);
+  tags.SetValue(0x0020, 0x0032, "-63\\-660.3196144104\\598.57627105713", false);
+  tags.SetValue(0x0020, 0x0037, "0\\1\\0\\0\\0\\-1", false);
+  tags.SetValue(0x0028, 0x0030, "3.25\\3.25", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("36");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("1");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("1.0").AddValue("0.0").AddValue("0.0");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_37)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_37(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 36);
+  ASSERT_NEAR(nifti.pixdim[3], 3.600000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 147456u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(54));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(54));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(54));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 2);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -3.600000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 63.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], 140.319641, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -126.173706, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.500000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, -0.500000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, -0.500000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=135908.285;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_38(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa', 'In/Orientation/sag/sagint35/MR.1.3.12.2.1107.5.2.32.35131.2014031013010980862390698')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "1", false);
+  tags.SetValue(0x0018, 0x0080, "3000", false);
+  tags.SetValue(0x0018, 0x0081, "30", false);
+  tags.SetValue(0x0018, 0x1312, "ROW", false);
+  tags.SetValue(0x0019, 0x1029, "0.0\\1292.50000001\\72.50000001\\1362.49999999\\144.99999999\\1435.0\\215.0\\1507.50000001\\287.50000001\\1577.50000001\\359.99999999\\1649.99999999\\432.5\\1722.5\\502.5\\1795.00000001\\575.00000001\\1864.99999999\\647.49999999\\1937.5\\717.5\\2010.00000001\\790.00000001\\2079.99999999\\862.49999999\\2152.5\\932.5\\2225.00000001\\1005.0\\2295.00000001\\1077.50000001\\2367.49999999\\1147.49999999\\2440.0\\1220.0", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "384", false);
+  tags.SetValue(0x0028, 0x0011, "384", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "12", false);
+  tags.SetValue(0x0028, 0x0102, "11", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "140105.875000", false);
+  tags.SetValue(0x0018, 0x0050, "3", false);
+  tags.SetValue(0x0018, 0x0088, "3.6000000448788", false);
+  tags.SetValue(0x0020, 0x0032, "-61.200000762939\\-660.3196144104\\598.57627105713", false);
+  tags.SetValue(0x0020, 0x0037, "0\\1\\0\\0\\0\\-1", false);
+  tags.SetValue(0x0028, 0x0030, "3.25\\3.25", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("35");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("1");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("1.0").AddValue("0.0").AddValue("0.0");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_38)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_38(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 35);
+  ASSERT_NEAR(nifti.pixdim[3], 3.600000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 143360u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(54));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(54));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(54));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 3);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -3.600000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 61.200001, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], 140.319641, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -126.173706, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.500000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, -0.500000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, -0.500000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=140105.875;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_39(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa', 'In/Orientation/sag/sagint35/MR.1.3.12.2.1107.5.2.32.35131.2014031013011261838990799')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "2", false);
+  tags.SetValue(0x0018, 0x0080, "3000", false);
+  tags.SetValue(0x0018, 0x0081, "30", false);
+  tags.SetValue(0x0018, 0x1312, "ROW", false);
+  tags.SetValue(0x0019, 0x1029, "0.0\\1292.49999998\\72.49999998\\1362.49999999\\144.99999999\\1435.0\\215.0\\1507.49999998\\287.49999998\\1577.49999998\\359.99999999\\1649.99999999\\429.99999999\\1722.49999997\\502.5\\1792.49999998\\574.99999998\\1864.99999999\\644.99999999\\1937.5\\717.5\\2007.49999998\\789.99999998\\2079.99999999\\859.99999999\\2152.5\\932.5\\2222.49999997\\1004.99999998\\2294.99999998\\1077.49999998\\2367.49999999\\1147.49999999\\2437.5\\1220.0", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "384", false);
+  tags.SetValue(0x0028, 0x0011, "384", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "12", false);
+  tags.SetValue(0x0028, 0x0102, "11", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "140108.887500", false);
+  tags.SetValue(0x0018, 0x0050, "3", false);
+  tags.SetValue(0x0018, 0x0088, "3.6000000448788", false);
+  tags.SetValue(0x0020, 0x0032, "-61.200000762939\\-660.3196144104\\598.57627105713", false);
+  tags.SetValue(0x0020, 0x0037, "0\\1\\0\\0\\0\\-1", false);
+  tags.SetValue(0x0028, 0x0030, "3.25\\3.25", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("35");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("1");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("1.0").AddValue("0.0").AddValue("0.0");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_39)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_39(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 35);
+  ASSERT_NEAR(nifti.pixdim[3], 3.600000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 143360u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(54));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(54));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(54));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 3);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -3.600000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 61.200001, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], 140.319641, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -126.173706, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.500000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, -0.500000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, -0.500000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=140108.888;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_40(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa', 'In/Orientation/sag/sagint36/MR.1.3.12.2.1107.5.2.32.35131.2014031012593442716690029')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "1", false);
+  tags.SetValue(0x0018, 0x0080, "3000", false);
+  tags.SetValue(0x0018, 0x0081, "30", false);
+  tags.SetValue(0x0018, 0x1312, "ROW", false);
+  tags.SetValue(0x0019, 0x1029, "1255.0\\0.0\\1325.00000001\\70.00000001\\1395.00000002\\140.00000001\\1465.0\\210.00000002\\1535.0\\280.0\\1605.00000001\\350.00000001\\1675.00000002\\420.00000001\\1745.00000002\\490.00000002\\1812.5\\557.50000002\\1882.50000001\\627.5\\1952.50000001\\697.50000001\\2022.50000002\\767.50000002\\2092.5\\837.50000002\\2162.50000001\\907.5\\2232.50000001\\977.50000001\\2302.50000002\\1047.50000002\\2372.5\\1117.50000002\\2440.0\\1185.0", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "384", false);
+  tags.SetValue(0x0028, 0x0011, "384", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "12", false);
+  tags.SetValue(0x0028, 0x0102, "11", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "135931.837500", false);
+  tags.SetValue(0x0018, 0x0050, "3", false);
+  tags.SetValue(0x0018, 0x0088, "3.6", false);
+  tags.SetValue(0x0020, 0x0032, "-63\\-660.3196144104\\598.57627105713", false);
+  tags.SetValue(0x0020, 0x0037, "0\\1\\0\\0\\0\\-1", false);
+  tags.SetValue(0x0028, 0x0030, "3.25\\3.25", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("36");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("1");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("1.0").AddValue("0.0").AddValue("0.0");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_40)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_40(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 36);
+  ASSERT_NEAR(nifti.pixdim[3], 3.600000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 147456u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(54));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(54));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(54));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 5);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -3.600000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 63.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], 140.319641, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -126.173706, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.500000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, -0.500000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, -0.500000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=135931.837;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_41(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa', 'In/Orientation/sag/sagint36/MR.1.3.12.2.1107.5.2.32.35131.2014031012593723427590139')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "2", false);
+  tags.SetValue(0x0018, 0x0080, "3000", false);
+  tags.SetValue(0x0018, 0x0081, "30", false);
+  tags.SetValue(0x0018, 0x1312, "ROW", false);
+  tags.SetValue(0x0019, 0x1029, "1255.0\\0.0\\1325.00000001\\70.00000001\\1394.99999999\\139.99999998\\1465.0\\209.99999999\\1535.0\\280.0\\1605.00000001\\350.00000001\\1674.99999999\\420.00000001\\1742.49999999\\487.49999999\\1812.5\\557.5\\1882.50000001\\627.5\\1952.49999998\\697.50000001\\2022.49999999\\767.49999999\\2092.5\\837.49999999\\2162.50000001\\907.5\\2232.50000001\\977.50000001\\2299.99999999\\1047.49999999\\2370.0\\1114.99999999\\2440.0\\1185.0", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "384", false);
+  tags.SetValue(0x0028, 0x0011, "384", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "12", false);
+  tags.SetValue(0x0028, 0x0102, "11", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "135934.850000", false);
+  tags.SetValue(0x0018, 0x0050, "3", false);
+  tags.SetValue(0x0018, 0x0088, "3.6", false);
+  tags.SetValue(0x0020, 0x0032, "-63\\-660.3196144104\\598.57627105713", false);
+  tags.SetValue(0x0020, 0x0037, "0\\1\\0\\0\\0\\-1", false);
+  tags.SetValue(0x0028, 0x0030, "3.25\\3.25", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("36");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("1");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("1.0").AddValue("0.0").AddValue("0.0");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_41)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_41(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 36);
+  ASSERT_NEAR(nifti.pixdim[3], 3.600000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 147456u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(54));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(54));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(54));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 5);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -3.600000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 63.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], 140.319641, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -126.173706, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.500000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, -0.500000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, -0.500000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=135934.850;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_42(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa', 'In/TotalReadoutTime/PF_PAT_POS_PFOV_PEres_INTERP_test2/58PF_NOPAT_NOPOS_PERES100_ES0P59_BW2222_AP_0016/PF_PAT_POS_BW_INTERP_TEST.MR.HEAD_HARMS.0016.0001.2017.09.20.16.16.44.39766.120117828.IMA')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "1", false);
+  tags.SetValue(0x0018, 0x0080, "6100", false);
+  tags.SetValue(0x0018, 0x0081, "60", false);
+  tags.SetValue(0x0018, 0x1312, "COL", false);
+  tags.SetValue(0x0019, 0x1029, "3039.99999998\\0.0\\3139.99999998\\99.99999998\\3242.49999999\\202.49999998\\3342.5\\304.99999999\\3444.99999998\\405.0\\3547.49999999\\507.49999998\\3647.49999999\\607.49999998\\3750.0\\709.99999999\\3849.99999998\\810.0\\3952.49999998\\912.49999998\\4052.49999999\\1012.49999998\\4155.0\\1114.99999999\\4254.99999998\\1215.0\\4357.49999998\\1317.49999998\\4457.49999999\\1417.49999998\\4560.0\\1519.99999999\\4659.99999997\\1620.0\\4762.49999998\\1722.49999997\\4862.49999999\\1822.49999998\\4965.0\\1924.99999999\\5067.49999998\\2027.5\\5167.49999998\\2127.49999997\\5269.99999999\\2229.99999998\\5370.0\\2329.99999999\\5472.49999997\\2432.5\\5572.49999998\\2532.5\\5674.99999999\\2634.99999998\\5774.99999999\\2734.99999999\\5877.49999997\\2837.49999999\\5977.49999998\\2937.5", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "720", false);
+  tags.SetValue(0x0028, 0x0011, "720", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "16", false);
+  tags.SetValue(0x0028, 0x0102, "15", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "153509.335000", false);
+  tags.SetValue(0x0018, 0x0050, "2.4000000953674", false);
+  tags.SetValue(0x0018, 0x0088, "2.3999999741376", false);
+  tags.SetValue(0x0020, 0x0032, "-866.17921113968\\-872.62231063843\\-64.073608398438", false);
+  tags.SetValue(0x0020, 0x0037, "1\\0\\0\\0\\1\\0", false);
+  tags.SetValue(0x0028, 0x0030, "2.4000000953674\\2.4000000953674", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("60");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("1");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("0.0").AddValue("0.0").AddValue("1.0");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_42)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_42(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 90);
+  ASSERT_NEAR(nifti.pixdim[1], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 90);
+  ASSERT_NEAR(nifti.pixdim[2], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 60);
+  ASSERT_NEAR(nifti.pixdim[3], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 486000u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 5);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 110.179169, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -96.977722, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -64.073608, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 1.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.000000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=60;Time=153509.335;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_43(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa', 'In/TotalReadoutTime/PF_PAT_POS_PFOV_PEres_INTERP_test2/58PF_NOPAT_NOPOS_PERES100_ES0P59_BW2222_PA_0017/PF_PAT_POS_BW_INTERP_TEST.MR.HEAD_HARMS.0017.0001.2017.09.20.16.16.44.39766.120121926.IMA')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "1", false);
+  tags.SetValue(0x0018, 0x0080, "6100", false);
+  tags.SetValue(0x0018, 0x0081, "60", false);
+  tags.SetValue(0x0018, 0x1312, "COL", false);
+  tags.SetValue(0x0019, 0x1029, "3040.00000001\\0.0\\3139.99999998\\100.00000001\\3242.49999999\\202.49999998\\3342.5\\304.99999999\\3445.00000001\\405.0\\3547.49999999\\507.50000001\\3647.49999999\\607.49999998\\3750.0\\709.99999999\\3850.00000001\\810.0\\3952.49999998\\912.50000001\\4052.49999999\\1012.49999998\\4155.0\\1114.99999999\\4255.0\\1215.0\\4357.49999998\\1317.5\\4457.49999999\\1417.49999998\\4560.0\\1519.99999999\\4660.0\\1620.0\\4762.49999998\\1722.5\\4862.49999999\\1822.49999998\\4965.0\\1924.99999999\\5067.5\\2027.5\\5167.49999998\\2127.5\\5269.99999999\\2229.99999998\\5370.0\\2329.99999999\\5472.5\\2432.5\\5572.49999998\\2532.5\\5674.99999999\\2634.99999998\\5774.99999999\\2734.99999999\\5877.5\\2837.49999999\\5977.50000001\\2937.5", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "720", false);
+  tags.SetValue(0x0028, 0x0011, "720", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "16", false);
+  tags.SetValue(0x0028, 0x0102, "15", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "153612.390000", false);
+  tags.SetValue(0x0018, 0x0050, "2.4000000953674", false);
+  tags.SetValue(0x0018, 0x0088, "2.3999999741376", false);
+  tags.SetValue(0x0020, 0x0032, "-866.17921113968\\-872.62231063843\\-64.073608398438", false);
+  tags.SetValue(0x0020, 0x0037, "1\\0\\0\\0\\1\\0", false);
+  tags.SetValue(0x0028, 0x0030, "2.4000000953674\\2.4000000953674", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("60");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("0");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("0.0").AddValue("0.0").AddValue("1.0");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_43)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_43(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 90);
+  ASSERT_NEAR(nifti.pixdim[1], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 90);
+  ASSERT_NEAR(nifti.pixdim[2], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 60);
+  ASSERT_NEAR(nifti.pixdim[3], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 486000u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 5);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 110.179169, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -96.977722, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -64.073608, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 1.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.000000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=60;Time=153612.390;phase=0");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_44(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa', 'In/TotalReadoutTime/PF_PAT_POS_PFOV_PEres_INTERP_test2/78PF_NOPAT_NOPOS_PERES100_ES0P59_BW2222_AP_0018/PF_PAT_POS_BW_INTERP_TEST.MR.HEAD_HARMS.0018.0001.2017.09.20.16.16.44.39766.120126024.IMA')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "1", false);
+  tags.SetValue(0x0018, 0x0080, "6100", false);
+  tags.SetValue(0x0018, 0x0081, "60", false);
+  tags.SetValue(0x0018, 0x1312, "COL", false);
+  tags.SetValue(0x0019, 0x1029, "3040.00000001\\0.0\\3142.50000002\\102.50000001\\3242.49999999\\202.50000001\\3345.0\\304.99999999\\3445.00000001\\405.0\\3547.50000002\\507.50000001\\3649.99999999\\610.00000002\\3750.0\\709.99999999\\3852.50000001\\812.5\\3952.50000001\\912.50000001\\4054.99999999\\1015.00000001\\4155.0\\1114.99999999\\4257.50000001\\1217.5\\4357.50000001\\1317.5\\4459.99999999\\1420.00000001\\4560.0\\1520.00000002\\4662.50000001\\1622.5\\4762.50000001\\1722.5\\4864.99999999\\1825.00000001\\4965.0\\1925.00000002\\5067.5\\2027.5\\5167.50000001\\2130.0\\5270.00000002\\2230.00000001\\5372.5\\2332.50000002\\5472.5\\2432.5\\5575.00000001\\2535.0\\5675.00000002\\2635.00000001\\5777.5\\2737.50000002\\5877.5\\2837.49999999\\5980.00000001\\2940.0", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "720", false);
+  tags.SetValue(0x0028, 0x0011, "720", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "16", false);
+  tags.SetValue(0x0028, 0x0102, "15", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "153702.160000", false);
+  tags.SetValue(0x0018, 0x0050, "2.4000000953674", false);
+  tags.SetValue(0x0018, 0x0088, "2.3999999741376", false);
+  tags.SetValue(0x0020, 0x0032, "-866.17921113968\\-872.62231063843\\-64.073608398438", false);
+  tags.SetValue(0x0020, 0x0037, "1\\0\\0\\0\\1\\0", false);
+  tags.SetValue(0x0028, 0x0030, "2.4000000953674\\2.4000000953674", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("60");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("1");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("0.0").AddValue("0.0").AddValue("1.0");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_44)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_44(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 90);
+  ASSERT_NEAR(nifti.pixdim[1], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 90);
+  ASSERT_NEAR(nifti.pixdim[2], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 60);
+  ASSERT_NEAR(nifti.pixdim[3], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 486000u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 5);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 110.179169, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -96.977722, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -64.073608, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 1.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.000000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=60;Time=153702.160;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_45(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa', 'In/TotalReadoutTime/PF_PAT_POS_PFOV_PEres_INTERP_test2/78PF_NOPAT_NOPOS_PERES100_ES0P59_BW2222_PA_0019/PF_PAT_POS_BW_INTERP_TEST.MR.HEAD_HARMS.0019.0001.2017.09.20.16.16.44.39766.120130122.IMA')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "1", false);
+  tags.SetValue(0x0018, 0x0080, "6100", false);
+  tags.SetValue(0x0018, 0x0081, "60", false);
+  tags.SetValue(0x0018, 0x1312, "COL", false);
+  tags.SetValue(0x0019, 0x1029, "3040.00000001\\0.0\\3140.00000001\\100.00000001\\3242.50000002\\202.50000001\\3342.5\\302.50000002\\3445.00000001\\405.0\\3545.00000001\\505.0\\3647.50000002\\607.50000001\\3747.5\\707.50000002\\3850.00000001\\810.0\\3950.00000001\\910.0\\4052.50000002\\1012.50000001\\4152.5\\1112.50000002\\4255.0\\1215.0\\4355.00000001\\1315.0\\4457.50000002\\1417.50000001\\4557.50000002\\1520.00000002\\4660.0\\1620.00000002\\4762.50000001\\1722.5\\4862.50000002\\1822.50000001\\4965.0\\1925.00000002\\5065.0\\2025.00000002\\5167.50000001\\2127.5\\5267.50000002\\2227.50000001\\5370.00000002\\2330.00000002\\5470.0\\2430.00000002\\5572.50000001\\2532.5\\5672.50000002\\2632.50000001\\5775.00000002\\2735.00000002\\5875.0\\2835.00000002\\5977.50000001\\2937.5", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "720", false);
+  tags.SetValue(0x0028, 0x0011, "720", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "16", false);
+  tags.SetValue(0x0028, 0x0102, "15", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "153738.497500", false);
+  tags.SetValue(0x0018, 0x0050, "2.4000000953674", false);
+  tags.SetValue(0x0018, 0x0088, "2.3999999741376", false);
+  tags.SetValue(0x0020, 0x0032, "-866.17921113968\\-872.62231063843\\-64.073608398438", false);
+  tags.SetValue(0x0020, 0x0037, "1\\0\\0\\0\\1\\0", false);
+  tags.SetValue(0x0028, 0x0030, "2.4000000953674\\2.4000000953674", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("60");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("0");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("0.0").AddValue("0.0").AddValue("1.0");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_45)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_45(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 90);
+  ASSERT_NEAR(nifti.pixdim[1], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 90);
+  ASSERT_NEAR(nifti.pixdim[2], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 60);
+  ASSERT_NEAR(nifti.pixdim[3], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 486000u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 5);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 110.179169, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -96.977722, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -64.073608, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 1.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.000000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=60;Time=153738.497;phase=0");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_46(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa', 'In/TotalReadoutTime/PF_PAT_POS_PFOV_PEres_INTERP_test2/NOPF_NOPAT_100POS_PERES100_ES0P59_BW2222_AP_0014/PF_PAT_POS_BW_INTERP_TEST.MR.HEAD_HARMS.0014.0001.2017.09.20.16.16.44.39766.120109632.IMA')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "1", false);
+  tags.SetValue(0x0018, 0x0080, "10730", false);
+  tags.SetValue(0x0018, 0x0081, "111.2", false);
+  tags.SetValue(0x0018, 0x1312, "COL", false);
+  tags.SetValue(0x0019, 0x1029, "5355.00000001\\0.0\\5532.5\\177.49999999\\5709.99999999\\355.00000001\\5889.99999998\\535.0\\6067.5\\712.49999999\\6245.0\\892.49999999\\6424.99999999\\1070.00000001\\6602.50000001\\1247.5\\6782.5\\1427.49999999\\6959.99999999\\1605.00000001\\7137.50000001\\1785.0\\7317.5\\1962.49999999\\7495.0\\2139.99999998\\7674.99999999\\2320.00000001\\7852.50000001\\2497.5\\8030.0\\2677.49999999\\8209.99999999\\2855.00000001\\8387.50000001\\3032.5\\8567.5\\3212.49999999\\8745.0\\3389.99999998\\8922.49999999\\3570.00000001\\9102.50000001\\3747.5\\9280.0\\3924.99999999\\9459.99999999\\4105.00000001\\9637.50000001\\4282.5\\9815.0\\4462.49999999\\9995.0\\4639.99999998\\10172.49999999\\4817.5\\10352.50000001\\4997.5\\10530.0\\5174.99999999", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "720", false);
+  tags.SetValue(0x0028, 0x0011, "720", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "16", false);
+  tags.SetValue(0x0028, 0x0102, "15", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "153309.137500", false);
+  tags.SetValue(0x0018, 0x0050, "2.4000000953674", false);
+  tags.SetValue(0x0018, 0x0088, "2.3999999741376", false);
+  tags.SetValue(0x0020, 0x0032, "-866.17921113968\\-872.62231063843\\-64.073608398438", false);
+  tags.SetValue(0x0020, 0x0037, "1\\0\\0\\0\\1\\0", false);
+  tags.SetValue(0x0028, 0x0030, "2.4000000953674\\2.4000000953674", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("60");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("1");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("0.0").AddValue("0.0").AddValue("1.0");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_46)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_46(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 90);
+  ASSERT_NEAR(nifti.pixdim[1], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 90);
+  ASSERT_NEAR(nifti.pixdim[2], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 60);
+  ASSERT_NEAR(nifti.pixdim[3], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 486000u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 5);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 110.179169, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -96.977722, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -64.073608, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 1.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.000000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=1.1e+02;Time=153309.138;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_47(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa', 'In/TotalReadoutTime/PF_PAT_POS_PFOV_PEres_INTERP_test2/NOPF_NOPAT_100POS_PERES100_ES0P59_BW2222_PA_0015/PF_PAT_POS_BW_INTERP_TEST.MR.HEAD_HARMS.0015.0001.2017.09.20.16.16.44.39766.120113730.IMA')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "1", false);
+  tags.SetValue(0x0018, 0x0080, "10730", false);
+  tags.SetValue(0x0018, 0x0081, "111.2", false);
+  tags.SetValue(0x0018, 0x1312, "COL", false);
+  tags.SetValue(0x0019, 0x1029, "5354.99999998\\0.0\\5535.0\\179.99999999\\5712.49999999\\357.49999998\\5889.99999998\\537.50000001\\6070.00000001\\715.0\\6247.5\\892.49999999\\6427.49999999\\1072.49999998\\6604.99999998\\1250.0\\6782.5\\1429.99999999\\6962.49999999\\1607.49999998\\7139.99999998\\1785.0\\7320.00000001\\1965.0\\7497.5\\2142.49999999\\7674.99999999\\2322.49999998\\7854.99999998\\2500.0\\8032.5\\2677.49999999\\8212.49999999\\2857.49999998\\8389.99999998\\3035.0\\8567.5\\3215.0\\8747.5\\3392.49999999\\8924.99999999\\3570.00000001\\9104.99999998\\3750.0\\9282.5\\3927.49999999\\9459.99999999\\4107.49999998\\9639.99999998\\4285.0\\9817.5\\4462.49999999\\9997.5\\4642.49999999\\10174.99999999\\4820.00000001\\10352.50000001\\4997.5\\10532.5\\5177.49999999", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "720", false);
+  tags.SetValue(0x0028, 0x0011, "720", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "16", false);
+  tags.SetValue(0x0028, 0x0102, "15", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "153352.892500", false);
+  tags.SetValue(0x0018, 0x0050, "2.4000000953674", false);
+  tags.SetValue(0x0018, 0x0088, "2.3999999741376", false);
+  tags.SetValue(0x0020, 0x0032, "-866.17921113968\\-872.62231063843\\-64.073608398438", false);
+  tags.SetValue(0x0020, 0x0037, "1\\0\\0\\0\\1\\0", false);
+  tags.SetValue(0x0028, 0x0030, "2.4000000953674\\2.4000000953674", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("60");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("0");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("0.0").AddValue("0.0").AddValue("1.0");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_47)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_47(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 90);
+  ASSERT_NEAR(nifti.pixdim[1], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 90);
+  ASSERT_NEAR(nifti.pixdim[2], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 60);
+  ASSERT_NEAR(nifti.pixdim[3], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 486000u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 5);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 110.179169, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -96.977722, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -64.073608, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 1.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.000000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=1.1e+02;Time=153352.892;phase=0");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_48(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa', 'In/TotalReadoutTime/PF_PAT_POS_PFOV_PEres_INTERP_test2/NOPF_NOPAT_50POS_PERES100_ES0P59_BW2222_AP_0012/PF_PAT_POS_BW_INTERP_TEST.MR.HEAD_HARMS.0012.0001.2017.09.20.16.16.44.39766.120101436.IMA')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "1", false);
+  tags.SetValue(0x0018, 0x0080, "8320", false);
+  tags.SetValue(0x0018, 0x0081, "84", false);
+  tags.SetValue(0x0018, 0x1312, "COL", false);
+  tags.SetValue(0x0019, 0x1029, "4149.99999999\\0.0\\4287.50000001\\137.49999998\\4424.99999999\\274.99999999\\4565.0\\415.00000001\\4702.49999998\\552.49999999\\4840.0\\690.0\\4979.99999998\\829.99999999\\5117.49999999\\967.5\\5255.0\\1104.99999998\\5394.99999999\\1245.0\\5532.5\\1382.50000001\\5669.99999998\\1519.99999999\\5810.0\\1660.0\\5947.49999998\\1797.49999999\\6084.99999999\\1935.0\\6225.00000001\\2074.99999998\\6362.49999999\\2212.49999999\\6500.0\\2350.00000001\\6639.99999998\\2489.99999999\\6777.5\\2627.5\\6915.00000001\\2764.99999998\\7054.99999999\\2905.0\\7192.5\\3042.49999998\\7329.99999999\\3179.99999999\\7470.0\\3320.00000001\\7607.49999998\\3457.49999999\\7745.0\\3595.0\\7884.99999998\\3734.99999999\\8022.49999999\\3872.5\\8160.0\\4009.99999998", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "720", false);
+  tags.SetValue(0x0028, 0x0011, "720", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "16", false);
+  tags.SetValue(0x0028, 0x0102, "15", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "153031.312500", false);
+  tags.SetValue(0x0018, 0x0050, "2.4000000953674", false);
+  tags.SetValue(0x0018, 0x0088, "2.3999999741376", false);
+  tags.SetValue(0x0020, 0x0032, "-866.17921113968\\-872.62231063843\\-64.073608398438", false);
+  tags.SetValue(0x0020, 0x0037, "1\\0\\0\\0\\1\\0", false);
+  tags.SetValue(0x0028, 0x0030, "2.4000000953674\\2.4000000953674", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("60");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("1");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("0.0").AddValue("0.0").AddValue("1.0");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_48)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_48(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 90);
+  ASSERT_NEAR(nifti.pixdim[1], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 90);
+  ASSERT_NEAR(nifti.pixdim[2], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 60);
+  ASSERT_NEAR(nifti.pixdim[3], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 486000u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 5);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 110.179169, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -96.977722, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -64.073608, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 1.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.000000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=84;Time=153031.312;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_49(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa', 'In/TotalReadoutTime/PF_PAT_POS_PFOV_PEres_INTERP_test2/NOPF_NOPAT_50POS_PERES100_ES0P59_BW2222_PA_0013/PF_PAT_POS_BW_INTERP_TEST.MR.HEAD_HARMS.0013.0001.2017.09.20.16.16.44.39766.120105534.IMA')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "1", false);
+  tags.SetValue(0x0018, 0x0080, "8320", false);
+  tags.SetValue(0x0018, 0x0081, "84", false);
+  tags.SetValue(0x0018, 0x1312, "COL", false);
+  tags.SetValue(0x0019, 0x1029, "4149.99999999\\0.0\\4287.49999998\\137.49999998\\4427.49999999\\277.5\\4565.0\\414.99999998\\4702.49999998\\552.49999999\\4842.5\\692.5\\4979.99999998\\829.99999999\\5117.49999999\\967.5\\5257.49999998\\1107.49999998\\5394.99999999\\1245.0\\5532.5\\1382.49999998\\5672.49999999\\1522.49999999\\5810.0\\1660.0\\5947.49999998\\1797.49999999\\6087.49999999\\1937.5\\6224.99999998\\2074.99999998\\6362.49999999\\2212.49999999\\6500.0\\2352.49999998\\6639.99999998\\2489.99999999\\6777.5\\2627.5\\6914.99999998\\2767.49999999\\7054.99999999\\2905.0\\7192.5\\3042.49999998\\7329.99999999\\3182.5\\7470.0\\3319.99999998\\7607.49999998\\3457.49999999\\7745.0\\3597.5\\7884.99999998\\3734.99999999\\8022.49999999\\3872.5\\8160.0\\4012.49999998", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "720", false);
+  tags.SetValue(0x0028, 0x0011, "720", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "16", false);
+  tags.SetValue(0x0028, 0x0102, "15", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "153129.910000", false);
+  tags.SetValue(0x0018, 0x0050, "2.4000000953674", false);
+  tags.SetValue(0x0018, 0x0088, "2.3999999741376", false);
+  tags.SetValue(0x0020, 0x0032, "-866.17921113968\\-872.62231063843\\-64.073608398438", false);
+  tags.SetValue(0x0020, 0x0037, "1\\0\\0\\0\\1\\0", false);
+  tags.SetValue(0x0028, 0x0030, "2.4000000953674\\2.4000000953674", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("60");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("0");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("0.0").AddValue("0.0").AddValue("1.0");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_49)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_49(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 90);
+  ASSERT_NEAR(nifti.pixdim[1], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 90);
+  ASSERT_NEAR(nifti.pixdim[2], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 60);
+  ASSERT_NEAR(nifti.pixdim[3], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 486000u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 5);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 110.179169, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -96.977722, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -64.073608, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 1.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.000000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=84;Time=153129.910;phase=0");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_50(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa', 'In/TotalReadoutTime/PF_PAT_POS_PFOV_PEres_INTERP_test2/NOPF_NOPAT_NOPOS_PERES100_ES0P59_BW2222_200PFOV_AP_0034/PF_PAT_POS_BW_INTERP_TEST.MR.HEAD_HARMS.0034.0001.2017.09.20.16.16.44.39766.120194668.IMA')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "1", false);
+  tags.SetValue(0x0018, 0x0080, "10730", false);
+  tags.SetValue(0x0018, 0x0081, "111.2", false);
+  tags.SetValue(0x0018, 0x1312, "COL", false);
+  tags.SetValue(0x0019, 0x1029, "5355.00000001\\0.0\\5532.5\\177.50000002\\5712.50000002\\357.50000001\\5890.00000001\\535.0\\6070.00000001\\715.00000003\\6247.5\\892.50000002\\6425.00000002\\1070.00000001\\6605.00000001\\1250.0\\6782.5\\1427.50000002\\6960.00000002\\1607.50000001\\7140.00000001\\1785.0\\7317.5\\1962.50000002\\7497.5\\2142.50000002\\7675.00000002\\2320.00000001\\7852.50000001\\2500.0\\8032.5\\2677.50000002\\8210.00000002\\2855.00000001\\8390.00000001\\3035.0\\8567.5\\3212.50000002\\8745.00000002\\3392.50000002\\8925.00000002\\3570.00000001\\9102.50000001\\3747.5\\9282.5\\3927.50000002\\9460.00000002\\4105.00000001\\9637.50000001\\4285.0\\9817.5\\4462.50000002\\9995.00000002\\4640.00000001\\10175.00000002\\4820.00000001\\10352.50000001\\4997.5\\10530.0\\5177.50000002", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "1440", false);
+  tags.SetValue(0x0028, 0x0011, "720", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "16", false);
+  tags.SetValue(0x0028, 0x0102, "15", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "160838.905000", false);
+  tags.SetValue(0x0018, 0x0050, "2.4000000953674", false);
+  tags.SetValue(0x0018, 0x0088, "2.3999999741376", false);
+  tags.SetValue(0x0020, 0x0032, "-866.17921113968\\-1736.6223449707\\-64.073608398438", false);
+  tags.SetValue(0x0020, 0x0037, "1\\0\\0\\0\\1\\0", false);
+  tags.SetValue(0x0028, 0x0030, "2.4000000953674\\2.4000000953674", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("60");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("1");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("0.0").AddValue("0.0").AddValue("1.0");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_50)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_50(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 90);
+  ASSERT_NEAR(nifti.pixdim[1], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 180);
+  ASSERT_NEAR(nifti.pixdim[2], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 60);
+  ASSERT_NEAR(nifti.pixdim[3], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 972000u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 5);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 110.179169, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -204.977753, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -64.073608, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 1.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.000000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=1.1e+02;Time=160838.905;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_51(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa', 'In/TotalReadoutTime/PF_PAT_POS_PFOV_PEres_INTERP_test2/NOPF_NOPAT_NOPOS_PERES100_ES0P59_BW2222_200PFOV_PA_0035/PF_PAT_POS_BW_INTERP_TEST.MR.HEAD_HARMS.0035.0001.2017.09.20.16.16.44.39766.120198766.IMA')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "1", false);
+  tags.SetValue(0x0018, 0x0080, "10730", false);
+  tags.SetValue(0x0018, 0x0081, "111.2", false);
+  tags.SetValue(0x0018, 0x1312, "COL", false);
+  tags.SetValue(0x0019, 0x1029, "5354.99999998\\0.0\\5532.5\\177.49999999\\5712.49999999\\357.49999998\\5889.99999998\\535.0\\6067.5\\712.49999999\\6247.5\\892.49999999\\6424.99999999\\1069.99999998\\6604.99999998\\1250.0\\6782.5\\1427.49999999\\6959.99999999\\1604.99999998\\7139.99999998\\1785.0\\7317.5\\1962.49999999\\7497.5\\2142.49999999\\7674.99999999\\2319.99999998\\7852.49999998\\2497.5\\8032.5\\2677.49999999\\8209.99999999\\2854.99999998\\8389.99999998\\3035.0\\8567.5\\3212.49999999\\8745.0\\3389.99999998\\8924.99999999\\3569.99999998\\9102.49999998\\3747.5\\9280.0\\3927.49999999\\9459.99999999\\4104.99999998\\9637.49999998\\4282.5\\9817.5\\4462.49999999\\9995.0\\4639.99999998\\10172.49999999\\4819.99999998\\10352.49999998\\4997.5\\10530.0\\5174.99999999", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "1440", false);
+  tags.SetValue(0x0028, 0x0011, "720", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "16", false);
+  tags.SetValue(0x0028, 0x0102, "15", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "160919.177500", false);
+  tags.SetValue(0x0018, 0x0050, "2.4000000953674", false);
+  tags.SetValue(0x0018, 0x0088, "2.3999999741376", false);
+  tags.SetValue(0x0020, 0x0032, "-866.17921113968\\-1736.6223449707\\-64.073608398438", false);
+  tags.SetValue(0x0020, 0x0037, "1\\0\\0\\0\\1\\0", false);
+  tags.SetValue(0x0028, 0x0030, "2.4000000953674\\2.4000000953674", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("60");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("0");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("0.0").AddValue("0.0").AddValue("1.0");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_51)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_51(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 90);
+  ASSERT_NEAR(nifti.pixdim[1], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 180);
+  ASSERT_NEAR(nifti.pixdim[2], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 60);
+  ASSERT_NEAR(nifti.pixdim[3], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 972000u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 5);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 110.179169, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -204.977753, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -64.073608, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 1.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.000000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=1.1e+02;Time=160919.177;phase=0");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_52(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa', 'In/TotalReadoutTime/PF_PAT_POS_PFOV_PEres_INTERP_test2/NOPF_NOPAT_NOPOS_PERES100_ES0P59_BW2222_AP_0008/PF_PAT_POS_BW_INTERP_TEST.MR.HEAD_HARMS.0008.0001.2017.09.20.16.16.44.39766.120085044.IMA')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "1", false);
+  tags.SetValue(0x0018, 0x0080, "6100", false);
+  tags.SetValue(0x0018, 0x0081, "60", false);
+  tags.SetValue(0x0018, 0x1312, "COL", false);
+  tags.SetValue(0x0019, 0x1029, "3040.00000001\\0.0\\3140.00000001\\100.00000001\\3242.50000002\\202.50000001\\3342.5\\305.00000002\\3445.00000001\\405.0\\3547.50000002\\507.50000001\\3647.50000002\\607.50000001\\3750.0\\710.00000002\\3850.00000001\\810.0\\3952.50000001\\912.50000001\\4052.50000002\\1012.50000001\\4155.0\\1115.00000002\\4255.0\\1215.00000003\\4357.50000001\\1317.5\\4457.50000002\\1417.50000001\\4560.0\\1520.00000002\\4660.0\\1620.00000002\\4762.50000001\\1722.5\\4862.50000002\\1822.50000001\\4965.00000003\\1925.00000002\\5067.5\\2027.50000003\\5167.50000001\\2127.5\\5270.00000002\\2230.00000001\\5370.00000002\\2330.00000002\\5472.5\\2432.50000002\\5572.50000001\\2532.5\\5675.00000002\\2635.00000001\\5775.00000002\\2735.00000002\\5877.5\\2837.50000002\\5977.50000001\\2937.5", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "720", false);
+  tags.SetValue(0x0028, 0x0011, "720", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "16", false);
+  tags.SetValue(0x0028, 0x0102, "15", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "152433.090000", false);
+  tags.SetValue(0x0018, 0x0050, "2.4000000953674", false);
+  tags.SetValue(0x0018, 0x0088, "2.3999999741376", false);
+  tags.SetValue(0x0020, 0x0032, "-866.17921113968\\-872.62231063843\\-64.073608398438", false);
+  tags.SetValue(0x0020, 0x0037, "1\\0\\0\\0\\1\\0", false);
+  tags.SetValue(0x0028, 0x0030, "2.4000000953674\\2.4000000953674", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("60");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("1");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("0.0").AddValue("0.0").AddValue("1.0");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_52)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_52(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 90);
+  ASSERT_NEAR(nifti.pixdim[1], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 90);
+  ASSERT_NEAR(nifti.pixdim[2], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 60);
+  ASSERT_NEAR(nifti.pixdim[3], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 486000u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 5);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 110.179169, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -96.977722, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -64.073608, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 1.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.000000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=60;Time=152433.090;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_53(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa', 'In/TotalReadoutTime/PF_PAT_POS_PFOV_PEres_INTERP_test2/NOPF_NOPAT_NOPOS_PERES100_ES0P59_BW2222_INTERP_AP_0028/PF_PAT_POS_BW_INTERP_TEST.MR.HEAD_HARMS.0028.0001.2017.09.20.16.16.44.39766.120168028.IMA')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "1", false);
+  tags.SetValue(0x0018, 0x0080, "6100", false);
+  tags.SetValue(0x0018, 0x0081, "60", false);
+  tags.SetValue(0x0018, 0x1312, "COL", false);
+  tags.SetValue(0x0019, 0x1029, "3040.00000001\\0.0\\3140.00000001\\100.00000001\\3242.49999999\\202.50000001\\3342.5\\302.49999999\\3445.00000001\\405.0\\3545.00000001\\505.0\\3647.49999999\\607.50000001\\3747.5\\707.50000002\\3850.00000001\\810.0\\3950.00000001\\910.0\\4052.49999999\\1012.50000001\\4152.5\\1114.99999999\\4255.0\\1215.0\\4357.50000001\\1317.5\\4457.50000002\\1417.50000001\\4560.0\\1520.00000002\\4660.0\\1620.0\\4762.50000001\\1722.5\\4862.50000002\\1822.50000001\\4965.0\\1925.00000002\\5065.0\\2024.99999999\\5167.50000001\\2127.5\\5267.50000002\\2227.50000001\\5370.0\\2330.00000002\\5470.0\\2429.99999999\\5572.50000001\\2532.5\\5672.50000002\\2635.00000001\\5774.99999999\\2735.00000002\\5877.5\\2837.49999999\\5977.50000001\\2937.5", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "1440", false);
+  tags.SetValue(0x0028, 0x0011, "1440", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "16", false);
+  tags.SetValue(0x0028, 0x0102, "15", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "155248.285000", false);
+  tags.SetValue(0x0018, 0x0050, "2.4000000953674", false);
+  tags.SetValue(0x0018, 0x0088, "2.3999999741376", false);
+  tags.SetValue(0x0020, 0x0032, "-866.17921113968\\-872.62231063843\\-64.073608398438", false);
+  tags.SetValue(0x0020, 0x0037, "1\\0\\0\\0\\1\\0", false);
+  tags.SetValue(0x0028, 0x0030, "1.2000000476837\\1.2000000476837", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("60");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("1");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("0.0").AddValue("0.0").AddValue("1.0");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_53)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_53(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 180);
+  ASSERT_NEAR(nifti.pixdim[1], 1.200000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 180);
+  ASSERT_NEAR(nifti.pixdim[2], 1.200000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 60);
+  ASSERT_NEAR(nifti.pixdim[3], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 1944000u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 5);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -1.200000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 110.179169, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 1.200000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -98.177719, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -64.073608, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 1.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.000000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=60;Time=155248.285;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_54(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa', 'In/TotalReadoutTime/PF_PAT_POS_PFOV_PEres_INTERP_test2/NOPF_NOPAT_NOPOS_PERES100_ES0P59_BW2222_INTERP_PA_0029/PF_PAT_POS_BW_INTERP_TEST.MR.HEAD_HARMS.0029.0001.2017.09.20.16.16.44.39766.120172126.IMA')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "1", false);
+  tags.SetValue(0x0018, 0x0080, "6100", false);
+  tags.SetValue(0x0018, 0x0081, "60", false);
+  tags.SetValue(0x0018, 0x1312, "COL", false);
+  tags.SetValue(0x0019, 0x1029, "3040.00000001\\0.0\\3142.50000002\\102.50000001\\3242.50000002\\202.50000001\\3345.0\\305.00000002\\3445.00000001\\405.0\\3547.50000002\\507.50000001\\3647.50000002\\607.50000001\\3750.0\\710.00000002\\3850.00000001\\810.0\\3952.50000001\\912.50000001\\4052.50000002\\1012.50000001\\4155.0\\1115.00000002\\4255.0\\1217.5\\4357.50000001\\1317.5\\4460.00000002\\1420.00000001\\4560.0\\1520.00000002\\4662.50000001\\1622.5\\4762.50000001\\1722.5\\4865.00000002\\1825.00000001\\4965.00000003\\1925.00000002\\5067.5\\2027.50000003\\5167.50000001\\2127.5\\5270.00000002\\2230.00000001\\5370.00000002\\2330.00000002\\5472.5\\2432.50000002\\5572.50000001\\2532.5\\5675.00000002\\2635.00000001\\5775.00000002\\2735.00000002\\5877.5\\2837.50000002\\5980.00000001\\2940.0", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "1440", false);
+  tags.SetValue(0x0028, 0x0011, "1440", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "16", false);
+  tags.SetValue(0x0028, 0x0102, "15", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "155321.340000", false);
+  tags.SetValue(0x0018, 0x0050, "2.4000000953674", false);
+  tags.SetValue(0x0018, 0x0088, "2.3999999741376", false);
+  tags.SetValue(0x0020, 0x0032, "-866.17921113968\\-872.62231063843\\-64.073608398438", false);
+  tags.SetValue(0x0020, 0x0037, "1\\0\\0\\0\\1\\0", false);
+  tags.SetValue(0x0028, 0x0030, "1.2000000476837\\1.2000000476837", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("60");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("0");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("0.0").AddValue("0.0").AddValue("1.0");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_54)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_54(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 180);
+  ASSERT_NEAR(nifti.pixdim[1], 1.200000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 180);
+  ASSERT_NEAR(nifti.pixdim[2], 1.200000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 60);
+  ASSERT_NEAR(nifti.pixdim[3], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 1944000u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 5);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -1.200000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 110.179169, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 1.200000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -98.177719, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -64.073608, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 1.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.000000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=60;Time=155321.340;phase=0");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_55(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa', 'In/TotalReadoutTime/PF_PAT_POS_PFOV_PEres_INTERP_test2/NOPF_NOPAT_NOPOS_PERES100_ES0P59_BW2222_PA_0009/PF_PAT_POS_BW_INTERP_TEST.MR.HEAD_HARMS.0009.0001.2017.09.20.16.16.44.39766.120089142.IMA')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "1", false);
+  tags.SetValue(0x0018, 0x0080, "6100", false);
+  tags.SetValue(0x0018, 0x0081, "60", false);
+  tags.SetValue(0x0018, 0x1312, "COL", false);
+  tags.SetValue(0x0019, 0x1029, "3040.00000001\\0.0\\3142.50000002\\102.50000001\\3242.49999999\\202.50000001\\3345.0\\304.99999999\\3445.00000001\\405.0\\3547.50000002\\507.50000001\\3647.50000002\\607.50000001\\3750.0\\710.00000002\\3850.00000001\\810.0\\3952.50000001\\912.50000001\\4052.50000002\\1012.50000001\\4155.0\\1115.00000002\\4255.0\\1215.0\\4357.50000001\\1317.5\\4457.50000002\\1420.00000001\\4560.0\\1520.00000002\\4662.50000001\\1622.5\\4762.50000001\\1722.5\\4865.00000002\\1825.00000001\\4965.0\\1925.00000002\\5067.5\\2027.5\\5167.50000001\\2127.5\\5270.00000002\\2230.00000001\\5370.0\\2330.00000002\\5472.5\\2432.5\\5572.50000001\\2532.5\\5675.00000002\\2635.00000001\\5774.99999999\\2735.00000002\\5877.5\\2837.49999999\\5977.50000001\\2940.0", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "720", false);
+  tags.SetValue(0x0028, 0x0011, "720", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "16", false);
+  tags.SetValue(0x0028, 0x0102, "15", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "152556.220000", false);
+  tags.SetValue(0x0018, 0x0050, "2.4000000953674", false);
+  tags.SetValue(0x0018, 0x0088, "2.3999999741376", false);
+  tags.SetValue(0x0020, 0x0032, "-866.17921113968\\-872.62231063843\\-64.073608398438", false);
+  tags.SetValue(0x0020, 0x0037, "1\\0\\0\\0\\1\\0", false);
+  tags.SetValue(0x0028, 0x0030, "2.4000000953674\\2.4000000953674", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("60");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("0");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("0.0").AddValue("0.0").AddValue("1.0");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_55)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_55(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 90);
+  ASSERT_NEAR(nifti.pixdim[1], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 90);
+  ASSERT_NEAR(nifti.pixdim[2], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 60);
+  ASSERT_NEAR(nifti.pixdim[3], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 486000u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 5);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 110.179169, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -96.977722, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -64.073608, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 1.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.000000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=60;Time=152556.220;phase=0");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_56(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa', 'In/TotalReadoutTime/PF_PAT_POS_PFOV_PEres_INTERP_test2/NOPF_NOPAT_NOPOS_PERES100_ES0P60_BW2222_LR_0031/PF_PAT_POS_BW_INTERP_TEST.MR.HEAD_HARMS.0031.0001.2017.09.20.16.16.44.39766.120182374.IMA')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "1", false);
+  tags.SetValue(0x0018, 0x0080, "6100", false);
+  tags.SetValue(0x0018, 0x0081, "60", false);
+  tags.SetValue(0x0018, 0x1312, "ROW", false);
+  tags.SetValue(0x0019, 0x1029, "3040.00000001\\0.0\\3142.49999999\\102.50000001\\3245.0\\204.99999999\\3345.0\\304.99999999\\3447.49999998\\407.5\\3547.49999999\\507.50000001\\3649.99999999\\609.99999999\\3750.0\\709.99999999\\3852.50000001\\812.5\\3952.49999998\\912.50000001\\4054.99999999\\1014.99999998\\4155.0\\1114.99999999\\4257.50000001\\1217.5\\4357.49999998\\1317.5\\4459.99999999\\1419.99999998\\4560.0\\1519.99999999\\4662.50000001\\1622.5\\4762.49999998\\1725.00000001\\4864.99999999\\1824.99999998\\4967.5\\1927.49999999\\5067.5\\2027.5\\5169.99999998\\2130.0\\5269.99999999\\2229.99999998\\5372.5\\2332.49999999\\5472.5\\2432.5\\5574.99999998\\2535.0\\5674.99999999\\2634.99999998\\5777.5\\2737.49999999\\5877.5\\2837.49999999\\5979.99999998\\2940.0", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "720", false);
+  tags.SetValue(0x0028, 0x0011, "720", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "16", false);
+  tags.SetValue(0x0028, 0x0102, "15", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "155859.327500", false);
+  tags.SetValue(0x0018, 0x0050, "2.4000000953674", false);
+  tags.SetValue(0x0018, 0x0088, "2.3999999741376", false);
+  tags.SetValue(0x0020, 0x0032, "-866.17921113545\\-872.62231064266\\-64.073608398438", false);
+  tags.SetValue(0x0020, 0x0037, "1\\4.896e-012\\0\\-4.896e-012\\1\\0", false);
+  tags.SetValue(0x0028, 0x0030, "2.4000000953674\\2.4000000953674", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("60");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("0");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("0.0").AddValue("0.0").AddValue("1.0");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_56)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_56(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 90);
+  ASSERT_NEAR(nifti.pixdim[1], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 90);
+  ASSERT_NEAR(nifti.pixdim[2], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 60);
+  ASSERT_NEAR(nifti.pixdim[3], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 486000u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(54));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(54));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(54));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 5);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 110.179169, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -96.977722, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -64.073608, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 1.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.000000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=60;Time=155859.328;phase=0");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_57(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa', 'In/TotalReadoutTime/PF_PAT_POS_PFOV_PEres_INTERP_test2/NOPF_NOPAT_NOPOS_PERES100_ES0P60_BW2222_RL_0030/PF_PAT_POS_BW_INTERP_TEST.MR.HEAD_HARMS.0030.0001.2017.09.20.16.16.44.39766.120178276.IMA')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "1", false);
+  tags.SetValue(0x0018, 0x0080, "6100", false);
+  tags.SetValue(0x0018, 0x0081, "60", false);
+  tags.SetValue(0x0018, 0x1312, "ROW", false);
+  tags.SetValue(0x0019, 0x1029, "3040.00000001\\0.0\\3142.49999999\\102.50000001\\3242.49999999\\202.49999998\\3345.0\\304.99999999\\3445.00000001\\405.0\\3547.49999999\\507.50000001\\3647.49999999\\607.49999998\\3750.0\\709.99999999\\3850.00000001\\810.0\\3952.49999998\\912.50000001\\4054.99999999\\1014.99999998\\4155.0\\1114.99999999\\4257.50000001\\1217.5\\4357.49999998\\1317.5\\4459.99999999\\1419.99999998\\4560.0\\1519.99999999\\4662.50000001\\1622.5\\4762.49999998\\1722.5\\4864.99999999\\1824.99999998\\4965.0\\1924.99999999\\5067.5\\2027.5\\5167.49999998\\2127.5\\5269.99999999\\2229.99999998\\5370.0\\2329.99999999\\5472.5\\2432.5\\5572.49999998\\2535.0\\5674.99999999\\2634.99999998\\5777.5\\2737.49999999\\5877.5\\2837.49999999\\5979.99999998\\2940.0", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "720", false);
+  tags.SetValue(0x0028, 0x0011, "720", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "16", false);
+  tags.SetValue(0x0028, 0x0102, "15", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "155814.140000", false);
+  tags.SetValue(0x0018, 0x0050, "2.4000000953674", false);
+  tags.SetValue(0x0018, 0x0088, "2.3999999741376", false);
+  tags.SetValue(0x0020, 0x0032, "-866.17921113968\\-872.62231063843\\-64.073608398438", false);
+  tags.SetValue(0x0020, 0x0037, "1\\0\\0\\0\\1\\0", false);
+  tags.SetValue(0x0028, 0x0030, "2.4000000953674\\2.4000000953674", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("60");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("1");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("0.0").AddValue("0.0").AddValue("1.0");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_57)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_57(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 90);
+  ASSERT_NEAR(nifti.pixdim[1], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 90);
+  ASSERT_NEAR(nifti.pixdim[2], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 60);
+  ASSERT_NEAR(nifti.pixdim[3], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 486000u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(54));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(54));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(54));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 5);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 110.179169, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -96.977722, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -64.073608, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 1.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.000000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=60;Time=155814.140;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_58(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa', 'In/TotalReadoutTime/PF_PAT_POS_PFOV_PEres_INTERP_test2/NOPF_NOPAT_NOPOS_PERES100_ES1P00_BW1112_AP_0026/PF_PAT_POS_BW_INTERP_TEST.MR.HEAD_HARMS.0026.0001.2017.09.20.16.16.44.39766.120159832.IMA')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "1", false);
+  tags.SetValue(0x0018, 0x0080, "9280", false);
+  tags.SetValue(0x0018, 0x0081, "95.4", false);
+  tags.SetValue(0x0018, 0x1312, "COL", false);
+  tags.SetValue(0x0019, 0x1029, "4630.0\\0.0\\4785.0\\155.0\\4940.0\\310.0\\5092.5\\462.49999999\\5247.5\\617.49999999\\5402.5\\772.49999999\\5554.99999999\\927.49999999\\5709.99999999\\1079.99999999\\5864.99999999\\1234.99999999\\6019.99999999\\1389.99999998\\6172.49999999\\1542.50000001\\6327.49999998\\1697.50000001\\6482.49999998\\1852.50000001\\6637.49999998\\2007.50000001\\6790.00000001\\2160.0\\6945.00000001\\2315.0\\7100.00000001\\2470.0\\7255.0\\2625.0\\7407.5\\2777.5\\7562.5\\2932.5\\7717.5\\3087.49999999\\7870.0\\3242.49999999\\8024.99999999\\3394.99999999\\8179.99999999\\3549.99999999\\8334.99999999\\3704.99999999\\8487.49999999\\3857.49999998\\8642.49999999\\4012.49999998\\8797.49999999\\4167.50000001\\8952.49999998\\4322.50000001\\9105.00000001\\4475.00000001", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "720", false);
+  tags.SetValue(0x0028, 0x0011, "720", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "16", false);
+  tags.SetValue(0x0028, 0x0102, "15", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "154900.915000", false);
+  tags.SetValue(0x0018, 0x0050, "2.4000000953674", false);
+  tags.SetValue(0x0018, 0x0088, "2.3999999741376", false);
+  tags.SetValue(0x0020, 0x0032, "-866.17921113968\\-872.62231063843\\-64.073608398438", false);
+  tags.SetValue(0x0020, 0x0037, "1\\0\\0\\0\\1\\0", false);
+  tags.SetValue(0x0028, 0x0030, "2.4000000953674\\2.4000000953674", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("60");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("1");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("0.0").AddValue("0.0").AddValue("1.0");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_58)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_58(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 90);
+  ASSERT_NEAR(nifti.pixdim[1], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 90);
+  ASSERT_NEAR(nifti.pixdim[2], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 60);
+  ASSERT_NEAR(nifti.pixdim[3], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 486000u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 5);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 110.179169, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -96.977722, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -64.073608, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 1.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.000000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=95;Time=154900.915;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_59(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa', 'In/TotalReadoutTime/PF_PAT_POS_PFOV_PEres_INTERP_test2/NOPF_NOPAT_NOPOS_PERES100_ES1P00_BW1112_PA_0027/PF_PAT_POS_BW_INTERP_TEST.MR.HEAD_HARMS.0027.0001.2017.09.20.16.16.44.39766.120163930.IMA')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "1", false);
+  tags.SetValue(0x0018, 0x0080, "9280", false);
+  tags.SetValue(0x0018, 0x0081, "95.4", false);
+  tags.SetValue(0x0018, 0x1312, "COL", false);
+  tags.SetValue(0x0019, 0x1029, "4630.0\\0.0\\4782.5\\152.5\\4937.5\\307.5\\5092.5\\462.49999999\\5247.5\\617.49999999\\5399.99999999\\769.99999999\\5554.99999999\\925.00000002\\5709.99999999\\1080.00000002\\5864.99999999\\1235.00000002\\6017.50000002\\1387.50000001\\6172.50000002\\1542.50000001\\6327.50000001\\1697.50000001\\6480.00000001\\1852.50000001\\6635.00000001\\2005.0\\6790.00000001\\2160.0\\6945.00000001\\2315.0\\7097.5\\2467.5\\7252.5\\2622.5\\7407.5\\2777.5\\7562.5\\2932.5\\7715.0\\3084.99999999\\7870.0\\3239.99999999\\8024.99999999\\3394.99999999\\8179.99999999\\3550.00000002\\8332.49999999\\3702.50000001\\8487.50000002\\3857.50000001\\8642.50000002\\4012.50000001\\8795.00000001\\4165.00000001\\8950.00000001\\4320.00000001\\9105.00000001\\4475.00000001", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "720", false);
+  tags.SetValue(0x0028, 0x0011, "720", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "16", false);
+  tags.SetValue(0x0028, 0x0102, "15", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "155026.907500", false);
+  tags.SetValue(0x0018, 0x0050, "2.4000000953674", false);
+  tags.SetValue(0x0018, 0x0088, "2.3999999741376", false);
+  tags.SetValue(0x0020, 0x0032, "-866.17921113968\\-872.62231063843\\-64.073608398438", false);
+  tags.SetValue(0x0020, 0x0037, "1\\0\\0\\0\\1\\0", false);
+  tags.SetValue(0x0028, 0x0030, "2.4000000953674\\2.4000000953674", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("60");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("0");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("0.0").AddValue("0.0").AddValue("1.0");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_59)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_59(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 90);
+  ASSERT_NEAR(nifti.pixdim[1], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 90);
+  ASSERT_NEAR(nifti.pixdim[2], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 60);
+  ASSERT_NEAR(nifti.pixdim[3], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 486000u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 5);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 110.179169, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -96.977722, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -64.073608, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 1.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.000000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=95;Time=155026.908;phase=0");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_60(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa', 'In/TotalReadoutTime/PF_PAT_POS_PFOV_PEres_INTERP_test2/NOPF_NOPAT_NOPOS_PERES100_ES1P00_BW2222_AP_0024/PF_PAT_POS_BW_INTERP_TEST.MR.HEAD_HARMS.0024.0001.2017.09.20.16.16.44.39766.120151636.IMA')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "1", false);
+  tags.SetValue(0x0018, 0x0080, "9280", false);
+  tags.SetValue(0x0018, 0x0081, "95.4", false);
+  tags.SetValue(0x0018, 0x1312, "COL", false);
+  tags.SetValue(0x0019, 0x1029, "4630.0\\0.0\\4782.5\\152.5\\4937.5\\307.5\\5092.5\\462.49999999\\5245.0\\617.49999999\\5399.99999999\\769.99999999\\5554.99999999\\924.99999999\\5709.99999999\\1079.99999999\\5862.49999999\\1232.49999998\\6017.49999999\\1387.49999998\\6172.49999999\\1542.49999998\\6327.49999998\\1697.49999998\\6479.99999998\\1849.99999998\\6634.99999998\\2005.0\\6789.99999998\\2160.0\\6944.99999998\\2315.0\\7097.5\\2467.5\\7252.5\\2622.5\\7407.5\\2777.5\\7560.0\\2932.5\\7715.0\\3084.99999999\\7870.0\\3239.99999999\\8024.99999999\\3394.99999999\\8177.49999999\\3547.49999999\\8332.49999999\\3702.49999998\\8487.49999999\\3857.49999998\\8642.49999999\\4012.49999998\\8794.99999998\\4164.99999998\\8949.99999998\\4319.99999998\\9104.99999998\\4474.99999998", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "720", false);
+  tags.SetValue(0x0028, 0x0011, "720", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "16", false);
+  tags.SetValue(0x0028, 0x0102, "15", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "154534.577500", false);
+  tags.SetValue(0x0018, 0x0050, "2.4000000953674", false);
+  tags.SetValue(0x0018, 0x0088, "2.3999999741376", false);
+  tags.SetValue(0x0020, 0x0032, "-866.17921113968\\-872.62231063843\\-64.073608398438", false);
+  tags.SetValue(0x0020, 0x0037, "1\\0\\0\\0\\1\\0", false);
+  tags.SetValue(0x0028, 0x0030, "2.4000000953674\\2.4000000953674", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("60");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("1");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("0.0").AddValue("0.0").AddValue("1.0");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_60)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_60(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 90);
+  ASSERT_NEAR(nifti.pixdim[1], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 90);
+  ASSERT_NEAR(nifti.pixdim[2], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 60);
+  ASSERT_NEAR(nifti.pixdim[3], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 486000u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 5);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 110.179169, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -96.977722, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -64.073608, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 1.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.000000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=95;Time=154534.578;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_61(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa', 'In/TotalReadoutTime/PF_PAT_POS_PFOV_PEres_INTERP_test2/NOPF_NOPAT_NOPOS_PERES100_ES1P00_BW2222_PA_0025/PF_PAT_POS_BW_INTERP_TEST.MR.HEAD_HARMS.0025.0001.2017.09.20.16.16.44.39766.120155734.IMA')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "1", false);
+  tags.SetValue(0x0018, 0x0080, "9280", false);
+  tags.SetValue(0x0018, 0x0081, "95.4", false);
+  tags.SetValue(0x0018, 0x1312, "COL", false);
+  tags.SetValue(0x0019, 0x1029, "4630.0\\0.0\\4785.0\\155.0\\4937.5\\310.0\\5092.5\\462.49999999\\5247.5\\617.49999999\\5402.5\\772.49999999\\5554.99999999\\924.99999999\\5709.99999999\\1079.99999999\\5864.99999999\\1234.99999999\\6019.99999999\\1389.99999998\\6172.49999999\\1542.49999998\\6327.49999998\\1697.49999998\\6482.49999998\\1852.50000001\\6637.49999998\\2007.50000001\\6790.00000001\\2160.0\\6945.00000001\\2315.0\\7100.00000001\\2470.0\\7252.5\\2625.0\\7407.5\\2777.5\\7562.5\\2932.5\\7717.5\\3087.49999999\\7870.0\\3239.99999999\\8024.99999999\\3394.99999999\\8179.99999999\\3549.99999999\\8334.99999999\\3704.99999999\\8487.49999999\\3857.49999998\\8642.49999999\\4012.49999998\\8797.49999999\\4167.49999998\\8952.49999998\\4322.49999998\\9104.99999998\\4475.00000001", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "720", false);
+  tags.SetValue(0x0028, 0x0011, "720", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "16", false);
+  tags.SetValue(0x0028, 0x0102, "15", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "154613.292500", false);
+  tags.SetValue(0x0018, 0x0050, "2.4000000953674", false);
+  tags.SetValue(0x0018, 0x0088, "2.3999999741376", false);
+  tags.SetValue(0x0020, 0x0032, "-866.17921113968\\-872.62231063843\\-64.073608398438", false);
+  tags.SetValue(0x0020, 0x0037, "1\\0\\0\\0\\1\\0", false);
+  tags.SetValue(0x0028, 0x0030, "2.4000000953674\\2.4000000953674", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("60");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("0");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("0.0").AddValue("0.0").AddValue("1.0");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_61)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_61(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 90);
+  ASSERT_NEAR(nifti.pixdim[1], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 90);
+  ASSERT_NEAR(nifti.pixdim[2], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 60);
+  ASSERT_NEAR(nifti.pixdim[3], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 486000u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 5);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 110.179169, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -96.977722, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -64.073608, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 1.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.000000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=95;Time=154613.293;phase=0");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_62(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa', 'In/TotalReadoutTime/PF_PAT_POS_PFOV_PEres_INTERP_test2/NOPF_NOPAT_NOPOS_PERES50_ES0P59_BW2222_AP_0020/PF_PAT_POS_BW_INTERP_TEST.MR.HEAD_HARMS.0020.0001.2017.09.20.16.16.44.39766.120134220.IMA')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "1", false);
+  tags.SetValue(0x0018, 0x0080, "6100", false);
+  tags.SetValue(0x0018, 0x0081, "60", false);
+  tags.SetValue(0x0018, 0x1312, "COL", false);
+  tags.SetValue(0x0019, 0x1029, "3040.00000001\\0.0\\3142.49999999\\102.50000001\\3242.49999999\\202.49999998\\3345.0\\304.99999999\\3445.00000001\\405.0\\3547.49999999\\507.50000001\\3647.49999999\\607.49999998\\3750.0\\709.99999999\\3850.00000001\\810.0\\3952.49999998\\912.50000001\\4052.49999999\\1012.49999998\\4155.0\\1114.99999999\\4255.0\\1215.0\\4357.49999998\\1317.5\\4457.49999999\\1419.99999998\\4560.0\\1519.99999999\\4662.50000001\\1622.5\\4762.49999998\\1722.5\\4864.99999999\\1824.99999998\\4965.0\\1924.99999999\\5067.5\\2027.5\\5167.50000001\\2127.5\\5269.99999999\\2230.00000001\\5370.0\\2329.99999999\\5472.5\\2432.5\\5572.50000001\\2532.5\\5674.99999999\\2635.00000001\\5774.99999999\\2734.99999999\\5877.5\\2837.49999999\\5977.50000001\\2937.5", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "720", false);
+  tags.SetValue(0x0028, 0x0011, "720", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "16", false);
+  tags.SetValue(0x0028, 0x0102, "15", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "154118.387500", false);
+  tags.SetValue(0x0018, 0x0050, "2.4000000953674", false);
+  tags.SetValue(0x0018, 0x0088, "2.3999999741376", false);
+  tags.SetValue(0x0020, 0x0032, "-866.17921113968\\-872.62231063843\\-64.073608398438", false);
+  tags.SetValue(0x0020, 0x0037, "1\\0\\0\\0\\1\\0", false);
+  tags.SetValue(0x0028, 0x0030, "2.4000000953674\\2.4000000953674", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("60");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("1");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("0.0").AddValue("0.0").AddValue("1.0");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_62)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_62(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 90);
+  ASSERT_NEAR(nifti.pixdim[1], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 90);
+  ASSERT_NEAR(nifti.pixdim[2], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 60);
+  ASSERT_NEAR(nifti.pixdim[3], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 486000u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 5);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 110.179169, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -96.977722, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -64.073608, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 1.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.000000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=60;Time=154118.388;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_63(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa', 'In/TotalReadoutTime/PF_PAT_POS_PFOV_PEres_INTERP_test2/NOPF_NOPAT_NOPOS_PERES50_ES0P59_BW2222_PA_0021/PF_PAT_POS_BW_INTERP_TEST.MR.HEAD_HARMS.0021.0001.2017.09.20.16.16.44.39766.120138318.IMA')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "1", false);
+  tags.SetValue(0x0018, 0x0080, "6100", false);
+  tags.SetValue(0x0018, 0x0081, "60", false);
+  tags.SetValue(0x0018, 0x1312, "COL", false);
+  tags.SetValue(0x0019, 0x1029, "3040.00000001\\0.0\\3140.00000001\\100.00000001\\3242.50000002\\202.50000001\\3345.0\\305.00000002\\3445.00000001\\405.0\\3547.50000002\\507.50000001\\3647.50000002\\607.50000001\\3750.0\\710.00000002\\3850.00000001\\810.0\\3952.50000001\\912.50000001\\4052.50000002\\1012.50000001\\4155.0\\1115.00000002\\4255.0\\1215.0\\4357.50000001\\1317.5\\4457.50000002\\1417.50000001\\4560.0\\1520.00000002\\4660.0\\1620.0\\4762.50000001\\1722.5\\4862.50000002\\1825.00000001\\4965.0\\1925.00000002\\5067.5\\2027.5\\5167.50000001\\2127.5\\5270.00000002\\2230.00000001\\5370.0\\2330.00000002\\5472.5\\2432.5\\5572.50000001\\2532.5\\5675.00000002\\2635.00000001\\5774.99999999\\2735.00000002\\5877.5\\2837.49999999\\5977.50000001\\2937.5", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "720", false);
+  tags.SetValue(0x0028, 0x0011, "720", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "16", false);
+  tags.SetValue(0x0028, 0x0102, "15", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "154152.187500", false);
+  tags.SetValue(0x0018, 0x0050, "2.4000000953674", false);
+  tags.SetValue(0x0018, 0x0088, "2.3999999741376", false);
+  tags.SetValue(0x0020, 0x0032, "-866.17921113968\\-872.62231063843\\-64.073608398438", false);
+  tags.SetValue(0x0020, 0x0037, "1\\0\\0\\0\\1\\0", false);
+  tags.SetValue(0x0028, 0x0030, "2.4000000953674\\2.4000000953674", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("60");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("0");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("0.0").AddValue("0.0").AddValue("1.0");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_63)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_63(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 90);
+  ASSERT_NEAR(nifti.pixdim[1], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 90);
+  ASSERT_NEAR(nifti.pixdim[2], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 60);
+  ASSERT_NEAR(nifti.pixdim[3], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 486000u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 5);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 110.179169, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -96.977722, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -64.073608, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 1.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.000000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=60;Time=154152.188;phase=0");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_64(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa', 'In/TotalReadoutTime/PF_PAT_POS_PFOV_PEres_INTERP_test2/NOPF_NOPAT_NOPOS_PERES75_ES0P59_BW2222_AP_0022/PF_PAT_POS_BW_INTERP_TEST.MR.HEAD_HARMS.0022.0001.2017.09.20.16.16.44.39766.120142416.IMA')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "1", false);
+  tags.SetValue(0x0018, 0x0080, "6100", false);
+  tags.SetValue(0x0018, 0x0081, "60", false);
+  tags.SetValue(0x0018, 0x1312, "COL", false);
+  tags.SetValue(0x0019, 0x1029, "3040.00000001\\0.0\\3142.50000002\\102.50000001\\3242.49999999\\202.50000001\\3345.0\\304.99999999\\3445.00000001\\405.0\\3547.50000002\\507.50000001\\3647.49999999\\607.50000001\\3750.0\\709.99999999\\3850.00000001\\810.0\\3952.50000001\\912.50000001\\4052.49999999\\1012.50000001\\4155.0\\1114.99999999\\4255.0\\1215.0\\4357.50000001\\1317.5\\4457.49999999\\1417.50000001\\4560.0\\1519.99999999\\4660.0\\1622.5\\4762.50000001\\1722.5\\4864.99999999\\1825.00000001\\4965.0\\1925.00000002\\5067.5\\2027.5\\5167.50000001\\2127.5\\5269.99999999\\2230.00000001\\5370.0\\2330.00000002\\5472.5\\2432.5\\5572.50000001\\2532.5\\5675.00000002\\2635.00000001\\5774.99999999\\2735.00000002\\5877.5\\2837.49999999\\5977.50000001\\2937.5", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "720", false);
+  tags.SetValue(0x0028, 0x0011, "720", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "16", false);
+  tags.SetValue(0x0028, 0x0102, "15", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "154329.567500", false);
+  tags.SetValue(0x0018, 0x0050, "2.4000000953674", false);
+  tags.SetValue(0x0018, 0x0088, "2.3999999741376", false);
+  tags.SetValue(0x0020, 0x0032, "-866.17921113968\\-872.62231063843\\-64.073608398438", false);
+  tags.SetValue(0x0020, 0x0037, "1\\0\\0\\0\\1\\0", false);
+  tags.SetValue(0x0028, 0x0030, "2.4000000953674\\2.4000000953674", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("60");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("1");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("0.0").AddValue("0.0").AddValue("1.0");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_64)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_64(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 90);
+  ASSERT_NEAR(nifti.pixdim[1], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 90);
+  ASSERT_NEAR(nifti.pixdim[2], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 60);
+  ASSERT_NEAR(nifti.pixdim[3], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 486000u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 5);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 110.179169, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -96.977722, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -64.073608, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 1.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.000000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=60;Time=154329.568;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_65(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa', 'In/TotalReadoutTime/PF_PAT_POS_PFOV_PEres_INTERP_test2/NOPF_NOPAT_NOPOS_PERES75_ES0P59_BW2222_PA_0023/PF_PAT_POS_BW_INTERP_TEST.MR.HEAD_HARMS.0023.0001.2017.09.20.16.16.44.39766.120147538.IMA')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "1", false);
+  tags.SetValue(0x0018, 0x0080, "6100", false);
+  tags.SetValue(0x0018, 0x0081, "60", false);
+  tags.SetValue(0x0018, 0x1312, "COL", false);
+  tags.SetValue(0x0019, 0x1029, "3040.00000001\\0.0\\3142.50000002\\102.50000001\\3242.49999999\\202.50000001\\3345.0\\304.99999999\\3445.00000001\\407.5\\3547.50000002\\507.50000001\\3649.99999999\\610.00000002\\3750.0\\709.99999999\\3852.50000001\\812.5\\3952.50000001\\912.50000001\\4054.99999999\\1015.00000001\\4155.0\\1115.00000002\\4257.50000001\\1217.5\\4357.50000001\\1317.5\\4459.99999999\\1420.00000001\\4560.0\\1520.00000002\\4662.50000001\\1622.5\\4762.50000001\\1722.5\\4865.00000002\\1825.00000001\\4965.0\\1927.50000002\\5067.5\\2027.5\\5170.00000001\\2130.0\\5270.00000002\\2230.00000001\\5372.5\\2332.50000002\\5472.5\\2432.5\\5575.00000001\\2535.0\\5675.00000002\\2635.00000001\\5777.5\\2737.50000002\\5877.5\\2837.49999999\\5980.00000001\\2940.0", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "720", false);
+  tags.SetValue(0x0028, 0x0011, "720", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "16", false);
+  tags.SetValue(0x0028, 0x0102, "15", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "154358.315000", false);
+  tags.SetValue(0x0018, 0x0050, "2.4000000953674", false);
+  tags.SetValue(0x0018, 0x0088, "2.3999999741376", false);
+  tags.SetValue(0x0020, 0x0032, "-866.17921113968\\-872.62231063843\\-64.073608398438", false);
+  tags.SetValue(0x0020, 0x0037, "1\\0\\0\\0\\1\\0", false);
+  tags.SetValue(0x0028, 0x0030, "2.4000000953674\\2.4000000953674", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("60");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("0");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("0.0").AddValue("0.0").AddValue("1.0");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_65)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_65(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 90);
+  ASSERT_NEAR(nifti.pixdim[1], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 90);
+  ASSERT_NEAR(nifti.pixdim[2], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 60);
+  ASSERT_NEAR(nifti.pixdim[3], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 486000u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 5);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 110.179169, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -96.977722, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -64.073608, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 1.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.000000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=60;Time=154358.315;phase=0");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_66(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa', 'In/TotalReadoutTime/PF_PAT_POS_PFOV_PEres_INTERP_test2/NOPF_P2_NOPOS_PERES100_ES0P59_BW2222_AP_0010/PF_PAT_POS_BW_INTERP_TEST.MR.HEAD_HARMS.0010.0001.2017.09.20.16.16.44.39766.120093240.IMA')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "1", false);
+  tags.SetValue(0x0018, 0x0080, "6100", false);
+  tags.SetValue(0x0018, 0x0081, "60", false);
+  tags.SetValue(0x0018, 0x1312, "COL", false);
+  tags.SetValue(0x0019, 0x1029, "3040.00000001\\0.0\\3140.00000001\\100.00000001\\3242.49999999\\202.50000001\\3342.5\\302.50000002\\3445.00000001\\405.0\\3545.00000001\\505.0\\3647.49999999\\607.50000001\\3750.0\\709.99999999\\3850.00000001\\810.0\\3952.50000001\\912.50000001\\4052.50000002\\1012.50000001\\4155.0\\1115.00000002\\4255.0\\1215.0\\4357.50000001\\1317.5\\4457.50000002\\1417.50000001\\4560.0\\1520.00000002\\4660.0\\1620.0\\4762.50000001\\1722.5\\4862.50000002\\1822.50000001\\4965.0\\1925.00000002\\5065.0\\2024.99999999\\5167.50000001\\2127.5\\5267.50000002\\2230.00000001\\5370.0\\2330.00000002\\5472.5\\2432.5\\5572.50000001\\2532.5\\5675.00000002\\2635.00000001\\5774.99999999\\2735.00000002\\5877.5\\2837.49999999\\5977.50000001\\2937.5", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "720", false);
+  tags.SetValue(0x0028, 0x0011, "720", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "16", false);
+  tags.SetValue(0x0028, 0x0102, "15", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "152737.565000", false);
+  tags.SetValue(0x0018, 0x0050, "2.4000000953674", false);
+  tags.SetValue(0x0018, 0x0088, "2.3999999741376", false);
+  tags.SetValue(0x0020, 0x0032, "-866.17921113968\\-872.62231063843\\-64.073608398438", false);
+  tags.SetValue(0x0020, 0x0037, "1\\0\\0\\0\\1\\0", false);
+  tags.SetValue(0x0028, 0x0030, "2.4000000953674\\2.4000000953674", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("60");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("1");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("0.0").AddValue("0.0").AddValue("1.0");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_66)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_66(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 90);
+  ASSERT_NEAR(nifti.pixdim[1], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 90);
+  ASSERT_NEAR(nifti.pixdim[2], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 60);
+  ASSERT_NEAR(nifti.pixdim[3], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 486000u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 5);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 110.179169, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -96.977722, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -64.073608, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 1.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.000000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=60;Time=152737.565;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_67(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa', 'In/TotalReadoutTime/PF_PAT_POS_PFOV_PEres_INTERP_test2/NOPF_P2_NOPOS_PERES100_ES0P59_BW2222_PA_0011/PF_PAT_POS_BW_INTERP_TEST.MR.HEAD_HARMS.0011.0001.2017.09.20.16.16.44.39766.120097338.IMA')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "1", false);
+  tags.SetValue(0x0018, 0x0080, "6100", false);
+  tags.SetValue(0x0018, 0x0081, "60", false);
+  tags.SetValue(0x0018, 0x1312, "COL", false);
+  tags.SetValue(0x0019, 0x1029, "3040.00000001\\0.0\\3142.50000002\\102.50000001\\3242.49999999\\202.50000001\\3345.0\\304.99999999\\3445.00000001\\405.0\\3547.50000002\\507.50000001\\3647.50000002\\610.00000002\\3750.0\\710.00000002\\3852.50000001\\812.5\\3952.50000001\\912.50000001\\4054.99999999\\1015.00000001\\4155.0\\1115.00000002\\4257.50000001\\1217.5\\4357.50000001\\1317.5\\4460.00000002\\1420.00000001\\4560.0\\1520.00000002\\4662.50000001\\1622.5\\4762.50000001\\1722.5\\4865.00000002\\1825.00000001\\4965.0\\1925.00000002\\5067.5\\2027.5\\5167.50000001\\2127.5\\5270.00000002\\2230.00000001\\5372.5\\2332.50000002\\5472.5\\2432.5\\5575.00000001\\2535.0\\5675.00000002\\2635.00000001\\5777.5\\2737.50000002\\5877.5\\2837.49999999\\5980.00000001\\2940.0", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "720", false);
+  tags.SetValue(0x0028, 0x0011, "720", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "16", false);
+  tags.SetValue(0x0028, 0x0102, "15", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "152834.595000", false);
+  tags.SetValue(0x0018, 0x0050, "2.4000000953674", false);
+  tags.SetValue(0x0018, 0x0088, "2.3999999741376", false);
+  tags.SetValue(0x0020, 0x0032, "-866.17921113968\\-872.62231063843\\-64.073608398438", false);
+  tags.SetValue(0x0020, 0x0037, "1\\0\\0\\0\\1\\0", false);
+  tags.SetValue(0x0028, 0x0030, "2.4000000953674\\2.4000000953674", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("60");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("0");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("0.0").AddValue("0.0").AddValue("1.0");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_67)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_67(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 90);
+  ASSERT_NEAR(nifti.pixdim[1], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 90);
+  ASSERT_NEAR(nifti.pixdim[2], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 60);
+  ASSERT_NEAR(nifti.pixdim[3], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 486000u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 5);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 110.179169, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -96.977722, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -64.073608, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 1.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.000000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=60;Time=152834.595;phase=0");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_68(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa', 'In/TotalReadoutTime/PF_PAT_POS_PFOV_PEres_INTERP_test2/NOPF_P4_NOPOS_PERES100_ES0P59_BW2222_AP_0032/PF_PAT_POS_BW_INTERP_TEST.MR.HEAD_HARMS.0032.0001.2017.09.20.16.16.44.39766.120186472.IMA')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "1", false);
+  tags.SetValue(0x0018, 0x0080, "6100", false);
+  tags.SetValue(0x0018, 0x0081, "60", false);
+  tags.SetValue(0x0018, 0x1312, "COL", false);
+  tags.SetValue(0x0019, 0x1029, "3040.00000001\\0.0\\3140.00000001\\100.00000001\\3242.50000002\\202.50000001\\3342.5\\302.50000002\\3445.00000001\\405.0\\3545.00000001\\505.0\\3647.50000002\\607.50000001\\3747.50000003\\707.50000002\\3850.00000001\\810.00000003\\3950.00000001\\910.0\\4052.50000002\\1012.50000001\\4152.50000003\\1112.50000002\\4255.0\\1215.00000003\\4357.50000001\\1317.5\\4457.50000002\\1417.50000001\\4560.00000003\\1520.00000002\\4660.0\\1620.00000002\\4762.50000001\\1722.5\\4862.50000002\\1822.50000001\\4965.00000003\\1925.00000002\\5065.0\\2025.00000002\\5167.50000001\\2127.5\\5267.50000002\\2227.50000001\\5370.00000002\\2330.00000002\\5470.0\\2430.00000002\\5572.50000001\\2532.5\\5672.50000002\\2632.50000001\\5775.00000002\\2735.00000002\\5877.5\\2837.50000002\\5977.50000001\\2937.5", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "720", false);
+  tags.SetValue(0x0028, 0x0011, "720", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "16", false);
+  tags.SetValue(0x0028, 0x0102, "15", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "160241.307500", false);
+  tags.SetValue(0x0018, 0x0050, "2.4000000953674", false);
+  tags.SetValue(0x0018, 0x0088, "2.3999999741376", false);
+  tags.SetValue(0x0020, 0x0032, "-866.17921113968\\-872.62231063843\\-64.073608398438", false);
+  tags.SetValue(0x0020, 0x0037, "1\\0\\0\\0\\1\\0", false);
+  tags.SetValue(0x0028, 0x0030, "2.4000000953674\\2.4000000953674", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("60");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("1");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("0.0").AddValue("0.0").AddValue("1.0");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_68)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_68(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 90);
+  ASSERT_NEAR(nifti.pixdim[1], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 90);
+  ASSERT_NEAR(nifti.pixdim[2], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 60);
+  ASSERT_NEAR(nifti.pixdim[3], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 486000u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 5);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 110.179169, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -96.977722, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -64.073608, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 1.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.000000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=60;Time=160241.307;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_69(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa', 'In/TotalReadoutTime/PF_PAT_POS_PFOV_PEres_INTERP_test2/NOPF_P4_NOPOS_PERES100_ES0P59_BW2222_PA_0033/PF_PAT_POS_BW_INTERP_TEST.MR.HEAD_HARMS.0033.0001.2017.09.20.16.16.44.39766.120190570.IMA')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "1", false);
+  tags.SetValue(0x0018, 0x0080, "6100", false);
+  tags.SetValue(0x0018, 0x0081, "60", false);
+  tags.SetValue(0x0018, 0x1312, "COL", false);
+  tags.SetValue(0x0019, 0x1029, "3040.00000001\\0.0\\3140.00000001\\100.00000001\\3242.49999999\\202.50000001\\3342.5\\302.49999999\\3445.00000001\\405.0\\3545.00000001\\505.0\\3647.49999999\\607.50000001\\3747.5\\707.49999999\\3850.00000001\\810.0\\3952.50000001\\912.50000001\\4052.49999999\\1012.50000001\\4155.0\\1114.99999999\\4255.0\\1215.0\\4357.50000001\\1317.5\\4457.49999999\\1417.50000001\\4560.0\\1519.99999999\\4660.0\\1620.0\\4762.50000001\\1722.5\\4862.49999999\\1822.50000001\\4965.0\\1924.99999999\\5065.0\\2024.99999999\\5167.50000001\\2127.5\\5267.50000002\\2227.50000001\\5370.0\\2330.00000002\\5472.5\\2432.5\\5572.50000001\\2532.5\\5674.99999999\\2635.00000001\\5774.99999999\\2735.00000002\\5877.5\\2837.49999999\\5977.50000001\\2937.5", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "720", false);
+  tags.SetValue(0x0028, 0x0011, "720", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "16", false);
+  tags.SetValue(0x0028, 0x0102, "15", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "160410.412500", false);
+  tags.SetValue(0x0018, 0x0050, "2.4000000953674", false);
+  tags.SetValue(0x0018, 0x0088, "2.3999999741376", false);
+  tags.SetValue(0x0020, 0x0032, "-866.17921113968\\-872.62231063843\\-64.073608398438", false);
+  tags.SetValue(0x0020, 0x0037, "1\\0\\0\\0\\1\\0", false);
+  tags.SetValue(0x0028, 0x0030, "2.4000000953674\\2.4000000953674", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("60");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("0");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("0.0").AddValue("0.0").AddValue("1.0");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_69)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_69(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 90);
+  ASSERT_NEAR(nifti.pixdim[1], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 90);
+  ASSERT_NEAR(nifti.pixdim[2], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 60);
+  ASSERT_NEAR(nifti.pixdim[3], 2.400000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 486000u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 5);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 110.179169, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -96.977722, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 2.400000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -64.073608, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 1.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.000000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=60;Time=160410.413;phase=0");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_70(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa_nih', 'In/20180918GE/mr_0004/axial_epi_fmri_interleaved_i_to_s-00001.dcm')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "GE MEDICAL SYSTEMS", false);
+  tags.SetValue(0x0020, 0x0013, "1", false);
+  tags.SetValue(0x0018, 0x0080, "5000", false);
+  tags.SetValue(0x0018, 0x0081, "30", false);
+  tags.SetValue(0x0018, 0x1312, "COL", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "64", false);
+  tags.SetValue(0x0028, 0x0011, "64", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "16", false);
+  tags.SetValue(0x0028, 0x0102, "15", false);
+  tags.SetValue(0x0028, 0x0103, "1", false);
+  tags.SetValue(0x0008, 0x0032, "114815", false);
+  tags.SetValue(0x0018, 0x0050, "3", false);
+  tags.SetValue(0x0018, 0x0088, "5", false);
+  tags.SetValue(0x0020, 0x0032, "-118.125\\-143.344\\-17.6875", false);
+  tags.SetValue(0x0020, 0x0037, "1.00000\\-0.00000\\0.00000\\-0.00000\\1.00000\\0.00000", false);
+  tags.SetValue(0x0028, 0x0030, "3.75\\3.75", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_70)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_70(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.750000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.750000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 1);
+  ASSERT_NEAR(nifti.pixdim[3], 5.000000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 4096u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 4);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.750000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 118.125000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 3.750000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -92.906006, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 5.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -17.687500, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 1.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.000000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=114815.000");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_71(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa_nih', 'In/20180918GE/mr_0004/axial_epi_fmri_interleaved_i_to_s-00002.dcm')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "GE MEDICAL SYSTEMS", false);
+  tags.SetValue(0x0020, 0x0013, "2", false);
+  tags.SetValue(0x0018, 0x0080, "5000", false);
+  tags.SetValue(0x0018, 0x0081, "30", false);
+  tags.SetValue(0x0018, 0x1312, "COL", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "64", false);
+  tags.SetValue(0x0028, 0x0011, "64", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "16", false);
+  tags.SetValue(0x0028, 0x0102, "15", false);
+  tags.SetValue(0x0028, 0x0103, "1", false);
+  tags.SetValue(0x0008, 0x0032, "114815", false);
+  tags.SetValue(0x0018, 0x0050, "3", false);
+  tags.SetValue(0x0018, 0x0088, "5", false);
+  tags.SetValue(0x0020, 0x0032, "-118.125\\-143.344\\-12.6875", false);
+  tags.SetValue(0x0020, 0x0037, "1.00000\\-0.00000\\0.00000\\-0.00000\\1.00000\\0.00000", false);
+  tags.SetValue(0x0028, 0x0030, "3.75\\3.75", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_71)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_71(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.750000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.750000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 1);
+  ASSERT_NEAR(nifti.pixdim[3], 5.000000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 4096u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 4);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.750000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 118.125000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 3.750000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -92.906006, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 5.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -12.687500, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 1.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.000000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=114815.000");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_72(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa_nih', 'In/20180918GE/mr_0005/axial_epi_fmri_sequential_i_to_s-00001.dcm')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "GE MEDICAL SYSTEMS", false);
+  tags.SetValue(0x0020, 0x0013, "1", false);
+  tags.SetValue(0x0018, 0x0080, "5000", false);
+  tags.SetValue(0x0018, 0x0081, "30", false);
+  tags.SetValue(0x0018, 0x1312, "COL", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "64", false);
+  tags.SetValue(0x0028, 0x0011, "64", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "16", false);
+  tags.SetValue(0x0028, 0x0102, "15", false);
+  tags.SetValue(0x0028, 0x0103, "1", false);
+  tags.SetValue(0x0008, 0x0032, "115224", false);
+  tags.SetValue(0x0018, 0x0050, "3", false);
+  tags.SetValue(0x0018, 0x0088, "5", false);
+  tags.SetValue(0x0020, 0x0032, "-118.125\\-143.344\\-17.6875", false);
+  tags.SetValue(0x0020, 0x0037, "1.00000\\-0.00000\\0.00000\\-0.00000\\1.00000\\0.00000", false);
+  tags.SetValue(0x0028, 0x0030, "3.75\\3.75", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_72)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_72(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.750000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.750000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 1);
+  ASSERT_NEAR(nifti.pixdim[3], 5.000000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 4096u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 4);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.750000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 118.125000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 3.750000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -92.906006, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 5.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -17.687500, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 1.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.000000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=115224.000");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_73(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa_nih', 'In/20180918GE/mr_0005/axial_epi_fmri_sequential_i_to_s-00002.dcm')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "GE MEDICAL SYSTEMS", false);
+  tags.SetValue(0x0020, 0x0013, "2", false);
+  tags.SetValue(0x0018, 0x0080, "5000", false);
+  tags.SetValue(0x0018, 0x0081, "30", false);
+  tags.SetValue(0x0018, 0x1312, "COL", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "64", false);
+  tags.SetValue(0x0028, 0x0011, "64", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "16", false);
+  tags.SetValue(0x0028, 0x0102, "15", false);
+  tags.SetValue(0x0028, 0x0103, "1", false);
+  tags.SetValue(0x0008, 0x0032, "115224", false);
+  tags.SetValue(0x0018, 0x0050, "3", false);
+  tags.SetValue(0x0018, 0x0088, "5", false);
+  tags.SetValue(0x0020, 0x0032, "-118.125\\-143.344\\-12.6875", false);
+  tags.SetValue(0x0020, 0x0037, "1.00000\\-0.00000\\0.00000\\-0.00000\\1.00000\\0.00000", false);
+  tags.SetValue(0x0028, 0x0030, "3.75\\3.75", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_73)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_73(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.750000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.750000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 1);
+  ASSERT_NEAR(nifti.pixdim[3], 5.000000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 4096u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 4);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.750000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 118.125000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 3.750000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -92.906006, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 5.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -12.687500, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 1.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.000000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=115224.000");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_74(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa_nih', 'In/20180918GE/mr_0006/axial_epi_fmri_interleaved_s_to_i-00001.dcm')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "GE MEDICAL SYSTEMS", false);
+  tags.SetValue(0x0020, 0x0013, "1", false);
+  tags.SetValue(0x0018, 0x0080, "5000", false);
+  tags.SetValue(0x0018, 0x0081, "30", false);
+  tags.SetValue(0x0018, 0x1312, "COL", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "64", false);
+  tags.SetValue(0x0028, 0x0011, "64", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "16", false);
+  tags.SetValue(0x0028, 0x0102, "15", false);
+  tags.SetValue(0x0028, 0x0103, "1", false);
+  tags.SetValue(0x0008, 0x0032, "115418", false);
+  tags.SetValue(0x0018, 0x0050, "3", false);
+  tags.SetValue(0x0018, 0x0088, "5", false);
+  tags.SetValue(0x0020, 0x0032, "-118.125\\-143.344\\52.3125", false);
+  tags.SetValue(0x0020, 0x0037, "1.00000\\-0.00000\\0.00000\\-0.00000\\1.00000\\0.00000", false);
+  tags.SetValue(0x0028, 0x0030, "3.75\\3.75", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_74)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_74(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.750000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.750000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 1);
+  ASSERT_NEAR(nifti.pixdim[3], 5.000000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 4096u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 4);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.750000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 118.125000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 3.750000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -92.906006, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 5.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], 52.312500, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 1.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.000000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=115418.000");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_75(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa_nih', 'In/20180918GE/mr_0006/axial_epi_fmri_interleaved_s_to_i-00002.dcm')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "GE MEDICAL SYSTEMS", false);
+  tags.SetValue(0x0020, 0x0013, "2", false);
+  tags.SetValue(0x0018, 0x0080, "5000", false);
+  tags.SetValue(0x0018, 0x0081, "30", false);
+  tags.SetValue(0x0018, 0x1312, "COL", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "64", false);
+  tags.SetValue(0x0028, 0x0011, "64", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "16", false);
+  tags.SetValue(0x0028, 0x0102, "15", false);
+  tags.SetValue(0x0028, 0x0103, "1", false);
+  tags.SetValue(0x0008, 0x0032, "115418", false);
+  tags.SetValue(0x0018, 0x0050, "3", false);
+  tags.SetValue(0x0018, 0x0088, "5", false);
+  tags.SetValue(0x0020, 0x0032, "-118.125\\-143.344\\47.3125", false);
+  tags.SetValue(0x0020, 0x0037, "1.00000\\-0.00000\\0.00000\\-0.00000\\1.00000\\0.00000", false);
+  tags.SetValue(0x0028, 0x0030, "3.75\\3.75", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_75)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_75(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.750000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.750000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 1);
+  ASSERT_NEAR(nifti.pixdim[3], 5.000000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 4096u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 4);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.750000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 118.125000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 3.750000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -92.906006, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 5.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], 47.312500, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 1.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.000000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=115418.000");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_76(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa_nih', 'In/20180918GE/mr_0007/axial_epi_fmri_sequential_s_to_i-00001.dcm')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "GE MEDICAL SYSTEMS", false);
+  tags.SetValue(0x0020, 0x0013, "1", false);
+  tags.SetValue(0x0018, 0x0080, "5000", false);
+  tags.SetValue(0x0018, 0x0081, "30", false);
+  tags.SetValue(0x0018, 0x1312, "COL", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "64", false);
+  tags.SetValue(0x0028, 0x0011, "64", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "16", false);
+  tags.SetValue(0x0028, 0x0102, "15", false);
+  tags.SetValue(0x0028, 0x0103, "1", false);
+  tags.SetValue(0x0008, 0x0032, "115551", false);
+  tags.SetValue(0x0018, 0x0050, "3", false);
+  tags.SetValue(0x0018, 0x0088, "5", false);
+  tags.SetValue(0x0020, 0x0032, "-118.125\\-143.344\\52.3125", false);
+  tags.SetValue(0x0020, 0x0037, "1.00000\\-0.00000\\0.00000\\-0.00000\\1.00000\\0.00000", false);
+  tags.SetValue(0x0028, 0x0030, "3.75\\3.75", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_76)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_76(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.750000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.750000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 1);
+  ASSERT_NEAR(nifti.pixdim[3], 5.000000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 4096u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 4);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.750000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 118.125000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 3.750000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -92.906006, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 5.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], 52.312500, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 1.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.000000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=115551.000");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_77(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa_nih', 'In/20180918GE/mr_0007/axial_epi_fmri_sequential_s_to_i-00002.dcm')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "GE MEDICAL SYSTEMS", false);
+  tags.SetValue(0x0020, 0x0013, "2", false);
+  tags.SetValue(0x0018, 0x0080, "5000", false);
+  tags.SetValue(0x0018, 0x0081, "30", false);
+  tags.SetValue(0x0018, 0x1312, "COL", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "64", false);
+  tags.SetValue(0x0028, 0x0011, "64", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "16", false);
+  tags.SetValue(0x0028, 0x0102, "15", false);
+  tags.SetValue(0x0028, 0x0103, "1", false);
+  tags.SetValue(0x0008, 0x0032, "115551", false);
+  tags.SetValue(0x0018, 0x0050, "3", false);
+  tags.SetValue(0x0018, 0x0088, "5", false);
+  tags.SetValue(0x0020, 0x0032, "-118.125\\-143.344\\47.3125", false);
+  tags.SetValue(0x0020, 0x0037, "1.00000\\-0.00000\\0.00000\\-0.00000\\1.00000\\0.00000", false);
+  tags.SetValue(0x0028, 0x0030, "3.75\\3.75", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_77)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_77(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.750000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.750000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 1);
+  ASSERT_NEAR(nifti.pixdim[3], 5.000000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 4096u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 4);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.750000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 118.125000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 3.750000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -92.906006, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 5.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], 47.312500, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 1.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.000000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=115551.000");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_78(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa_nih', 'In/20180918Si/mr_0003/epi_pe_ap-00001.dcm')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "1", false);
+  tags.SetValue(0x0018, 0x0080, "2435.37", false);
+  tags.SetValue(0x0018, 0x0081, "50", false);
+  tags.SetValue(0x0018, 0x1312, "COL", false);
+  tags.SetValue(0x0019, 0x1029, "0.0\\1450.00000001\\482.50000001\\1932.50000001\\967.50000001", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "216", false);
+  tags.SetValue(0x0028, 0x0011, "216", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "12", false);
+  tags.SetValue(0x0028, 0x0102, "11", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "122458.102500", false);
+  tags.SetValue(0x0018, 0x0050, "3", false);
+  tags.SetValue(0x0018, 0x0088, "12", false);
+  tags.SetValue(0x0020, 0x0032, "-324\\-324\\16", false);
+  tags.SetValue(0x0020, 0x0037, "1\\0\\0\\0\\1\\0", false);
+  tags.SetValue(0x0028, 0x0030, "3\\3", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("5");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("1");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("0.0").AddValue("0.0").AddValue("1.0");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_78)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_78(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 72);
+  ASSERT_NEAR(nifti.pixdim[1], 3.000000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 72);
+  ASSERT_NEAR(nifti.pixdim[2], 3.000000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 5);
+  ASSERT_NEAR(nifti.pixdim[3], 12.000000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 25920u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 3);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 108.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 3.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -105.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 12.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], 16.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 1.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.000000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=50;Time=122458.102;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_79(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa_nih', 'In/20180918Si/mr_0003/epi_pe_ap-00002.dcm')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "2", false);
+  tags.SetValue(0x0018, 0x0080, "2435.37", false);
+  tags.SetValue(0x0018, 0x0081, "50", false);
+  tags.SetValue(0x0018, 0x1312, "COL", false);
+  tags.SetValue(0x0019, 0x1029, "0.0\\1450.0\\482.5\\1932.5\\967.5", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "216", false);
+  tags.SetValue(0x0028, 0x0011, "216", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "12", false);
+  tags.SetValue(0x0028, 0x0102, "11", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "122500.537500", false);
+  tags.SetValue(0x0018, 0x0050, "3", false);
+  tags.SetValue(0x0018, 0x0088, "12", false);
+  tags.SetValue(0x0020, 0x0032, "-324\\-324\\16", false);
+  tags.SetValue(0x0020, 0x0037, "1\\0\\0\\0\\1\\0", false);
+  tags.SetValue(0x0028, 0x0030, "3\\3", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("5");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("1");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("0.0").AddValue("0.0").AddValue("1.0");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_79)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_79(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 72);
+  ASSERT_NEAR(nifti.pixdim[1], 3.000000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 72);
+  ASSERT_NEAR(nifti.pixdim[2], 3.000000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 5);
+  ASSERT_NEAR(nifti.pixdim[3], 12.000000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 25920u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 3);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 108.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 3.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -105.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 12.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], 16.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 1.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.000000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=50;Time=122500.538;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_80(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa_nih', 'In/20180918Si/mr_0004/epi_pe_pa-00001.dcm')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "1", false);
+  tags.SetValue(0x0018, 0x0080, "2435.37", false);
+  tags.SetValue(0x0018, 0x0081, "50", false);
+  tags.SetValue(0x0018, 0x1312, "COL", false);
+  tags.SetValue(0x0019, 0x1029, "0.0\\1447.49999999\\482.5\\1929.99999999\\965.0", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "216", false);
+  tags.SetValue(0x0028, 0x0011, "216", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "12", false);
+  tags.SetValue(0x0028, 0x0102, "11", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "122654.517500", false);
+  tags.SetValue(0x0018, 0x0050, "3", false);
+  tags.SetValue(0x0018, 0x0088, "12", false);
+  tags.SetValue(0x0020, 0x0032, "-324\\-324\\16", false);
+  tags.SetValue(0x0020, 0x0037, "1\\0\\0\\0\\1\\0", false);
+  tags.SetValue(0x0028, 0x0030, "3\\3", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("5");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("0");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("0.0").AddValue("0.0").AddValue("1.0");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_80)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_80(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 72);
+  ASSERT_NEAR(nifti.pixdim[1], 3.000000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 72);
+  ASSERT_NEAR(nifti.pixdim[2], 3.000000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 5);
+  ASSERT_NEAR(nifti.pixdim[3], 12.000000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 25920u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 3);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 108.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 3.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -105.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 12.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], 16.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 1.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.000000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=50;Time=122654.518;phase=0");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_81(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa_nih', 'In/20180918Si/mr_0004/epi_pe_pa-00002.dcm')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "2", false);
+  tags.SetValue(0x0018, 0x0080, "2435.37", false);
+  tags.SetValue(0x0018, 0x0081, "50", false);
+  tags.SetValue(0x0018, 0x1312, "COL", false);
+  tags.SetValue(0x0019, 0x1029, "0.0\\1447.49999999\\482.5\\1932.5\\965.0", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "216", false);
+  tags.SetValue(0x0028, 0x0011, "216", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "12", false);
+  tags.SetValue(0x0028, 0x0102, "11", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "122656.952500", false);
+  tags.SetValue(0x0018, 0x0050, "3", false);
+  tags.SetValue(0x0018, 0x0088, "12", false);
+  tags.SetValue(0x0020, 0x0032, "-324\\-324\\16", false);
+  tags.SetValue(0x0020, 0x0037, "1\\0\\0\\0\\1\\0", false);
+  tags.SetValue(0x0028, 0x0030, "3\\3", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("5");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("0");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("0.0").AddValue("0.0").AddValue("1.0");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_81)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_81(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 72);
+  ASSERT_NEAR(nifti.pixdim[1], 3.000000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 72);
+  ASSERT_NEAR(nifti.pixdim[2], 3.000000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 5);
+  ASSERT_NEAR(nifti.pixdim[3], 12.000000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 25920u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 3);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 108.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 3.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -105.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 12.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], 16.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 1.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.000000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=50;Time=122656.952;phase=0");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_82(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa_nih', 'In/20180918Si/mr_0005/epi_pe_rl-00001.dcm')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "1", false);
+  tags.SetValue(0x0018, 0x0080, "2435.37", false);
+  tags.SetValue(0x0018, 0x0081, "50", false);
+  tags.SetValue(0x0018, 0x1312, "ROW", false);
+  tags.SetValue(0x0019, 0x1029, "0.0\\1450.0\\482.5\\1932.5\\967.5", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "216", false);
+  tags.SetValue(0x0028, 0x0011, "216", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "12", false);
+  tags.SetValue(0x0028, 0x0102, "11", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "122812.085000", false);
+  tags.SetValue(0x0018, 0x0050, "3", false);
+  tags.SetValue(0x0018, 0x0088, "12", false);
+  tags.SetValue(0x0020, 0x0032, "-324.00000000007\\-323.99999999993\\16", false);
+  tags.SetValue(0x0020, 0x0037, "1\\-2.07e-013\\0\\2.07e-013\\1\\0", false);
+  tags.SetValue(0x0028, 0x0030, "3\\3", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("5");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("1");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("0.0").AddValue("0.0").AddValue("1.0");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_82)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_82(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 72);
+  ASSERT_NEAR(nifti.pixdim[1], 3.000000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 72);
+  ASSERT_NEAR(nifti.pixdim[2], 3.000000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 5);
+  ASSERT_NEAR(nifti.pixdim[3], 12.000000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 25920u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(54));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(54));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(54));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 3);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 108.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 3.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -105.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 12.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], 16.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 1.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.000000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=50;Time=122812.085;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_83(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa_nih', 'In/20180918Si/mr_0005/epi_pe_rl-00002.dcm')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "2", false);
+  tags.SetValue(0x0018, 0x0080, "2435.37", false);
+  tags.SetValue(0x0018, 0x0081, "50", false);
+  tags.SetValue(0x0018, 0x1312, "ROW", false);
+  tags.SetValue(0x0019, 0x1029, "0.0\\1450.0\\485.0\\1932.5\\967.5", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "216", false);
+  tags.SetValue(0x0028, 0x0011, "216", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "12", false);
+  tags.SetValue(0x0028, 0x0102, "11", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "122814.520000", false);
+  tags.SetValue(0x0018, 0x0050, "3", false);
+  tags.SetValue(0x0018, 0x0088, "12", false);
+  tags.SetValue(0x0020, 0x0032, "-324.00000000007\\-323.99999999993\\16", false);
+  tags.SetValue(0x0020, 0x0037, "1\\-2.07e-013\\0\\2.07e-013\\1\\0", false);
+  tags.SetValue(0x0028, 0x0030, "3\\3", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("5");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("1");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("0.0").AddValue("0.0").AddValue("1.0");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_83)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_83(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 72);
+  ASSERT_NEAR(nifti.pixdim[1], 3.000000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 72);
+  ASSERT_NEAR(nifti.pixdim[2], 3.000000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 5);
+  ASSERT_NEAR(nifti.pixdim[3], 12.000000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 25920u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(54));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(54));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(54));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 3);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 108.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 3.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -105.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 12.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], 16.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 1.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.000000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=50;Time=122814.520;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_84(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa_nih', 'In/20180918Si/mr_0006/epi_pe_lr-00001.dcm')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "1", false);
+  tags.SetValue(0x0018, 0x0080, "2435.37", false);
+  tags.SetValue(0x0018, 0x0081, "50", false);
+  tags.SetValue(0x0018, 0x1312, "ROW", false);
+  tags.SetValue(0x0019, 0x1029, "0.0\\1447.49999999\\482.5\\1932.5\\965.0", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "216", false);
+  tags.SetValue(0x0028, 0x0011, "216", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "12", false);
+  tags.SetValue(0x0028, 0x0102, "11", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "123002.285000", false);
+  tags.SetValue(0x0018, 0x0050, "3", false);
+  tags.SetValue(0x0018, 0x0088, "12", false);
+  tags.SetValue(0x0020, 0x0032, "-324\\-324\\16", false);
+  tags.SetValue(0x0020, 0x0037, "1\\0\\0\\0\\1\\0", false);
+  tags.SetValue(0x0028, 0x0030, "3\\3", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("5");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("0");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("0.0").AddValue("0.0").AddValue("1.0");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_84)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_84(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 72);
+  ASSERT_NEAR(nifti.pixdim[1], 3.000000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 72);
+  ASSERT_NEAR(nifti.pixdim[2], 3.000000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 5);
+  ASSERT_NEAR(nifti.pixdim[3], 12.000000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 25920u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(54));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(54));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(54));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 3);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 108.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 3.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -105.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 12.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], 16.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 1.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.000000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=50;Time=123002.285;phase=0");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_85(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa_nih', 'In/20180918Si/mr_0006/epi_pe_lr-00002.dcm')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "SIEMENS", false);
+  tags.SetValue(0x0020, 0x0013, "2", false);
+  tags.SetValue(0x0018, 0x0080, "2435.37", false);
+  tags.SetValue(0x0018, 0x0081, "50", false);
+  tags.SetValue(0x0018, 0x1312, "ROW", false);
+  tags.SetValue(0x0019, 0x1029, "0.0\\1450.0\\482.5\\1932.5\\965.0", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "216", false);
+  tags.SetValue(0x0028, 0x0011, "216", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "12", false);
+  tags.SetValue(0x0028, 0x0102, "11", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "123004.720000", false);
+  tags.SetValue(0x0018, 0x0050, "3", false);
+  tags.SetValue(0x0018, 0x0088, "12", false);
+  tags.SetValue(0x0020, 0x0032, "-324\\-324\\16", false);
+  tags.SetValue(0x0020, 0x0037, "1\\0\\0\\0\\1\\0", false);
+  tags.SetValue(0x0028, 0x0030, "3\\3", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  dicom->GetCSAHeader().AddTag("NumberOfImagesInMosaic", "US").AddValue("5");
+  dicom->GetCSAHeader().AddTag("PhaseEncodingDirectionPositive", "IS").AddValue("0");
+  dicom->GetCSAHeader().AddTag("SliceNormalVector", "FD").AddValue("0.0").AddValue("0.0").AddValue("1.0");
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_85)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_85(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 72);
+  ASSERT_NEAR(nifti.pixdim[1], 3.000000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 72);
+  ASSERT_NEAR(nifti.pixdim[2], 3.000000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 5);
+  ASSERT_NEAR(nifti.pixdim[3], 12.000000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 25920u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(54));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(54));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(54));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 3);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 108.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 3.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -105.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 12.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], 16.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 1.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.000000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=50;Time=123004.720;phase=0");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_86(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa_uih', 'In/DTI_132225/dti_tra_dir16_PA_rot_SaveBySlc__134057/00000001.dcm')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "UIH", false);
+  tags.SetValue(0x0020, 0x0013, "1", false);
+  tags.SetValue(0x0018, 0x0080, "5865.0", false);
+  tags.SetValue(0x0018, 0x0081, "70.7", false);
+  tags.SetValue(0x0018, 0x1312, "COL", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "256", false);
+  tags.SetValue(0x0028, 0x0011, "320", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "16", false);
+  tags.SetValue(0x0028, 0x0102, "15", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0018, 0x0050, "3.5", false);
+  tags.SetValue(0x0018, 0x0088, "4.19999981", false);
+  tags.SetValue(0x0020, 0x0037, "0.906671703\\-0.4210186\\0.026263684\\0.420842081\\0.90705198\\0.0121905897", false);
+  tags.SetValue(0x0028, 0x0030, "3.5\\3.5", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925133748.283000", false);
+    uih.SetValue(0x0008, 0x0032, "133748.283000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-154.647507\\-58.5786934\\-9.21860886", false);
+    uih.SetValue(0x0020, 0x1041, "-4.73692799", false);
+    uih.SetValue(0x0065, 0x100f, "F4.7", false);
+    uih.SetValue(0x0065, 0x1015, "-5.96596575\\-4.14295006\\-4.91173077", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925133745.357000", false);
+    uih.SetValue(0x0008, 0x0032, "133745.357000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-154.769119\\-58.5786934\\-5.02037001", false);
+    uih.SetValue(0x0020, 0x1041, "-0.536926806", false);
+    uih.SetValue(0x0065, 0x100f, "F0.5", false);
+    uih.SetValue(0x0065, 0x1015, "-6.08757687\\-4.14295006\\-0.713491559", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925133747.990000", false);
+    uih.SetValue(0x0008, 0x0032, "133747.990000", false);
+    uih.SetValue(0x0018, 0x9073, "22.0", false);
+    uih.SetValue(0x0020, 0x0032, "-154.890732\\-58.5786934\\-0.822131157", false);
+    uih.SetValue(0x0020, 0x1041, "3.66307425", false);
+    uih.SetValue(0x0065, 0x100f, "H3.7", false);
+    uih.SetValue(0x0065, 0x1015, "-6.20918798\\-4.14295006\\3.48474741", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925133745.065000", false);
+    uih.SetValue(0x0008, 0x0032, "133745.065000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-155.012344\\-58.5786934\\3.37610793", false);
+    uih.SetValue(0x0020, 0x1041, "7.86307573", false);
+    uih.SetValue(0x0065, 0x100f, "H7.9", false);
+    uih.SetValue(0x0065, 0x1015, "-6.33079863\\-4.14295006\\7.68298626", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925133747.698000", false);
+    uih.SetValue(0x0008, 0x0032, "133747.698000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-155.133957\\-58.5786934\\7.57434702", false);
+    uih.SetValue(0x0020, 0x1041, "12.063077", false);
+    uih.SetValue(0x0065, 0x100f, "H12.1", false);
+    uih.SetValue(0x0065, 0x1015, "-6.45240974\\-4.14295006\\11.8812256", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925133744.772000", false);
+    uih.SetValue(0x0008, 0x0032, "133744.772000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-155.255569\\-58.5786934\\11.7725859", false);
+    uih.SetValue(0x0020, 0x1041, "16.2630787", false);
+    uih.SetValue(0x0065, 0x100f, "H16.3", false);
+    uih.SetValue(0x0065, 0x1015, "-6.57402086\\-4.14295006\\16.079464", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925133747.405000", false);
+    uih.SetValue(0x0008, 0x0032, "133747.405000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-155.377167\\-58.5786934\\15.9708252", false);
+    uih.SetValue(0x0020, 0x1041, "20.4630795", false);
+    uih.SetValue(0x0065, 0x100f, "H20.5", false);
+    uih.SetValue(0x0065, 0x1015, "-6.6956315\\-4.14295006\\20.2777042", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925133744.479000", false);
+    uih.SetValue(0x0008, 0x0032, "133744.479000", false);
+    uih.SetValue(0x0018, 0x9073, "22.0", false);
+    uih.SetValue(0x0020, 0x0032, "-155.498779\\-58.5786934\\20.1690636", false);
+    uih.SetValue(0x0020, 0x1041, "24.6630802", false);
+    uih.SetValue(0x0065, 0x100f, "H24.7", false);
+    uih.SetValue(0x0065, 0x1015, "-6.81724262\\-4.14295006\\24.4759426", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925133747.113000", false);
+    uih.SetValue(0x0008, 0x0032, "133747.113000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-155.620392\\-58.5786934\\24.3673019", false);
+    uih.SetValue(0x0020, 0x1041, "28.863081", false);
+    uih.SetValue(0x0065, 0x100f, "H28.9", false);
+    uih.SetValue(0x0065, 0x1015, "-6.93885374\\-4.14295006\\28.674181", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925133744.187000", false);
+    uih.SetValue(0x0008, 0x0032, "133744.187000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-155.742004\\-58.5786934\\28.5655422", false);
+    uih.SetValue(0x0020, 0x1041, "33.0630836", false);
+    uih.SetValue(0x0065, 0x100f, "H33.1", false);
+    uih.SetValue(0x0065, 0x1015, "-7.06046438\\-4.14295006\\32.8724213", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925133746.820000", false);
+    uih.SetValue(0x0008, 0x0032, "133746.820000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-155.863617\\-58.5786934\\32.7637825", false);
+    uih.SetValue(0x0020, 0x1041, "37.2630844", false);
+    uih.SetValue(0x0065, 0x100f, "H37.3", false);
+    uih.SetValue(0x0065, 0x1015, "-7.1820755\\-4.14295006\\37.0706596", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925133743.894000", false);
+    uih.SetValue(0x0008, 0x0032, "133743.894000", false);
+    uih.SetValue(0x0018, 0x9073, "22.0", false);
+    uih.SetValue(0x0020, 0x0032, "-155.985229\\-58.5786934\\36.9620209", false);
+    uih.SetValue(0x0020, 0x1041, "41.4630852", false);
+    uih.SetValue(0x0065, 0x100f, "H41.5", false);
+    uih.SetValue(0x0065, 0x1015, "-7.30368662\\-4.14295006\\41.268898", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925133746.527000", false);
+    uih.SetValue(0x0008, 0x0032, "133746.527000", false);
+    uih.SetValue(0x0018, 0x9073, "22.0", false);
+    uih.SetValue(0x0020, 0x0032, "-156.106842\\-58.5786934\\41.1602592", false);
+    uih.SetValue(0x0020, 0x1041, "45.6630859", false);
+    uih.SetValue(0x0065, 0x100f, "H45.7", false);
+    uih.SetValue(0x0065, 0x1015, "-7.42529726\\-4.14295006\\45.4671364", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925133743.602000", false);
+    uih.SetValue(0x0008, 0x0032, "133743.602000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-156.228455\\-58.5786934\\45.3584976", false);
+    uih.SetValue(0x0020, 0x1041, "49.8630867", false);
+    uih.SetValue(0x0065, 0x100f, "H49.9", false);
+    uih.SetValue(0x0065, 0x1015, "-7.54690838\\-4.14295006\\49.6653748", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925133746.235000", false);
+    uih.SetValue(0x0008, 0x0032, "133746.235000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-156.350067\\-58.5786934\\49.556736", false);
+    uih.SetValue(0x0020, 0x1041, "54.0630875", false);
+    uih.SetValue(0x0065, 0x100f, "H54.1", false);
+    uih.SetValue(0x0065, 0x1015, "-7.6685195\\-4.14295006\\53.8636169", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925133743.309000", false);
+    uih.SetValue(0x0008, 0x0032, "133743.309000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-156.471664\\-58.5786934\\53.7549744", false);
+    uih.SetValue(0x0020, 0x1041, "58.2630882", false);
+    uih.SetValue(0x0065, 0x100f, "H58.3", false);
+    uih.SetValue(0x0065, 0x1015, "-7.79013014\\-4.14295006\\58.0618553", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925133745.942000", false);
+    uih.SetValue(0x0008, 0x0032, "133745.942000", false);
+    uih.SetValue(0x0018, 0x9073, "22.0", false);
+    uih.SetValue(0x0020, 0x0032, "-156.593277\\-58.5786934\\57.9532166", false);
+    uih.SetValue(0x0020, 0x1041, "62.4630928", false);
+    uih.SetValue(0x0065, 0x100f, "H62.5", false);
+    uih.SetValue(0x0065, 0x1015, "-7.91174126\\-4.14295006\\62.2600937", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925133743.017000", false);
+    uih.SetValue(0x0008, 0x0032, "133743.017000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-156.71489\\-58.5786934\\62.1514549", false);
+    uih.SetValue(0x0020, 0x1041, "66.6630936", false);
+    uih.SetValue(0x0065, 0x100f, "H66.7", false);
+    uih.SetValue(0x0065, 0x1015, "-8.0333519\\-4.14295006\\66.4583359", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925133745.650000", false);
+    uih.SetValue(0x0008, 0x0032, "133745.650000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-156.836502\\-58.5786934\\66.3496933", false);
+    uih.SetValue(0x0020, 0x1041, "70.8630905", false);
+    uih.SetValue(0x0065, 0x100f, "H70.9", false);
+    uih.SetValue(0x0065, 0x1015, "-8.15496349\\-4.14295006\\70.6565704", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925133742.724000", false);
+    uih.SetValue(0x0008, 0x0032, "133742.724000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-156.958115\\-58.5786934\\70.5479355", false);
+    uih.SetValue(0x0020, 0x1041, "75.0630951", false);
+    uih.SetValue(0x0065, 0x100f, "H75.1", false);
+    uih.SetValue(0x0065, 0x1015, "-8.27657413\\-4.14295006\\74.8548126", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_86)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_86(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.500000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.500000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 20);
+  ASSERT_NEAR(nifti.pixdim[3], 4.200000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 81920u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.173351, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 1.472947, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], 0.121611, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 61.851830, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], 1.473565, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 3.174682, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -141.426270, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.091923, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], -0.042667, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 4.198239, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -6.530584, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, -0.215556, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, -0.976384, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.003121, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=71;Time=133742.724");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_87(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa_uih', 'In/DTI_132225/dti_tra_dir16_PA_rot_SaveBySlc__134057/00000002.dcm')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "UIH", false);
+  tags.SetValue(0x0020, 0x0013, "2", false);
+  tags.SetValue(0x0018, 0x0080, "5865.0", false);
+  tags.SetValue(0x0018, 0x0081, "70.7", false);
+  tags.SetValue(0x0018, 0x1312, "COL", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "256", false);
+  tags.SetValue(0x0028, 0x0011, "320", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "16", false);
+  tags.SetValue(0x0028, 0x0102, "15", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0018, 0x0050, "3.5", false);
+  tags.SetValue(0x0018, 0x0088, "4.19999981", false);
+  tags.SetValue(0x0020, 0x0037, "0.906671703\\-0.4210186\\0.026263684\\0.420842081\\0.90705198\\0.0121905897", false);
+  tags.SetValue(0x0028, 0x0030, "3.5\\3.5", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925133759.986000", false);
+    uih.SetValue(0x0008, 0x0032, "133759.986000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-154.647507\\-58.5786934\\-9.21860886", false);
+    uih.SetValue(0x0020, 0x1041, "-4.73692799", false);
+    uih.SetValue(0x0065, 0x100f, "F4.7", false);
+    uih.SetValue(0x0065, 0x1015, "-5.96596575\\-4.14295006\\-4.91173077", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925133757.060000", false);
+    uih.SetValue(0x0008, 0x0032, "133757.060000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-154.769119\\-58.5786934\\-5.02037001", false);
+    uih.SetValue(0x0020, 0x1041, "-0.536926806", false);
+    uih.SetValue(0x0065, 0x100f, "F0.5", false);
+    uih.SetValue(0x0065, 0x1015, "-6.08757687\\-4.14295006\\-0.713491559", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925133759.693000", false);
+    uih.SetValue(0x0008, 0x0032, "133759.693000", false);
+    uih.SetValue(0x0018, 0x9073, "22.0", false);
+    uih.SetValue(0x0020, 0x0032, "-154.890732\\-58.5786934\\-0.822131157", false);
+    uih.SetValue(0x0020, 0x1041, "3.66307425", false);
+    uih.SetValue(0x0065, 0x100f, "H3.7", false);
+    uih.SetValue(0x0065, 0x1015, "-6.20918798\\-4.14295006\\3.48474741", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925133756.767000", false);
+    uih.SetValue(0x0008, 0x0032, "133756.767000", false);
+    uih.SetValue(0x0018, 0x9073, "22.0", false);
+    uih.SetValue(0x0020, 0x0032, "-155.012344\\-58.5786934\\3.37610793", false);
+    uih.SetValue(0x0020, 0x1041, "7.86307573", false);
+    uih.SetValue(0x0065, 0x100f, "H7.9", false);
+    uih.SetValue(0x0065, 0x1015, "-6.33079863\\-4.14295006\\7.68298626", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925133759.401000", false);
+    uih.SetValue(0x0008, 0x0032, "133759.401000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-155.133957\\-58.5786934\\7.57434702", false);
+    uih.SetValue(0x0020, 0x1041, "12.063077", false);
+    uih.SetValue(0x0065, 0x100f, "H12.1", false);
+    uih.SetValue(0x0065, 0x1015, "-6.45240974\\-4.14295006\\11.8812256", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925133756.475000", false);
+    uih.SetValue(0x0008, 0x0032, "133756.475000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-155.255569\\-58.5786934\\11.7725859", false);
+    uih.SetValue(0x0020, 0x1041, "16.2630787", false);
+    uih.SetValue(0x0065, 0x100f, "H16.3", false);
+    uih.SetValue(0x0065, 0x1015, "-6.57402086\\-4.14295006\\16.079464", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925133759.108000", false);
+    uih.SetValue(0x0008, 0x0032, "133759.108000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-155.377167\\-58.5786934\\15.9708252", false);
+    uih.SetValue(0x0020, 0x1041, "20.4630795", false);
+    uih.SetValue(0x0065, 0x100f, "H20.5", false);
+    uih.SetValue(0x0065, 0x1015, "-6.6956315\\-4.14295006\\20.2777042", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925133756.182000", false);
+    uih.SetValue(0x0008, 0x0032, "133756.182000", false);
+    uih.SetValue(0x0018, 0x9073, "22.0", false);
+    uih.SetValue(0x0020, 0x0032, "-155.498779\\-58.5786934\\20.1690636", false);
+    uih.SetValue(0x0020, 0x1041, "24.6630802", false);
+    uih.SetValue(0x0065, 0x100f, "H24.7", false);
+    uih.SetValue(0x0065, 0x1015, "-6.81724262\\-4.14295006\\24.4759426", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925133758.815000", false);
+    uih.SetValue(0x0008, 0x0032, "133758.815000", false);
+    uih.SetValue(0x0018, 0x9073, "22.0", false);
+    uih.SetValue(0x0020, 0x0032, "-155.620392\\-58.5786934\\24.3673019", false);
+    uih.SetValue(0x0020, 0x1041, "28.863081", false);
+    uih.SetValue(0x0065, 0x100f, "H28.9", false);
+    uih.SetValue(0x0065, 0x1015, "-6.93885374\\-4.14295006\\28.674181", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925133755.890000", false);
+    uih.SetValue(0x0008, 0x0032, "133755.890000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-155.742004\\-58.5786934\\28.5655422", false);
+    uih.SetValue(0x0020, 0x1041, "33.0630836", false);
+    uih.SetValue(0x0065, 0x100f, "H33.1", false);
+    uih.SetValue(0x0065, 0x1015, "-7.06046438\\-4.14295006\\32.8724213", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925133758.523000", false);
+    uih.SetValue(0x0008, 0x0032, "133758.523000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-155.863617\\-58.5786934\\32.7637825", false);
+    uih.SetValue(0x0020, 0x1041, "37.2630844", false);
+    uih.SetValue(0x0065, 0x100f, "H37.3", false);
+    uih.SetValue(0x0065, 0x1015, "-7.1820755\\-4.14295006\\37.0706596", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925133755.597000", false);
+    uih.SetValue(0x0008, 0x0032, "133755.597000", false);
+    uih.SetValue(0x0018, 0x9073, "22.0", false);
+    uih.SetValue(0x0020, 0x0032, "-155.985229\\-58.5786934\\36.9620209", false);
+    uih.SetValue(0x0020, 0x1041, "41.4630852", false);
+    uih.SetValue(0x0065, 0x100f, "H41.5", false);
+    uih.SetValue(0x0065, 0x1015, "-7.30368662\\-4.14295006\\41.268898", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925133758.230000", false);
+    uih.SetValue(0x0008, 0x0032, "133758.230000", false);
+    uih.SetValue(0x0018, 0x9073, "22.0", false);
+    uih.SetValue(0x0020, 0x0032, "-156.106842\\-58.5786934\\41.1602592", false);
+    uih.SetValue(0x0020, 0x1041, "45.6630859", false);
+    uih.SetValue(0x0065, 0x100f, "H45.7", false);
+    uih.SetValue(0x0065, 0x1015, "-7.42529726\\-4.14295006\\45.4671364", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925133755.305000", false);
+    uih.SetValue(0x0008, 0x0032, "133755.305000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-156.228455\\-58.5786934\\45.3584976", false);
+    uih.SetValue(0x0020, 0x1041, "49.8630867", false);
+    uih.SetValue(0x0065, 0x100f, "H49.9", false);
+    uih.SetValue(0x0065, 0x1015, "-7.54690838\\-4.14295006\\49.6653748", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925133757.938000", false);
+    uih.SetValue(0x0008, 0x0032, "133757.938000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-156.350067\\-58.5786934\\49.556736", false);
+    uih.SetValue(0x0020, 0x1041, "54.0630875", false);
+    uih.SetValue(0x0065, 0x100f, "H54.1", false);
+    uih.SetValue(0x0065, 0x1015, "-7.6685195\\-4.14295006\\53.8636169", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925133755.012000", false);
+    uih.SetValue(0x0008, 0x0032, "133755.012000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-156.471664\\-58.5786934\\53.7549744", false);
+    uih.SetValue(0x0020, 0x1041, "58.2630882", false);
+    uih.SetValue(0x0065, 0x100f, "H58.3", false);
+    uih.SetValue(0x0065, 0x1015, "-7.79013014\\-4.14295006\\58.0618553", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925133757.645000", false);
+    uih.SetValue(0x0008, 0x0032, "133757.645000", false);
+    uih.SetValue(0x0018, 0x9073, "22.0", false);
+    uih.SetValue(0x0020, 0x0032, "-156.593277\\-58.5786934\\57.9532166", false);
+    uih.SetValue(0x0020, 0x1041, "62.4630928", false);
+    uih.SetValue(0x0065, 0x100f, "H62.5", false);
+    uih.SetValue(0x0065, 0x1015, "-7.91174126\\-4.14295006\\62.2600937", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925133754.719000", false);
+    uih.SetValue(0x0008, 0x0032, "133754.719000", false);
+    uih.SetValue(0x0018, 0x9073, "22.0", false);
+    uih.SetValue(0x0020, 0x0032, "-156.71489\\-58.5786934\\62.1514549", false);
+    uih.SetValue(0x0020, 0x1041, "66.6630936", false);
+    uih.SetValue(0x0065, 0x100f, "H66.7", false);
+    uih.SetValue(0x0065, 0x1015, "-8.0333519\\-4.14295006\\66.4583359", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925133757.353000", false);
+    uih.SetValue(0x0008, 0x0032, "133757.353000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-156.836502\\-58.5786934\\66.3496933", false);
+    uih.SetValue(0x0020, 0x1041, "70.8630905", false);
+    uih.SetValue(0x0065, 0x100f, "H70.9", false);
+    uih.SetValue(0x0065, 0x1015, "-8.15496349\\-4.14295006\\70.6565704", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925133754.427000", false);
+    uih.SetValue(0x0008, 0x0032, "133754.427000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-156.958115\\-58.5786934\\70.5479355", false);
+    uih.SetValue(0x0020, 0x1041, "75.0630951", false);
+    uih.SetValue(0x0065, 0x100f, "H75.1", false);
+    uih.SetValue(0x0065, 0x1015, "-8.27657413\\-4.14295006\\74.8548126", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_87)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_87(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.500000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.500000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 20);
+  ASSERT_NEAR(nifti.pixdim[3], 4.200000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 81920u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.173351, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 1.472947, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], 0.121611, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 61.851830, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], 1.473565, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 3.174682, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -141.426270, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.091923, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], -0.042667, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 4.198239, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -6.530584, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, -0.215556, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, -0.976384, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.003121, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=71;Time=133754.427");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_88(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa_uih', 'In/DTI_132225/t1_gre_fsp_3d_sag__132750/00000001.dcm')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "UIH", false);
+  tags.SetValue(0x0020, 0x0013, "1", false);
+  tags.SetValue(0x0018, 0x0080, "7.25", false);
+  tags.SetValue(0x0018, 0x0081, "3.1", false);
+  tags.SetValue(0x0018, 0x1312, "ROW", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "512", false);
+  tags.SetValue(0x0028, 0x0011, "460", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "16", false);
+  tags.SetValue(0x0028, 0x0102, "15", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "132417.803000", false);
+  tags.SetValue(0x0018, 0x0050, "1", false);
+  tags.SetValue(0x0018, 0x0088, "1", false);
+  tags.SetValue(0x0020, 0x0032, "-93.2203293\\-110.039673\\126.103348", false);
+  tags.SetValue(0x0020, 0x0037, "0.0679929852\\0.99768579\\-0\\-0\\-0\\-1", false);
+  tags.SetValue(0x0028, 0x0030, "0.5\\0.5", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_88)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_88(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 460);
+  ASSERT_NEAR(nifti.pixdim[1], 0.500000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 512);
+  ASSERT_NEAR(nifti.pixdim[2], 0.500000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 1);
+  ASSERT_NEAR(nifti.pixdim[3], 1.000000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 235520u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(54));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(54));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(54));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -0.033996, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], 0.997686, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 93.220329, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -0.498843, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.067993, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], 110.039673, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.500000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -129.396652, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.482703, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, -0.516719, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, -0.516719, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=3.1;Time=132417.803");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_89(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa_uih', 'In/DTI_132225/t1_gre_fsp_3d_sag__132750/00000002.dcm')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "UIH", false);
+  tags.SetValue(0x0020, 0x0013, "2", false);
+  tags.SetValue(0x0018, 0x0080, "7.25", false);
+  tags.SetValue(0x0018, 0x0081, "3.1", false);
+  tags.SetValue(0x0018, 0x1312, "ROW", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "512", false);
+  tags.SetValue(0x0028, 0x0011, "460", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "16", false);
+  tags.SetValue(0x0028, 0x0102, "15", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "132417.803000", false);
+  tags.SetValue(0x0018, 0x0050, "1", false);
+  tags.SetValue(0x0018, 0x0088, "1", false);
+  tags.SetValue(0x0020, 0x0032, "-92.222641\\-110.107666\\126.103348", false);
+  tags.SetValue(0x0020, 0x0037, "0.0679929852\\0.99768579\\-0\\-0\\-0\\-1", false);
+  tags.SetValue(0x0028, 0x0030, "0.5\\0.5", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_89)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_89(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 460);
+  ASSERT_NEAR(nifti.pixdim[1], 0.500000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 512);
+  ASSERT_NEAR(nifti.pixdim[2], 0.500000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 1);
+  ASSERT_NEAR(nifti.pixdim[3], 1.000000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 235520u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(54));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(54));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(54));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -0.033996, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], 0.997686, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 92.222641, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -0.498843, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.067993, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], 110.107666, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.500000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -129.396652, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.482703, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, -0.516719, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, -0.516719, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=3.1;Time=132417.803");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_90(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa_uih', 'In/DTI_134434/dti_tra_dir16_AP_SaveBySlc__140028/00000001.dcm')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "UIH", false);
+  tags.SetValue(0x0020, 0x0013, "1", false);
+  tags.SetValue(0x0018, 0x0080, "5865.0", false);
+  tags.SetValue(0x0018, 0x0081, "71.5", false);
+  tags.SetValue(0x0018, 0x1312, "COL", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "256", false);
+  tags.SetValue(0x0028, 0x0011, "320", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "16", false);
+  tags.SetValue(0x0028, 0x0102, "15", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0018, 0x0050, "3.5", false);
+  tags.SetValue(0x0018, 0x0088, "4.19999981", false);
+  tags.SetValue(0x0020, 0x0037, "0.999113142\\0.0305822305\\0.0289414488\\-0.0305694081\\0.999532282\\-0.000885508256", false);
+  tags.SetValue(0x0028, 0x0030, "3.5\\3.5", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135718.843000", false);
+    uih.SetValue(0x0008, 0x0032, "135718.843000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-114.442863\\-112.324295\\-6.39442587", false);
+    uih.SetValue(0x0020, 0x1041, "-3.07805347", false);
+    uih.SetValue(0x0065, 0x100f, "F3.1", false);
+    uih.SetValue(0x0065, 0x1015, "-5.96596575\\3.0485301\\-3.25216055", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135715.917000", false);
+    uih.SetValue(0x0008, 0x0032, "135715.917000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-114.564476\\-112.324295\\-2.19618678", false);
+    uih.SetValue(0x0020, 0x1041, "1.12194777", false);
+    uih.SetValue(0x0065, 0x100f, "H1.1", false);
+    uih.SetValue(0x0065, 0x1015, "-6.08757687\\3.0485301\\0.94607842", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135718.550000", false);
+    uih.SetValue(0x0008, 0x0032, "135718.550000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-114.686089\\-112.324295\\2.00205207", false);
+    uih.SetValue(0x0020, 0x1041, "5.32194901", false);
+    uih.SetValue(0x0065, 0x100f, "H5.3", false);
+    uih.SetValue(0x0065, 0x1015, "-6.20918798\\3.0485301\\5.14431763", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135715.624000", false);
+    uih.SetValue(0x0008, 0x0032, "135715.624000", false);
+    uih.SetValue(0x0018, 0x9073, "22.0", false);
+    uih.SetValue(0x0020, 0x0032, "-114.807701\\-112.324295\\6.20029116", false);
+    uih.SetValue(0x0020, 0x1041, "9.52194977", false);
+    uih.SetValue(0x0065, 0x100f, "H9.5", false);
+    uih.SetValue(0x0065, 0x1015, "-6.33079863\\3.0485301\\9.342556", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135718.258000", false);
+    uih.SetValue(0x0008, 0x0032, "135718.258000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-114.929314\\-112.324295\\10.39853", false);
+    uih.SetValue(0x0020, 0x1041, "13.7219515", false);
+    uih.SetValue(0x0065, 0x100f, "H13.7", false);
+    uih.SetValue(0x0065, 0x1015, "-6.45240974\\3.0485301\\13.5407953", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135715.332000", false);
+    uih.SetValue(0x0008, 0x0032, "135715.332000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-115.050919\\-112.324295\\14.5967693", false);
+    uih.SetValue(0x0020, 0x1041, "17.9219532", false);
+    uih.SetValue(0x0065, 0x100f, "H17.9", false);
+    uih.SetValue(0x0065, 0x1015, "-6.57402086\\3.0485301\\17.7390347", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135717.965000", false);
+    uih.SetValue(0x0008, 0x0032, "135717.965000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-115.172531\\-112.324295\\18.7950077", false);
+    uih.SetValue(0x0020, 0x1041, "22.121954", false);
+    uih.SetValue(0x0065, 0x100f, "H22.1", false);
+    uih.SetValue(0x0065, 0x1015, "-6.6956315\\3.0485301\\21.937273", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135715.039000", false);
+    uih.SetValue(0x0008, 0x0032, "135715.039000", false);
+    uih.SetValue(0x0018, 0x9073, "22.0", false);
+    uih.SetValue(0x0020, 0x0032, "-115.294144\\-112.324295\\22.993248", false);
+    uih.SetValue(0x0020, 0x1041, "26.3219547", false);
+    uih.SetValue(0x0065, 0x100f, "H26.3", false);
+    uih.SetValue(0x0065, 0x1015, "-6.81724262\\3.0485301\\26.1355133", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135717.672000", false);
+    uih.SetValue(0x0008, 0x0032, "135717.672000", false);
+    uih.SetValue(0x0018, 0x9073, "22.0", false);
+    uih.SetValue(0x0020, 0x0032, "-115.415756\\-112.324295\\27.1914864", false);
+    uih.SetValue(0x0020, 0x1041, "30.5219555", false);
+    uih.SetValue(0x0065, 0x100f, "H30.5", false);
+    uih.SetValue(0x0065, 0x1015, "-6.93885374\\3.0485301\\30.3337517", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135714.747000", false);
+    uih.SetValue(0x0008, 0x0032, "135714.747000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-115.537369\\-112.324295\\31.3897247", false);
+    uih.SetValue(0x0020, 0x1041, "34.7219582", false);
+    uih.SetValue(0x0065, 0x100f, "H34.7", false);
+    uih.SetValue(0x0065, 0x1015, "-7.06046438\\3.0485301\\34.5319901", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135717.380000", false);
+    uih.SetValue(0x0008, 0x0032, "135717.380000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-115.658974\\-112.324295\\35.5879631", false);
+    uih.SetValue(0x0020, 0x1041, "38.9219589", false);
+    uih.SetValue(0x0065, 0x100f, "H38.9", false);
+    uih.SetValue(0x0065, 0x1015, "-7.1820755\\3.0485301\\38.7302284", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135714.454000", false);
+    uih.SetValue(0x0008, 0x0032, "135714.454000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-115.780586\\-112.324295\\39.7862015", false);
+    uih.SetValue(0x0020, 0x1041, "43.1219597", false);
+    uih.SetValue(0x0065, 0x100f, "H43.1", false);
+    uih.SetValue(0x0065, 0x1015, "-7.30368662\\3.0485301\\42.9284668", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135717.087000", false);
+    uih.SetValue(0x0008, 0x0032, "135717.087000", false);
+    uih.SetValue(0x0018, 0x9073, "22.0", false);
+    uih.SetValue(0x0020, 0x0032, "-115.902199\\-112.324295\\43.9844437", false);
+    uih.SetValue(0x0020, 0x1041, "47.3219604", false);
+    uih.SetValue(0x0065, 0x100f, "H47.3", false);
+    uih.SetValue(0x0065, 0x1015, "-7.42529726\\3.0485301\\47.126709", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135714.161000", false);
+    uih.SetValue(0x0008, 0x0032, "135714.161000", false);
+    uih.SetValue(0x0018, 0x9073, "22.0", false);
+    uih.SetValue(0x0020, 0x0032, "-116.023811\\-112.324295\\48.182682", false);
+    uih.SetValue(0x0020, 0x1041, "51.5219612", false);
+    uih.SetValue(0x0065, 0x100f, "H51.5", false);
+    uih.SetValue(0x0065, 0x1015, "-7.54690838\\3.0485301\\51.3249474", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135716.795000", false);
+    uih.SetValue(0x0008, 0x0032, "135716.795000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-116.145424\\-112.324295\\52.3809204", false);
+    uih.SetValue(0x0020, 0x1041, "55.721962", false);
+    uih.SetValue(0x0065, 0x100f, "H55.7", false);
+    uih.SetValue(0x0065, 0x1015, "-7.6685195\\3.0485301\\55.5231857", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135713.869000", false);
+    uih.SetValue(0x0008, 0x0032, "135713.869000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-116.267029\\-112.324295\\56.5791588", false);
+    uih.SetValue(0x0020, 0x1041, "59.9219627", false);
+    uih.SetValue(0x0065, 0x100f, "H59.9", false);
+    uih.SetValue(0x0065, 0x1015, "-7.79013014\\3.0485301\\59.7214241", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135716.502000", false);
+    uih.SetValue(0x0008, 0x0032, "135716.502000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-116.388641\\-112.324295\\60.7773972", false);
+    uih.SetValue(0x0020, 0x1041, "64.1219635", false);
+    uih.SetValue(0x0065, 0x100f, "H64.1", false);
+    uih.SetValue(0x0065, 0x1015, "-7.91174126\\3.0485301\\63.9196625", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135713.576000", false);
+    uih.SetValue(0x0008, 0x0032, "135713.576000", false);
+    uih.SetValue(0x0018, 0x9073, "22.0", false);
+    uih.SetValue(0x0020, 0x0032, "-116.510254\\-112.324295\\64.9756393", false);
+    uih.SetValue(0x0020, 0x1041, "68.3219681", false);
+    uih.SetValue(0x0065, 0x100f, "H68.3", false);
+    uih.SetValue(0x0065, 0x1015, "-8.0333519\\3.0485301\\68.1179047", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135716.209000", false);
+    uih.SetValue(0x0008, 0x0032, "135716.209000", false);
+    uih.SetValue(0x0018, 0x9073, "22.0", false);
+    uih.SetValue(0x0020, 0x0032, "-116.631866\\-112.324295\\69.1738739", false);
+    uih.SetValue(0x0020, 0x1041, "72.521965", false);
+    uih.SetValue(0x0065, 0x100f, "H72.5", false);
+    uih.SetValue(0x0065, 0x1015, "-8.15496349\\3.0485301\\72.3161392", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135713.284000", false);
+    uih.SetValue(0x0008, 0x0032, "135713.284000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-116.753471\\-112.324295\\73.3721161", false);
+    uih.SetValue(0x0020, 0x1041, "76.7219696", false);
+    uih.SetValue(0x0065, 0x100f, "H76.7", false);
+    uih.SetValue(0x0065, 0x1015, "-8.27657413\\3.0485301\\76.5143814", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_90)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_90(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.500000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.500000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 20);
+  ASSERT_NEAR(nifti.pixdim[3], 4.200000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 81920u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.496896, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], -0.106993, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], 0.121611, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 121.183418, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -0.107038, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 3.498363, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -108.072556, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.101295, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.003099, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 4.198239, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -6.589681, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.015291, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, -0.999778, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, -0.000221, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=72;Time=135713.284");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_91(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa_uih', 'In/DTI_134434/dti_tra_dir16_AP_SaveBySlc__140028/00000002.dcm')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "UIH", false);
+  tags.SetValue(0x0020, 0x0013, "2", false);
+  tags.SetValue(0x0018, 0x0080, "5865.0", false);
+  tags.SetValue(0x0018, 0x0081, "71.5", false);
+  tags.SetValue(0x0018, 0x1312, "COL", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "256", false);
+  tags.SetValue(0x0028, 0x0011, "320", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "16", false);
+  tags.SetValue(0x0028, 0x0102, "15", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0018, 0x0050, "3.5", false);
+  tags.SetValue(0x0018, 0x0088, "4.19999981", false);
+  tags.SetValue(0x0020, 0x0037, "0.999113142\\0.0305822305\\0.0289414488\\-0.0305694081\\0.999532282\\-0.000885508256", false);
+  tags.SetValue(0x0028, 0x0030, "3.5\\3.5", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135730.546000", false);
+    uih.SetValue(0x0008, 0x0032, "135730.546000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-114.442863\\-112.324295\\-6.39442587", false);
+    uih.SetValue(0x0020, 0x1041, "-3.07805347", false);
+    uih.SetValue(0x0065, 0x100f, "F3.1", false);
+    uih.SetValue(0x0065, 0x1015, "-5.96596575\\3.0485301\\-3.25216055", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135727.620000", false);
+    uih.SetValue(0x0008, 0x0032, "135727.620000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-114.564476\\-112.324295\\-2.19618678", false);
+    uih.SetValue(0x0020, 0x1041, "1.12194777", false);
+    uih.SetValue(0x0065, 0x100f, "H1.1", false);
+    uih.SetValue(0x0065, 0x1015, "-6.08757687\\3.0485301\\0.94607842", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135730.253000", false);
+    uih.SetValue(0x0008, 0x0032, "135730.253000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-114.686089\\-112.324295\\2.00205207", false);
+    uih.SetValue(0x0020, 0x1041, "5.32194901", false);
+    uih.SetValue(0x0065, 0x100f, "H5.3", false);
+    uih.SetValue(0x0065, 0x1015, "-6.20918798\\3.0485301\\5.14431763", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135727.327000", false);
+    uih.SetValue(0x0008, 0x0032, "135727.327000", false);
+    uih.SetValue(0x0018, 0x9073, "22.0", false);
+    uih.SetValue(0x0020, 0x0032, "-114.807701\\-112.324295\\6.20029116", false);
+    uih.SetValue(0x0020, 0x1041, "9.52194977", false);
+    uih.SetValue(0x0065, 0x100f, "H9.5", false);
+    uih.SetValue(0x0065, 0x1015, "-6.33079863\\3.0485301\\9.342556", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135729.960000", false);
+    uih.SetValue(0x0008, 0x0032, "135729.960000", false);
+    uih.SetValue(0x0018, 0x9073, "22.0", false);
+    uih.SetValue(0x0020, 0x0032, "-114.929314\\-112.324295\\10.39853", false);
+    uih.SetValue(0x0020, 0x1041, "13.7219515", false);
+    uih.SetValue(0x0065, 0x100f, "H13.7", false);
+    uih.SetValue(0x0065, 0x1015, "-6.45240974\\3.0485301\\13.5407953", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135727.035000", false);
+    uih.SetValue(0x0008, 0x0032, "135727.035000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-115.050919\\-112.324295\\14.5967693", false);
+    uih.SetValue(0x0020, 0x1041, "17.9219532", false);
+    uih.SetValue(0x0065, 0x100f, "H17.9", false);
+    uih.SetValue(0x0065, 0x1015, "-6.57402086\\3.0485301\\17.7390347", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135729.668000", false);
+    uih.SetValue(0x0008, 0x0032, "135729.668000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-115.172531\\-112.324295\\18.7950077", false);
+    uih.SetValue(0x0020, 0x1041, "22.121954", false);
+    uih.SetValue(0x0065, 0x100f, "H22.1", false);
+    uih.SetValue(0x0065, 0x1015, "-6.6956315\\3.0485301\\21.937273", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135726.742000", false);
+    uih.SetValue(0x0008, 0x0032, "135726.742000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-115.294144\\-112.324295\\22.993248", false);
+    uih.SetValue(0x0020, 0x1041, "26.3219547", false);
+    uih.SetValue(0x0065, 0x100f, "H26.3", false);
+    uih.SetValue(0x0065, 0x1015, "-6.81724262\\3.0485301\\26.1355133", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135729.375000", false);
+    uih.SetValue(0x0008, 0x0032, "135729.375000", false);
+    uih.SetValue(0x0018, 0x9073, "22.0", false);
+    uih.SetValue(0x0020, 0x0032, "-115.415756\\-112.324295\\27.1914864", false);
+    uih.SetValue(0x0020, 0x1041, "30.5219555", false);
+    uih.SetValue(0x0065, 0x100f, "H30.5", false);
+    uih.SetValue(0x0065, 0x1015, "-6.93885374\\3.0485301\\30.3337517", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135726.450000", false);
+    uih.SetValue(0x0008, 0x0032, "135726.450000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-115.537369\\-112.324295\\31.3897247", false);
+    uih.SetValue(0x0020, 0x1041, "34.7219582", false);
+    uih.SetValue(0x0065, 0x100f, "H34.7", false);
+    uih.SetValue(0x0065, 0x1015, "-7.06046438\\3.0485301\\34.5319901", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135729.083000", false);
+    uih.SetValue(0x0008, 0x0032, "135729.083000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-115.658974\\-112.324295\\35.5879631", false);
+    uih.SetValue(0x0020, 0x1041, "38.9219589", false);
+    uih.SetValue(0x0065, 0x100f, "H38.9", false);
+    uih.SetValue(0x0065, 0x1015, "-7.1820755\\3.0485301\\38.7302284", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135726.157000", false);
+    uih.SetValue(0x0008, 0x0032, "135726.157000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-115.780586\\-112.324295\\39.7862015", false);
+    uih.SetValue(0x0020, 0x1041, "43.1219597", false);
+    uih.SetValue(0x0065, 0x100f, "H43.1", false);
+    uih.SetValue(0x0065, 0x1015, "-7.30368662\\3.0485301\\42.9284668", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135728.790000", false);
+    uih.SetValue(0x0008, 0x0032, "135728.790000", false);
+    uih.SetValue(0x0018, 0x9073, "22.0", false);
+    uih.SetValue(0x0020, 0x0032, "-115.902199\\-112.324295\\43.9844437", false);
+    uih.SetValue(0x0020, 0x1041, "47.3219604", false);
+    uih.SetValue(0x0065, 0x100f, "H47.3", false);
+    uih.SetValue(0x0065, 0x1015, "-7.42529726\\3.0485301\\47.126709", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135725.864000", false);
+    uih.SetValue(0x0008, 0x0032, "135725.864000", false);
+    uih.SetValue(0x0018, 0x9073, "22.0", false);
+    uih.SetValue(0x0020, 0x0032, "-116.023811\\-112.324295\\48.182682", false);
+    uih.SetValue(0x0020, 0x1041, "51.5219612", false);
+    uih.SetValue(0x0065, 0x100f, "H51.5", false);
+    uih.SetValue(0x0065, 0x1015, "-7.54690838\\3.0485301\\51.3249474", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135728.498000", false);
+    uih.SetValue(0x0008, 0x0032, "135728.498000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-116.145424\\-112.324295\\52.3809204", false);
+    uih.SetValue(0x0020, 0x1041, "55.721962", false);
+    uih.SetValue(0x0065, 0x100f, "H55.7", false);
+    uih.SetValue(0x0065, 0x1015, "-7.6685195\\3.0485301\\55.5231857", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135725.572000", false);
+    uih.SetValue(0x0008, 0x0032, "135725.572000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-116.267029\\-112.324295\\56.5791588", false);
+    uih.SetValue(0x0020, 0x1041, "59.9219627", false);
+    uih.SetValue(0x0065, 0x100f, "H59.9", false);
+    uih.SetValue(0x0065, 0x1015, "-7.79013014\\3.0485301\\59.7214241", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135728.205000", false);
+    uih.SetValue(0x0008, 0x0032, "135728.205000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-116.388641\\-112.324295\\60.7773972", false);
+    uih.SetValue(0x0020, 0x1041, "64.1219635", false);
+    uih.SetValue(0x0065, 0x100f, "H64.1", false);
+    uih.SetValue(0x0065, 0x1015, "-7.91174126\\3.0485301\\63.9196625", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135725.279000", false);
+    uih.SetValue(0x0008, 0x0032, "135725.279000", false);
+    uih.SetValue(0x0018, 0x9073, "22.0", false);
+    uih.SetValue(0x0020, 0x0032, "-116.510254\\-112.324295\\64.9756393", false);
+    uih.SetValue(0x0020, 0x1041, "68.3219681", false);
+    uih.SetValue(0x0065, 0x100f, "H68.3", false);
+    uih.SetValue(0x0065, 0x1015, "-8.0333519\\3.0485301\\68.1179047", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135727.912000", false);
+    uih.SetValue(0x0008, 0x0032, "135727.912000", false);
+    uih.SetValue(0x0018, 0x9073, "22.0", false);
+    uih.SetValue(0x0020, 0x0032, "-116.631866\\-112.324295\\69.1738739", false);
+    uih.SetValue(0x0020, 0x1041, "72.521965", false);
+    uih.SetValue(0x0065, 0x100f, "H72.5", false);
+    uih.SetValue(0x0065, 0x1015, "-8.15496349\\3.0485301\\72.3161392", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135724.987000", false);
+    uih.SetValue(0x0008, 0x0032, "135724.987000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-116.753471\\-112.324295\\73.3721161", false);
+    uih.SetValue(0x0020, 0x1041, "76.7219696", false);
+    uih.SetValue(0x0065, 0x100f, "H76.7", false);
+    uih.SetValue(0x0065, 0x1015, "-8.27657413\\3.0485301\\76.5143814", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_91)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_91(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.500000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.500000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 20);
+  ASSERT_NEAR(nifti.pixdim[3], 4.200000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 81920u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.496896, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], -0.106993, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], 0.121611, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 121.183418, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -0.107038, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 3.498363, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -108.072556, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.101295, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.003099, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 4.198239, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -6.589681, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.015291, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, -0.999778, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, -0.000221, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=72;Time=135724.987");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_92(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa_uih', 'In/DTI_134434/dti_tra_dir16_PA_SaveBySlc__135612/00000001.dcm')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "UIH", false);
+  tags.SetValue(0x0020, 0x0013, "1", false);
+  tags.SetValue(0x0018, 0x0080, "5865.0", false);
+  tags.SetValue(0x0018, 0x0081, "70.7", false);
+  tags.SetValue(0x0018, 0x1312, "COL", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "256", false);
+  tags.SetValue(0x0028, 0x0011, "320", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "16", false);
+  tags.SetValue(0x0028, 0x0102, "15", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0018, 0x0050, "3.5", false);
+  tags.SetValue(0x0018, 0x0088, "4.19999981", false);
+  tags.SetValue(0x0020, 0x0037, "0.999113142\\0.0305822305\\0.0289414488\\-0.0305694081\\0.999532282\\-0.000885508256", false);
+  tags.SetValue(0x0028, 0x0030, "3.5\\3.5", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135303.242000", false);
+    uih.SetValue(0x0008, 0x0032, "135303.242000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-114.442863\\-112.324295\\-6.39442587", false);
+    uih.SetValue(0x0020, 0x1041, "-3.07805347", false);
+    uih.SetValue(0x0065, 0x100f, "F3.1", false);
+    uih.SetValue(0x0065, 0x1015, "-5.96596575\\3.0485301\\-3.25216055", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135300.316000", false);
+    uih.SetValue(0x0008, 0x0032, "135300.316000", false);
+    uih.SetValue(0x0018, 0x9073, "22.0", false);
+    uih.SetValue(0x0020, 0x0032, "-114.564476\\-112.324295\\-2.19618678", false);
+    uih.SetValue(0x0020, 0x1041, "1.12194777", false);
+    uih.SetValue(0x0065, 0x100f, "H1.1", false);
+    uih.SetValue(0x0065, 0x1015, "-6.08757687\\3.0485301\\0.94607842", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135302.950000", false);
+    uih.SetValue(0x0008, 0x0032, "135302.950000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-114.686089\\-112.324295\\2.00205207", false);
+    uih.SetValue(0x0020, 0x1041, "5.32194901", false);
+    uih.SetValue(0x0065, 0x100f, "H5.3", false);
+    uih.SetValue(0x0065, 0x1015, "-6.20918798\\3.0485301\\5.14431763", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135300.024000", false);
+    uih.SetValue(0x0008, 0x0032, "135300.024000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-114.807701\\-112.324295\\6.20029116", false);
+    uih.SetValue(0x0020, 0x1041, "9.52194977", false);
+    uih.SetValue(0x0065, 0x100f, "H9.5", false);
+    uih.SetValue(0x0065, 0x1015, "-6.33079863\\3.0485301\\9.342556", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135302.657000", false);
+    uih.SetValue(0x0008, 0x0032, "135302.657000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-114.929314\\-112.324295\\10.39853", false);
+    uih.SetValue(0x0020, 0x1041, "13.7219515", false);
+    uih.SetValue(0x0065, 0x100f, "H13.7", false);
+    uih.SetValue(0x0065, 0x1015, "-6.45240974\\3.0485301\\13.5407953", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135259.731000", false);
+    uih.SetValue(0x0008, 0x0032, "135259.731000", false);
+    uih.SetValue(0x0018, 0x9073, "22.0", false);
+    uih.SetValue(0x0020, 0x0032, "-115.050919\\-112.324295\\14.5967693", false);
+    uih.SetValue(0x0020, 0x1041, "17.9219532", false);
+    uih.SetValue(0x0065, 0x100f, "H17.9", false);
+    uih.SetValue(0x0065, 0x1015, "-6.57402086\\3.0485301\\17.7390347", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135302.364000", false);
+    uih.SetValue(0x0008, 0x0032, "135302.364000", false);
+    uih.SetValue(0x0018, 0x9073, "22.0", false);
+    uih.SetValue(0x0020, 0x0032, "-115.172531\\-112.324295\\18.7950077", false);
+    uih.SetValue(0x0020, 0x1041, "22.121954", false);
+    uih.SetValue(0x0065, 0x100f, "H22.1", false);
+    uih.SetValue(0x0065, 0x1015, "-6.6956315\\3.0485301\\21.937273", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135259.439000", false);
+    uih.SetValue(0x0008, 0x0032, "135259.439000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-115.294144\\-112.324295\\22.993248", false);
+    uih.SetValue(0x0020, 0x1041, "26.3219547", false);
+    uih.SetValue(0x0065, 0x100f, "H26.3", false);
+    uih.SetValue(0x0065, 0x1015, "-6.81724262\\3.0485301\\26.1355133", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135302.072000", false);
+    uih.SetValue(0x0008, 0x0032, "135302.072000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-115.415756\\-112.324295\\27.1914864", false);
+    uih.SetValue(0x0020, 0x1041, "30.5219555", false);
+    uih.SetValue(0x0065, 0x100f, "H30.5", false);
+    uih.SetValue(0x0065, 0x1015, "-6.93885374\\3.0485301\\30.3337517", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135259.146000", false);
+    uih.SetValue(0x0008, 0x0032, "135259.146000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-115.537369\\-112.324295\\31.3897247", false);
+    uih.SetValue(0x0020, 0x1041, "34.7219582", false);
+    uih.SetValue(0x0065, 0x100f, "H34.7", false);
+    uih.SetValue(0x0065, 0x1015, "-7.06046438\\3.0485301\\34.5319901", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135301.779000", false);
+    uih.SetValue(0x0008, 0x0032, "135301.779000", false);
+    uih.SetValue(0x0018, 0x9073, "22.0", false);
+    uih.SetValue(0x0020, 0x0032, "-115.658974\\-112.324295\\35.5879631", false);
+    uih.SetValue(0x0020, 0x1041, "38.9219589", false);
+    uih.SetValue(0x0065, 0x100f, "H38.9", false);
+    uih.SetValue(0x0065, 0x1015, "-7.1820755\\3.0485301\\38.7302284", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135258.854000", false);
+    uih.SetValue(0x0008, 0x0032, "135258.854000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-115.780586\\-112.324295\\39.7862015", false);
+    uih.SetValue(0x0020, 0x1041, "43.1219597", false);
+    uih.SetValue(0x0065, 0x100f, "H43.1", false);
+    uih.SetValue(0x0065, 0x1015, "-7.30368662\\3.0485301\\42.9284668", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135301.487000", false);
+    uih.SetValue(0x0008, 0x0032, "135301.487000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-115.902199\\-112.324295\\43.9844437", false);
+    uih.SetValue(0x0020, 0x1041, "47.3219604", false);
+    uih.SetValue(0x0065, 0x100f, "H47.3", false);
+    uih.SetValue(0x0065, 0x1015, "-7.42529726\\3.0485301\\47.126709", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135258.561000", false);
+    uih.SetValue(0x0008, 0x0032, "135258.561000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-116.023811\\-112.324295\\48.182682", false);
+    uih.SetValue(0x0020, 0x1041, "51.5219612", false);
+    uih.SetValue(0x0065, 0x100f, "H51.5", false);
+    uih.SetValue(0x0065, 0x1015, "-7.54690838\\3.0485301\\51.3249474", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135301.194000", false);
+    uih.SetValue(0x0008, 0x0032, "135301.194000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-116.145424\\-112.324295\\52.3809204", false);
+    uih.SetValue(0x0020, 0x1041, "55.721962", false);
+    uih.SetValue(0x0065, 0x100f, "H55.7", false);
+    uih.SetValue(0x0065, 0x1015, "-7.6685195\\3.0485301\\55.5231857", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135258.268000", false);
+    uih.SetValue(0x0008, 0x0032, "135258.268000", false);
+    uih.SetValue(0x0018, 0x9073, "22.0", false);
+    uih.SetValue(0x0020, 0x0032, "-116.267029\\-112.324295\\56.5791588", false);
+    uih.SetValue(0x0020, 0x1041, "59.9219627", false);
+    uih.SetValue(0x0065, 0x100f, "H59.9", false);
+    uih.SetValue(0x0065, 0x1015, "-7.79013014\\3.0485301\\59.7214241", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135300.902000", false);
+    uih.SetValue(0x0008, 0x0032, "135300.902000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-116.388641\\-112.324295\\60.7773972", false);
+    uih.SetValue(0x0020, 0x1041, "64.1219635", false);
+    uih.SetValue(0x0065, 0x100f, "H64.1", false);
+    uih.SetValue(0x0065, 0x1015, "-7.91174126\\3.0485301\\63.9196625", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135257.976000", false);
+    uih.SetValue(0x0008, 0x0032, "135257.976000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-116.510254\\-112.324295\\64.9756393", false);
+    uih.SetValue(0x0020, 0x1041, "68.3219681", false);
+    uih.SetValue(0x0065, 0x100f, "H68.3", false);
+    uih.SetValue(0x0065, 0x1015, "-8.0333519\\3.0485301\\68.1179047", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135300.609000", false);
+    uih.SetValue(0x0008, 0x0032, "135300.609000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-116.631866\\-112.324295\\69.1738739", false);
+    uih.SetValue(0x0020, 0x1041, "72.521965", false);
+    uih.SetValue(0x0065, 0x100f, "H72.5", false);
+    uih.SetValue(0x0065, 0x1015, "-8.15496349\\3.0485301\\72.3161392", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135257.683000", false);
+    uih.SetValue(0x0008, 0x0032, "135257.683000", false);
+    uih.SetValue(0x0018, 0x9073, "22.0", false);
+    uih.SetValue(0x0020, 0x0032, "-116.753471\\-112.324295\\73.3721161", false);
+    uih.SetValue(0x0020, 0x1041, "76.7219696", false);
+    uih.SetValue(0x0065, 0x100f, "H76.7", false);
+    uih.SetValue(0x0065, 0x1015, "-8.27657413\\3.0485301\\76.5143814", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_92)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_92(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.500000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.500000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 20);
+  ASSERT_NEAR(nifti.pixdim[3], 4.200000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 81920u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.496896, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], -0.106993, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], 0.121611, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 121.183418, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -0.107038, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 3.498363, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -108.072556, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.101295, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.003099, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 4.198239, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -6.589681, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.015291, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, -0.999778, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, -0.000221, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=71;Time=135257.683");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_93(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa_uih', 'In/DTI_134434/dti_tra_dir16_PA_SaveBySlc__135612/00000002.dcm')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "UIH", false);
+  tags.SetValue(0x0020, 0x0013, "2", false);
+  tags.SetValue(0x0018, 0x0080, "5865.0", false);
+  tags.SetValue(0x0018, 0x0081, "70.7", false);
+  tags.SetValue(0x0018, 0x1312, "COL", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "256", false);
+  tags.SetValue(0x0028, 0x0011, "320", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "16", false);
+  tags.SetValue(0x0028, 0x0102, "15", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0018, 0x0050, "3.5", false);
+  tags.SetValue(0x0018, 0x0088, "4.19999981", false);
+  tags.SetValue(0x0020, 0x0037, "0.999113142\\0.0305822305\\0.0289414488\\-0.0305694081\\0.999532282\\-0.000885508256", false);
+  tags.SetValue(0x0028, 0x0030, "3.5\\3.5", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135314.945000", false);
+    uih.SetValue(0x0008, 0x0032, "135314.945000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-114.442863\\-112.324295\\-6.39442587", false);
+    uih.SetValue(0x0020, 0x1041, "-3.07805347", false);
+    uih.SetValue(0x0065, 0x100f, "F3.1", false);
+    uih.SetValue(0x0065, 0x1015, "-5.96596575\\3.0485301\\-3.25216055", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135312.019000", false);
+    uih.SetValue(0x0008, 0x0032, "135312.019000", false);
+    uih.SetValue(0x0018, 0x9073, "22.0", false);
+    uih.SetValue(0x0020, 0x0032, "-114.564476\\-112.324295\\-2.19618678", false);
+    uih.SetValue(0x0020, 0x1041, "1.12194777", false);
+    uih.SetValue(0x0065, 0x100f, "H1.1", false);
+    uih.SetValue(0x0065, 0x1015, "-6.08757687\\3.0485301\\0.94607842", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135314.653000", false);
+    uih.SetValue(0x0008, 0x0032, "135314.653000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-114.686089\\-112.324295\\2.00205207", false);
+    uih.SetValue(0x0020, 0x1041, "5.32194901", false);
+    uih.SetValue(0x0065, 0x100f, "H5.3", false);
+    uih.SetValue(0x0065, 0x1015, "-6.20918798\\3.0485301\\5.14431763", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135311.727000", false);
+    uih.SetValue(0x0008, 0x0032, "135311.727000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-114.807701\\-112.324295\\6.20029116", false);
+    uih.SetValue(0x0020, 0x1041, "9.52194977", false);
+    uih.SetValue(0x0065, 0x100f, "H9.5", false);
+    uih.SetValue(0x0065, 0x1015, "-6.33079863\\3.0485301\\9.342556", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135314.360000", false);
+    uih.SetValue(0x0008, 0x0032, "135314.360000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-114.929314\\-112.324295\\10.39853", false);
+    uih.SetValue(0x0020, 0x1041, "13.7219515", false);
+    uih.SetValue(0x0065, 0x100f, "H13.7", false);
+    uih.SetValue(0x0065, 0x1015, "-6.45240974\\3.0485301\\13.5407953", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135311.434000", false);
+    uih.SetValue(0x0008, 0x0032, "135311.434000", false);
+    uih.SetValue(0x0018, 0x9073, "22.0", false);
+    uih.SetValue(0x0020, 0x0032, "-115.050919\\-112.324295\\14.5967693", false);
+    uih.SetValue(0x0020, 0x1041, "17.9219532", false);
+    uih.SetValue(0x0065, 0x100f, "H17.9", false);
+    uih.SetValue(0x0065, 0x1015, "-6.57402086\\3.0485301\\17.7390347", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135314.067000", false);
+    uih.SetValue(0x0008, 0x0032, "135314.067000", false);
+    uih.SetValue(0x0018, 0x9073, "22.0", false);
+    uih.SetValue(0x0020, 0x0032, "-115.172531\\-112.324295\\18.7950077", false);
+    uih.SetValue(0x0020, 0x1041, "22.121954", false);
+    uih.SetValue(0x0065, 0x100f, "H22.1", false);
+    uih.SetValue(0x0065, 0x1015, "-6.6956315\\3.0485301\\21.937273", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135311.142000", false);
+    uih.SetValue(0x0008, 0x0032, "135311.142000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-115.294144\\-112.324295\\22.993248", false);
+    uih.SetValue(0x0020, 0x1041, "26.3219547", false);
+    uih.SetValue(0x0065, 0x100f, "H26.3", false);
+    uih.SetValue(0x0065, 0x1015, "-6.81724262\\3.0485301\\26.1355133", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135313.775000", false);
+    uih.SetValue(0x0008, 0x0032, "135313.775000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-115.415756\\-112.324295\\27.1914864", false);
+    uih.SetValue(0x0020, 0x1041, "30.5219555", false);
+    uih.SetValue(0x0065, 0x100f, "H30.5", false);
+    uih.SetValue(0x0065, 0x1015, "-6.93885374\\3.0485301\\30.3337517", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135310.849000", false);
+    uih.SetValue(0x0008, 0x0032, "135310.849000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-115.537369\\-112.324295\\31.3897247", false);
+    uih.SetValue(0x0020, 0x1041, "34.7219582", false);
+    uih.SetValue(0x0065, 0x100f, "H34.7", false);
+    uih.SetValue(0x0065, 0x1015, "-7.06046438\\3.0485301\\34.5319901", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135313.482000", false);
+    uih.SetValue(0x0008, 0x0032, "135313.482000", false);
+    uih.SetValue(0x0018, 0x9073, "22.0", false);
+    uih.SetValue(0x0020, 0x0032, "-115.658974\\-112.324295\\35.5879631", false);
+    uih.SetValue(0x0020, 0x1041, "38.9219589", false);
+    uih.SetValue(0x0065, 0x100f, "H38.9", false);
+    uih.SetValue(0x0065, 0x1015, "-7.1820755\\3.0485301\\38.7302284", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135310.556000", false);
+    uih.SetValue(0x0008, 0x0032, "135310.556000", false);
+    uih.SetValue(0x0018, 0x9073, "22.0", false);
+    uih.SetValue(0x0020, 0x0032, "-115.780586\\-112.324295\\39.7862015", false);
+    uih.SetValue(0x0020, 0x1041, "43.1219597", false);
+    uih.SetValue(0x0065, 0x100f, "H43.1", false);
+    uih.SetValue(0x0065, 0x1015, "-7.30368662\\3.0485301\\42.9284668", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135313.190000", false);
+    uih.SetValue(0x0008, 0x0032, "135313.190000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-115.902199\\-112.324295\\43.9844437", false);
+    uih.SetValue(0x0020, 0x1041, "47.3219604", false);
+    uih.SetValue(0x0065, 0x100f, "H47.3", false);
+    uih.SetValue(0x0065, 0x1015, "-7.42529726\\3.0485301\\47.126709", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135310.264000", false);
+    uih.SetValue(0x0008, 0x0032, "135310.264000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-116.023811\\-112.324295\\48.182682", false);
+    uih.SetValue(0x0020, 0x1041, "51.5219612", false);
+    uih.SetValue(0x0065, 0x100f, "H51.5", false);
+    uih.SetValue(0x0065, 0x1015, "-7.54690838\\3.0485301\\51.3249474", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135312.897000", false);
+    uih.SetValue(0x0008, 0x0032, "135312.897000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-116.145424\\-112.324295\\52.3809204", false);
+    uih.SetValue(0x0020, 0x1041, "55.721962", false);
+    uih.SetValue(0x0065, 0x100f, "H55.7", false);
+    uih.SetValue(0x0065, 0x1015, "-7.6685195\\3.0485301\\55.5231857", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135309.971000", false);
+    uih.SetValue(0x0008, 0x0032, "135309.971000", false);
+    uih.SetValue(0x0018, 0x9073, "22.0", false);
+    uih.SetValue(0x0020, 0x0032, "-116.267029\\-112.324295\\56.5791588", false);
+    uih.SetValue(0x0020, 0x1041, "59.9219627", false);
+    uih.SetValue(0x0065, 0x100f, "H59.9", false);
+    uih.SetValue(0x0065, 0x1015, "-7.79013014\\3.0485301\\59.7214241", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135312.604000", false);
+    uih.SetValue(0x0008, 0x0032, "135312.604000", false);
+    uih.SetValue(0x0018, 0x9073, "22.0", false);
+    uih.SetValue(0x0020, 0x0032, "-116.388641\\-112.324295\\60.7773972", false);
+    uih.SetValue(0x0020, 0x1041, "64.1219635", false);
+    uih.SetValue(0x0065, 0x100f, "H64.1", false);
+    uih.SetValue(0x0065, 0x1015, "-7.91174126\\3.0485301\\63.9196625", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135309.679000", false);
+    uih.SetValue(0x0008, 0x0032, "135309.679000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-116.510254\\-112.324295\\64.9756393", false);
+    uih.SetValue(0x0020, 0x1041, "68.3219681", false);
+    uih.SetValue(0x0065, 0x100f, "H68.3", false);
+    uih.SetValue(0x0065, 0x1015, "-8.0333519\\3.0485301\\68.1179047", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135312.312000", false);
+    uih.SetValue(0x0008, 0x0032, "135312.312000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-116.631866\\-112.324295\\69.1738739", false);
+    uih.SetValue(0x0020, 0x1041, "72.521965", false);
+    uih.SetValue(0x0065, 0x100f, "H72.5", false);
+    uih.SetValue(0x0065, 0x1015, "-8.15496349\\3.0485301\\72.3161392", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925135309.386000", false);
+    uih.SetValue(0x0008, 0x0032, "135309.386000", false);
+    uih.SetValue(0x0018, 0x9073, "22.0", false);
+    uih.SetValue(0x0020, 0x0032, "-116.753471\\-112.324295\\73.3721161", false);
+    uih.SetValue(0x0020, 0x1041, "76.7219696", false);
+    uih.SetValue(0x0065, 0x100f, "H76.7", false);
+    uih.SetValue(0x0065, 0x1015, "-8.27657413\\3.0485301\\76.5143814", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_93)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_93(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.500000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.500000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 20);
+  ASSERT_NEAR(nifti.pixdim[3], 4.200000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 81920u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.496896, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], -0.106993, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], 0.121611, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 121.183418, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -0.107038, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 3.498363, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -108.072556, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.101295, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.003099, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 4.198239, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -6.589681, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.015291, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, -0.999778, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, -0.000221, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=71;Time=135309.386");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_94(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa_uih', 'In/DTI_134434/dti_tra_dir16_PA_rot_SaveBySlc__140451/00000001.dcm')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "UIH", false);
+  tags.SetValue(0x0020, 0x0013, "1", false);
+  tags.SetValue(0x0018, 0x0080, "5865.0", false);
+  tags.SetValue(0x0018, 0x0081, "70.7", false);
+  tags.SetValue(0x0018, 0x1312, "COL", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "256", false);
+  tags.SetValue(0x0028, 0x0011, "320", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "16", false);
+  tags.SetValue(0x0028, 0x0102, "15", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0018, 0x0050, "3.5", false);
+  tags.SetValue(0x0018, 0x0088, "4.19999981", false);
+  tags.SetValue(0x0020, 0x0037, "0.961447895\\0.27357319\\0.0278503932\\-0.273458511\\0.96185118\\-0.00792131014", false);
+  tags.SetValue(0x0028, 0x0030, "3.5\\3.5", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925140141.741000", false);
+    uih.SetValue(0x0008, 0x0032, "140141.741000", false);
+    uih.SetValue(0x0018, 0x9073, "22.0", false);
+    uih.SetValue(0x0020, 0x0032, "-83.0207825\\-135.319\\-5.48421812", false);
+    uih.SetValue(0x0020, 0x1041, "-3.07805347", false);
+    uih.SetValue(0x0065, 0x100f, "F3.1", false);
+    uih.SetValue(0x0065, 0x1015, "-5.96596575\\3.0485301\\-3.25216055", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925140138.816000", false);
+    uih.SetValue(0x0008, 0x0032, "140138.816000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-83.1423874\\-135.319\\-1.28597891", false);
+    uih.SetValue(0x0020, 0x1041, "1.12194777", false);
+    uih.SetValue(0x0065, 0x100f, "H1.1", false);
+    uih.SetValue(0x0065, 0x1015, "-6.08757687\\3.0485301\\0.94607842", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925140141.449000", false);
+    uih.SetValue(0x0008, 0x0032, "140141.449000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-83.2639999\\-135.319\\2.91226006", false);
+    uih.SetValue(0x0020, 0x1041, "5.32194901", false);
+    uih.SetValue(0x0065, 0x100f, "H5.3", false);
+    uih.SetValue(0x0065, 0x1015, "-6.20918798\\3.0485301\\5.14431763", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925140138.523000", false);
+    uih.SetValue(0x0008, 0x0032, "140138.523000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-83.3856125\\-135.319\\7.11049891", false);
+    uih.SetValue(0x0020, 0x1041, "9.52194977", false);
+    uih.SetValue(0x0065, 0x100f, "H9.5", false);
+    uih.SetValue(0x0065, 0x1015, "-6.33079863\\3.0485301\\9.342556", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925140141.156000", false);
+    uih.SetValue(0x0008, 0x0032, "140141.156000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-83.507225\\-135.319\\11.3087378", false);
+    uih.SetValue(0x0020, 0x1041, "13.7219515", false);
+    uih.SetValue(0x0065, 0x100f, "H13.7", false);
+    uih.SetValue(0x0065, 0x1015, "-6.45240974\\3.0485301\\13.5407953", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925140138.230000", false);
+    uih.SetValue(0x0008, 0x0032, "140138.230000", false);
+    uih.SetValue(0x0018, 0x9073, "22.0", false);
+    uih.SetValue(0x0020, 0x0032, "-83.62883\\-135.319\\15.5069771", false);
+    uih.SetValue(0x0020, 0x1041, "17.9219532", false);
+    uih.SetValue(0x0065, 0x100f, "H17.9", false);
+    uih.SetValue(0x0065, 0x1015, "-6.57402086\\3.0485301\\17.7390347", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925140140.864000", false);
+    uih.SetValue(0x0008, 0x0032, "140140.864000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-83.7504425\\-135.319\\19.7052155", false);
+    uih.SetValue(0x0020, 0x1041, "22.121954", false);
+    uih.SetValue(0x0065, 0x100f, "H22.1", false);
+    uih.SetValue(0x0065, 0x1015, "-6.6956315\\3.0485301\\21.937273", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925140137.938000", false);
+    uih.SetValue(0x0008, 0x0032, "140137.938000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-83.8720551\\-135.319\\23.9034557", false);
+    uih.SetValue(0x0020, 0x1041, "26.3219547", false);
+    uih.SetValue(0x0065, 0x100f, "H26.3", false);
+    uih.SetValue(0x0065, 0x1015, "-6.81724262\\3.0485301\\26.1355133", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925140140.571000", false);
+    uih.SetValue(0x0008, 0x0032, "140140.571000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-83.9936676\\-135.319\\28.1016941", false);
+    uih.SetValue(0x0020, 0x1041, "30.5219555", false);
+    uih.SetValue(0x0065, 0x100f, "H30.5", false);
+    uih.SetValue(0x0065, 0x1015, "-6.93885374\\3.0485301\\30.3337517", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925140137.645000", false);
+    uih.SetValue(0x0008, 0x0032, "140137.645000", false);
+    uih.SetValue(0x0018, 0x9073, "22.0", false);
+    uih.SetValue(0x0020, 0x0032, "-84.1152802\\-135.319\\32.2999344", false);
+    uih.SetValue(0x0020, 0x1041, "34.7219582", false);
+    uih.SetValue(0x0065, 0x100f, "H34.7", false);
+    uih.SetValue(0x0065, 0x1015, "-7.06046438\\3.0485301\\34.5319901", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925140140.278000", false);
+    uih.SetValue(0x0008, 0x0032, "140140.278000", false);
+    uih.SetValue(0x0018, 0x9073, "22.0", false);
+    uih.SetValue(0x0020, 0x0032, "-84.2368851\\-135.319\\36.4981728", false);
+    uih.SetValue(0x0020, 0x1041, "38.9219589", false);
+    uih.SetValue(0x0065, 0x100f, "H38.9", false);
+    uih.SetValue(0x0065, 0x1015, "-7.1820755\\3.0485301\\38.7302284", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925140137.353000", false);
+    uih.SetValue(0x0008, 0x0032, "140137.353000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-84.3584976\\-135.319\\40.6964111", false);
+    uih.SetValue(0x0020, 0x1041, "43.1219597", false);
+    uih.SetValue(0x0065, 0x100f, "H43.1", false);
+    uih.SetValue(0x0065, 0x1015, "-7.30368662\\3.0485301\\42.9284668", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925140139.986000", false);
+    uih.SetValue(0x0008, 0x0032, "140139.986000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-84.4801102\\-135.319\\44.8946495", false);
+    uih.SetValue(0x0020, 0x1041, "47.3219604", false);
+    uih.SetValue(0x0065, 0x100f, "H47.3", false);
+    uih.SetValue(0x0065, 0x1015, "-7.42529726\\3.0485301\\47.126709", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925140137.060000", false);
+    uih.SetValue(0x0008, 0x0032, "140137.060000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-84.6017227\\-135.319\\49.0928879", false);
+    uih.SetValue(0x0020, 0x1041, "51.5219612", false);
+    uih.SetValue(0x0065, 0x100f, "H51.5", false);
+    uih.SetValue(0x0065, 0x1015, "-7.54690838\\3.0485301\\51.3249474", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925140139.693000", false);
+    uih.SetValue(0x0008, 0x0032, "140139.693000", false);
+    uih.SetValue(0x0018, 0x9073, "22.0", false);
+    uih.SetValue(0x0020, 0x0032, "-84.7233353\\-135.319\\53.2911301", false);
+    uih.SetValue(0x0020, 0x1041, "55.721962", false);
+    uih.SetValue(0x0065, 0x100f, "H55.7", false);
+    uih.SetValue(0x0065, 0x1015, "-7.6685195\\3.0485301\\55.5231857", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925140136.768000", false);
+    uih.SetValue(0x0008, 0x0032, "140136.768000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-84.8449402\\-135.319\\57.4893684", false);
+    uih.SetValue(0x0020, 0x1041, "59.9219627", false);
+    uih.SetValue(0x0065, 0x100f, "H59.9", false);
+    uih.SetValue(0x0065, 0x1015, "-7.79013014\\3.0485301\\59.7214241", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925140139.401000", false);
+    uih.SetValue(0x0008, 0x0032, "140139.401000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-84.9665527\\-135.319\\61.6876068", false);
+    uih.SetValue(0x0020, 0x1041, "64.1219635", false);
+    uih.SetValue(0x0065, 0x100f, "H64.1", false);
+    uih.SetValue(0x0065, 0x1015, "-7.91174126\\3.0485301\\63.9196625", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925140136.475000", false);
+    uih.SetValue(0x0008, 0x0032, "140136.475000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-85.0881653\\-135.319\\65.885849", false);
+    uih.SetValue(0x0020, 0x1041, "68.3219681", false);
+    uih.SetValue(0x0065, 0x100f, "H68.3", false);
+    uih.SetValue(0x0065, 0x1015, "-8.0333519\\3.0485301\\68.1179047", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925140139.108000", false);
+    uih.SetValue(0x0008, 0x0032, "140139.108000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-85.2097778\\-135.319\\70.0840836", false);
+    uih.SetValue(0x0020, 0x1041, "72.521965", false);
+    uih.SetValue(0x0065, 0x100f, "H72.5", false);
+    uih.SetValue(0x0065, 0x1015, "-8.15496349\\3.0485301\\72.3161392", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925140136.182000", false);
+    uih.SetValue(0x0008, 0x0032, "140136.182000", false);
+    uih.SetValue(0x0018, 0x9073, "22.0", false);
+    uih.SetValue(0x0020, 0x0032, "-85.3313904\\-135.319\\74.2823257", false);
+    uih.SetValue(0x0020, 0x1041, "76.7219696", false);
+    uih.SetValue(0x0065, 0x100f, "H76.7", false);
+    uih.SetValue(0x0065, 0x1015, "-8.27657413\\3.0485301\\76.5143814", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_94)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_94(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.500000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.500000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 20);
+  ASSERT_NEAR(nifti.pixdim[3], 4.200000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 81920u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.365068, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], -0.957105, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], 0.121611, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 143.318390, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -0.957506, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 3.366479, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -76.769180, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.097476, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.027725, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 4.198239, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -7.230867, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.138096, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, -0.990313, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, -0.002000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=71;Time=140136.182");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_95(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa_uih', 'In/DTI_134434/dti_tra_dir16_PA_rot_SaveBySlc__140451/00000002.dcm')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "UIH", false);
+  tags.SetValue(0x0020, 0x0013, "2", false);
+  tags.SetValue(0x0018, 0x0080, "5865.0", false);
+  tags.SetValue(0x0018, 0x0081, "70.7", false);
+  tags.SetValue(0x0018, 0x1312, "COL", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "256", false);
+  tags.SetValue(0x0028, 0x0011, "320", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "16", false);
+  tags.SetValue(0x0028, 0x0102, "15", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0018, 0x0050, "3.5", false);
+  tags.SetValue(0x0018, 0x0088, "4.19999981", false);
+  tags.SetValue(0x0020, 0x0037, "0.961447895\\0.27357319\\0.0278503932\\-0.273458511\\0.96185118\\-0.00792131014", false);
+  tags.SetValue(0x0028, 0x0030, "3.5\\3.5", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925140153.444000", false);
+    uih.SetValue(0x0008, 0x0032, "140153.444000", false);
+    uih.SetValue(0x0018, 0x9073, "22.0", false);
+    uih.SetValue(0x0020, 0x0032, "-83.0207825\\-135.319\\-5.48421812", false);
+    uih.SetValue(0x0020, 0x1041, "-3.07805347", false);
+    uih.SetValue(0x0065, 0x100f, "F3.1", false);
+    uih.SetValue(0x0065, 0x1015, "-5.96596575\\3.0485301\\-3.25216055", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925140150.518000", false);
+    uih.SetValue(0x0008, 0x0032, "140150.518000", false);
+    uih.SetValue(0x0018, 0x9073, "22.0", false);
+    uih.SetValue(0x0020, 0x0032, "-83.1423874\\-135.319\\-1.28597891", false);
+    uih.SetValue(0x0020, 0x1041, "1.12194777", false);
+    uih.SetValue(0x0065, 0x100f, "H1.1", false);
+    uih.SetValue(0x0065, 0x1015, "-6.08757687\\3.0485301\\0.94607842", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925140153.152000", false);
+    uih.SetValue(0x0008, 0x0032, "140153.152000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-83.2639999\\-135.319\\2.91226006", false);
+    uih.SetValue(0x0020, 0x1041, "5.32194901", false);
+    uih.SetValue(0x0065, 0x100f, "H5.3", false);
+    uih.SetValue(0x0065, 0x1015, "-6.20918798\\3.0485301\\5.14431763", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925140150.226000", false);
+    uih.SetValue(0x0008, 0x0032, "140150.226000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-83.3856125\\-135.319\\7.11049891", false);
+    uih.SetValue(0x0020, 0x1041, "9.52194977", false);
+    uih.SetValue(0x0065, 0x100f, "H9.5", false);
+    uih.SetValue(0x0065, 0x1015, "-6.33079863\\3.0485301\\9.342556", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925140152.859000", false);
+    uih.SetValue(0x0008, 0x0032, "140152.859000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-83.507225\\-135.319\\11.3087378", false);
+    uih.SetValue(0x0020, 0x1041, "13.7219515", false);
+    uih.SetValue(0x0065, 0x100f, "H13.7", false);
+    uih.SetValue(0x0065, 0x1015, "-6.45240974\\3.0485301\\13.5407953", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925140149.933000", false);
+    uih.SetValue(0x0008, 0x0032, "140149.933000", false);
+    uih.SetValue(0x0018, 0x9073, "22.0", false);
+    uih.SetValue(0x0020, 0x0032, "-83.62883\\-135.319\\15.5069771", false);
+    uih.SetValue(0x0020, 0x1041, "17.9219532", false);
+    uih.SetValue(0x0065, 0x100f, "H17.9", false);
+    uih.SetValue(0x0065, 0x1015, "-6.57402086\\3.0485301\\17.7390347", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925140152.567000", false);
+    uih.SetValue(0x0008, 0x0032, "140152.567000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-83.7504425\\-135.319\\19.7052155", false);
+    uih.SetValue(0x0020, 0x1041, "22.121954", false);
+    uih.SetValue(0x0065, 0x100f, "H22.1", false);
+    uih.SetValue(0x0065, 0x1015, "-6.6956315\\3.0485301\\21.937273", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925140149.641000", false);
+    uih.SetValue(0x0008, 0x0032, "140149.641000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-83.8720551\\-135.319\\23.9034557", false);
+    uih.SetValue(0x0020, 0x1041, "26.3219547", false);
+    uih.SetValue(0x0065, 0x100f, "H26.3", false);
+    uih.SetValue(0x0065, 0x1015, "-6.81724262\\3.0485301\\26.1355133", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925140152.274000", false);
+    uih.SetValue(0x0008, 0x0032, "140152.274000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-83.9936676\\-135.319\\28.1016941", false);
+    uih.SetValue(0x0020, 0x1041, "30.5219555", false);
+    uih.SetValue(0x0065, 0x100f, "H30.5", false);
+    uih.SetValue(0x0065, 0x1015, "-6.93885374\\3.0485301\\30.3337517", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925140149.348000", false);
+    uih.SetValue(0x0008, 0x0032, "140149.348000", false);
+    uih.SetValue(0x0018, 0x9073, "22.0", false);
+    uih.SetValue(0x0020, 0x0032, "-84.1152802\\-135.319\\32.2999344", false);
+    uih.SetValue(0x0020, 0x1041, "34.7219582", false);
+    uih.SetValue(0x0065, 0x100f, "H34.7", false);
+    uih.SetValue(0x0065, 0x1015, "-7.06046438\\3.0485301\\34.5319901", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925140151.981000", false);
+    uih.SetValue(0x0008, 0x0032, "140151.981000", false);
+    uih.SetValue(0x0018, 0x9073, "22.0", false);
+    uih.SetValue(0x0020, 0x0032, "-84.2368851\\-135.319\\36.4981728", false);
+    uih.SetValue(0x0020, 0x1041, "38.9219589", false);
+    uih.SetValue(0x0065, 0x100f, "H38.9", false);
+    uih.SetValue(0x0065, 0x1015, "-7.1820755\\3.0485301\\38.7302284", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925140149.056000", false);
+    uih.SetValue(0x0008, 0x0032, "140149.056000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-84.3584976\\-135.319\\40.6964111", false);
+    uih.SetValue(0x0020, 0x1041, "43.1219597", false);
+    uih.SetValue(0x0065, 0x100f, "H43.1", false);
+    uih.SetValue(0x0065, 0x1015, "-7.30368662\\3.0485301\\42.9284668", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925140151.689000", false);
+    uih.SetValue(0x0008, 0x0032, "140151.689000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-84.4801102\\-135.319\\44.8946495", false);
+    uih.SetValue(0x0020, 0x1041, "47.3219604", false);
+    uih.SetValue(0x0065, 0x100f, "H47.3", false);
+    uih.SetValue(0x0065, 0x1015, "-7.42529726\\3.0485301\\47.126709", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925140148.763000", false);
+    uih.SetValue(0x0008, 0x0032, "140148.763000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-84.6017227\\-135.319\\49.0928879", false);
+    uih.SetValue(0x0020, 0x1041, "51.5219612", false);
+    uih.SetValue(0x0065, 0x100f, "H51.5", false);
+    uih.SetValue(0x0065, 0x1015, "-7.54690838\\3.0485301\\51.3249474", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925140151.396000", false);
+    uih.SetValue(0x0008, 0x0032, "140151.396000", false);
+    uih.SetValue(0x0018, 0x9073, "22.0", false);
+    uih.SetValue(0x0020, 0x0032, "-84.7233353\\-135.319\\53.2911301", false);
+    uih.SetValue(0x0020, 0x1041, "55.721962", false);
+    uih.SetValue(0x0065, 0x100f, "H55.7", false);
+    uih.SetValue(0x0065, 0x1015, "-7.6685195\\3.0485301\\55.5231857", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925140148.470000", false);
+    uih.SetValue(0x0008, 0x0032, "140148.470000", false);
+    uih.SetValue(0x0018, 0x9073, "22.0", false);
+    uih.SetValue(0x0020, 0x0032, "-84.8449402\\-135.319\\57.4893684", false);
+    uih.SetValue(0x0020, 0x1041, "59.9219627", false);
+    uih.SetValue(0x0065, 0x100f, "H59.9", false);
+    uih.SetValue(0x0065, 0x1015, "-7.79013014\\3.0485301\\59.7214241", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925140151.104000", false);
+    uih.SetValue(0x0008, 0x0032, "140151.104000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-84.9665527\\-135.319\\61.6876068", false);
+    uih.SetValue(0x0020, 0x1041, "64.1219635", false);
+    uih.SetValue(0x0065, 0x100f, "H64.1", false);
+    uih.SetValue(0x0065, 0x1015, "-7.91174126\\3.0485301\\63.9196625", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925140148.178000", false);
+    uih.SetValue(0x0008, 0x0032, "140148.178000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-85.0881653\\-135.319\\65.885849", false);
+    uih.SetValue(0x0020, 0x1041, "68.3219681", false);
+    uih.SetValue(0x0065, 0x100f, "H68.3", false);
+    uih.SetValue(0x0065, 0x1015, "-8.0333519\\3.0485301\\68.1179047", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925140150.811000", false);
+    uih.SetValue(0x0008, 0x0032, "140150.811000", false);
+    uih.SetValue(0x0018, 0x9073, "21.0", false);
+    uih.SetValue(0x0020, 0x0032, "-85.2097778\\-135.319\\70.0840836", false);
+    uih.SetValue(0x0020, 0x1041, "72.521965", false);
+    uih.SetValue(0x0065, 0x100f, "H72.5", false);
+    uih.SetValue(0x0065, 0x1015, "-8.15496349\\3.0485301\\72.3161392", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  {
+    Orthanc::DicomMap uih;
+    uih.SetValue(0x0008, 0x0022, "20180925", false);
+    uih.SetValue(0x0008, 0x002a, "20180925140147.885000", false);
+    uih.SetValue(0x0008, 0x0032, "140147.885000", false);
+    uih.SetValue(0x0018, 0x9073, "22.0", false);
+    uih.SetValue(0x0020, 0x0032, "-85.3313904\\-135.319\\74.2823257", false);
+    uih.SetValue(0x0020, 0x1041, "76.7219696", false);
+    uih.SetValue(0x0065, 0x100f, "H76.7", false);
+    uih.SetValue(0x0065, 0x1015, "-8.27657413\\3.0485301\\76.5143814", false);
+    uih.SetValue(0x0065, 0x1029, "0.02", false);
+    dicom->AddUIHFrameSequenceItem(uih);
+  }
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_95)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_95(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.500000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.500000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 20);
+  ASSERT_NEAR(nifti.pixdim[3], 4.200000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 81920u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.365068, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], -0.957105, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], 0.121611, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 143.318390, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -0.957506, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 3.366479, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -76.769180, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.097476, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.027725, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 4.198239, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -7.230867, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.138096, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, -0.990313, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, -0.002000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=71;Time=140147.885");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_96(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa_uih', 'In/DTI_134434/t1_gre_fsp_3d_sag__134917/00000001.dcm')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "UIH", false);
+  tags.SetValue(0x0020, 0x0013, "1", false);
+  tags.SetValue(0x0018, 0x0080, "7.25", false);
+  tags.SetValue(0x0018, 0x0081, "3.1", false);
+  tags.SetValue(0x0018, 0x1312, "ROW", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "512", false);
+  tags.SetValue(0x0028, 0x0011, "460", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "16", false);
+  tags.SetValue(0x0028, 0x0102, "15", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "134548.132000", false);
+  tags.SetValue(0x0018, 0x0050, "1", false);
+  tags.SetValue(0x0018, 0x0088, "1", false);
+  tags.SetValue(0x0020, 0x0032, "-90.5559769\\-112.983368\\120.478172", false);
+  tags.SetValue(0x0020, 0x0037, "-0.00459422544\\0.9999879\\0.00176440715\\0.0258876719\\0.00188276928\\-0.999663115", false);
+  tags.SetValue(0x0028, 0x0030, "0.5\\0.5", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_96)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_96(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 460);
+  ASSERT_NEAR(nifti.pixdim[1], 0.500000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 512);
+  ASSERT_NEAR(nifti.pixdim[2], 0.500000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 1);
+  ASSERT_NEAR(nifti.pixdim[3], 1.000000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 235520u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(54));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(54));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(54));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], 0.002297, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.012944, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], 0.999654, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 83.941681, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -0.499994, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 0.000941, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], 0.004547, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], 112.502319, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000882, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.499832, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], -0.025896, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -134.935730, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.494170, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, -0.492796, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, -0.504831, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=3.1;Time=134548.132");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_97(Neuro::DicomInstancesCollection& target)
+{
+  // ('dcm_qa_uih', 'In/DTI_134434/t1_gre_fsp_3d_sag__134917/00000002.dcm')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "UIH", false);
+  tags.SetValue(0x0020, 0x0013, "2", false);
+  tags.SetValue(0x0018, 0x0080, "7.25", false);
+  tags.SetValue(0x0018, 0x0081, "3.1", false);
+  tags.SetValue(0x0018, 0x1312, "ROW", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "512", false);
+  tags.SetValue(0x0028, 0x0011, "460", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "16", false);
+  tags.SetValue(0x0028, 0x0102, "15", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "134548.132000", false);
+  tags.SetValue(0x0018, 0x0050, "1", false);
+  tags.SetValue(0x0018, 0x0088, "1", false);
+  tags.SetValue(0x0020, 0x0032, "-89.5563278\\-112.978821\\120.504074", false);
+  tags.SetValue(0x0020, 0x0037, "-0.00459422544\\0.9999879\\0.00176440715\\0.0258876719\\0.00188276928\\-0.999663115", false);
+  tags.SetValue(0x0028, 0x0030, "0.5\\0.5", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_97)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_97(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 460);
+  ASSERT_NEAR(nifti.pixdim[1], 0.500000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 512);
+  ASSERT_NEAR(nifti.pixdim[2], 0.500000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 1);
+  ASSERT_NEAR(nifti.pixdim[3], 1.000000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 235520u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(54));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(54));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(54));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], 0.002297, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.012944, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], 0.999654, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 82.942032, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -0.499994, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 0.000941, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], 0.004547, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], 112.497772, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000882, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.499832, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], -0.025896, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -134.909836, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.494170, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, -0.492796, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, -0.504831, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=3.1;Time=134548.132");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadInstance_98(Neuro::DicomInstancesCollection& target)
+{
+  // ('heudiconv', 'inputs/rawdata/dicoms/MR.1.3.46.670589.11.38317.5.0.4476.2014042516093364426')
+  Orthanc::DicomMap tags;
+  tags.SetValue(0x0008, 0x0060, "MR", false);
+  tags.SetValue(0x0008, 0x0070, "Philips Medical Systems", false);
+  tags.SetValue(0x0020, 0x0013, "4835", false);
+  tags.SetValue(0x0018, 0x0080, "2000.00061035156", false);
+  tags.SetValue(0x0018, 0x0081, "30", false);
+  tags.SetValue(0x0018, 0x1312, "COL", false);
+  tags.SetValue(0x2005, 0x100e, "0.013238308019936085", false);
+  tags.SetValue(0x0008, 0x0016, "1.2.840.10008.5.1.4.1.1.4", false);
+  tags.SetValue(0x0028, 0x0002, "1", false);
+  tags.SetValue(0x0028, 0x0004, "MONOCHROME2", false);
+  tags.SetValue(0x0028, 0x0010, "80", false);
+  tags.SetValue(0x0028, 0x0011, "80", false);
+  tags.SetValue(0x0028, 0x0100, "16", false);
+  tags.SetValue(0x0028, 0x0101, "12", false);
+  tags.SetValue(0x0028, 0x0102, "11", false);
+  tags.SetValue(0x0028, 0x0103, "0", false);
+  tags.SetValue(0x0008, 0x0032, "160221.19", false);
+  tags.SetValue(0x0018, 0x0050, "3", false);
+  tags.SetValue(0x0018, 0x0088, "3.3", false);
+  tags.SetValue(0x0020, 0x0032, "-120.9212288591\\-120.34524778928\\95.9285560837016", false);
+  tags.SetValue(0x0020, 0x0037, "0.99980485439300\\-0.0114281568676\\0.01611445285379\\0.01479053776711\\0.97376281023025\\-0.2270841896533", false);
+  tags.SetValue(0x0028, 0x0030, "3\\3", false);
+  std::unique_ptr<Neuro::InputDicomInstance> dicom(new Neuro::InputDicomInstance(tags));
+  target.AddInstance(dicom.release(), "nope");
+}
+
+TEST(Autogenerated, Test_98)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadInstance_98(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 80);
+  ASSERT_NEAR(nifti.pixdim[1], 3.000000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 80);
+  ASSERT_NEAR(nifti.pixdim[2], 3.000000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 1);
+  ASSERT_NEAR(nifti.pixdim[3], 3.300000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 6400u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 75.538353);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -2.999414, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.044372, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], 0.043218, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 117.415871, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], 0.034284, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 2.921288, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.750018, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -110.436539, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.048343, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.681253, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 3.213348, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], 42.109604, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.006598, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 0.993392, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.114346, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=160221.190");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadSeries_99(Neuro::DicomInstancesCollection& target)
+{
+  LoadInstance_2(target);
+  LoadInstance_3(target);
+}
+
+TEST(Autogenerated, Test_99)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadSeries_99(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 35);
+  ASSERT_NEAR(nifti.pixdim[3], 3.600000, 0.00001);
+  ASSERT_EQ(nifti.dim[4], 2);
+  ASSERT_NEAR(nifti.pixdim[4], 3.000000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 4);
+  ASSERT_EQ(nifti.dim[0], 4);
+  ASSERT_EQ(nifti.nvox, 286720u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 1);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 104.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 3.230991, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.388798, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -58.684311, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.350998, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 3.578943, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -84.798035, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 0.998537, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.054079, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=134935.305;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadSeries_100(Neuro::DicomInstancesCollection& target)
+{
+  LoadInstance_4(target);
+  LoadInstance_5(target);
+}
+
+TEST(Autogenerated, Test_100)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadSeries_100(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 36);
+  ASSERT_NEAR(nifti.pixdim[3], 3.600000, 0.00001);
+  ASSERT_EQ(nifti.dim[4], 2);
+  ASSERT_NEAR(nifti.pixdim[4], 3.000000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 4);
+  ASSERT_EQ(nifti.dim[0], 4);
+  ASSERT_EQ(nifti.nvox, 294912u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 1);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 104.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 3.230991, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.388798, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -58.489914, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.350998, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 3.578943, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -86.587509, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 0.998537, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.054079, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=135252.445;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadSeries_101(Neuro::DicomInstancesCollection& target)
+{
+  LoadInstance_6(target);
+  LoadInstance_7(target);
+}
+
+TEST(Autogenerated, Test_101)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadSeries_101(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 36);
+  ASSERT_NEAR(nifti.pixdim[3], 3.600000, 0.00001);
+  ASSERT_EQ(nifti.dim[4], 2);
+  ASSERT_NEAR(nifti.pixdim[4], 3.000000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 4);
+  ASSERT_EQ(nifti.dim[0], 4);
+  ASSERT_EQ(nifti.nvox, 294912u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 1);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 104.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 3.230991, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.388798, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -58.489914, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.350998, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 3.578943, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -86.587509, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 0.998537, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.054079, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=135416.225;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadSeries_102(Neuro::DicomInstancesCollection& target)
+{
+  LoadInstance_8(target);
+  LoadInstance_9(target);
+}
+
+TEST(Autogenerated, Test_102)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadSeries_102(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 35);
+  ASSERT_NEAR(nifti.pixdim[3], 3.600000, 0.00001);
+  ASSERT_EQ(nifti.dim[4], 2);
+  ASSERT_NEAR(nifti.pixdim[4], 3.000000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 4);
+  ASSERT_EQ(nifti.dim[0], 4);
+  ASSERT_EQ(nifti.nvox, 286720u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 2);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 104.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 3.230991, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.388798, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -58.684311, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.350998, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 3.578943, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -84.798035, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 0.998537, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.054079, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=135041.527;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadSeries_103(Neuro::DicomInstancesCollection& target)
+{
+  LoadInstance_10(target);
+  LoadInstance_11(target);
+}
+
+TEST(Autogenerated, Test_103)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadSeries_103(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 36);
+  ASSERT_NEAR(nifti.pixdim[3], 3.600000, 0.00001);
+  ASSERT_EQ(nifti.dim[4], 2);
+  ASSERT_NEAR(nifti.pixdim[4], 3.000000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 4);
+  ASSERT_EQ(nifti.dim[0], 4);
+  ASSERT_EQ(nifti.nvox, 294912u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 2);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 104.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 3.230991, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.388798, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -58.489914, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.350998, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 3.578943, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -86.587509, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 0.998537, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.054079, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=135332.235;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadSeries_104(Neuro::DicomInstancesCollection& target)
+{
+  LoadInstance_12(target);
+  LoadInstance_13(target);
+}
+
+TEST(Autogenerated, Test_104)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadSeries_104(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 35);
+  ASSERT_NEAR(nifti.pixdim[3], 3.600000, 0.00001);
+  ASSERT_EQ(nifti.dim[4], 2);
+  ASSERT_NEAR(nifti.pixdim[4], 3.000000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 4);
+  ASSERT_EQ(nifti.dim[0], 4);
+  ASSERT_EQ(nifti.nvox, 286720u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 3);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 104.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 3.230991, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.388798, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -58.684311, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.350998, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 3.578943, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -84.798035, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 0.998537, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.054079, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=135110.870;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadSeries_105(Neuro::DicomInstancesCollection& target)
+{
+  LoadInstance_14(target);
+  LoadInstance_15(target);
+}
+
+TEST(Autogenerated, Test_105)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadSeries_105(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 36);
+  ASSERT_NEAR(nifti.pixdim[3], 3.600000, 0.00001);
+  ASSERT_EQ(nifti.dim[4], 2);
+  ASSERT_NEAR(nifti.pixdim[4], 3.000000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 4);
+  ASSERT_EQ(nifti.dim[0], 4);
+  ASSERT_EQ(nifti.nvox, 294912u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 5);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 104.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 3.230991, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.388798, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -58.489914, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.350998, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 3.578943, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -86.587509, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 0.998537, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.054079, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=135444.723;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadSeries_106(Neuro::DicomInstancesCollection& target)
+{
+  LoadInstance_16(target);
+  LoadInstance_17(target);
+}
+
+TEST(Autogenerated, Test_106)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadSeries_106(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 86);
+  ASSERT_NEAR(nifti.pixdim[1], 2.697675, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 86);
+  ASSERT_NEAR(nifti.pixdim[2], 2.697675, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 36);
+  ASSERT_NEAR(nifti.pixdim[3], 3.600000, 0.00001);
+  ASSERT_EQ(nifti.dim[4], 2);
+  ASSERT_NEAR(nifti.pixdim[4], 3.000000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 4);
+  ASSERT_EQ(nifti.dim[0], 4);
+  ASSERT_EQ(nifti.nvox, 532512u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 1);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -2.697675, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 115.999977, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 2.654202, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.643688, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -58.807571, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.482350, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 3.541986, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -93.139343, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 0.995963, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.089763, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=34;Time=140149.418;phase=1;mb=2");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadSeries_107(Neuro::DicomInstancesCollection& target)
+{
+  LoadInstance_18(target);
+  LoadInstance_19(target);
+}
+
+TEST(Autogenerated, Test_107)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadSeries_107(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 35);
+  ASSERT_NEAR(nifti.pixdim[3], 3.600000, 0.00001);
+  ASSERT_EQ(nifti.dim[4], 2);
+  ASSERT_NEAR(nifti.pixdim[4], 3.000000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 4);
+  ASSERT_EQ(nifti.dim[0], 4);
+  ASSERT_EQ(nifti.nvox, 286720u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(54));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(54));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(54));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 1);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 104.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], -0.497204, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -3.557622, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], 148.532135, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 3.211742, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], -0.550749, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -92.380424, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 0.650774, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.759271, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=135701.908;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadSeries_108(Neuro::DicomInstancesCollection& target)
+{
+  LoadInstance_20(target);
+  LoadInstance_21(target);
+}
+
+TEST(Autogenerated, Test_108)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadSeries_108(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 36);
+  ASSERT_NEAR(nifti.pixdim[3], 3.600000, 0.00001);
+  ASSERT_EQ(nifti.dim[4], 2);
+  ASSERT_NEAR(nifti.pixdim[4], 3.000000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 4);
+  ASSERT_EQ(nifti.dim[0], 4);
+  ASSERT_EQ(nifti.nvox, 294912u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(54));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(54));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(54));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 1);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 104.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], -0.497204, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -3.557622, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], 150.310944, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 3.211742, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], -0.550749, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -92.105034, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 0.650774, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.759271, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=135534.370;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadSeries_109(Neuro::DicomInstancesCollection& target)
+{
+  LoadInstance_22(target);
+  LoadInstance_23(target);
+}
+
+TEST(Autogenerated, Test_109)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadSeries_109(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 35);
+  ASSERT_NEAR(nifti.pixdim[3], 3.600000, 0.00001);
+  ASSERT_EQ(nifti.dim[4], 2);
+  ASSERT_NEAR(nifti.pixdim[4], 3.000000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 4);
+  ASSERT_EQ(nifti.dim[0], 4);
+  ASSERT_EQ(nifti.nvox, 286720u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(54));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(54));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(54));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 2);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 104.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], -0.497204, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -3.557622, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], 148.532135, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 3.211742, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], -0.550749, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -92.380424, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 0.650774, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.759271, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=135734.797;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadSeries_110(Neuro::DicomInstancesCollection& target)
+{
+  LoadInstance_24(target);
+  LoadInstance_25(target);
+}
+
+TEST(Autogenerated, Test_110)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadSeries_110(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 36);
+  ASSERT_NEAR(nifti.pixdim[3], 3.600000, 0.00001);
+  ASSERT_EQ(nifti.dim[4], 2);
+  ASSERT_NEAR(nifti.pixdim[4], 3.000000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 4);
+  ASSERT_EQ(nifti.dim[0], 4);
+  ASSERT_EQ(nifti.nvox, 294912u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(54));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(54));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(54));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 2);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 104.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], -0.497204, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -3.557622, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], 150.310944, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 3.211742, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], -0.550749, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -92.105034, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 0.650774, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.759271, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=135604.842;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadSeries_111(Neuro::DicomInstancesCollection& target)
+{
+  LoadInstance_26(target);
+  LoadInstance_27(target);
+}
+
+TEST(Autogenerated, Test_111)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadSeries_111(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 35);
+  ASSERT_NEAR(nifti.pixdim[3], 3.600000, 0.00001);
+  ASSERT_EQ(nifti.dim[4], 2);
+  ASSERT_NEAR(nifti.pixdim[4], 3.000000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 4);
+  ASSERT_EQ(nifti.dim[0], 4);
+  ASSERT_EQ(nifti.nvox, 286720u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(54));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(54));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(54));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 3);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 104.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], -0.497204, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -3.557622, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], 148.532135, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 3.211742, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], -0.550749, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -92.380424, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 0.650774, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.759271, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=135801.485;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadSeries_112(Neuro::DicomInstancesCollection& target)
+{
+  LoadInstance_28(target);
+  LoadInstance_29(target);
+}
+
+TEST(Autogenerated, Test_112)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadSeries_112(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 36);
+  ASSERT_NEAR(nifti.pixdim[3], 3.600000, 0.00001);
+  ASSERT_EQ(nifti.dim[4], 2);
+  ASSERT_NEAR(nifti.pixdim[4], 3.000000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 4);
+  ASSERT_EQ(nifti.dim[0], 4);
+  ASSERT_EQ(nifti.nvox, 294912u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(54));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(54));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(54));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 5);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 104.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], -0.497204, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -3.557622, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], 150.310944, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 3.211742, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], -0.550749, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -92.105034, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 0.650774, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.759271, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=135633.508;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadSeries_113(Neuro::DicomInstancesCollection& target)
+{
+  LoadInstance_30(target);
+  LoadInstance_31(target);
+}
+
+TEST(Autogenerated, Test_113)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadSeries_113(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 35);
+  ASSERT_NEAR(nifti.pixdim[3], 3.600000, 0.00001);
+  ASSERT_EQ(nifti.dim[4], 2);
+  ASSERT_NEAR(nifti.pixdim[4], 3.000000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 4);
+  ASSERT_EQ(nifti.dim[0], 4);
+  ASSERT_EQ(nifti.nvox, 286720u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(54));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(54));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(54));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 1);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -3.600000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 61.200001, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], 140.319641, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -126.173706, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.500000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, -0.500000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, -0.500000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=140000.990;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadSeries_114(Neuro::DicomInstancesCollection& target)
+{
+  LoadInstance_32(target);
+  LoadInstance_33(target);
+}
+
+TEST(Autogenerated, Test_114)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadSeries_114(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 36);
+  ASSERT_NEAR(nifti.pixdim[3], 3.600000, 0.00001);
+  ASSERT_EQ(nifti.dim[4], 2);
+  ASSERT_NEAR(nifti.pixdim[4], 3.000000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 4);
+  ASSERT_EQ(nifti.dim[0], 4);
+  ASSERT_EQ(nifti.nvox, 294912u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(54));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(54));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(54));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 1);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -3.600000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 63.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], 140.319641, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -126.173706, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.500000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, -0.500000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, -0.500000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=135835.760;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadSeries_115(Neuro::DicomInstancesCollection& target)
+{
+  LoadInstance_34(target);
+  LoadInstance_35(target);
+}
+
+TEST(Autogenerated, Test_115)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadSeries_115(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 35);
+  ASSERT_NEAR(nifti.pixdim[3], 3.600000, 0.00001);
+  ASSERT_EQ(nifti.dim[4], 2);
+  ASSERT_NEAR(nifti.pixdim[4], 3.000000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 4);
+  ASSERT_EQ(nifti.dim[0], 4);
+  ASSERT_EQ(nifti.nvox, 286720u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(54));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(54));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(54));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 2);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -3.600000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 61.200001, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], 140.319641, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -126.173706, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.500000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, -0.500000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, -0.500000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=140034.397;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadSeries_116(Neuro::DicomInstancesCollection& target)
+{
+  LoadInstance_36(target);
+  LoadInstance_37(target);
+}
+
+TEST(Autogenerated, Test_116)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadSeries_116(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 36);
+  ASSERT_NEAR(nifti.pixdim[3], 3.600000, 0.00001);
+  ASSERT_EQ(nifti.dim[4], 2);
+  ASSERT_NEAR(nifti.pixdim[4], 3.000000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 4);
+  ASSERT_EQ(nifti.dim[0], 4);
+  ASSERT_EQ(nifti.nvox, 294912u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(54));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(54));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(54));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 2);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -3.600000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 63.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], 140.319641, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -126.173706, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.500000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, -0.500000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, -0.500000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=135905.272;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadSeries_117(Neuro::DicomInstancesCollection& target)
+{
+  LoadInstance_38(target);
+  LoadInstance_39(target);
+}
+
+TEST(Autogenerated, Test_117)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadSeries_117(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 35);
+  ASSERT_NEAR(nifti.pixdim[3], 3.600000, 0.00001);
+  ASSERT_EQ(nifti.dim[4], 2);
+  ASSERT_NEAR(nifti.pixdim[4], 3.000000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 4);
+  ASSERT_EQ(nifti.dim[0], 4);
+  ASSERT_EQ(nifti.nvox, 286720u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(54));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(54));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(54));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 3);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -3.600000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 61.200001, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], 140.319641, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -126.173706, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.500000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, -0.500000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, -0.500000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=140105.875;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadSeries_118(Neuro::DicomInstancesCollection& target)
+{
+  LoadInstance_40(target);
+  LoadInstance_41(target);
+}
+
+TEST(Autogenerated, Test_118)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadSeries_118(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.250000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 36);
+  ASSERT_NEAR(nifti.pixdim[3], 3.600000, 0.00001);
+  ASSERT_EQ(nifti.dim[4], 2);
+  ASSERT_NEAR(nifti.pixdim[4], 3.000000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 4);
+  ASSERT_EQ(nifti.dim[0], 4);
+  ASSERT_EQ(nifti.nvox, 294912u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(54));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(54));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(54));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 5);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -3.600000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 63.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], 140.319641, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 3.250000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -126.173706, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.500000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, -0.500000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, -0.500000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=135931.837;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadSeries_119(Neuro::DicomInstancesCollection& target)
+{
+  LoadInstance_70(target);
+  LoadInstance_71(target);
+}
+
+TEST(Autogenerated, Test_119)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadSeries_119(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.750000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.750000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 2);
+  ASSERT_NEAR(nifti.pixdim[3], 5.000000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 8192u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 4);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.750000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 118.125000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 3.750000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -92.906006, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 5.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -17.687500, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 1.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.000000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=114815.000");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadSeries_120(Neuro::DicomInstancesCollection& target)
+{
+  LoadInstance_72(target);
+  LoadInstance_73(target);
+}
+
+TEST(Autogenerated, Test_120)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadSeries_120(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.750000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.750000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 2);
+  ASSERT_NEAR(nifti.pixdim[3], 5.000000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 8192u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 4);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.750000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 118.125000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 3.750000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -92.906006, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 5.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -17.687500, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 1.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.000000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=115224.000");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadSeries_121(Neuro::DicomInstancesCollection& target)
+{
+  LoadInstance_74(target);
+  LoadInstance_75(target);
+}
+
+TEST(Autogenerated, Test_121)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadSeries_121(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.750000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.750000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 2);
+  ASSERT_NEAR(nifti.pixdim[3], 5.000000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 8192u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 4);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.750000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 118.125000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 3.750000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -92.906006, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 5.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], 47.312500, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 1.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.000000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=115418.000");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadSeries_122(Neuro::DicomInstancesCollection& target)
+{
+  LoadInstance_76(target);
+  LoadInstance_77(target);
+}
+
+TEST(Autogenerated, Test_122)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadSeries_122(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.750000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.750000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 2);
+  ASSERT_NEAR(nifti.pixdim[3], 5.000000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 8192u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 4);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.750000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 118.125000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 3.750000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -92.906006, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 5.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], 47.312500, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 1.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.000000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=30;Time=115551.000");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadSeries_123(Neuro::DicomInstancesCollection& target)
+{
+  LoadInstance_78(target);
+  LoadInstance_79(target);
+}
+
+TEST(Autogenerated, Test_123)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadSeries_123(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 72);
+  ASSERT_NEAR(nifti.pixdim[1], 3.000000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 72);
+  ASSERT_NEAR(nifti.pixdim[2], 3.000000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 5);
+  ASSERT_NEAR(nifti.pixdim[3], 12.000000, 0.00001);
+  ASSERT_EQ(nifti.dim[4], 2);
+  ASSERT_NEAR(nifti.pixdim[4], 2.435370, 0.00001);
+  ASSERT_EQ(nifti.ndim, 4);
+  ASSERT_EQ(nifti.dim[0], 4);
+  ASSERT_EQ(nifti.nvox, 51840u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 3);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 108.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 3.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -105.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 12.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], 16.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 1.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.000000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=50;Time=122458.102;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadSeries_124(Neuro::DicomInstancesCollection& target)
+{
+  LoadInstance_80(target);
+  LoadInstance_81(target);
+}
+
+TEST(Autogenerated, Test_124)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadSeries_124(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 72);
+  ASSERT_NEAR(nifti.pixdim[1], 3.000000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 72);
+  ASSERT_NEAR(nifti.pixdim[2], 3.000000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 5);
+  ASSERT_NEAR(nifti.pixdim[3], 12.000000, 0.00001);
+  ASSERT_EQ(nifti.dim[4], 2);
+  ASSERT_NEAR(nifti.pixdim[4], 2.435370, 0.00001);
+  ASSERT_EQ(nifti.ndim, 4);
+  ASSERT_EQ(nifti.dim[0], 4);
+  ASSERT_EQ(nifti.nvox, 51840u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 3);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 108.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 3.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -105.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 12.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], 16.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 1.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.000000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=50;Time=122654.518;phase=0");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadSeries_125(Neuro::DicomInstancesCollection& target)
+{
+  LoadInstance_82(target);
+  LoadInstance_83(target);
+}
+
+TEST(Autogenerated, Test_125)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadSeries_125(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 72);
+  ASSERT_NEAR(nifti.pixdim[1], 3.000000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 72);
+  ASSERT_NEAR(nifti.pixdim[2], 3.000000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 5);
+  ASSERT_NEAR(nifti.pixdim[3], 12.000000, 0.00001);
+  ASSERT_EQ(nifti.dim[4], 2);
+  ASSERT_NEAR(nifti.pixdim[4], 2.435370, 0.00001);
+  ASSERT_EQ(nifti.ndim, 4);
+  ASSERT_EQ(nifti.dim[0], 4);
+  ASSERT_EQ(nifti.nvox, 51840u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(54));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(54));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(54));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 3);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 108.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 3.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -105.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 12.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], 16.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 1.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.000000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=50;Time=122812.085;phase=1");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadSeries_126(Neuro::DicomInstancesCollection& target)
+{
+  LoadInstance_84(target);
+  LoadInstance_85(target);
+}
+
+TEST(Autogenerated, Test_126)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadSeries_126(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 72);
+  ASSERT_NEAR(nifti.pixdim[1], 3.000000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 72);
+  ASSERT_NEAR(nifti.pixdim[2], 3.000000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 5);
+  ASSERT_NEAR(nifti.pixdim[3], 12.000000, 0.00001);
+  ASSERT_EQ(nifti.dim[4], 2);
+  ASSERT_NEAR(nifti.pixdim[4], 2.435370, 0.00001);
+  ASSERT_EQ(nifti.ndim, 4);
+  ASSERT_EQ(nifti.dim[0], 4);
+  ASSERT_EQ(nifti.nvox, 51840u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(54));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(54));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(54));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 3);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 108.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 3.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -105.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 12.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], 16.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 1.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.000000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=50;Time=123002.285;phase=0");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadSeries_127(Neuro::DicomInstancesCollection& target)
+{
+  LoadInstance_86(target);
+  LoadInstance_87(target);
+}
+
+TEST(Autogenerated, Test_127)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadSeries_127(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.500000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.500000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 20);
+  ASSERT_NEAR(nifti.pixdim[3], 4.200000, 0.00001);
+  ASSERT_EQ(nifti.dim[4], 2);
+  ASSERT_NEAR(nifti.pixdim[4], 5.865000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 4);
+  ASSERT_EQ(nifti.dim[0], 4);
+  ASSERT_EQ(nifti.nvox, 163840u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.173351, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 1.472947, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], 0.121611, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 61.851830, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], 1.473565, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 3.174682, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -141.426270, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.091923, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], -0.042667, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 4.198239, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -6.530584, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, -0.215556, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, -0.976384, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.003121, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=71;Time=133742.724");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadSeries_128(Neuro::DicomInstancesCollection& target)
+{
+  LoadInstance_88(target);
+  LoadInstance_89(target);
+}
+
+TEST(Autogenerated, DISABLED_Test_128)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadSeries_128(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 2);
+  ASSERT_NEAR(nifti.pixdim[1], 1.000000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 460);
+  ASSERT_NEAR(nifti.pixdim[2], 0.500000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 512);
+  ASSERT_NEAR(nifti.pixdim[3], 0.500000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 471040u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(54));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(54));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(54));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], 0.997688, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], 0.033996, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 76.618248, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -0.067993, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 0.498843, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -118.861221, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 0.500000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -129.396652, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.034016, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=3.1;Time=132417.803");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadSeries_129(Neuro::DicomInstancesCollection& target)
+{
+  LoadInstance_90(target);
+  LoadInstance_91(target);
+}
+
+TEST(Autogenerated, Test_129)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadSeries_129(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.500000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.500000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 20);
+  ASSERT_NEAR(nifti.pixdim[3], 4.200000, 0.00001);
+  ASSERT_EQ(nifti.dim[4], 2);
+  ASSERT_NEAR(nifti.pixdim[4], 5.865000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 4);
+  ASSERT_EQ(nifti.dim[0], 4);
+  ASSERT_EQ(nifti.nvox, 163840u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.496896, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], -0.106993, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], 0.121611, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 121.183418, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -0.107038, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 3.498363, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -108.072556, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.101295, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.003099, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 4.198239, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -6.589681, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.015291, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, -0.999778, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, -0.000221, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=72;Time=135713.284");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadSeries_130(Neuro::DicomInstancesCollection& target)
+{
+  LoadInstance_92(target);
+  LoadInstance_93(target);
+}
+
+TEST(Autogenerated, Test_130)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadSeries_130(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.500000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.500000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 20);
+  ASSERT_NEAR(nifti.pixdim[3], 4.200000, 0.00001);
+  ASSERT_EQ(nifti.dim[4], 2);
+  ASSERT_NEAR(nifti.pixdim[4], 5.865000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 4);
+  ASSERT_EQ(nifti.dim[0], 4);
+  ASSERT_EQ(nifti.nvox, 163840u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.496896, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], -0.106993, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], 0.121611, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 121.183418, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -0.107038, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 3.498363, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], 0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -108.072556, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.101295, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.003099, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 4.198239, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -6.589681, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.015291, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, -0.999778, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, -0.000221, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=71;Time=135257.683");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadSeries_131(Neuro::DicomInstancesCollection& target)
+{
+  LoadInstance_94(target);
+  LoadInstance_95(target);
+}
+
+TEST(Autogenerated, Test_131)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadSeries_131(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 64);
+  ASSERT_NEAR(nifti.pixdim[1], 3.500000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 64);
+  ASSERT_NEAR(nifti.pixdim[2], 3.500000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 20);
+  ASSERT_NEAR(nifti.pixdim[3], 4.200000, 0.00001);
+  ASSERT_EQ(nifti.dim[4], 2);
+  ASSERT_NEAR(nifti.pixdim[4], 5.865000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 4);
+  ASSERT_EQ(nifti.dim[0], 4);
+  ASSERT_EQ(nifti.nvox, 163840u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(57));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(57));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(57));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], -1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], -3.365068, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], -0.957105, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], 0.121611, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 143.318390, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], -0.957506, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 3.366479, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], -0.000000, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -76.769180, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], 0.097476, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], 0.027725, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 4.198239, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -7.230867, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, 0.138096, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, -0.990313, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, -0.002000, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=71;Time=140136.182");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
+
+static void LoadSeries_132(Neuro::DicomInstancesCollection& target)
+{
+  LoadInstance_96(target);
+  LoadInstance_97(target);
+}
+
+TEST(Autogenerated, DISABLED_Test_132)
+{
+  Neuro::DicomInstancesCollection instances;
+  LoadSeries_132(instances);
+  nifti_image nifti;
+  std::vector<Neuro::Slice> slices;
+  instances.CreateNiftiHeader(nifti, slices);
+  CheckConsistency(nifti);
+  ASSERT_EQ(nifti.dim[1], 2);
+  ASSERT_NEAR(nifti.pixdim[1], 1.000000, 0.00001);
+  ASSERT_EQ(nifti.dim[2], 460);
+  ASSERT_NEAR(nifti.pixdim[2], 0.500000, 0.00001);
+  ASSERT_EQ(nifti.dim[3], 512);
+  ASSERT_NEAR(nifti.pixdim[3], 0.500000, 0.00001);
+  ASSERT_EQ(nifti.ndim, 3);
+  ASSERT_EQ(nifti.dim[0], 3);
+  ASSERT_EQ(nifti.nvox, 471040u);
+  ASSERT_EQ(nifti.nbyper, 2);
+  ASSERT_EQ(nifti.datatype, 512);
+  ASSERT_EQ(nifti.freq_dim, DIM_INFO_TO_FREQ_DIM(54));
+  ASSERT_EQ(nifti.phase_dim, DIM_INFO_TO_PHASE_DIM(54));
+  ASSERT_EQ(nifti.slice_dim, DIM_INFO_TO_SLICE_DIM(54));
+  ASSERT_EQ(nifti.intent_code, 0);
+  ASSERT_EQ(nifti.slice_start, 0);
+  ASSERT_EQ(nifti.slice_end, 0);
+  ASSERT_EQ(nifti.slice_code, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p1, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p2, 0);
+  ASSERT_FLOAT_EQ(nifti.intent_p3, 0);
+  ASSERT_FLOAT_EQ(nifti.pixdim[0], 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_slope, 1.000000);
+  ASSERT_FLOAT_EQ(nifti.scl_inter, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_min, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.cal_max, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.slice_duration, 0.000000);
+  ASSERT_FLOAT_EQ(nifti.toffset, 0.000000);
+  ASSERT_EQ(nifti.qform_code, 1);
+  ASSERT_EQ(nifti.sform_code, 1);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][0], 0.999649, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][1], -0.002297, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][2], 0.012944, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[0][3], 83.996399, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][0], 0.004547, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][1], 0.499994, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][2], 0.000941, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[1][3], -116.999451, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][0], -0.025896, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][1], -0.000882, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][2], 0.499832, 0.0001);
+  ASSERT_NEAR(nifti.sto_xyz.m[2][3], -134.504913, 0.0001);
+  ASSERT_NEAR(nifti.quatern_b, -0.000912, 0.0001);
+  ASSERT_NEAR(nifti.quatern_c, 0.012947, 0.0001);
+  ASSERT_NEAR(nifti.quatern_d, 0.002286, 0.0001);
+  ASSERT_STREQ(nifti.descrip, "TE=3.1;Time=134548.132");
+  ASSERT_STREQ(nifti.intent_name, "");
+  if (nifti.ndim == 3)
+    ASSERT_EQ(nifti.nz, static_cast<int>(slices.size()));
+  else
+    ASSERT_EQ(nifti.nz * nifti.nt, static_cast<int>(slices.size()));
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Sources/UnitTestsSources/UnitTestsMain.cpp	Fri Apr 22 17:58:59 2022 +0200
@@ -0,0 +1,36 @@
+/**
+ * Neuroimaging plugin for Orthanc
+ * Copyright (C) 2021-2022 Sebastien Jodogne, UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include <gtest/gtest.h>
+
+#include <Logging.h>
+
+
+int main(int argc, char **argv)
+{
+  Orthanc::Logging::Initialize();
+  Orthanc::Logging::EnableInfoLevel(true);
+
+  ::testing::InitGoogleTest(&argc, argv);
+  int result = RUN_ALL_TESTS();
+
+  Orthanc::Logging::Finalize();
+
+  return result;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/TODO	Fri Apr 22 17:58:59 2022 +0200
@@ -0,0 +1,5 @@
+Roadmap for the neuroimaging plugin for Orthanc
+===============================================
+
+* BIDS support
+