changeset 0:02f7a0400a91

initial commit
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 25 Feb 2015 13:45:35 +0100
parents
children 828c61fc8253
files AUTHORS CMakeLists.txt COPYING NEWS Orthanc/ChunkedBuffer.cpp Orthanc/ChunkedBuffer.h Orthanc/Enumerations.cpp Orthanc/Enumerations.h Orthanc/FileStorage/FilesystemStorage.cpp Orthanc/FileStorage/FilesystemStorage.h Orthanc/IDynamicObject.h Orthanc/ImageFormats/ImageAccessor.cpp Orthanc/ImageFormats/ImageAccessor.h Orthanc/ImageFormats/ImageBuffer.cpp Orthanc/ImageFormats/ImageBuffer.h Orthanc/ImageFormats/ImageProcessing.cpp Orthanc/ImageFormats/ImageProcessing.h Orthanc/ImageFormats/PngWriter.cpp Orthanc/ImageFormats/PngWriter.h Orthanc/MultiThreading/SharedMessageQueue.cpp Orthanc/MultiThreading/SharedMessageQueue.h Orthanc/OrthancException.cpp Orthanc/OrthancException.h Orthanc/PrecompiledHeaders.cpp Orthanc/PrecompiledHeaders.h Orthanc/README.txt Orthanc/SQLite/Connection.cpp Orthanc/SQLite/Connection.h Orthanc/SQLite/FunctionContext.cpp Orthanc/SQLite/FunctionContext.h Orthanc/SQLite/IScalarFunction.h Orthanc/SQLite/ITransaction.h Orthanc/SQLite/NonCopyable.h Orthanc/SQLite/OrthancSQLiteException.h Orthanc/SQLite/README.txt Orthanc/SQLite/Statement.cpp Orthanc/SQLite/Statement.h Orthanc/SQLite/StatementId.cpp Orthanc/SQLite/StatementId.h Orthanc/SQLite/StatementReference.cpp Orthanc/SQLite/StatementReference.h Orthanc/SQLite/Transaction.cpp Orthanc/SQLite/Transaction.h Orthanc/Toolbox.cpp Orthanc/Toolbox.h Orthanc/Uuid.cpp Orthanc/Uuid.h Plugin/Cache/CacheIndex.h Plugin/Cache/CacheManager.cpp Plugin/Cache/CacheManager.h Plugin/Cache/CacheScheduler.cpp Plugin/Cache/CacheScheduler.h Plugin/Cache/ICacheFactory.h Plugin/Cache/IPrefetchPolicy.h Plugin/DecodedImageAdapter.cpp Plugin/DecodedImageAdapter.h Plugin/InstanceInformation.cpp Plugin/InstanceInformation.h Plugin/InstanceInformationAdapter.cpp Plugin/InstanceInformationAdapter.h Plugin/JpegWriter.cpp Plugin/JpegWriter.h Plugin/ParsedDicomImage.cpp Plugin/ParsedDicomImage.h Plugin/Plugin.cpp Plugin/SeriesInformationAdapter.cpp Plugin/SeriesInformationAdapter.h Plugin/SeriesVolumeSorter.cpp Plugin/SeriesVolumeSorter.h Plugin/ViewerPrefetchPolicy.cpp Plugin/ViewerPrefetchPolicy.h Plugin/ViewerToolbox.cpp Plugin/ViewerToolbox.h README Resources/BuildInstructions.txt Resources/CMake/AutoGeneratedCode.cmake Resources/CMake/BoostConfiguration.cmake Resources/CMake/DownloadPackage.cmake Resources/CMake/GdcmConfiguration.cmake Resources/CMake/GoogleTestConfiguration.cmake Resources/CMake/JavaScriptLibraries.cmake Resources/CMake/JsonCppConfiguration.cmake Resources/CMake/LibJpegConfiguration.cmake Resources/CMake/LibPngConfiguration.cmake Resources/CMake/SQLiteConfiguration.cmake Resources/CMake/ZlibConfiguration.cmake Resources/EmbedResources.py Resources/ImplementationNotes.txt Resources/MinGWToolchain.cmake Resources/OrthancExplorer.js Resources/ThirdParty/base64/base64.cpp Resources/ThirdParty/base64/base64.h Resources/VersionScript.map UnitTestsSources/UnitTestsMain.cpp WebApplication/images/bone.png WebApplication/images/default.png WebApplication/images/interpolation.png WebApplication/images/inversion.png WebApplication/images/lung.png WebApplication/images/orthanc-icon.png WebApplication/images/orthanc-logo.png WebApplication/images/stretch.png WebApplication/jpeg-decoder.js WebApplication/viewer.css WebApplication/viewer.html WebApplication/viewer.js
diffstat 106 files changed, 13902 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/AUTHORS	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,13 @@
+Web Viewer plugin for Orthanc
+=============================
+
+
+Authors
+-------
+
+* Sebastien Jodogne <s.jodogne@gmail.com>
+  Department of Medical Physics
+  University Hospital of Liege
+  Belgium
+
+  Overall design and lead developer.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CMakeLists.txt	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,194 @@
+# Orthanc - A Lightweight, RESTful DICOM Store
+# Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+# Department, University Hospital of Liege, Belgium
+#
+# This program is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Affero 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
+# Affero General Public License for more details.
+# 
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+cmake_minimum_required(VERSION 2.8)
+
+project(OrthancPostgreSQL)
+
+# Parameters of the build
+set(STATIC_BUILD OFF CACHE BOOL "Static build of the third-party libraries (necessary for Windows)")
+SET(STANDALONE_BUILD ON CACHE BOOL "Standalone build (all the resources are embedded, necessary for releases)")
+set(ALLOW_DOWNLOADS OFF CACHE BOOL "Allow CMake to download packages")
+
+# Advanced parameters to fine-tune linking against system libraries
+set(USE_SYSTEM_BOOST ON CACHE BOOL "Use the system version of Boost")
+set(USE_SYSTEM_GDCM ON CACHE BOOL "Use the system version of Grassroot DICOM (GDCM)")
+set(USE_SYSTEM_GOOGLE_TEST ON CACHE BOOL "Use the system version of Google Test")
+set(USE_SYSTEM_JSONCPP ON CACHE BOOL "Use the system version of JsonCpp")
+set(USE_SYSTEM_LIBJPEG ON CACHE BOOL "Use the system version of libjpeg")
+set(USE_SYSTEM_LIBPNG ON CACHE BOOL "Use the system version of libpng")
+set(USE_SYSTEM_ZLIB ON CACHE BOOL "Use the system version of zlib")
+set(USE_SYSTEM_SQLITE ON CACHE BOOL "Use the system version of SQLite")
+
+# Distribution-specific settings
+set(USE_GTEST_DEBIAN_SOURCE_PACKAGE OFF CACHE BOOL "Use the sources of Google Test shipped with libgtest-dev (Debian only)")
+mark_as_advanced(USE_GTEST_DEBIAN_SOURCE_PACKAGE)
+
+# Force static build when cross-compiling
+if (CMAKE_CROSSCOMPILING)
+  set(STATIC_BUILD ON)
+  set(STANDALONE_BUILD ON)
+endif()
+
+if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
+  SET(OS_LIBRARIES uuid rt dl)
+  SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pthread")
+  SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -pthread")
+elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
+  SET(OS_LIBRARIES rpcrt4 ws2_32 secur32)
+  if (CMAKE_COMPILER_IS_GNUCXX)
+    SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static-libgcc -static-libstdc++")
+    SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -static-libgcc -static-libstdc++")
+  endif()
+endif ()
+
+if (CMAKE_COMPILER_IS_GNUCXX)
+  SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--version-script=${CMAKE_SOURCE_DIR}/Resources/VersionScript.map -Wl,--no-undefined")
+endif()
+
+
+include(CheckIncludeFiles)
+include(CheckIncludeFileCXX)
+include(CheckLibraryExists)
+include(${CMAKE_SOURCE_DIR}/Resources/CMake/AutoGeneratedCode.cmake)
+include(${CMAKE_SOURCE_DIR}/Resources/CMake/DownloadPackage.cmake)
+
+include(${CMAKE_SOURCE_DIR}/Resources/CMake/BoostConfiguration.cmake)
+include(${CMAKE_SOURCE_DIR}/Resources/CMake/GdcmConfiguration.cmake)
+include(${CMAKE_SOURCE_DIR}/Resources/CMake/GoogleTestConfiguration.cmake)
+include(${CMAKE_SOURCE_DIR}/Resources/CMake/JsonCppConfiguration.cmake)
+include(${CMAKE_SOURCE_DIR}/Resources/CMake/LibJpegConfiguration.cmake)
+include(${CMAKE_SOURCE_DIR}/Resources/CMake/LibPngConfiguration.cmake)
+include(${CMAKE_SOURCE_DIR}/Resources/CMake/ZlibConfiguration.cmake)
+include(${CMAKE_SOURCE_DIR}/Resources/CMake/SQLiteConfiguration.cmake)
+
+include(${CMAKE_SOURCE_DIR}/Resources/CMake/JavaScriptLibraries.cmake)
+
+
+# Check that the Orthanc SDK headers are available or download them
+if (STATIC_BUILD)
+  set(ORTHANC_SDK_URL "http://orthanc.googlecode.com/hg") # TODO use 0.8.6 release
+  file(MAKE_DIRECTORY ${AUTOGENERATED_DIR}/orthanc)
+  file(DOWNLOAD "${ORTHANC_SDK_URL}/Plugins/Include/OrthancCPlugin.h"
+    "${AUTOGENERATED_DIR}/orthanc/OrthancCPlugin.h" SHOW_PROGRESS)
+  if (${MSVC})
+    add_definitions(-D_CRT_SECURE_NO_WARNINGS=1)
+    file(DOWNLOAD "${ORTHANC_SDK_URL}/Resources/ThirdParty/VisualStudio/stdint.h" 
+      "${AUTOGENERATED_DIR}/stdint.h" SHOW_PROGRESS)
+  endif()
+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()
+
+
+if (STANDALONE_BUILD)
+  add_definitions(
+    -DORTHANC_STANDALONE=1
+    )
+  set(EMBEDDED_RESOURCES
+    WEB_VIEWER  ${CMAKE_SOURCE_DIR}/WebApplication
+    )
+else()
+  add_definitions(
+    -DORTHANC_STANDALONE=0
+    -DWEB_VIEWER_PATH="${CMAKE_SOURCE_DIR}/WebApplication/"
+    )
+endif()
+
+EmbedResources(
+  ORTHANC_EXPLORER  ${CMAKE_SOURCE_DIR}/Resources/OrthancExplorer.js
+  JAVASCRIPT_LIBS   ${JAVASCRIPT_LIBS_DIR}
+  ${EMBEDDED_RESOURCES}
+  )
+
+add_definitions(
+  -DORTHANC_SQLITE_STANDALONE=1
+  )
+
+set(CORE_SOURCES
+  ${BOOST_SOURCES}
+  ${JSONCPP_SOURCES}
+  ${SQLITE_SOURCES}
+  ${LIBJPEG_SOURCES}
+  ${ZLIB_SOURCES}
+  ${LIBPNG_SOURCES}
+
+  # Sources inherited from Orthanc core
+  ${CMAKE_SOURCE_DIR}/Orthanc/ChunkedBuffer.cpp
+  ${CMAKE_SOURCE_DIR}/Orthanc/Enumerations.cpp
+  ${CMAKE_SOURCE_DIR}/Orthanc/FileStorage/FilesystemStorage.cpp
+  ${CMAKE_SOURCE_DIR}/Orthanc/ImageFormats/ImageAccessor.cpp
+  ${CMAKE_SOURCE_DIR}/Orthanc/ImageFormats/ImageBuffer.cpp
+  ${CMAKE_SOURCE_DIR}/Orthanc/ImageFormats/ImageProcessing.cpp
+  ${CMAKE_SOURCE_DIR}/Orthanc/ImageFormats/PngWriter.cpp
+  ${CMAKE_SOURCE_DIR}/Orthanc/MultiThreading/SharedMessageQueue.cpp
+  ${CMAKE_SOURCE_DIR}/Orthanc/OrthancException.cpp
+  ${CMAKE_SOURCE_DIR}/Orthanc/SQLite/Connection.cpp
+  ${CMAKE_SOURCE_DIR}/Orthanc/SQLite/FunctionContext.cpp
+  ${CMAKE_SOURCE_DIR}/Orthanc/SQLite/Statement.cpp
+  ${CMAKE_SOURCE_DIR}/Orthanc/SQLite/StatementId.cpp
+  ${CMAKE_SOURCE_DIR}/Orthanc/SQLite/StatementReference.cpp
+  ${CMAKE_SOURCE_DIR}/Orthanc/SQLite/Transaction.cpp
+  ${CMAKE_SOURCE_DIR}/Orthanc/Toolbox.cpp
+  ${CMAKE_SOURCE_DIR}/Orthanc/Uuid.cpp
+  ${CMAKE_SOURCE_DIR}/Resources/ThirdParty/base64/base64.cpp
+
+  ${CMAKE_SOURCE_DIR}/Plugin/Cache/CacheManager.cpp
+  ${CMAKE_SOURCE_DIR}/Plugin/Cache/CacheScheduler.cpp
+  ${CMAKE_SOURCE_DIR}/Plugin/JpegWriter.cpp
+  ${CMAKE_SOURCE_DIR}/Plugin/ViewerToolbox.cpp
+  ${CMAKE_SOURCE_DIR}/Plugin/SeriesVolumeSorter.cpp
+  ${CMAKE_SOURCE_DIR}/Plugin/ViewerPrefetchPolicy.cpp
+  ${CMAKE_SOURCE_DIR}/Plugin/InstanceInformation.cpp
+  ${CMAKE_SOURCE_DIR}/Plugin/InstanceInformationAdapter.cpp
+  ${CMAKE_SOURCE_DIR}/Plugin/SeriesInformationAdapter.cpp
+  )
+
+add_library(OrthancWebViewer
+  SHARED
+  ${CORE_SOURCES}
+  ${AUTOGENERATED_SOURCES}
+  ${CMAKE_SOURCE_DIR}/Plugin/Plugin.cpp
+
+  # The following files depend on GDCM
+  ${CMAKE_SOURCE_DIR}/Plugin/ParsedDicomImage.cpp
+  ${CMAKE_SOURCE_DIR}/Plugin/DecodedImageAdapter.cpp
+  )
+
+if (STATIC_BUILD OR NOT USE_SYSTEM_GDCM)
+  add_dependencies(OrthancWebViewer GDCM)
+endif()
+
+target_link_libraries(OrthancWebViewer ${GDCM_LIBRARIES} ${OS_LIBRARIES})
+
+install(
+    TARGETS OrthancWebViewer
+    RUNTIME DESTINATION lib    # Destination for Windows
+    LIBRARY DESTINATION share/orthanc/plugins    # Destination for Linux
+    )
+
+add_executable(UnitTests
+  ${CORE_SOURCES}
+  ${GTEST_SOURCES}
+  UnitTestsSources/UnitTestsMain.cpp
+  )
+
+target_link_libraries(UnitTests ${OS_LIBRARIES})
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/COPYING	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,661 @@
+                    GNU AFFERO GENERAL PUBLIC LICENSE
+                       Version 3, 19 November 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 Affero General Public License is a free, copyleft license for
+software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+our General Public Licenses are 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.
+
+  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.
+
+  Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+  A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate.  Many developers of free software are heartened and
+encouraged by the resulting cooperation.  However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+  The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community.  It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server.  Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+  An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals.  This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.
+
+  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 Affero 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. Remote Network Interaction; Use with the GNU General Public License.
+
+  Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your version
+supports such interaction) an opportunity to receive the Corresponding
+Source of your version by providing access to the Corresponding Source
+from a network server at no charge, through some standard or customary
+means of facilitating copying of software.  This Corresponding Source
+shall include the Corresponding Source for any work covered by version 3
+of the GNU General Public License that is incorporated pursuant to the
+following paragraph.
+
+  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 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 work with which it is combined will remain governed by version
+3 of the GNU General Public License.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU Affero 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 Affero 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 Affero 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 Affero 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 Affero 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 Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero 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 your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source.  For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code.  There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+  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 AGPL, see
+<http://www.gnu.org/licenses/>.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/NEWS	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,9 @@
+Pending changes in the mainline
+===============================
+
+
+
+2015-02-25
+==========
+
+* Initial release
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Orthanc/ChunkedBuffer.cpp	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,99 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, 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.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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 "PrecompiledHeaders.h"
+#include "ChunkedBuffer.h"
+
+#include <cassert>
+#include <string.h>
+
+
+namespace Orthanc
+{
+  void ChunkedBuffer::Clear()
+  {
+    numBytes_ = 0;
+
+    for (Chunks::iterator it = chunks_.begin(); 
+         it != chunks_.end(); ++it)
+    {
+      delete *it;
+    }
+  }
+
+
+  void ChunkedBuffer::AddChunk(const char* chunkData,
+                               size_t chunkSize)
+  {
+    if (chunkSize == 0)
+    {
+      return;
+    }
+
+    assert(chunkData != NULL);
+    chunks_.push_back(new std::string(chunkData, chunkSize));
+    numBytes_ += chunkSize;
+  }
+
+
+  void ChunkedBuffer::AddChunk(const std::string& chunk)
+  {
+    if (chunk.size() > 0)
+    {
+      AddChunk(&chunk[0], chunk.size());
+    }
+  }
+
+
+  void ChunkedBuffer::Flatten(std::string& result)
+  {
+    result.resize(numBytes_);
+
+    size_t pos = 0;
+    for (Chunks::iterator it = chunks_.begin(); 
+         it != chunks_.end(); ++it)
+    {
+      assert(*it != NULL);
+
+      size_t s = (*it)->size();
+      if (s != 0)
+      {
+        memcpy(&result[pos], (*it)->c_str(), s);
+        pos += s;
+      }
+
+      delete *it;
+    }
+
+    chunks_.clear();
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Orthanc/ChunkedBuffer.h	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,71 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, 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.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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 <list>
+#include <string>
+
+namespace Orthanc
+{
+  class ChunkedBuffer
+  {
+  private:
+    typedef std::list<std::string*>  Chunks;
+    size_t numBytes_;
+    Chunks chunks_;
+  
+    void Clear();
+
+  public:
+    ChunkedBuffer() : numBytes_(0)
+    {
+    }
+
+    ~ChunkedBuffer()
+    {
+      Clear();
+    }
+
+    size_t GetNumBytes() const
+    {
+      return numBytes_;
+    }
+
+    void AddChunk(const char* chunkData,
+                  size_t chunkSize);
+
+    void AddChunk(const std::string& chunk);
+
+    void Flatten(std::string& result);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Orthanc/Enumerations.cpp	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,61 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, 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.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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 "PrecompiledHeaders.h"
+#include "Enumerations.h"
+
+#include "OrthancException.h"
+
+namespace Orthanc
+{
+  unsigned int GetBytesPerPixel(PixelFormat format)
+  {
+    switch (format)
+    {
+      case PixelFormat_Grayscale8:
+        return 1;
+
+      case PixelFormat_Grayscale16:
+      case PixelFormat_SignedGrayscale16:
+        return 2;
+
+      case PixelFormat_RGB24:
+        return 3;
+
+      case PixelFormat_RGBA32:
+        return 4;
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Orthanc/Enumerations.h	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,119 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, 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.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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 Orthanc
+{
+  enum Endianness
+  {
+    Endianness_Unknown,
+    Endianness_Big,
+    Endianness_Little
+  };
+
+  enum ErrorCode
+  {
+    // Generic error codes
+    ErrorCode_Success,
+    ErrorCode_Custom,
+    ErrorCode_InternalError,
+    ErrorCode_NotImplemented,
+    ErrorCode_ParameterOutOfRange,
+    ErrorCode_NotEnoughMemory,
+    ErrorCode_BadParameterType,
+    ErrorCode_BadSequenceOfCalls,
+    ErrorCode_InexistentItem,
+    ErrorCode_BadRequest,
+    ErrorCode_NetworkProtocol,
+    ErrorCode_SystemCommand,
+    ErrorCode_Database,
+
+    // Specific error codes
+    ErrorCode_UriSyntax,
+    ErrorCode_InexistentFile,
+    ErrorCode_CannotWriteFile,
+    ErrorCode_BadFileFormat,
+    ErrorCode_Timeout,
+    ErrorCode_UnknownResource,
+    ErrorCode_IncompatibleDatabaseVersion,
+    ErrorCode_FullStorage,
+    ErrorCode_CorruptedFile,
+    ErrorCode_InexistentTag,
+    ErrorCode_ReadOnly,
+    ErrorCode_IncompatibleImageFormat,
+    ErrorCode_IncompatibleImageSize,
+    ErrorCode_SharedLibrary,
+    ErrorCode_Plugin
+  };
+
+  /**
+   * {summary}{The memory layout of the pixels (resp. voxels) of a 2D (resp. 3D) image.}
+   **/
+  enum PixelFormat
+  {
+    /**
+     * {summary}{Color image in RGB24 format.}
+     * {description}{This format describes a color image. The pixels are stored in 3
+     * consecutive bytes. The memory layout is RGB.}
+     **/
+    PixelFormat_RGB24 = 1,
+
+    /**
+     * {summary}{Color image in RGBA32 format.}
+     * {description}{This format describes a color image. The pixels are stored in 4
+     * consecutive bytes. The memory layout is RGBA.}
+     **/
+    PixelFormat_RGBA32 = 2,
+
+    /**
+     * {summary}{Graylevel 8bpp image.}
+     * {description}{The image is graylevel. Each pixel is unsigned and stored in one byte.}
+     **/
+    PixelFormat_Grayscale8 = 3,
+      
+    /**
+     * {summary}{Graylevel, unsigned 16bpp image.}
+     * {description}{The image is graylevel. Each pixel is unsigned and stored in two bytes.}
+     **/
+    PixelFormat_Grayscale16 = 4,
+      
+    /**
+     * {summary}{Graylevel, signed 16bpp image.}
+     * {description}{The image is graylevel. Each pixel is signed and stored in two bytes.}
+     **/
+    PixelFormat_SignedGrayscale16 = 5
+  };
+
+
+  unsigned int GetBytesPerPixel(PixelFormat format);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Orthanc/FileStorage/FilesystemStorage.cpp	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,254 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, 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.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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 "../PrecompiledHeaders.h"
+#include "FilesystemStorage.h"
+
+// http://stackoverflow.com/questions/1576272/storing-large-number-of-files-in-file-system
+// http://stackoverflow.com/questions/446358/storing-a-large-number-of-images
+
+#include "../OrthancException.h"
+#include "../Toolbox.h"
+#include "../Uuid.h"
+
+#include <boost/filesystem/fstream.hpp>
+
+static std::string ToString(const boost::filesystem::path& p)
+{
+#if BOOST_HAS_FILESYSTEM_V3 == 1
+  return p.filename().string();
+#else
+  return p.filename();
+#endif
+}
+
+
+namespace Orthanc
+{
+  boost::filesystem::path FilesystemStorage::GetPath(const std::string& uuid) const
+  {
+    namespace fs = boost::filesystem;
+
+    if (!Toolbox::IsUuid(uuid))
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    fs::path path = root_;
+
+    path /= std::string(&uuid[0], &uuid[2]);
+    path /= std::string(&uuid[2], &uuid[4]);
+    path /= uuid;
+
+#if BOOST_HAS_FILESYSTEM_V3 == 1
+    path.make_preferred();
+#endif
+
+    return path;
+  }
+
+  FilesystemStorage::FilesystemStorage(std::string root)
+  {
+    //root_ = boost::filesystem::absolute(root).string();
+    root_ = root;
+
+    Toolbox::CreateNewDirectory(root);
+  }
+
+  void FilesystemStorage::Create(const std::string& uuid,
+                                 const void* content, 
+                                 size_t size)
+  {
+    boost::filesystem::path path;
+    
+    path = GetPath(uuid);
+
+    if (boost::filesystem::exists(path))
+    {
+      // Extremely unlikely case: This Uuid has already been created
+      // in the past.
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
+    if (boost::filesystem::exists(path.parent_path()))
+    {
+      if (!boost::filesystem::is_directory(path.parent_path()))
+      {
+        throw OrthancException("The subdirectory to be created is already occupied by a regular file");        
+      }
+    }
+    else
+    {
+      if (!boost::filesystem::create_directories(path.parent_path()))
+      {
+        throw OrthancException("Unable to create a subdirectory in the file storage");        
+      }
+    }
+
+    boost::filesystem::ofstream f;
+    f.open(path, std::ofstream::out | std::ios::binary);
+    if (!f.good())
+    {
+      throw OrthancException("Unable to create a new file in the file storage");
+    }
+
+    if (size != 0)
+    {
+      f.write(static_cast<const char*>(content), size);
+      if (!f.good())
+      {
+        f.close();
+        throw OrthancException("Unable to write to the new file in the file storage");
+      }
+    }
+
+    f.close();
+  }
+
+
+  void FilesystemStorage::Read(std::string& content,
+                               const std::string& uuid)
+  {
+    content.clear();
+    Toolbox::ReadFile(content, GetPath(uuid).string());
+  }
+
+
+  uintmax_t FilesystemStorage::GetSize(const std::string& uuid) const
+  {
+    boost::filesystem::path path = GetPath(uuid);
+    return boost::filesystem::file_size(path);
+  }
+
+
+
+  void FilesystemStorage::ListAllFiles(std::set<std::string>& result) const
+  {
+    namespace fs = boost::filesystem;
+
+    result.clear();
+
+    if (fs::exists(root_) && fs::is_directory(root_))
+    {
+      for (fs::recursive_directory_iterator current(root_), end; current != end ; ++current)
+      {
+        if (fs::is_regular_file(current->status()))
+        {
+          try
+          {
+            fs::path d = current->path();
+            std::string uuid = ToString(d);
+            if (Toolbox::IsUuid(uuid))
+            {
+              fs::path p0 = d.parent_path().parent_path().parent_path();
+              std::string p1 = ToString(d.parent_path().parent_path());
+              std::string p2 = ToString(d.parent_path());
+              if (p1.length() == 2 &&
+                  p2.length() == 2 &&
+                  p1 == uuid.substr(0, 2) &&
+                  p2 == uuid.substr(2, 2) &&
+                  p0 == root_)
+              {
+                result.insert(uuid);
+              }
+            }
+          }
+          catch (fs::filesystem_error)
+          {
+          }
+        }
+      }
+    }
+  }
+
+
+  void FilesystemStorage::Clear()
+  {
+    namespace fs = boost::filesystem;
+    typedef std::set<std::string> List;
+
+    List result;
+    ListAllFiles(result);
+
+    for (List::const_iterator it = result.begin(); it != result.end(); ++it)
+    {
+      Remove(*it);
+    }
+  }
+
+
+  void FilesystemStorage::Remove(const std::string& uuid)
+  {
+    namespace fs = boost::filesystem;
+
+    fs::path p = GetPath(uuid);
+
+    try
+    {
+      fs::remove(p);
+    }
+    catch (...)
+    {
+      // Ignore the error
+    }
+
+    // Remove the two parent directories, ignoring the error code if
+    // these directories are not empty
+
+    try
+    {
+#if BOOST_HAS_FILESYSTEM_V3 == 1
+      boost::system::error_code err;
+      fs::remove(p.parent_path(), err);
+      fs::remove(p.parent_path().parent_path(), err);
+#else
+      fs::remove(p.parent_path());
+      fs::remove(p.parent_path().parent_path());
+#endif
+    }
+    catch (...)
+    {
+      // Ignore the error
+    }
+  }
+
+
+  uintmax_t FilesystemStorage::GetCapacity() const
+  {
+    return boost::filesystem::space(root_).capacity;
+  }
+
+  uintmax_t FilesystemStorage::GetAvailableSpace() const
+  {
+    return boost::filesystem::space(root_).available;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Orthanc/FileStorage/FilesystemStorage.h	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,69 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, 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.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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/filesystem.hpp>
+#include <set>
+
+namespace Orthanc
+{
+  class FilesystemStorage : public boost::noncopyable
+  {
+  private:
+    boost::filesystem::path root_;
+
+    boost::filesystem::path GetPath(const std::string& uuid) const;
+
+  public:
+    FilesystemStorage(std::string root);
+
+    virtual void Create(const std::string& uuid,
+                        const void* content, 
+                        size_t size);
+
+    virtual void Read(std::string& content,
+                      const std::string& uuid);
+
+    virtual void Remove(const std::string& uuid);
+
+    void ListAllFiles(std::set<std::string>& result) const;
+
+    uintmax_t GetSize(const std::string& uuid) const;
+
+    void Clear();
+
+    uintmax_t GetCapacity() const;
+
+    uintmax_t GetAvailableSpace() const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Orthanc/IDynamicObject.h	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,52 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, 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.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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>
+
+namespace Orthanc
+{
+  /**
+   * This class should be the ancestor to any class whose type is
+   * determined at the runtime, and that can be dynamically allocated.
+   * Being a child of IDynamicObject only implies the existence of a
+   * virtual destructor.
+   **/
+  class IDynamicObject : public boost::noncopyable
+  {
+  public:
+    virtual ~IDynamicObject()
+    {
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Orthanc/ImageFormats/ImageAccessor.cpp	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,129 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, 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.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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 "../PrecompiledHeaders.h"
+#include "ImageAccessor.h"
+
+#include "../OrthancException.h"
+
+#include <stdint.h>
+#include <cassert>
+#include <boost/lexical_cast.hpp>
+
+namespace Orthanc
+{
+  void* ImageAccessor::GetBuffer() const
+  {
+    if (readOnly_)
+    {
+      throw OrthancException(ErrorCode_ReadOnly);
+    }
+
+    return buffer_;
+  }
+
+
+  const void* ImageAccessor::GetConstRow(unsigned int y) const
+  {
+    if (buffer_ != NULL)
+    {
+      return reinterpret_cast<const uint8_t*>(buffer_) + y * pitch_;
+    }
+    else
+    {
+      return NULL;
+    }
+  }
+
+
+  void* ImageAccessor::GetRow(unsigned int y) const
+  {
+    if (readOnly_)
+    {
+      throw OrthancException(ErrorCode_ReadOnly);
+    }
+
+    if (buffer_ != NULL)
+    {
+      return reinterpret_cast<uint8_t*>(buffer_) + y * pitch_;
+    }
+    else
+    {
+      return NULL;
+    }
+  }
+
+
+  void ImageAccessor::AssignEmpty(PixelFormat format)
+  {
+    readOnly_ = false;
+    format_ = format;
+    width_ = 0;
+    height_ = 0;
+    pitch_ = 0;
+    buffer_ = NULL;
+  }
+
+
+  void ImageAccessor::AssignReadOnly(PixelFormat format,
+                                     unsigned int width,
+                                     unsigned int height,
+                                     unsigned int pitch,
+                                     const void *buffer)
+  {
+    readOnly_ = true;
+    format_ = format;
+    width_ = width;
+    height_ = height;
+    pitch_ = pitch;
+    buffer_ = const_cast<void*>(buffer);
+
+    assert(GetBytesPerPixel() * width_ <= pitch_);
+  }
+
+
+  void ImageAccessor::AssignWritable(PixelFormat format,
+                                     unsigned int width,
+                                     unsigned int height,
+                                     unsigned int pitch,
+                                     void *buffer)
+  {
+    readOnly_ = false;
+    format_ = format;
+    width_ = width;
+    height_ = height;
+    pitch_ = pitch;
+    buffer_ = buffer;
+
+    assert(GetBytesPerPixel() * width_ <= pitch_);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Orthanc/ImageFormats/ImageAccessor.h	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,115 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, 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.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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 "../Enumerations.h"
+
+namespace Orthanc
+{
+  class ImageAccessor
+  {
+  private:
+    bool readOnly_;
+    PixelFormat format_;
+    unsigned int width_;
+    unsigned int height_;
+    unsigned int pitch_;
+    void *buffer_;
+
+  public:
+    ImageAccessor()
+    {
+      AssignEmpty(PixelFormat_Grayscale8);
+    }
+
+    bool IsReadOnly() const
+    {
+      return readOnly_;
+    }
+
+    PixelFormat GetFormat() const
+    {
+      return format_;
+    }
+
+    unsigned int GetBytesPerPixel() const
+    {
+      return ::Orthanc::GetBytesPerPixel(format_);
+    }
+
+    unsigned int GetWidth() const
+    {
+      return width_;
+    }
+
+    unsigned int GetHeight() const
+    {
+      return height_;
+    }
+
+    unsigned int GetPitch() const
+    {
+      return pitch_;
+    }
+
+    unsigned int GetSize() const
+    {
+      return GetHeight() * GetPitch();
+    }
+
+    const void* GetConstBuffer() const
+    {
+      return buffer_;
+    }
+
+    void* GetBuffer() const;
+
+    const void* GetConstRow(unsigned int y) const;
+
+    void* GetRow(unsigned int y) const;
+
+    void AssignEmpty(PixelFormat format);
+
+    void AssignReadOnly(PixelFormat format,
+                        unsigned int width,
+                        unsigned int height,
+                        unsigned int pitch,
+                        const void *buffer);
+
+    void AssignWritable(PixelFormat format,
+                        unsigned int width,
+                        unsigned int height,
+                        unsigned int pitch,
+                        void *buffer);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Orthanc/ImageFormats/ImageBuffer.cpp	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,192 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, 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.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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 "../PrecompiledHeaders.h"
+#include "ImageBuffer.h"
+
+#include "../OrthancException.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+
+namespace Orthanc
+{
+  void ImageBuffer::Allocate()
+  {
+    if (changed_)
+    {
+      Deallocate();
+
+      /*
+        if (forceMinimalPitch_)
+        {
+        TODO: Align pitch and memory buffer to optimal size for SIMD.
+        }
+      */
+
+      pitch_ = GetBytesPerPixel() * width_;
+      size_t size = pitch_ * height_;
+
+      if (size == 0)
+      {
+        buffer_ = NULL;
+      }
+      else
+      {
+        buffer_ = malloc(size);
+        if (buffer_ == NULL)
+        {
+          throw OrthancException(ErrorCode_NotEnoughMemory);
+        }
+      }
+
+      changed_ = false;
+    }
+  }
+
+
+  void ImageBuffer::Deallocate()
+  {
+    if (buffer_ != NULL)
+    {
+      free(buffer_);
+      buffer_ = NULL;
+      changed_ = true;
+    }
+  }
+
+
+  ImageBuffer::ImageBuffer(unsigned int width,
+                           unsigned int height,
+                           PixelFormat format)
+  {
+    Initialize();
+    SetWidth(width);
+    SetHeight(height);
+    SetFormat(format);
+  }
+
+
+  void ImageBuffer::Initialize()
+  {
+    changed_ = false;
+    forceMinimalPitch_ = true;
+    format_ = PixelFormat_Grayscale8;
+    width_ = 0;
+    height_ = 0;
+    pitch_ = 0;
+    buffer_ = NULL;
+  }
+
+
+  void ImageBuffer::SetFormat(PixelFormat format)
+  {
+    if (format != format_)
+    {
+      changed_ = true;
+      format_ = format;
+    }
+  }
+
+
+  void ImageBuffer::SetWidth(unsigned int width)
+  {
+    if (width != width_)
+    {
+      changed_ = true;
+      width_ = width;     
+    }
+  }
+
+
+  void ImageBuffer::SetHeight(unsigned int height)
+  {
+    if (height != height_)
+    {
+      changed_ = true;
+      height_ = height;     
+    }
+  }
+
+
+  ImageAccessor ImageBuffer::GetAccessor()
+  {
+    Allocate();
+
+    ImageAccessor accessor;
+    accessor.AssignWritable(format_, width_, height_, pitch_, buffer_);
+    return accessor;
+  }
+
+
+  ImageAccessor ImageBuffer::GetConstAccessor()
+  {
+    Allocate();
+
+    ImageAccessor accessor;
+    accessor.AssignReadOnly(format_, width_, height_, pitch_, buffer_);
+    return accessor;
+  }
+
+
+  void ImageBuffer::SetMinimalPitchForced(bool force)
+  {
+    if (force != forceMinimalPitch_)
+    {
+      changed_ = true;
+      forceMinimalPitch_ = force;
+    }
+  }
+
+
+  void ImageBuffer::AcquireOwnership(ImageBuffer& other)
+  {
+    // Remove the content of the current image
+    Deallocate();
+
+    // Force the allocation of the other image (if not already
+    // allocated)
+    other.Allocate();
+
+    // Transfer the content of the other image
+    changed_ = false;
+    forceMinimalPitch_ = other.forceMinimalPitch_;
+    format_ = other.format_;
+    width_ = other.width_;
+    height_ = other.height_;
+    pitch_ = other.pitch_;
+    buffer_ = other.buffer_;
+
+    // Force the reinitialization of the other image
+    other.Initialize();
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Orthanc/ImageFormats/ImageBuffer.h	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,115 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, 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.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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 "ImageAccessor.h"
+
+#include <vector>
+#include <stdint.h>
+#include <boost/noncopyable.hpp>
+
+namespace Orthanc
+{
+  class ImageBuffer : public boost::noncopyable
+  {
+  private:
+    bool changed_;
+
+    bool forceMinimalPitch_;  // Currently unused
+    PixelFormat format_;
+    unsigned int width_;
+    unsigned int height_;
+    unsigned int pitch_;
+    void *buffer_;
+
+    void Initialize();
+    
+    void Allocate();
+
+    void Deallocate();
+
+  public:
+    ImageBuffer(unsigned int width,
+                unsigned int height,
+                PixelFormat format);
+
+    ImageBuffer()
+    {
+      Initialize();
+    }
+
+    ~ImageBuffer()
+    {
+      Deallocate();
+    }
+
+    PixelFormat GetFormat() const
+    {
+      return format_;
+    }
+
+    void SetFormat(PixelFormat format);
+
+    unsigned int GetWidth() const
+    {
+      return width_;
+    }
+
+    void SetWidth(unsigned int width);
+
+    unsigned int GetHeight() const
+    {
+      return height_;
+    }
+
+    void SetHeight(unsigned int height);
+
+    unsigned int GetBytesPerPixel() const
+    {
+      return ::Orthanc::GetBytesPerPixel(format_);
+    }
+
+    ImageAccessor GetAccessor();
+
+    ImageAccessor GetConstAccessor();
+
+    bool IsMinimalPitchForced() const
+    {
+      return forceMinimalPitch_;
+    }
+
+    void SetMinimalPitchForced(bool force);
+
+    void AcquireOwnership(ImageBuffer& other);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Orthanc/ImageFormats/ImageProcessing.cpp	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,532 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, 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.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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 "../PrecompiledHeaders.h"
+#include "ImageProcessing.h"
+
+#include "../OrthancException.h"
+
+#include <boost/math/special_functions/round.hpp>
+
+#include <cassert>
+#include <string.h>
+#include <limits>
+#include <stdint.h>
+
+namespace Orthanc
+{
+  template <typename TargetType, typename SourceType>
+  static void ConvertInternal(ImageAccessor& target,
+                              const ImageAccessor& source)
+  {
+    const TargetType minValue = std::numeric_limits<TargetType>::min();
+    const TargetType maxValue = std::numeric_limits<TargetType>::max();
+
+    for (unsigned int y = 0; y < source.GetHeight(); y++)
+    {
+      TargetType* t = reinterpret_cast<TargetType*>(target.GetRow(y));
+      const SourceType* s = reinterpret_cast<const SourceType*>(source.GetConstRow(y));
+
+      for (unsigned int x = 0; x < source.GetWidth(); x++, t++, s++)
+      {
+        if (static_cast<int32_t>(*s) < static_cast<int32_t>(minValue))
+        {
+          *t = minValue;
+        }
+        else if (static_cast<int32_t>(*s) > static_cast<int32_t>(maxValue))
+        {
+          *t = maxValue;
+        }
+        else
+        {
+          *t = static_cast<TargetType>(*s);
+        }
+      }
+    }
+  }
+
+
+  template <typename TargetType>
+  static void ConvertColorToGrayscale(ImageAccessor& target,
+                              const ImageAccessor& source)
+  {
+    assert(source.GetFormat() == PixelFormat_RGB24);
+
+    const TargetType minValue = std::numeric_limits<TargetType>::min();
+    const TargetType maxValue = std::numeric_limits<TargetType>::max();
+
+    for (unsigned int y = 0; y < source.GetHeight(); y++)
+    {
+      TargetType* t = reinterpret_cast<TargetType*>(target.GetRow(y));
+      const uint8_t* s = reinterpret_cast<const uint8_t*>(source.GetConstRow(y));
+
+      for (unsigned int x = 0; x < source.GetWidth(); x++, t++, s += 3)
+      {
+        // Y = 0.2126 R + 0.7152 G + 0.0722 B
+        int32_t v = (2126 * static_cast<int32_t>(s[0]) +
+                     7152 * static_cast<int32_t>(s[1]) +
+                     0722 * static_cast<int32_t>(s[2])) / 1000;
+        
+        if (static_cast<int32_t>(v) < static_cast<int32_t>(minValue))
+        {
+          *t = minValue;
+        }
+        else if (static_cast<int32_t>(v) > static_cast<int32_t>(maxValue))
+        {
+          *t = maxValue;
+        }
+        else
+        {
+          *t = static_cast<TargetType>(v);
+        }
+      }
+    }
+  }
+
+
+  template <typename PixelType>
+  static void SetInternal(ImageAccessor& image,
+                          int64_t constant)
+  {
+    for (unsigned int y = 0; y < image.GetHeight(); y++)
+    {
+      PixelType* p = reinterpret_cast<PixelType*>(image.GetRow(y));
+
+      for (unsigned int x = 0; x < image.GetWidth(); x++, p++)
+      {
+        *p = static_cast<PixelType>(constant);
+      }
+    }
+  }
+
+
+  template <typename PixelType>
+  static void GetMinMaxValueInternal(PixelType& minValue,
+                                     PixelType& maxValue,
+                                     const ImageAccessor& source)
+  {
+    // Deal with the special case of empty image
+    if (source.GetWidth() == 0 ||
+        source.GetHeight() == 0)
+    {
+      minValue = 0;
+      maxValue = 0;
+      return;
+    }
+
+    minValue = std::numeric_limits<PixelType>::max();
+    maxValue = std::numeric_limits<PixelType>::min();
+
+    for (unsigned int y = 0; y < source.GetHeight(); y++)
+    {
+      const PixelType* p = reinterpret_cast<const PixelType*>(source.GetConstRow(y));
+
+      for (unsigned int x = 0; x < source.GetWidth(); x++, p++)
+      {
+        if (*p < minValue)
+        {
+          minValue = *p;
+        }
+
+        if (*p > maxValue)
+        {
+          maxValue = *p;
+        }
+      }
+    }
+  }
+
+
+
+  template <typename PixelType>
+  static void AddConstantInternal(ImageAccessor& image,
+                                  int64_t constant)
+  {
+    if (constant == 0)
+    {
+      return;
+    }
+
+    const int64_t minValue = std::numeric_limits<PixelType>::min();
+    const int64_t maxValue = std::numeric_limits<PixelType>::max();
+
+    for (unsigned int y = 0; y < image.GetHeight(); y++)
+    {
+      PixelType* p = reinterpret_cast<PixelType*>(image.GetRow(y));
+
+      for (unsigned int x = 0; x < image.GetWidth(); x++, p++)
+      {
+        int64_t v = static_cast<int64_t>(*p) + constant;
+
+        if (v > maxValue)
+        {
+          *p = std::numeric_limits<PixelType>::max();
+        }
+        else if (v < minValue)
+        {
+          *p = std::numeric_limits<PixelType>::min();
+        }
+        else
+        {
+          *p = static_cast<PixelType>(v);
+        }
+      }
+    }
+  }
+
+
+
+  template <typename PixelType>
+  void MultiplyConstantInternal(ImageAccessor& image,
+                                float factor)
+  {
+    if (abs(factor - 1.0f) <= std::numeric_limits<float>::epsilon())
+    {
+      return;
+    }
+
+    const int64_t minValue = std::numeric_limits<PixelType>::min();
+    const int64_t maxValue = std::numeric_limits<PixelType>::max();
+
+    for (unsigned int y = 0; y < image.GetHeight(); y++)
+    {
+      PixelType* p = reinterpret_cast<PixelType*>(image.GetRow(y));
+
+      for (unsigned int x = 0; x < image.GetWidth(); x++, p++)
+      {
+        int64_t v = boost::math::llround(static_cast<float>(*p) * factor);
+
+        if (v > maxValue)
+        {
+          *p = std::numeric_limits<PixelType>::max();
+        }
+        else if (v < minValue)
+        {
+          *p = std::numeric_limits<PixelType>::min();
+        }
+        else
+        {
+          *p = static_cast<PixelType>(v);
+        }
+      }
+    }
+  }
+
+
+  template <typename PixelType>
+  void ShiftScaleInternal(ImageAccessor& image,
+                          float offset,
+                          float scaling)
+  {
+    const float minValue = static_cast<float>(std::numeric_limits<PixelType>::min());
+    const float maxValue = static_cast<float>(std::numeric_limits<PixelType>::max());
+
+    for (unsigned int y = 0; y < image.GetHeight(); y++)
+    {
+      PixelType* p = reinterpret_cast<PixelType*>(image.GetRow(y));
+
+      for (unsigned int x = 0; x < image.GetWidth(); x++, p++)
+      {
+        float v = (static_cast<float>(*p) + offset) * scaling;
+
+        if (v > maxValue)
+        {
+          *p = std::numeric_limits<PixelType>::max();
+        }
+        else if (v < minValue)
+        {
+          *p = std::numeric_limits<PixelType>::min();
+        }
+        else
+        {
+          *p = static_cast<PixelType>(boost::math::iround(v));
+        }
+      }
+    }
+  }
+
+
+  void ImageProcessing::Copy(ImageAccessor& target,
+                             const ImageAccessor& source)
+  {
+    if (target.GetWidth() != source.GetWidth() ||
+        target.GetHeight() != source.GetHeight())
+    {
+      throw OrthancException(ErrorCode_IncompatibleImageSize);
+    }
+
+    if (target.GetFormat() != source.GetFormat())
+    {
+      throw OrthancException(ErrorCode_IncompatibleImageFormat);
+    }
+
+    unsigned int lineSize = GetBytesPerPixel(source.GetFormat()) * source.GetWidth();
+
+    assert(source.GetPitch() >= lineSize && target.GetPitch() >= lineSize);
+
+    for (unsigned int y = 0; y < source.GetHeight(); y++)
+    {
+      memcpy(target.GetRow(y), source.GetConstRow(y), lineSize);
+    }
+  }
+
+
+  void ImageProcessing::Convert(ImageAccessor& target,
+                                const ImageAccessor& source)
+  {
+    if (target.GetWidth() != source.GetWidth() ||
+        target.GetHeight() != source.GetHeight())
+    {
+      throw OrthancException(ErrorCode_IncompatibleImageSize);
+    }
+
+    if (source.GetFormat() == target.GetFormat())
+    {
+      Copy(target, source);
+      return;
+    }
+
+    if (target.GetFormat() == PixelFormat_Grayscale16 &&
+        source.GetFormat() == PixelFormat_Grayscale8)
+    {
+      ConvertInternal<uint16_t, uint8_t>(target, source);
+      return;
+    }
+
+    if (target.GetFormat() == PixelFormat_SignedGrayscale16 &&
+        source.GetFormat() == PixelFormat_Grayscale8)
+    {
+      ConvertInternal<int16_t, uint8_t>(target, source);
+      return;
+    }
+
+    if (target.GetFormat() == PixelFormat_Grayscale8 &&
+        source.GetFormat() == PixelFormat_Grayscale16)
+    {
+      ConvertInternal<uint8_t, uint16_t>(target, source);
+      return;
+    }
+
+    if (target.GetFormat() == PixelFormat_SignedGrayscale16 &&
+        source.GetFormat() == PixelFormat_Grayscale16)
+    {
+      ConvertInternal<int16_t, uint16_t>(target, source);
+      return;
+    }
+
+    if (target.GetFormat() == PixelFormat_Grayscale8 &&
+        source.GetFormat() == PixelFormat_SignedGrayscale16)
+    {
+      ConvertInternal<uint8_t, int16_t>(target, source);
+      return;
+    }
+
+    if (target.GetFormat() == PixelFormat_Grayscale16 &&
+        source.GetFormat() == PixelFormat_SignedGrayscale16)
+    {
+      ConvertInternal<uint16_t, int16_t>(target, source);
+      return;
+    }
+
+    if (target.GetFormat() == PixelFormat_Grayscale8 &&
+        source.GetFormat() == PixelFormat_RGB24)
+    {
+      ConvertColorToGrayscale<uint8_t>(target, source);
+      return;
+    }
+
+    if (target.GetFormat() == PixelFormat_Grayscale16 &&
+        source.GetFormat() == PixelFormat_RGB24)
+    {
+      ConvertColorToGrayscale<uint16_t>(target, source);
+      return;
+    }
+
+    if (target.GetFormat() == PixelFormat_SignedGrayscale16 &&
+        source.GetFormat() == PixelFormat_RGB24)
+    {
+      ConvertColorToGrayscale<int16_t>(target, source);
+      return;
+    }
+
+    throw OrthancException(ErrorCode_NotImplemented);
+  }
+
+
+
+  void ImageProcessing::Set(ImageAccessor& image,
+                            int64_t value)
+  {
+    switch (image.GetFormat())
+    {
+      case PixelFormat_Grayscale8:
+        SetInternal<uint8_t>(image, value);
+        return;
+
+      case PixelFormat_Grayscale16:
+        SetInternal<uint16_t>(image, value);
+        return;
+
+      case PixelFormat_SignedGrayscale16:
+        SetInternal<int16_t>(image, value);
+        return;
+
+      default:
+        throw OrthancException(ErrorCode_NotImplemented);
+    }
+  }
+
+
+  void ImageProcessing::ShiftRight(ImageAccessor& image,
+                                   unsigned int shift)
+  {
+    if (image.GetWidth() == 0 ||
+        image.GetHeight() == 0 ||
+        shift == 0)
+    {
+      // Nothing to do
+      return;
+    }
+
+    throw OrthancException(ErrorCode_NotImplemented);
+  }
+
+
+  void ImageProcessing::GetMinMaxValue(int64_t& minValue,
+                                       int64_t& maxValue,
+                                       const ImageAccessor& image)
+  {
+    switch (image.GetFormat())
+    {
+      case PixelFormat_Grayscale8:
+      {
+        uint8_t a, b;
+        GetMinMaxValueInternal<uint8_t>(a, b, image);
+        minValue = a;
+        maxValue = b;
+        break;
+      }
+
+      case PixelFormat_Grayscale16:
+      {
+        uint16_t a, b;
+        GetMinMaxValueInternal<uint16_t>(a, b, image);
+        minValue = a;
+        maxValue = b;
+        break;
+      }
+
+      case PixelFormat_SignedGrayscale16:
+      {
+        int16_t a, b;
+        GetMinMaxValueInternal<int16_t>(a, b, image);
+        minValue = a;
+        maxValue = b;
+        break;
+      }
+
+      default:
+        throw OrthancException(ErrorCode_NotImplemented);
+    }
+  }
+
+
+
+  void ImageProcessing::AddConstant(ImageAccessor& image,
+                                    int64_t value)
+  {
+    switch (image.GetFormat())
+    {
+      case PixelFormat_Grayscale8:
+        AddConstantInternal<uint8_t>(image, value);
+        return;
+
+      case PixelFormat_Grayscale16:
+        AddConstantInternal<uint16_t>(image, value);
+        return;
+
+      case PixelFormat_SignedGrayscale16:
+        AddConstantInternal<int16_t>(image, value);
+        return;
+
+      default:
+        throw OrthancException(ErrorCode_NotImplemented);
+    }
+  }
+
+
+  void ImageProcessing::MultiplyConstant(ImageAccessor& image,
+                                         float factor)
+  {
+    switch (image.GetFormat())
+    {
+      case PixelFormat_Grayscale8:
+        MultiplyConstantInternal<uint8_t>(image, factor);
+        return;
+
+      case PixelFormat_Grayscale16:
+        MultiplyConstantInternal<uint16_t>(image, factor);
+        return;
+
+      case PixelFormat_SignedGrayscale16:
+        MultiplyConstantInternal<int16_t>(image, factor);
+        return;
+
+      default:
+        throw OrthancException(ErrorCode_NotImplemented);
+    }
+  }
+
+
+  void ImageProcessing::ShiftScale(ImageAccessor& image,
+                                   float offset,
+                                   float scaling)
+  {
+    switch (image.GetFormat())
+    {
+      case PixelFormat_Grayscale8:
+        ShiftScaleInternal<uint8_t>(image, offset, scaling);
+        return;
+
+      case PixelFormat_Grayscale16:
+        ShiftScaleInternal<uint16_t>(image, offset, scaling);
+        return;
+
+      case PixelFormat_SignedGrayscale16:
+        ShiftScaleInternal<int16_t>(image, offset, scaling);
+        return;
+
+      default:
+        throw OrthancException(ErrorCode_NotImplemented);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Orthanc/ImageFormats/ImageProcessing.h	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,70 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, 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.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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 "ImageAccessor.h"
+
+#include <stdint.h>
+
+namespace Orthanc
+{
+  class ImageProcessing
+  {
+  public:
+    static void Copy(ImageAccessor& target,
+                     const ImageAccessor& source);
+
+    static void Convert(ImageAccessor& target,
+                        const ImageAccessor& source);
+
+    static void Set(ImageAccessor& image,
+                    int64_t value);
+
+    static void ShiftRight(ImageAccessor& target,
+                           unsigned int shift);
+
+    static void GetMinMaxValue(int64_t& minValue,
+                               int64_t& maxValue,
+                               const ImageAccessor& image);
+
+    static void AddConstant(ImageAccessor& image,
+                            int64_t value);
+
+    static void MultiplyConstant(ImageAccessor& image,
+                                 float factor);
+
+    static void ShiftScale(ImageAccessor& image,
+                           float offset,
+                           float scaling);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Orthanc/ImageFormats/PngWriter.cpp	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,269 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, 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.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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 "../PrecompiledHeaders.h"
+#include "PngWriter.h"
+
+#include <vector>
+#include <stdint.h>
+#include <png.h>
+#include "../OrthancException.h"
+#include "../ChunkedBuffer.h"
+#include "../Toolbox.h"
+
+
+// http://www.libpng.org/pub/png/libpng-1.2.5-manual.html#section-4
+// http://zarb.org/~gc/html/libpng.html
+/*
+  void write_row_callback(png_ptr, png_uint_32 row, int pass)
+  {
+  }*/
+
+
+
+
+/*  bool isError_;
+
+// http://www.libpng.org/pub/png/book/chapter14.html#png.ch14.div.2
+
+static void ErrorHandler(png_structp png, png_const_charp message)
+{
+printf("** [%s]\n", message);
+
+PngWriter* that = (PngWriter*) png_get_error_ptr(png);
+that->isError_ = true;
+printf("** %d\n", (int)that);
+
+//((PngWriter*) payload)->isError_ = true;
+}
+
+static void WarningHandler(png_structp png, png_const_charp message)
+{
+  printf("++ %d\n", (int)message);
+}*/
+
+
+namespace Orthanc
+{
+  struct PngWriter::PImpl
+  {
+    png_structp png_;
+    png_infop info_;
+
+    // Filled by Prepare()
+    std::vector<uint8_t*> rows_;
+    int bitDepth_;
+    int colorType_;
+  };
+
+
+
+  PngWriter::PngWriter() : pimpl_(new PImpl)
+  {
+    pimpl_->png_ = NULL;
+    pimpl_->info_ = NULL;
+
+    pimpl_->png_ = png_create_write_struct
+      (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); //this, ErrorHandler, WarningHandler);
+    if (!pimpl_->png_)
+    {
+      throw OrthancException(ErrorCode_NotEnoughMemory);
+    }
+
+    pimpl_->info_ = png_create_info_struct(pimpl_->png_);
+    if (!pimpl_->info_)
+    {
+      png_destroy_write_struct(&pimpl_->png_, NULL);
+      throw OrthancException(ErrorCode_NotEnoughMemory);
+    }
+  }
+
+  PngWriter::~PngWriter()
+  {
+    if (pimpl_->info_)
+    {
+      png_destroy_info_struct(pimpl_->png_, &pimpl_->info_);
+    }
+
+    if (pimpl_->png_)
+    {
+      png_destroy_write_struct(&pimpl_->png_, NULL);
+    }
+  }
+
+
+
+  void PngWriter::Prepare(unsigned int width,
+                          unsigned int height,
+                          unsigned int pitch,
+                          PixelFormat format,
+                          const void* buffer)
+  {
+    pimpl_->rows_.resize(height);
+    for (unsigned int y = 0; y < height; y++)
+    {
+      pimpl_->rows_[y] = const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(buffer)) + y * pitch;
+    }
+
+    switch (format)
+    {
+    case PixelFormat_RGB24:
+      pimpl_->bitDepth_ = 8;
+      pimpl_->colorType_ = PNG_COLOR_TYPE_RGB;
+      break;
+
+    case PixelFormat_RGBA32:
+      pimpl_->bitDepth_ = 8;
+      pimpl_->colorType_ = PNG_COLOR_TYPE_RGBA;
+      break;
+
+    case PixelFormat_Grayscale8:
+      pimpl_->bitDepth_ = 8;
+      pimpl_->colorType_ = PNG_COLOR_TYPE_GRAY;
+      break;
+
+    case PixelFormat_Grayscale16:
+    case PixelFormat_SignedGrayscale16:
+      pimpl_->bitDepth_ = 16;
+      pimpl_->colorType_ = PNG_COLOR_TYPE_GRAY;
+      break;
+
+    default:
+      throw OrthancException(ErrorCode_NotImplemented);
+    }
+  }
+
+
+  void PngWriter::Compress(unsigned int width,
+                           unsigned int height,
+                           unsigned int pitch,
+                           PixelFormat format)
+  {
+    png_set_IHDR(pimpl_->png_, pimpl_->info_, width, height,
+                 pimpl_->bitDepth_, pimpl_->colorType_, PNG_INTERLACE_NONE,
+                 PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
+
+    png_write_info(pimpl_->png_, pimpl_->info_);
+
+    if (height > 0)
+    {
+      switch (format)
+      {
+      case PixelFormat_Grayscale16:
+      case PixelFormat_SignedGrayscale16:
+      {
+        int transforms = 0;
+        if (Toolbox::DetectEndianness() == Endianness_Little)
+        {
+          transforms = PNG_TRANSFORM_SWAP_ENDIAN;
+        }
+
+        png_set_rows(pimpl_->png_, pimpl_->info_, &pimpl_->rows_[0]);
+        png_write_png(pimpl_->png_, pimpl_->info_, transforms, NULL);
+
+        break;
+      }
+
+      default:
+        png_write_image(pimpl_->png_, &pimpl_->rows_[0]);
+      }
+    }
+
+    png_write_end(pimpl_->png_, NULL);
+  }
+
+
+  void PngWriter::WriteToFile(const char* filename,
+                              unsigned int width,
+                              unsigned int height,
+                              unsigned int pitch,
+                              PixelFormat format,
+                              const void* buffer)
+  {
+    Prepare(width, height, pitch, format, buffer);
+
+    FILE* fp = fopen(filename, "wb");
+    if (!fp)
+    {
+      throw OrthancException(ErrorCode_CannotWriteFile);
+    }    
+
+    png_init_io(pimpl_->png_, fp);
+
+    if (setjmp(png_jmpbuf(pimpl_->png_)))
+    {
+      // Error during writing PNG
+      throw OrthancException(ErrorCode_CannotWriteFile);      
+    }
+
+    Compress(width, height, pitch, format);
+
+    fclose(fp);
+  }
+
+
+
+
+  static void MemoryCallback(png_structp png_ptr, 
+                             png_bytep data, 
+                             png_size_t size)
+  {
+    ChunkedBuffer* buffer = reinterpret_cast<ChunkedBuffer*>(png_get_io_ptr(png_ptr));
+    buffer->AddChunk(reinterpret_cast<const char*>(data), size);
+  }
+
+
+
+  void PngWriter::WriteToMemory(std::string& png,
+                                unsigned int width,
+                                unsigned int height,
+                                unsigned int pitch,
+                                PixelFormat format,
+                                const void* buffer)
+  {
+    ChunkedBuffer chunks;
+
+    Prepare(width, height, pitch, format, buffer);
+
+    if (setjmp(png_jmpbuf(pimpl_->png_)))
+    {
+      // Error during writing PNG
+      throw OrthancException(ErrorCode_InternalError);      
+    }
+
+    png_set_write_fn(pimpl_->png_, &chunks, MemoryCallback, NULL);
+
+    Compress(width, height, pitch, format);
+
+    chunks.Flatten(png);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Orthanc/ImageFormats/PngWriter.h	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,92 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, 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.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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 "ImageAccessor.h"
+
+#include <boost/shared_ptr.hpp>
+#include <string>
+
+namespace Orthanc
+{
+  class PngWriter
+  {
+  private:
+    struct PImpl;
+    boost::shared_ptr<PImpl> pimpl_;
+
+    void Compress(unsigned int width,
+                  unsigned int height,
+                  unsigned int pitch,
+                  PixelFormat format);
+
+    void Prepare(unsigned int width,
+                 unsigned int height,
+                 unsigned int pitch,
+                 PixelFormat format,
+                 const void* buffer);
+
+  public:
+    PngWriter();
+
+    ~PngWriter();
+
+    void WriteToFile(const char* filename,
+                     unsigned int width,
+                     unsigned int height,
+                     unsigned int pitch,
+                     PixelFormat format,
+                     const void* buffer);
+
+    void WriteToMemory(std::string& png,
+                       unsigned int width,
+                       unsigned int height,
+                       unsigned int pitch,
+                       PixelFormat format,
+                       const void* buffer);
+
+    void WriteToFile(const char* filename,
+                     const ImageAccessor& accessor)
+    {
+      WriteToFile(filename, accessor.GetWidth(), accessor.GetHeight(),
+                  accessor.GetPitch(), accessor.GetFormat(), accessor.GetConstBuffer());
+    }
+
+    void WriteToMemory(std::string& png,
+                       const ImageAccessor& accessor)
+    {
+      WriteToMemory(png, accessor.GetWidth(), accessor.GetHeight(),
+                    accessor.GetPitch(), accessor.GetFormat(), accessor.GetConstBuffer());
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Orthanc/MultiThreading/SharedMessageQueue.cpp	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,189 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, 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.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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 "../PrecompiledHeaders.h"
+#include "SharedMessageQueue.h"
+
+
+
+/**
+ * FIFO (queue):
+ * 
+ *            back                         front
+ *            +--+--+--+--+--+--+--+--+--+--+--+
+ * Enqueue -> |  |  |  |  |  |  |  |  |  |  |  |
+ *            |  |  |  |  |  |  |  |  |  |  |  | -> Dequeue
+ *            +--+--+--+--+--+--+--+--+--+--+--+
+ *                                            ^
+ *                                            |
+ *                                      Make room here
+ *
+ *
+ * LIFO (stack):
+ * 
+ *            back                         front
+ *            +--+--+--+--+--+--+--+--+--+--+--+
+ *            |  |  |  |  |  |  |  |  |  |  |  | <- Enqueue
+ *            |  |  |  |  |  |  |  |  |  |  |  | -> Dequeue
+ *            +--+--+--+--+--+--+--+--+--+--+--+
+ *              ^
+ *              |
+ *        Make room here
+ **/
+
+
+
+namespace Orthanc
+{
+  SharedMessageQueue::SharedMessageQueue(unsigned int maxSize) :
+    isFifo_(true),
+    maxSize_(maxSize)
+  {
+  }
+
+
+  SharedMessageQueue::~SharedMessageQueue()
+  {
+    for (Queue::iterator it = queue_.begin(); it != queue_.end(); ++it)
+    {
+      delete *it;
+    }
+  }
+
+
+  void SharedMessageQueue::Enqueue(IDynamicObject* message)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    if (maxSize_ != 0 && queue_.size() > maxSize_)
+    {
+      if (isFifo_)
+      {
+        // Too many elements in the queue: Make room
+        delete queue_.front();
+        queue_.pop_front();
+      }
+      else
+      {
+        // Too many elements in the stack: Make room
+        delete queue_.back();
+        queue_.pop_back();
+      }
+    }
+
+    if (isFifo_)
+    {
+      // Queue policy (FIFO)
+      queue_.push_back(message);
+    }
+    else
+    {
+      // Stack policy (LIFO)
+      queue_.push_front(message);
+    }
+
+    elementAvailable_.notify_one();
+  }
+
+
+  IDynamicObject* SharedMessageQueue::Dequeue(int32_t millisecondsTimeout)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    // Wait for a message to arrive in the FIFO queue
+    while (queue_.empty())
+    {
+      if (millisecondsTimeout == 0)
+      {
+        elementAvailable_.wait(lock);
+      }
+      else
+      {
+        bool success = elementAvailable_.timed_wait
+          (lock, boost::posix_time::milliseconds(millisecondsTimeout));
+        if (!success)
+        {
+          return NULL;
+        }
+      }
+    }
+
+    std::auto_ptr<IDynamicObject> message(queue_.front());
+    queue_.pop_front();
+
+    if (queue_.empty())
+    {
+      emptied_.notify_all();
+    }
+
+    return message.release();
+  }
+
+
+
+  bool SharedMessageQueue::WaitEmpty(int32_t millisecondsTimeout)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+    
+    // Wait for the queue to become empty
+    while (!queue_.empty())
+    {
+      if (millisecondsTimeout == 0)
+      {
+        emptied_.wait(lock);
+      }
+      else
+      {
+        if (!emptied_.timed_wait
+            (lock, boost::posix_time::milliseconds(millisecondsTimeout)))
+        {
+          return false;
+        }
+      }
+    }
+
+    return true;
+  }
+
+
+  void SharedMessageQueue::SetFifoPolicy()
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+    isFifo_ = true;
+  }
+
+  void SharedMessageQueue::SetLifoPolicy()
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+    isFifo_ = false;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Orthanc/MultiThreading/SharedMessageQueue.h	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,82 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, 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.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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 "../IDynamicObject.h"
+
+#include <stdint.h>
+#include <list>
+#include <boost/thread.hpp>
+
+namespace Orthanc
+{
+  class SharedMessageQueue : public boost::noncopyable
+  {
+  private:
+    typedef std::list<IDynamicObject*>  Queue;
+
+    bool isFifo_;
+    unsigned int maxSize_;
+    Queue queue_;
+    boost::mutex mutex_;
+    boost::condition_variable elementAvailable_;
+    boost::condition_variable emptied_;
+
+  public:
+    explicit SharedMessageQueue(unsigned int maxSize = 0);
+    
+    ~SharedMessageQueue();
+
+    // This transfers the ownership of the message
+    void Enqueue(IDynamicObject* message);
+
+    // The caller is responsible to delete the dequeud message!
+    IDynamicObject* Dequeue(int32_t millisecondsTimeout);
+
+    bool WaitEmpty(int32_t millisecondsTimeout);
+
+    bool IsFifoPolicy() const
+    {
+      return isFifo_;
+    }
+
+    bool IsLifoPolicy() const
+    {
+      return !isFifo_;
+    }
+
+    void SetFifoPolicy();
+
+    void SetLifoPolicy();
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Orthanc/OrthancException.cpp	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,141 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, 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.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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 "PrecompiledHeaders.h"
+#include "OrthancException.h"
+
+namespace Orthanc
+{
+  const char* OrthancException::What() const
+  {
+    if (error_ == ErrorCode_Custom)
+    {
+      return custom_.c_str();
+    }
+    else
+    {
+      return GetDescription(error_);
+    }
+  }
+
+
+  const char* OrthancException::GetDescription(ErrorCode error)
+  {
+    switch (error)
+    {
+      case ErrorCode_Success:
+        return "Success";
+
+      case ErrorCode_ParameterOutOfRange:
+        return "Parameter out of range";
+
+      case ErrorCode_NotImplemented:
+        return "Not implemented yet";
+
+      case ErrorCode_InternalError:
+        return "Internal error";
+
+      case ErrorCode_NotEnoughMemory:
+        return "Not enough memory";
+
+      case ErrorCode_UriSyntax:
+        return "Badly formatted URI";
+
+      case ErrorCode_BadParameterType:
+        return "Bad type for a parameter";
+
+      case ErrorCode_InexistentFile:
+        return "Inexistent file";
+
+      case ErrorCode_BadFileFormat:
+        return "Bad file format";
+
+      case ErrorCode_CannotWriteFile:
+        return "Cannot write to file";
+
+      case ErrorCode_Timeout:
+        return "Timeout";
+
+      case ErrorCode_UnknownResource:
+        return "Unknown resource";
+
+      case ErrorCode_BadSequenceOfCalls:
+        return "Bad sequence of calls";
+
+      case ErrorCode_IncompatibleDatabaseVersion:
+        return "Incompatible version of the database";
+
+      case ErrorCode_FullStorage:
+        return "The file storage is full";
+
+      case ErrorCode_InexistentItem:
+        return "Accessing an inexistent item";
+
+      case ErrorCode_BadRequest:
+        return "Bad request";
+
+      case ErrorCode_NetworkProtocol:
+        return "Error in the network protocol";
+
+      case ErrorCode_CorruptedFile:
+        return "Corrupted file (inconsistent MD5 hash)";
+
+      case ErrorCode_InexistentTag:
+        return "Inexistent tag";
+
+      case ErrorCode_ReadOnly:
+        return "Cannot modify a read-only data structure";
+
+      case ErrorCode_IncompatibleImageSize:
+        return "Incompatible size of the images";
+
+      case ErrorCode_IncompatibleImageFormat:
+        return "Incompatible format of the images";
+
+      case ErrorCode_SharedLibrary:
+        return "Error while using a shared library (plugin)";
+
+      case ErrorCode_SystemCommand:
+        return "Error while calling a system command";
+
+      case ErrorCode_Plugin:
+        return "Error encountered inside a plugin";
+
+      case ErrorCode_Database:
+        return "Error with the database engine";
+
+      case ErrorCode_Custom:
+      default:
+        return "???";
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Orthanc/OrthancException.h	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,72 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, 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.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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 <string>
+#include "Enumerations.h"
+
+namespace Orthanc
+{
+  class OrthancException
+  {
+  protected:
+    ErrorCode error_;
+    std::string custom_;
+
+  public:
+    static const char* GetDescription(ErrorCode error);
+
+    OrthancException(const char* custom) : 
+      error_(ErrorCode_Custom),
+      custom_(custom)
+    {
+    }
+
+    OrthancException(const std::string& custom) : 
+      error_(ErrorCode_Custom),
+      custom_(custom)
+    {
+    }
+
+    OrthancException(ErrorCode error) : error_(error)
+    {
+    }
+
+    ErrorCode GetErrorCode() const
+    {
+      return error_;
+    }
+
+    const char* What() const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Orthanc/PrecompiledHeaders.cpp	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,33 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, 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.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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 "PrecompiledHeaders.h"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Orthanc/PrecompiledHeaders.h	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,52 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, 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.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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(_WIN32) && !defined(NOMINMAX)
+#define NOMINMAX
+#endif
+
+#if ORTHANC_USE_PRECOMPILED_HEADERS == 1
+
+#include <boost/filesystem.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/thread.hpp>
+#include <boost/thread/shared_mutex.hpp>
+#include <json/value.h>
+
+#include "Enumerations.h"
+#include "OrthancException.h"
+#include "Toolbox.h"
+#include "Uuid.h"
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Orthanc/README.txt	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,1 @@
+This folder contains an excerpt of the source code of Orthanc 0.8.6.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Orthanc/SQLite/Connection.cpp	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,393 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ *
+ * Copyright (C) 2012-2015 Sebastien Jodogne <s.jodogne@gmail.com>,
+ * Medical Physics Department, CHU of Liege, Belgium
+ *
+ * Copyright (c) 2012 The Chromium Authors. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *    * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *    * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *    * Neither the name of Google Inc., the name of the CHU of Liege,
+ * nor the names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ **/
+
+
+#if ORTHANC_SQLITE_STANDALONE != 1
+#include "../PrecompiledHeaders.h"
+#endif
+
+#include "Connection.h"
+#include "OrthancSQLiteException.h"
+
+#include <memory>
+#include <cassert>
+#include <sqlite3.h>
+#include <string.h>
+
+#if ORTHANC_SQLITE_STANDALONE != 1
+#include <glog/logging.h>
+#endif
+
+
+namespace Orthanc
+{
+  namespace SQLite
+  {
+    Connection::Connection() :
+      db_(NULL),
+      transactionNesting_(0),
+      needsRollback_(false)
+    {
+    }
+
+
+    Connection::~Connection()
+    {
+      Close();
+    }
+
+
+    void Connection::CheckIsOpen() const
+    {
+      if (!db_)
+      {
+        throw OrthancSQLiteException("SQLite: The database is not opened");
+      }
+    }
+
+    void Connection::Open(const std::string& path)
+    {
+      if (db_) 
+      {
+        throw OrthancSQLiteException("SQLite: Connection is already open");
+      }
+
+      int err = sqlite3_open(path.c_str(), &db_);
+      if (err != SQLITE_OK) 
+      {
+        Close();
+        db_ = NULL;
+        throw OrthancSQLiteException("SQLite: Unable to open the database");
+      }
+
+      // Execute PRAGMAs at this point
+      // http://www.sqlite.org/pragma.html
+      Execute("PRAGMA FOREIGN_KEYS=ON;");
+      Execute("PRAGMA RECURSIVE_TRIGGERS=ON;");
+    }
+
+    void Connection::OpenInMemory()
+    {
+      Open(":memory:");
+    }
+
+    void Connection::Close() 
+    {
+      ClearCache();
+
+      if (db_)
+      {
+        sqlite3_close(db_);
+        db_ = NULL;
+      }
+    }
+
+    void Connection::ClearCache()
+    {
+      for (CachedStatements::iterator 
+             it = cachedStatements_.begin(); 
+           it != cachedStatements_.end(); ++it)
+      {
+        delete it->second;
+      }
+
+      cachedStatements_.clear();
+    }
+
+
+    StatementReference& Connection::GetCachedStatement(const StatementId& id,
+                                                       const char* sql)
+    {
+      CachedStatements::iterator i = cachedStatements_.find(id);
+      if (i != cachedStatements_.end())
+      {
+        if (i->second->GetReferenceCount() >= 1)
+        {
+          throw OrthancSQLiteException("SQLite: This cached statement is already being referred to");
+        }
+
+        return *i->second;
+      }
+      else
+      {
+        StatementReference* statement = new StatementReference(db_, sql);
+        cachedStatements_[id] = statement;
+        return *statement;
+      }
+    }
+
+
+    bool Connection::Execute(const char* sql) 
+    {
+#if ORTHANC_SQLITE_STANDALONE != 1
+      VLOG(1) << "SQLite::Connection::Execute " << sql;
+#endif
+
+      CheckIsOpen();
+
+      int error = sqlite3_exec(db_, sql, NULL, NULL, NULL);
+      if (error == SQLITE_ERROR)
+      {
+        throw OrthancSQLiteException("SQLite Execute error: " + std::string(sqlite3_errmsg(db_)));
+      }
+      else
+      {
+        return error == SQLITE_OK;
+      }
+    }
+
+    int  Connection::ExecuteAndReturnErrorCode(const char* sql)
+    {
+      CheckIsOpen();
+      return sqlite3_exec(db_, sql, NULL, NULL, NULL);
+    }
+
+    // Info querying -------------------------------------------------------------
+
+    bool Connection::IsSQLValid(const char* sql) 
+    {
+      sqlite3_stmt* stmt = NULL;
+      if (sqlite3_prepare_v2(db_, sql, -1, &stmt, NULL) != SQLITE_OK)
+        return false;
+
+      sqlite3_finalize(stmt);
+      return true;
+    }
+
+    bool Connection::DoesTableOrIndexExist(const char* name, 
+                                           const char* type) const
+    {
+      // Our SQL is non-mutating, so this cast is OK.
+      Statement statement(const_cast<Connection&>(*this), 
+                          "SELECT name FROM sqlite_master WHERE type=? AND name=?");
+      statement.BindString(0, type);
+      statement.BindString(1, name);
+      return statement.Step();  // Table exists if any row was returned.
+    }
+
+    bool Connection::DoesTableExist(const char* table_name) const
+    {
+      return DoesTableOrIndexExist(table_name, "table");
+    }
+
+    bool Connection::DoesIndexExist(const char* index_name) const
+    {
+      return DoesTableOrIndexExist(index_name, "index");
+    }
+
+    bool Connection::DoesColumnExist(const char* table_name, const char* column_name) const
+    {
+      std::string sql("PRAGMA TABLE_INFO(");
+      sql.append(table_name);
+      sql.append(")");
+
+      // Our SQL is non-mutating, so this cast is OK.
+      Statement statement(const_cast<Connection&>(*this), sql.c_str());
+
+      while (statement.Step()) {
+        if (!statement.ColumnString(1).compare(column_name))
+          return true;
+      }
+      return false;
+    }
+
+    int64_t Connection::GetLastInsertRowId() const
+    {
+      return sqlite3_last_insert_rowid(db_);
+    }
+
+    int Connection::GetLastChangeCount() const
+    {
+      return sqlite3_changes(db_);
+    }
+
+    int Connection::GetErrorCode() const 
+    {
+      return sqlite3_errcode(db_);
+    }
+
+    int Connection::GetLastErrno() const 
+    {
+      int err = 0;
+      if (SQLITE_OK != sqlite3_file_control(db_, NULL, SQLITE_LAST_ERRNO, &err))
+        return -2;
+
+      return err;
+    }
+
+    const char* Connection::GetErrorMessage() const 
+    {
+      return sqlite3_errmsg(db_);
+    }
+
+
+    bool Connection::BeginTransaction()
+    {
+      if (needsRollback_)
+      {
+        assert(transactionNesting_ > 0);
+
+        // When we're going to rollback, fail on this begin and don't actually
+        // mark us as entering the nested transaction.
+        return false;
+      }
+
+      bool success = true;
+      if (!transactionNesting_) 
+      {
+        needsRollback_ = false;
+
+        Statement begin(*this, SQLITE_FROM_HERE, "BEGIN TRANSACTION");
+        if (!begin.Run())
+          return false;
+      }
+      transactionNesting_++;
+      return success;
+    }
+
+    void Connection::RollbackTransaction()
+    {
+      if (!transactionNesting_)
+      {
+        throw OrthancSQLiteException("Rolling back a nonexistent transaction");
+      }
+
+      transactionNesting_--;
+
+      if (transactionNesting_ > 0)
+      {
+        // Mark the outermost transaction as needing rollback.
+        needsRollback_ = true;
+        return;
+      }
+
+      DoRollback();
+    }
+
+    bool Connection::CommitTransaction() 
+    {
+      if (!transactionNesting_) 
+      {
+        throw OrthancSQLiteException("Committing a nonexistent transaction");
+      }
+      transactionNesting_--;
+
+      if (transactionNesting_ > 0) 
+      {
+        // Mark any nested transactions as failing after we've already got one.
+        return !needsRollback_;
+      }
+
+      if (needsRollback_) 
+      {
+        DoRollback();
+        return false;
+      }
+
+      Statement commit(*this, SQLITE_FROM_HERE, "COMMIT");
+      return commit.Run();
+    }
+
+    void Connection::DoRollback() 
+    {
+      Statement rollback(*this, SQLITE_FROM_HERE, "ROLLBACK");
+      rollback.Run();
+      needsRollback_ = false;
+    }
+
+
+
+
+
+
+    static void ScalarFunctionCaller(sqlite3_context* rawContext,
+                                     int argc,
+                                     sqlite3_value** argv)
+    {
+      FunctionContext context(rawContext, argc, argv);
+
+      void* payload = sqlite3_user_data(rawContext);
+      assert(payload != NULL);
+
+      IScalarFunction& func = *reinterpret_cast<IScalarFunction*>(payload);
+      func.Compute(context);
+    }
+
+
+    static void ScalarFunctionDestroyer(void* payload)
+    {
+      assert(payload != NULL);
+      delete reinterpret_cast<IScalarFunction*>(payload);
+    }
+
+
+    IScalarFunction* Connection::Register(IScalarFunction* func)
+    {
+      int err = sqlite3_create_function_v2(db_, 
+                                           func->GetName(), 
+                                           func->GetCardinality(),
+                                           SQLITE_UTF8, 
+                                           func,
+                                           ScalarFunctionCaller,
+                                           NULL,
+                                           NULL,
+                                           ScalarFunctionDestroyer);
+
+      if (err != SQLITE_OK)
+      {
+        delete func;
+        throw OrthancSQLiteException("SQLite: Unable to register a function");
+      }
+
+      return func;
+    }
+
+
+    void Connection::FlushToDisk()
+    {
+#if ORTHANC_SQLITE_STANDALONE != 1
+      VLOG(1) << "SQLite::Connection::FlushToDisk";
+#endif
+
+      int err = sqlite3_wal_checkpoint(db_, NULL);
+
+      if (err != SQLITE_OK)
+      {
+        throw OrthancSQLiteException("SQLite: Unable to flush the database");
+      }
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Orthanc/SQLite/Connection.h	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,177 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ *
+ * Copyright (C) 2012-2015 Sebastien Jodogne <s.jodogne@gmail.com>,
+ * Medical Physics Department, CHU of Liege, Belgium
+ *
+ * Copyright (c) 2012 The Chromium Authors. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *    * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *    * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *    * Neither the name of Google Inc., the name of the CHU of Liege,
+ * nor the names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ **/
+
+
+#pragma once
+
+#include "Statement.h"
+#include "IScalarFunction.h"
+
+#include <string>
+#include <map>
+
+struct sqlite3;
+struct sqlite3_stmt;
+
+#define SQLITE_FROM_HERE SQLite::StatementId(__FILE__, __LINE__)
+
+namespace Orthanc
+{
+  namespace SQLite
+  {
+    class Connection : NonCopyable
+    {
+      friend class Statement;
+      friend class Transaction;
+
+    private:
+      // All cached statements. Keeping a reference to these statements means that
+      // they'll remain active.
+      typedef std::map<StatementId, StatementReference*>  CachedStatements;
+      CachedStatements cachedStatements_;
+
+      // The actual sqlite database. Will be NULL before Init has been called or if
+      // Init resulted in an error.
+      sqlite3* db_;
+
+      // Number of currently-nested transactions.
+      int transactionNesting_;
+
+      // True if any of the currently nested transactions have been rolled back.
+      // When we get to the outermost transaction, this will determine if we do
+      // a rollback instead of a commit.
+      bool needsRollback_;
+
+      void ClearCache();
+
+      void CheckIsOpen() const;
+
+      sqlite3* GetWrappedObject()
+      {
+        return db_;
+      }
+
+      StatementReference& GetCachedStatement(const StatementId& id,
+                                             const char* sql);
+
+      bool DoesTableOrIndexExist(const char* name, 
+                                 const char* type) const;
+
+      void DoRollback();
+
+    public:
+      // The database is opened by calling Open[InMemory](). Any uncommitted
+      // transactions will be rolled back when this object is deleted.
+      Connection();
+      ~Connection();
+
+      void Open(const std::string& path);
+
+      void OpenInMemory();
+
+      void Close();
+
+      bool Execute(const char* sql);
+
+      bool Execute(const std::string& sql)
+      {
+        return Execute(sql.c_str());
+      }
+
+      void FlushToDisk();
+
+      IScalarFunction* Register(IScalarFunction* func);  // Takes the ownership of the function
+
+      // Info querying -------------------------------------------------------------
+
+      // Used to check a |sql| statement for syntactic validity. If the
+      // statement is valid SQL, returns true.
+      bool IsSQLValid(const char* sql);
+
+      // Returns true if the given table exists.
+      bool DoesTableExist(const char* table_name) const;
+
+      // Returns true if the given index exists.
+      bool DoesIndexExist(const char* index_name) const;
+    
+      // Returns true if a column with the given name exists in the given table.
+      bool DoesColumnExist(const char* table_name, const char* column_name) const;
+
+      // Returns sqlite's internal ID for the last inserted row. Valid only
+      // immediately after an insert.
+      int64_t GetLastInsertRowId() const;
+
+      // Returns sqlite's count of the number of rows modified by the last
+      // statement executed. Will be 0 if no statement has executed or the database
+      // is closed.
+      int GetLastChangeCount() const;
+
+      // Errors --------------------------------------------------------------------
+
+      // Returns the error code associated with the last sqlite operation.
+      int GetErrorCode() const;
+
+      // Returns the errno associated with GetErrorCode().  See
+      // SQLITE_LAST_ERRNO in SQLite documentation.
+      int GetLastErrno() const;
+
+      // Returns a pointer to a statically allocated string associated with the
+      // last sqlite operation.
+      const char* GetErrorMessage() const;
+
+
+      // Diagnostics (for unit tests) ----------------------------------------------
+
+      int ExecuteAndReturnErrorCode(const char* sql);
+    
+      bool HasCachedStatement(const StatementId& id) const
+      {
+        return cachedStatements_.find(id) != cachedStatements_.end();
+      }
+
+      int GetTransactionNesting() const
+      {
+        return transactionNesting_;
+      }
+
+      // Transactions --------------------------------------------------------------
+
+      bool BeginTransaction();
+      void RollbackTransaction();
+      bool CommitTransaction();      
+    };
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Orthanc/SQLite/FunctionContext.cpp	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,125 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ *
+ * Copyright (C) 2012-2015 Sebastien Jodogne <s.jodogne@gmail.com>,
+ * Medical Physics Department, CHU of Liege, Belgium
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *    * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *    * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *    * Neither the name of the CHU of Liege, nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ **/
+
+
+#if ORTHANC_SQLITE_STANDALONE != 1
+#include "../PrecompiledHeaders.h"
+#endif
+
+#include "FunctionContext.h"
+#include "OrthancSQLiteException.h"
+
+#include <sqlite3.h>
+
+namespace Orthanc
+{
+  namespace SQLite
+  {
+    FunctionContext::FunctionContext(struct sqlite3_context* context,
+                                     int argc,
+                                     struct ::Mem** argv)
+    {
+      assert(context != NULL);
+      assert(argc >= 0);
+      assert(argv != NULL);
+
+      context_ = context;
+      argc_ = static_cast<unsigned int>(argc);
+      argv_ = argv;
+    }
+
+    void FunctionContext::CheckIndex(unsigned int index) const
+    {
+      if (index >= argc_)
+      {
+        throw OrthancSQLiteException("Parameter out of range");
+      }
+    }
+
+    ColumnType FunctionContext::GetColumnType(unsigned int index) const
+    {
+      CheckIndex(index);
+      return static_cast<SQLite::ColumnType>(sqlite3_value_type(argv_[index]));
+    }
+
+    int FunctionContext::GetIntValue(unsigned int index) const
+    {
+      CheckIndex(index);
+      return sqlite3_value_int(argv_[index]);
+    }
+
+    int64_t FunctionContext::GetInt64Value(unsigned int index) const
+    {
+      CheckIndex(index);
+      return sqlite3_value_int64(argv_[index]);
+    }
+
+    double FunctionContext::GetDoubleValue(unsigned int index) const
+    {
+      CheckIndex(index);
+      return sqlite3_value_double(argv_[index]);
+    }
+
+    std::string FunctionContext::GetStringValue(unsigned int index) const
+    {
+      CheckIndex(index);
+      return std::string(reinterpret_cast<const char*>(sqlite3_value_text(argv_[index])));
+    }
+
+    bool FunctionContext::IsNullValue(unsigned int index) const
+    {
+      CheckIndex(index);
+      return sqlite3_value_type(argv_[index]) == SQLITE_NULL;
+    }
+  
+    void FunctionContext::SetNullResult()
+    {
+      sqlite3_result_null(context_);
+    }
+
+    void FunctionContext::SetIntResult(int value)
+    {
+      sqlite3_result_int(context_, value);
+    }
+
+    void FunctionContext::SetDoubleResult(double value)
+    {
+      sqlite3_result_double(context_, value);
+    }
+
+    void FunctionContext::SetStringResult(const std::string& str)
+    {
+      sqlite3_result_text(context_, str.data(), str.size(), SQLITE_TRANSIENT);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Orthanc/SQLite/FunctionContext.h	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,88 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ *
+ * Copyright (C) 2012-2015 Sebastien Jodogne <s.jodogne@gmail.com>,
+ * Medical Physics Department, CHU of Liege, Belgium
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *    * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *    * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *    * Neither the name of the CHU of Liege, nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ **/
+
+
+#pragma once
+
+#include "Statement.h"
+
+struct sqlite3_context;
+struct Mem;  // This corresponds to the opaque type "sqlite3_value"
+ 
+namespace Orthanc
+{
+  namespace SQLite
+  {
+    class FunctionContext : public NonCopyable
+    {
+      friend class Connection;
+
+    private:
+      struct sqlite3_context* context_;
+      unsigned int argc_;
+      struct ::Mem** argv_;
+
+      void CheckIndex(unsigned int index) const;
+
+    public:
+      FunctionContext(struct sqlite3_context* context,
+                      int argc,
+                      struct ::Mem** argv);
+
+      ColumnType GetColumnType(unsigned int index) const;
+ 
+      unsigned int GetParameterCount() const
+      {
+        return argc_;
+      }
+
+      int GetIntValue(unsigned int index) const;
+
+      int64_t GetInt64Value(unsigned int index) const;
+
+      double GetDoubleValue(unsigned int index) const;
+
+      std::string GetStringValue(unsigned int index) const;
+
+      bool IsNullValue(unsigned int index) const;
+  
+      void SetNullResult();
+
+      void SetIntResult(int value);
+
+      void SetDoubleResult(double value);
+
+      void SetStringResult(const std::string& str);
+    };
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Orthanc/SQLite/IScalarFunction.h	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,58 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ *
+ * Copyright (C) 2012-2015 Sebastien Jodogne <s.jodogne@gmail.com>,
+ * Medical Physics Department, CHU of Liege, Belgium
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *    * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *    * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *    * Neither the name of the CHU of Liege, nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ **/
+
+
+#pragma once
+
+#include "NonCopyable.h"
+#include "FunctionContext.h"
+
+namespace Orthanc
+{
+  namespace SQLite
+  {
+    class IScalarFunction : public NonCopyable
+    {
+    public:
+      virtual ~IScalarFunction()
+      {
+      }
+
+      virtual const char* GetName() const = 0;
+
+      virtual unsigned int GetCardinality() const = 0;
+
+      virtual void Compute(FunctionContext& context) = 0;
+    };
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Orthanc/SQLite/ITransaction.h	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,66 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ *
+ * Copyright (C) 2012-2015 Sebastien Jodogne <s.jodogne@gmail.com>,
+ * Medical Physics Department, CHU of Liege, Belgium
+ *
+ * Copyright (c) 2012 The Chromium Authors. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *    * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *    * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *    * Neither the name of Google Inc., the name of the CHU of Liege,
+ * nor the names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ **/
+
+
+#pragma once
+
+#include "NonCopyable.h"
+
+namespace Orthanc
+{
+  namespace SQLite
+  {
+    class ITransaction : public NonCopyable
+    {
+    public:
+      virtual ~ITransaction()
+      {
+      }
+
+      // Begins the transaction. This uses the default sqlite "deferred" transaction
+      // type, which means that the DB lock is lazily acquired the next time the
+      // database is accessed, not in the begin transaction command.
+      virtual void Begin() = 0;
+
+      // Rolls back the transaction. This will happen automatically if you do
+      // nothing when the transaction goes out of scope.
+      virtual void Rollback() = 0;
+
+      // Commits the transaction, returning true on success.
+      virtual void Commit() = 0;
+    };
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Orthanc/SQLite/NonCopyable.h	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,60 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ *
+ * Copyright (C) 2012-2015 Sebastien Jodogne <s.jodogne@gmail.com>,
+ * Medical Physics Department, CHU of Liege, Belgium
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *    * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *    * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *    * Neither the name of Google Inc., the name of the CHU of Liege,
+ * nor the names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ **/
+
+
+#pragma once
+
+namespace Orthanc
+{
+  namespace SQLite
+  {
+    // This class mimics "boost::noncopyable"
+    class NonCopyable
+    {
+    private:
+      NonCopyable(const NonCopyable&);
+
+      NonCopyable& operator= (const NonCopyable&);
+
+    protected:
+      NonCopyable()
+      {
+      }
+
+      ~NonCopyable()
+      {
+      }
+    };
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Orthanc/SQLite/OrthancSQLiteException.h	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,67 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ *
+ * Copyright (C) 2012-2015 Sebastien Jodogne <s.jodogne@gmail.com>,
+ * Medical Physics Department, CHU of Liege, Belgium
+ *
+ * Copyright (c) 2012 The Chromium Authors. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *    * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *    * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *    * Neither the name of Google Inc., the name of the CHU of Liege,
+ * nor the names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ **/
+
+
+#pragma once
+
+
+#if ORTHANC_SQLITE_STANDALONE == 1
+#include <stdexcept>
+
+namespace Orthanc
+{
+  namespace SQLite
+  {
+    class OrthancSQLiteException : public ::std::runtime_error
+    {
+    public:
+      OrthancSQLiteException(const std::string& what) :
+        ::std::runtime_error(what)
+      {
+      }
+
+      OrthancSQLiteException(const char* what) : 
+        ::std::runtime_error(what)
+      {
+      }
+    };
+  }
+}
+
+#else
+#  include "../OrthancException.h"
+#  define OrthancSQLiteException ::Orthanc::OrthancException
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Orthanc/SQLite/README.txt	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,40 @@
+Introduction
+============
+
+The code in this folder is a standalone object-oriented wrapper around
+SQLite3. It is derived from the code of Chromium:
+
+http://src.chromium.org/viewvc/chrome/trunk/src/sql/
+http://maxradi.us/documents/sqlite/
+
+
+Main differences with Chromium
+==============================
+
+* The reference counting mechanism has been reimplemented to make it 
+  simpler.
+* The OrthancException class is used for the exception mechanisms.
+* A statement is always valid (is_valid() always return true).
+* The classes and the methods have been renamed to meet Orthanc's
+  coding conventions.
+
+
+Reuse in another software
+=========================
+
+To use the Orthanc SQLite wrapper in another project than Orthanc, you
+just have to define the "ORTHANC_SQLITE_STANDALONE" macro.
+
+All the C++ exceptions generated by the wrapper will be objects of the
+class "::Orthanc::SQLite::OrthancSQLiteException", that derives from
+the standard exception class "::std::runtime_error".
+
+
+Licensing
+=========
+
+The code in this folder is licensed under the 3-clause BSD license, in
+order to respect the original license of the code.
+
+It is pretty straightforward to extract the code from this folder and
+to include it in another project. 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Orthanc/SQLite/Statement.cpp	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,340 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ *
+ * Copyright (C) 2012-2015 Sebastien Jodogne <s.jodogne@gmail.com>,
+ * Medical Physics Department, CHU of Liege, Belgium
+ *
+ * Copyright (c) 2012 The Chromium Authors. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *    * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *    * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *    * Neither the name of Google Inc., the name of the CHU of Liege,
+ * nor the names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ **/
+
+
+#if ORTHANC_SQLITE_STANDALONE != 1
+#include "../PrecompiledHeaders.h"
+#endif
+
+#include "Statement.h"
+#include "Connection.h"
+
+#include <sqlite3.h>
+#include <string.h>
+#include <stdio.h>
+#include <algorithm>
+
+#if ORTHANC_SQLITE_STANDALONE != 1
+#include <glog/logging.h>
+#endif
+
+#if defined(_MSC_VER)
+#define snprintf _snprintf
+#endif
+
+namespace Orthanc
+{
+  namespace SQLite
+  {
+    int Statement::CheckError(int err) const
+    {
+      bool succeeded = (err == SQLITE_OK || err == SQLITE_ROW || err == SQLITE_DONE);
+      if (!succeeded)
+      {
+        char buffer[128];
+        snprintf(buffer, sizeof(buffer) - 1, "SQLite error code %d", err);
+        throw OrthancSQLiteException(buffer);
+      }
+
+      return err;
+    }
+
+    void Statement::CheckOk(int err) const 
+    {
+      if (err == SQLITE_RANGE)
+      {
+        // Binding to a non-existent variable is evidence of a serious error.
+        throw OrthancSQLiteException("Bind value out of range");
+      }
+      else if (err != SQLITE_OK)
+      {
+        char buffer[128];
+        snprintf(buffer, sizeof(buffer) - 1, "SQLite error code %d", err);
+        throw OrthancSQLiteException(buffer);
+      }
+    }
+
+
+    Statement::Statement(Connection& database,
+                         const StatementId& id,
+                         const std::string& sql) : 
+      reference_(database.GetCachedStatement(id, sql.c_str()))
+    {
+      Reset(true);
+    }
+
+
+    Statement::Statement(Connection& database,
+                         const StatementId& id,
+                         const char* sql) : 
+      reference_(database.GetCachedStatement(id, sql))
+    {
+      Reset(true);
+    }
+
+
+    Statement::Statement(Connection& database,
+                         const std::string& sql) :
+      reference_(database.GetWrappedObject(), sql.c_str())
+    {
+    }
+
+
+    Statement::Statement(Connection& database,
+                         const char* sql) :
+      reference_(database.GetWrappedObject(), sql)
+    {
+    }
+
+
+    bool Statement::Run()
+    {
+#if ORTHANC_SQLITE_STANDALONE != 1
+      VLOG(1) << "SQLite::Statement::Run " << sqlite3_sql(GetStatement());
+#endif
+
+      return CheckError(sqlite3_step(GetStatement())) == SQLITE_DONE;
+    }
+
+    bool Statement::Step()
+    {
+#if ORTHANC_SQLITE_STANDALONE != 1
+      VLOG(1) << "SQLite::Statement::Step " << sqlite3_sql(GetStatement());
+#endif
+
+      return CheckError(sqlite3_step(GetStatement())) == SQLITE_ROW;
+    }
+
+    void Statement::Reset(bool clear_bound_vars) 
+    {
+      // We don't call CheckError() here because sqlite3_reset() returns
+      // the last error that Step() caused thereby generating a second
+      // spurious error callback.
+      if (clear_bound_vars)
+        sqlite3_clear_bindings(GetStatement());
+      //VLOG(1) << "SQLite::Statement::Reset";
+      sqlite3_reset(GetStatement());
+    }
+
+    std::string Statement::GetOriginalSQLStatement()
+    {
+      return std::string(sqlite3_sql(GetStatement()));
+    }
+
+
+    void Statement::BindNull(int col)
+    {
+      CheckOk(sqlite3_bind_null(GetStatement(), col + 1));
+    }
+
+    void Statement::BindBool(int col, bool val) 
+    {
+      BindInt(col, val ? 1 : 0);
+    }
+
+    void Statement::BindInt(int col, int val) 
+    {
+      CheckOk(sqlite3_bind_int(GetStatement(), col + 1, val));
+    }
+
+    void Statement::BindInt64(int col, int64_t val) 
+    {
+      CheckOk(sqlite3_bind_int64(GetStatement(), col + 1, val));
+    }
+
+    void Statement::BindDouble(int col, double val) 
+    {
+      CheckOk(sqlite3_bind_double(GetStatement(), col + 1, val));
+    }
+
+    void Statement::BindCString(int col, const char* val) 
+    {
+      CheckOk(sqlite3_bind_text(GetStatement(), col + 1, val, -1, SQLITE_TRANSIENT));
+    }
+
+    void Statement::BindString(int col, const std::string& val) 
+    {
+      CheckOk(sqlite3_bind_text(GetStatement(),
+                                col + 1,
+                                val.data(),
+                                val.size(),
+                                SQLITE_TRANSIENT));
+    }
+
+    /*void Statement::BindString16(int col, const string16& value) 
+      {
+      BindString(col, UTF16ToUTF8(value));
+      }*/
+
+    void Statement::BindBlob(int col, const void* val, int val_len) 
+    {
+      CheckOk(sqlite3_bind_blob(GetStatement(), col + 1, val, val_len, SQLITE_TRANSIENT));
+    }
+
+
+    int Statement::ColumnCount() const 
+    {
+      return sqlite3_column_count(GetStatement());
+    }
+
+
+    ColumnType Statement::GetColumnType(int col) const 
+    {
+      // Verify that our enum matches sqlite's values.
+      assert(COLUMN_TYPE_INTEGER == SQLITE_INTEGER);
+      assert(COLUMN_TYPE_FLOAT == SQLITE_FLOAT);
+      assert(COLUMN_TYPE_TEXT == SQLITE_TEXT);
+      assert(COLUMN_TYPE_BLOB == SQLITE_BLOB);
+      assert(COLUMN_TYPE_NULL == SQLITE_NULL);
+
+      return static_cast<ColumnType>(sqlite3_column_type(GetStatement(), col));
+    }
+
+    ColumnType Statement::GetDeclaredColumnType(int col) const 
+    {
+      std::string column_type(sqlite3_column_decltype(GetStatement(), col));
+      std::transform(column_type.begin(), column_type.end(), column_type.begin(), tolower);
+
+      if (column_type == "integer")
+        return COLUMN_TYPE_INTEGER;
+      else if (column_type == "float")
+        return COLUMN_TYPE_FLOAT;
+      else if (column_type == "text")
+        return COLUMN_TYPE_TEXT;
+      else if (column_type == "blob")
+        return COLUMN_TYPE_BLOB;
+
+      return COLUMN_TYPE_NULL;
+    }
+
+    bool Statement::ColumnIsNull(int col) const 
+    {
+      return sqlite3_column_type(GetStatement(), col) == SQLITE_NULL;
+    }
+
+    bool Statement::ColumnBool(int col) const 
+    {
+      return !!ColumnInt(col);
+    }
+
+    int Statement::ColumnInt(int col) const 
+    {
+      return sqlite3_column_int(GetStatement(), col);
+    }
+
+    int64_t Statement::ColumnInt64(int col) const 
+    {
+      return sqlite3_column_int64(GetStatement(), col);
+    }
+
+    double Statement::ColumnDouble(int col) const 
+    {
+      return sqlite3_column_double(GetStatement(), col);
+    }
+
+    std::string Statement::ColumnString(int col) const 
+    {
+      const char* str = reinterpret_cast<const char*>(
+        sqlite3_column_text(GetStatement(), col));
+      int len = sqlite3_column_bytes(GetStatement(), col);
+
+      std::string result;
+      if (str && len > 0)
+        result.assign(str, len);
+      return result;
+    }
+
+    /*string16 Statement::ColumnString16(int col) const 
+      {
+      std::string s = ColumnString(col);
+      return !s.empty() ? UTF8ToUTF16(s) : string16();
+      }*/
+
+    int Statement::ColumnByteLength(int col) const 
+    {
+      return sqlite3_column_bytes(GetStatement(), col);
+    }
+
+    const void* Statement::ColumnBlob(int col) const 
+    {
+      return sqlite3_column_blob(GetStatement(), col);
+    }
+
+    bool Statement::ColumnBlobAsString(int col, std::string* blob) 
+    {
+      const void* p = ColumnBlob(col);
+      size_t len = ColumnByteLength(col);
+      blob->resize(len);
+      if (blob->size() != len) {
+        return false;
+      }
+      blob->assign(reinterpret_cast<const char*>(p), len);
+      return true;
+    }
+
+    /*bool Statement::ColumnBlobAsString16(int col, string16* val) const 
+      {
+      const void* data = ColumnBlob(col);
+      size_t len = ColumnByteLength(col) / sizeof(char16);
+      val->resize(len);
+      if (val->size() != len)
+      return false;
+      val->assign(reinterpret_cast<const char16*>(data), len);
+      return true;
+      }*/
+
+    /*bool Statement::ColumnBlobAsVector(int col, std::vector<char>* val) const 
+    {
+      val->clear();
+
+      const void* data = sqlite3_column_blob(GetStatement(), col);
+      int len = sqlite3_column_bytes(GetStatement(), col);
+      if (data && len > 0) {
+        val->resize(len);
+        memcpy(&(*val)[0], data, len);
+      }
+      return true;
+      }*/
+
+    /*bool Statement::ColumnBlobAsVector(
+      int col,
+      std::vector<unsigned char>* val) const 
+    {
+      return ColumnBlobAsVector(col, reinterpret_cast< std::vector<char>* >(val));
+      }*/
+
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Orthanc/SQLite/Statement.h	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,174 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ *
+ * Copyright (C) 2012-2015 Sebastien Jodogne <s.jodogne@gmail.com>,
+ * Medical Physics Department, CHU of Liege, Belgium
+ *
+ * Copyright (c) 2012 The Chromium Authors. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *    * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *    * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *    * Neither the name of Google Inc., the name of the CHU of Liege,
+ * nor the names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ **/
+
+
+#pragma once
+
+#include "NonCopyable.h"
+#include "OrthancSQLiteException.h"
+#include "StatementId.h"
+#include "StatementReference.h"
+
+#include <vector>
+#include <stdint.h>
+
+#if ORTHANC_BUILD_UNIT_TESTS == 1
+#include <gtest/gtest_prod.h>
+#endif
+
+struct sqlite3_stmt;
+
+
+namespace Orthanc
+{
+  namespace SQLite
+  {
+    class Connection;
+
+    // Possible return values from ColumnType in a statement. These
+    // should match the values in sqlite3.h.
+    enum ColumnType 
+    {
+      COLUMN_TYPE_INTEGER = 1,
+      COLUMN_TYPE_FLOAT = 2,
+      COLUMN_TYPE_TEXT = 3,
+      COLUMN_TYPE_BLOB = 4,
+      COLUMN_TYPE_NULL = 5
+    };
+
+    class Statement : public NonCopyable
+    {
+      friend class Connection;
+
+#if ORTHANC_BUILD_UNIT_TESTS == 1
+      FRIEND_TEST(SQLStatementTest, Run);
+      FRIEND_TEST(SQLStatementTest, Reset);
+#endif
+
+    private:
+      StatementReference  reference_;
+
+      int CheckError(int err) const;
+
+      void CheckOk(int err) const;
+
+      struct sqlite3_stmt* GetStatement() const
+      {
+        return reference_.GetWrappedObject();
+      }
+
+    public:
+      Statement(Connection& database,
+                const std::string& sql);
+
+      Statement(Connection& database,
+                const StatementId& id,
+                const std::string& sql);
+
+      Statement(Connection& database,
+                const char* sql);
+
+      Statement(Connection& database,
+                const StatementId& id,
+                const char* sql);
+
+      ~Statement()
+      {
+        Reset();
+      }
+
+      bool Run();
+
+      bool Step();
+
+      // Diagnostics --------------------------------------------------------------
+
+      std::string GetOriginalSQLStatement();
+
+
+      // Binding -------------------------------------------------------------------
+
+      // These all take a 0-based argument index
+      void BindNull(int col);
+      void BindBool(int col, bool val);
+      void BindInt(int col, int val);
+      void BindInt64(int col, int64_t val);
+      void BindDouble(int col, double val);
+      void BindCString(int col, const char* val);
+      void BindString(int col, const std::string& val);
+      //void BindString16(int col, const string16& value);
+      void BindBlob(int col, const void* value, int value_len);
+
+
+      // Retrieving ----------------------------------------------------------------
+
+      // Returns the number of output columns in the result.
+      int ColumnCount() const;
+
+      // Returns the type associated with the given column.
+      //
+      // Watch out: the type may be undefined if you've done something to cause a
+      // "type conversion." This means requesting the value of a column of a type
+      // where that type is not the native type. For safety, call ColumnType only
+      // on a column before getting the value out in any way.
+      ColumnType GetColumnType(int col) const;
+      ColumnType GetDeclaredColumnType(int col) const;
+
+      // These all take a 0-based argument index.
+      bool ColumnIsNull(int col) const ;
+      bool ColumnBool(int col) const;
+      int ColumnInt(int col) const;
+      int64_t ColumnInt64(int col) const;
+      double ColumnDouble(int col) const;
+      std::string ColumnString(int col) const;
+      //string16 ColumnString16(int col) const;
+
+      // When reading a blob, you can get a raw pointer to the underlying data,
+      // along with the length, or you can just ask us to copy the blob into a
+      // vector. Danger! ColumnBlob may return NULL if there is no data!
+      int ColumnByteLength(int col) const;
+      const void* ColumnBlob(int col) const;
+      bool ColumnBlobAsString(int col, std::string* blob);
+      //bool ColumnBlobAsString16(int col, string16* val) const;
+      //bool ColumnBlobAsVector(int col, std::vector<char>* val) const;
+      //bool ColumnBlobAsVector(int col, std::vector<unsigned char>* val) const;
+
+      // Resets the statement to its initial condition. This includes any current
+      // result row, and also the bound variables if the |clear_bound_vars| is true.
+      void Reset(bool clear_bound_vars = true);
+    };
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Orthanc/SQLite/StatementId.cpp	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,58 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ *
+ * Copyright (C) 2012-2015 Sebastien Jodogne <s.jodogne@gmail.com>,
+ * Medical Physics Department, CHU of Liege, Belgium
+ *
+ * Copyright (c) 2012 The Chromium Authors. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *    * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *    * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *    * Neither the name of Google Inc., the name of the CHU of Liege,
+ * nor the names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ **/
+
+
+#if ORTHANC_SQLITE_STANDALONE != 1
+#include "../PrecompiledHeaders.h"
+#endif
+
+#include "StatementId.h"
+
+#include <string.h>
+
+namespace Orthanc
+{
+  namespace SQLite
+  {
+    bool StatementId::operator< (const StatementId& other) const
+    {
+      if (line_ != other.line_)
+        return line_ < other.line_;
+
+      return strcmp(file_, other.file_) < 0;
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Orthanc/SQLite/StatementId.h	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,60 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ *
+ * Copyright (C) 2012-2015 Sebastien Jodogne <s.jodogne@gmail.com>,
+ * Medical Physics Department, CHU of Liege, Belgium
+ *
+ * Copyright (c) 2012 The Chromium Authors. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *    * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *    * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *    * Neither the name of Google Inc., the name of the CHU of Liege,
+ * nor the names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ **/
+
+
+#pragma once
+
+namespace Orthanc
+{
+  namespace SQLite
+  {
+    class StatementId
+    {
+    private:
+      const char* file_;
+      int line_;
+
+      StatementId(); // Forbidden
+
+    public:
+      StatementId(const char* file, int line) : file_(file), line_(line)
+      {
+      }
+
+      bool operator< (const StatementId& other) const;
+    };
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Orthanc/SQLite/StatementReference.cpp	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,151 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ *
+ * Copyright (C) 2012-2015 Sebastien Jodogne <s.jodogne@gmail.com>,
+ * Medical Physics Department, CHU of Liege, Belgium
+ *
+ * Copyright (c) 2012 The Chromium Authors. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *    * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *    * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *    * Neither the name of Google Inc., the name of the CHU of Liege,
+ * nor the names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ **/
+
+
+#if ORTHANC_SQLITE_STANDALONE != 1
+#include "../PrecompiledHeaders.h"
+#endif
+
+#include "StatementReference.h"
+#include "OrthancSQLiteException.h"
+
+#if ORTHANC_SQLITE_STANDALONE != 1
+#include <glog/logging.h>
+#endif
+
+#include <cassert>
+#include "sqlite3.h"
+
+namespace Orthanc
+{
+  namespace SQLite
+  {
+    bool StatementReference::IsRoot() const
+    {
+      return root_ == NULL;
+    }
+
+    StatementReference::StatementReference()
+    {
+      root_ = NULL;
+      refCount_ = 0;
+      statement_ = NULL;
+      assert(IsRoot());
+    }
+
+    StatementReference::StatementReference(sqlite3* database,
+                                           const char* sql)
+    {
+      if (database == NULL || sql == NULL)
+      {
+        throw OrthancSQLiteException("Parameter out of range");
+      }
+
+      root_ = NULL;
+      refCount_ = 0;
+
+      int error = sqlite3_prepare_v2(database, sql, -1, &statement_, NULL);
+      if (error != SQLITE_OK)
+      {
+        throw OrthancSQLiteException("SQLite: " + std::string(sqlite3_errmsg(database)));
+      }
+
+      assert(IsRoot());
+    }
+
+    StatementReference::StatementReference(StatementReference& other)
+    {
+      refCount_ = 0;
+
+      if (other.IsRoot())
+      {
+        root_ = &other;
+      }
+      else
+      {
+        root_ = other.root_;
+      }
+
+      root_->refCount_++;
+      statement_ = root_->statement_;
+
+      assert(!IsRoot());
+    }
+
+    StatementReference::~StatementReference()
+    {
+      if (IsRoot())
+      {
+        if (refCount_ != 0)
+        {
+          // There remain references to this object. We cannot throw
+          // an exception because:
+          // http://www.parashift.com/c++-faq/dtors-shouldnt-throw.html
+
+#if ORTHANC_SQLITE_STANDALONE != 1
+          LOG(ERROR) << "Bad value of the reference counter";
+#endif
+        }
+        else if (statement_ != NULL)
+        {
+          sqlite3_finalize(statement_);
+        }
+      }
+      else
+      {
+        if (root_->refCount_ == 0)
+        {
+          // There remain references to this object. We cannot throw
+          // an exception because:
+          // http://www.parashift.com/c++-faq/dtors-shouldnt-throw.html
+
+#if ORTHANC_SQLITE_STANDALONE != 1
+          LOG(ERROR) << "Bad value of the reference counter";
+#endif
+        }
+        else
+        {
+          root_->refCount_--;
+        }
+      }
+    }
+
+    uint32_t StatementReference::GetReferenceCount() const
+    {
+      return refCount_;
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Orthanc/SQLite/StatementReference.h	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,81 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ *
+ * Copyright (C) 2012-2015 Sebastien Jodogne <s.jodogne@gmail.com>,
+ * Medical Physics Department, CHU of Liege, Belgium
+ *
+ * Copyright (c) 2012 The Chromium Authors. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *    * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *    * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *    * Neither the name of Google Inc., the name of the CHU of Liege,
+ * nor the names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ **/
+
+
+#pragma once
+
+#include "NonCopyable.h"
+
+#include <stdint.h>
+#include <cassert>
+#include <stdlib.h>
+
+struct sqlite3;
+struct sqlite3_stmt;
+
+namespace Orthanc
+{
+  namespace SQLite
+  {
+    class StatementReference : NonCopyable
+    {
+    private:
+      StatementReference* root_;   // Only used for non-root nodes
+      uint32_t refCount_;         // Only used for root node
+      struct sqlite3_stmt* statement_;
+
+      bool IsRoot() const;
+
+    public:
+      StatementReference();
+
+      StatementReference(sqlite3* database,
+                         const char* sql);
+
+      StatementReference(StatementReference& other);
+
+      ~StatementReference();
+
+      uint32_t GetReferenceCount() const;
+
+      struct sqlite3_stmt* GetWrappedObject() const
+      {
+        assert(statement_ != NULL);
+        return statement_;
+      }
+    };
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Orthanc/SQLite/Transaction.cpp	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,106 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ *
+ * Copyright (C) 2012-2015 Sebastien Jodogne <s.jodogne@gmail.com>,
+ * Medical Physics Department, CHU of Liege, Belgium
+ *
+ * Copyright (c) 2012 The Chromium Authors. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *    * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *    * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *    * Neither the name of Google Inc., the name of the CHU of Liege,
+ * nor the names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ **/
+
+
+#if ORTHANC_SQLITE_STANDALONE != 1
+#include "../PrecompiledHeaders.h"
+#endif
+
+#include "Transaction.h"
+#include "OrthancSQLiteException.h"
+
+namespace Orthanc
+{
+  namespace SQLite
+  {
+    Transaction::Transaction(Connection& connection) :
+      connection_(connection),
+      isOpen_(false)
+    {
+    }
+
+    Transaction::~Transaction()
+    {
+      if (isOpen_)
+      {
+        connection_.RollbackTransaction();
+      }
+    }
+
+    void Transaction::Begin()
+    {
+      if (isOpen_) 
+      {
+        throw OrthancSQLiteException("SQLite: Beginning a transaction twice!");
+      }
+
+      isOpen_ = connection_.BeginTransaction();
+      if (!isOpen_)
+      {
+        throw OrthancSQLiteException("SQLite: Unable to create a transaction");
+      }
+    }
+
+    void Transaction::Rollback() 
+    {
+      if (!isOpen_) 
+      {
+        throw OrthancSQLiteException("SQLite: Attempting to roll back a nonexistent transaction. "
+                                     "Did you remember to call Begin()?");
+      }
+
+      isOpen_ = false;
+
+      connection_.RollbackTransaction();
+    }
+
+    void Transaction::Commit() 
+    {
+      if (!isOpen_) 
+      {
+        throw OrthancSQLiteException("SQLite: Attempting to roll back a nonexistent transaction. "
+                                     "Did you remember to call Begin()?");
+      }
+
+      isOpen_ = false;
+
+      if (!connection_.CommitTransaction())
+      {
+        throw OrthancSQLiteException("SQLite: Failure when committing the transaction");
+      }
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Orthanc/SQLite/Transaction.h	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,71 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ *
+ * Copyright (C) 2012-2015 Sebastien Jodogne <s.jodogne@gmail.com>,
+ * Medical Physics Department, CHU of Liege, Belgium
+ *
+ * Copyright (c) 2012 The Chromium Authors. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *    * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *    * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *    * Neither the name of Google Inc., the name of the CHU of Liege,
+ * nor the names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ **/
+
+
+#pragma once
+
+#include "Connection.h"
+#include "ITransaction.h"
+
+namespace Orthanc
+{
+  namespace SQLite
+  {
+    class Transaction : public ITransaction
+    {
+    private:
+      Connection& connection_;
+
+      // True when the transaction is open, false when it's already been committed
+      // or rolled back.
+      bool isOpen_;
+
+    public:
+      explicit Transaction(Connection& connection);
+
+      virtual ~Transaction();
+
+      // Returns true when there is a transaction that has been successfully begun.
+      bool IsOpen() const { return isOpen_; }
+
+      virtual void Begin();
+
+      virtual void Rollback();
+
+      virtual void Commit();
+    };
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Orthanc/Toolbox.cpp	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,246 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, 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.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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 "PrecompiledHeaders.h"
+#include "Toolbox.h"
+
+#include "OrthancException.h"
+
+#include <stdint.h>
+#include <string.h>
+#include <boost/filesystem.hpp>
+#include <boost/filesystem/fstream.hpp>
+#include <algorithm>
+#include <ctype.h>
+
+#if defined(_WIN32)
+#include <windows.h>
+#include <process.h>   // For "_spawnvp()"
+#else
+#include <unistd.h>    // For "execvp()"
+#include <sys/wait.h>  // For "waitpid()"
+#endif
+
+#if defined(__APPLE__) && defined(__MACH__)
+#include <mach-o/dyld.h> /* _NSGetExecutablePath */
+#include <limits.h>      /* PATH_MAX */
+#endif
+
+#if defined(__linux) || defined(__FreeBSD_kernel__)
+#include <limits.h>      /* PATH_MAX */
+#include <signal.h>
+#include <unistd.h>
+#endif
+
+#if BOOST_HAS_LOCALE != 1
+#error Since version 0.7.6, Orthanc entirely relies on boost::locale
+#endif
+
+#include <boost/locale.hpp>
+
+
+
+namespace Orthanc
+{
+  void Toolbox::TokenizeString(std::vector<std::string>& result,
+                               const std::string& value,
+                               char separator)
+  {
+    result.clear();
+
+    std::string currentItem;
+
+    for (size_t i = 0; i < value.size(); i++)
+    {
+      if (value[i] == separator)
+      {
+        result.push_back(currentItem);
+        currentItem.clear();
+      }
+      else
+      {
+        currentItem.push_back(value[i]);
+      }
+    }
+
+    result.push_back(currentItem);
+  }
+
+
+  void Toolbox::CreateNewDirectory(const std::string& path)
+  {
+    if (boost::filesystem::exists(path))
+    {
+      if (!boost::filesystem::is_directory(path))
+      {
+        throw OrthancException("Cannot create the directory over an existing file: " + path);
+      }
+    }
+    else
+    {
+      if (!boost::filesystem::create_directories(path))
+      {
+        throw OrthancException("Unable to create the directory: " + path);
+      }
+    }
+  }
+
+
+  bool Toolbox::IsExistingFile(const std::string& path)
+  {
+    return boost::filesystem::exists(path);
+  }
+
+
+  void Toolbox::ReadFile(std::string& content,
+                         const std::string& path) 
+  {
+    boost::filesystem::ifstream f;
+    f.open(path, std::ifstream::in | std::ifstream::binary);
+    if (!f.good())
+    {
+      throw OrthancException(ErrorCode_InexistentFile);
+    }
+
+    // http://www.cplusplus.com/reference/iostream/istream/tellg/
+    f.seekg(0, std::ios::end);
+    std::streamsize size = f.tellg();
+    f.seekg(0, std::ios::beg);
+
+    content.resize(size);
+    if (size != 0)
+    {
+      f.read(reinterpret_cast<char*>(&content[0]), size);
+    }
+
+    f.close();
+  }
+
+
+  void Toolbox::WriteFile(const std::string& content,
+                          const std::string& path)
+  {
+    boost::filesystem::ofstream f;
+    f.open(path, std::ofstream::binary);
+    if (!f.good())
+    {
+      throw OrthancException(ErrorCode_CannotWriteFile);
+    }
+
+    if (content.size() != 0)
+    {
+      f.write(content.c_str(), content.size());
+    }
+
+    f.close();
+  }
+
+
+
+  void Toolbox::RemoveFile(const std::string& path)
+  {
+    if (boost::filesystem::exists(path))
+    {
+      if (boost::filesystem::is_regular_file(path))
+        boost::filesystem::remove(path);
+      else
+        throw OrthancException("The path is not a regular file: " + path);
+    }
+  }
+
+
+
+  void Toolbox::USleep(uint64_t microSeconds)
+  {
+#if defined(_WIN32)
+    ::Sleep(static_cast<DWORD>(microSeconds / static_cast<uint64_t>(1000)));
+#elif defined(__linux) || defined(__APPLE__) || defined(__FreeBSD_kernel__)
+    usleep(microSeconds);
+#else
+#error Support your platform here
+#endif
+  }
+
+
+  Endianness Toolbox::DetectEndianness()
+  {
+    // http://sourceforge.net/p/predef/wiki/Endianness/
+
+    uint8_t buffer[4];
+
+    buffer[0] = 0x00;
+    buffer[1] = 0x01;
+    buffer[2] = 0x02;
+    buffer[3] = 0x03;
+
+    switch (*((uint32_t *)buffer)) 
+    {
+      case 0x00010203: 
+        return Endianness_Big;
+
+      case 0x03020100: 
+        return Endianness_Little;
+        
+      default:
+        throw OrthancException(ErrorCode_NotImplemented);
+    }
+  }
+
+
+  std::string Toolbox::StripSpaces(const std::string& source)
+  {
+    size_t first = 0;
+
+    while (first < source.length() &&
+           isspace(source[first]))
+    {
+      first++;
+    }
+
+    if (first == source.length())
+    {
+      // String containing only spaces
+      return "";
+    }
+
+    size_t last = source.length();
+    while (last > first &&
+           isspace(source[last - 1]))
+    {
+      last--;
+    }          
+    
+    assert(first <= last);
+    return source.substr(first, last - first);
+  }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Orthanc/Toolbox.h	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,70 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, 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.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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 "Enumerations.h"
+
+#include <stdint.h>
+#include <vector>
+#include <string>
+#include <json/json.h>
+
+namespace Orthanc
+{
+  typedef std::vector<std::string> UriComponents;
+
+  namespace Toolbox
+  {
+    Endianness DetectEndianness();
+
+    void TokenizeString(std::vector<std::string>& result,
+                        const std::string& source,
+                        char separator);
+
+    void CreateNewDirectory(const std::string& path);
+
+    bool IsExistingFile(const std::string& path);
+
+    void ReadFile(std::string& content,
+                  const std::string& path);
+
+    void WriteFile(const std::string& content,
+                   const std::string& path);
+
+    void USleep(uint64_t microSeconds);
+
+    void RemoveFile(const std::string& path);
+
+    std::string StripSpaces(const std::string& source);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Orthanc/Uuid.cpp	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,162 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, 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.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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 "PrecompiledHeaders.h"
+#include "Uuid.h"
+
+// http://stackoverflow.com/a/1626302
+
+extern "C"
+{
+#ifdef WIN32
+#include <rpc.h>
+#else
+#include <uuid/uuid.h>
+#endif
+}
+
+#include <boost/filesystem.hpp>
+
+namespace Orthanc
+{
+  namespace Toolbox
+  {
+    std::string GenerateUuid()
+    {
+#ifdef WIN32
+      UUID uuid;
+      UuidCreate ( &uuid );
+
+      unsigned char * str;
+      UuidToStringA ( &uuid, &str );
+
+      std::string s( ( char* ) str );
+
+      RpcStringFreeA ( &str );
+#else
+      uuid_t uuid;
+      uuid_generate_random ( uuid );
+      char s[37];
+      uuid_unparse ( uuid, s );
+#endif
+      return s;
+    }
+
+
+    bool IsUuid(const std::string& str)
+    {
+      if (str.size() != 36)
+      {
+        return false;
+      }
+
+      for (size_t i = 0; i < str.length(); i++)
+      {
+        if (i == 8 || i == 13 || i == 18 || i == 23)
+        {
+          if (str[i] != '-')
+            return false;
+        }
+        else
+        {
+          if (!isalnum(str[i]))
+            return false;
+        }
+      }
+
+      return true;
+    }
+
+
+    bool StartsWithUuid(const std::string& str)
+    {
+      if (str.size() < 36)
+      {
+        return false;
+      }
+
+      if (str.size() == 36)
+      {
+        return IsUuid(str);
+      }
+
+      assert(str.size() > 36);
+      if (!isspace(str[36]))
+      {
+        return false;
+      }
+
+      return IsUuid(str.substr(0, 36));
+    }
+
+
+    static std::string CreateTemporaryPath(const char* extension)
+    {
+#if BOOST_HAS_FILESYSTEM_V3 == 1
+      boost::filesystem::path tmpDir = boost::filesystem::temp_directory_path();
+#elif defined(__linux__)
+      boost::filesystem::path tmpDir("/tmp");
+#else
+#error Support your platform here
+#endif
+
+      // We use UUID to create unique path to temporary files
+      std::string filename = "Orthanc-" + Orthanc::Toolbox::GenerateUuid();
+
+      if (extension != NULL)
+      {
+        filename.append(extension);
+      }
+
+      tmpDir /= filename;
+      return tmpDir.string();
+    }
+
+
+    TemporaryFile::TemporaryFile() : 
+      path_(CreateTemporaryPath(NULL))
+    {
+    }
+
+
+    TemporaryFile::TemporaryFile(const char* extension) :
+      path_(CreateTemporaryPath(extension))
+    {
+    }
+
+
+    TemporaryFile::~TemporaryFile()
+    {
+      boost::filesystem::remove(path_);
+    }  
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Orthanc/Uuid.h	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,86 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, 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.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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 <string>
+
+/**
+ * GUID vs. UUID
+ * The simple answer is: no difference, they are the same thing. Treat
+ * them as a 16 byte (128 bits) value that is used as a unique
+ * value. In Microsoft-speak they are called GUIDs, but call them
+ * UUIDs when not using Microsoft-speak.
+ * http://stackoverflow.com/questions/246930/is-there-any-difference-between-a-guid-and-a-uuid
+ **/
+
+#include "Toolbox.h"
+
+namespace Orthanc
+{
+  namespace Toolbox
+  {
+    std::string GenerateUuid();
+
+    bool IsUuid(const std::string& str);
+
+    bool StartsWithUuid(const std::string& str);
+
+    class TemporaryFile
+    {
+    private:
+      std::string path_;
+
+    public:
+      TemporaryFile();
+
+      TemporaryFile(const char* extension);
+
+      ~TemporaryFile();
+
+      const std::string& GetPath() const
+      {
+        return path_;
+      }
+
+      void Write(const std::string& content)
+      {
+        Toolbox::WriteFile(content, path_);
+      }
+
+      void Read(std::string& content) const
+      {
+        Toolbox::ReadFile(content, path_);
+      }
+    };
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/Cache/CacheIndex.h	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,65 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero 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
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../../Orthanc/IDynamicObject.h"
+
+#include <string>
+
+namespace OrthancPlugins
+{
+  class CacheIndex : public Orthanc::IDynamicObject
+  {
+  private:
+    int          bundle_;
+    std::string  item_;
+
+  public:
+    CacheIndex(const CacheIndex& other) :
+    bundle_(other.bundle_),
+    item_(other.item_)
+    {
+    }
+
+    CacheIndex(int bundle,
+               const std::string& item) :
+      bundle_(bundle),
+      item_(item)
+    {
+    }
+
+    int GetBundle() const
+    {
+      return bundle_;
+    }
+
+    const std::string& GetItem() const
+    {
+      return item_;
+    }
+
+    bool operator== (const CacheIndex& other) const
+    {
+      return (bundle_ == other.bundle_ &&
+              item_ == other.item_);
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/Cache/CacheManager.cpp	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,592 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero 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
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "CacheManager.h"
+
+#include "../../Orthanc/Uuid.h"
+#include "../../Orthanc/SQLite/Transaction.h"
+
+#include <boost/lexical_cast.hpp>
+
+
+namespace OrthancPlugins
+{
+  class CacheManager::Bundle
+  {
+  private:
+    uint32_t  count_;
+    uint64_t  space_;
+
+  public:
+    Bundle() : count_(0), space_(0)
+    {
+    }
+
+    Bundle(uint32_t count,
+           uint64_t space) : 
+      count_(count), space_(space)
+    {
+    }
+
+    uint32_t GetCount() const
+    {
+      return count_;
+    }
+
+    uint64_t GetSpace() const
+    {
+      return space_;
+    }
+
+    void Remove(uint64_t fileSize)
+    {
+      if (count_ == 0 ||
+          space_ < fileSize)
+      {
+        throw std::runtime_error("Internal error");
+      }
+
+      count_ -= 1;
+      space_ -= fileSize;
+    }
+
+    void Add(uint64_t fileSize)
+    {
+      count_ += 1;
+      space_ += fileSize;
+    }
+  };
+
+
+  class CacheManager::BundleQuota
+  {
+  private:   
+    uint32_t maxCount_;
+    uint64_t maxSpace_;
+
+  public:
+    BundleQuota(uint32_t maxCount,
+                uint64_t maxSpace) : 
+      maxCount_(maxCount), maxSpace_(maxSpace)
+    {
+    }
+
+    BundleQuota()
+    {
+      // Default quota
+      maxCount_ = 0;  // No limit on the number of files
+      maxSpace_ = 100 * 1024 * 1024;  // Max 100MB per bundle
+    }
+
+    uint32_t GetMaxCount() const
+    {
+      return maxCount_;
+    }
+
+    uint64_t GetMaxSpace() const
+    {
+      return maxSpace_;
+    }
+
+    bool IsSatisfied(const Bundle& bundle) const
+    {
+      if (maxCount_ != 0 &&
+          bundle.GetCount() > maxCount_)
+      {
+        return false;
+      }
+
+      if (maxSpace_ != 0 &&
+          bundle.GetSpace() > maxSpace_)
+      {
+        return false;
+      }
+
+      return true;
+    }
+  };
+
+
+  struct CacheManager::PImpl
+  {
+    Orthanc::SQLite::Connection& db_;
+    Orthanc::FilesystemStorage& storage_;
+
+    bool sanityCheck_;
+    Bundles  bundles_;
+    BundleQuota  defaultQuota_;
+    BundleQuotas  quotas_;
+
+    PImpl(Orthanc::SQLite::Connection& db,
+          Orthanc::FilesystemStorage& storage) :
+      db_(db), 
+      storage_(storage), 
+      sanityCheck_(false)
+    {
+    }
+  };
+
+
+  const CacheManager::BundleQuota& CacheManager::GetBundleQuota(int bundleIndex) const
+  {
+    BundleQuotas::const_iterator found = pimpl_->quotas_.find(bundleIndex);
+
+    if (found == pimpl_->quotas_.end())
+    {
+      return pimpl_->defaultQuota_;
+    }
+    else
+    {
+      return found->second;
+    }
+  }
+
+
+  CacheManager::Bundle CacheManager::GetBundle(int bundleIndex) const
+  {
+    Bundles::const_iterator it = pimpl_->bundles_.find(bundleIndex);
+  
+    if (it == pimpl_->bundles_.end())
+    {
+      return Bundle();
+    }
+    else
+    {
+      return it->second;
+    }
+  }
+
+
+  void CacheManager::MakeRoom(Bundle& bundle,
+                              std::list<std::string>& toRemove,
+                              int bundleIndex,
+                              const BundleQuota& quota)
+  {
+    using namespace Orthanc;
+
+    toRemove.clear();
+
+    // Make room in the bundle
+    while (!quota.IsSatisfied(bundle))
+    {
+      SQLite::Statement s(pimpl_->db_, SQLITE_FROM_HERE, "SELECT seq, fileUuid, fileSize FROM Cache WHERE bundle=? ORDER BY seq");
+      s.BindInt(0, bundleIndex);
+
+      if (s.Step())
+      {
+        SQLite::Statement t(pimpl_->db_, SQLITE_FROM_HERE, "DELETE FROM Cache WHERE seq=?");
+        t.BindInt64(0, s.ColumnInt64(0));
+        t.Run();
+
+        toRemove.push_back(s.ColumnString(1));
+        bundle.Remove(s.ColumnInt64(2));
+      }
+      else
+      {
+        // Should never happen
+        throw std::runtime_error("Internal error");
+      }
+    }
+  }
+
+
+
+  void CacheManager::EnsureQuota(int bundleIndex,
+                                 const BundleQuota& quota)
+  {
+    using namespace Orthanc;
+
+    // Remove the cached files that exceed the quota
+    std::auto_ptr<SQLite::Transaction> transaction(new SQLite::Transaction(pimpl_->db_));
+    transaction->Begin();
+
+    Bundle bundle = GetBundle(bundleIndex);
+
+    std::list<std::string> toRemove;
+    MakeRoom(bundle, toRemove, bundleIndex, quota);
+
+    transaction->Commit();
+    for (std::list<std::string>::const_iterator
+           it = toRemove.begin(); it != toRemove.end(); it++)
+    {
+      pimpl_->storage_.Remove(*it);
+    }
+
+    pimpl_->bundles_[bundleIndex] = bundle;
+  }
+
+
+
+  void CacheManager::ReadBundleStatistics()
+  {
+    using namespace Orthanc;
+
+    pimpl_->bundles_.clear();
+
+    SQLite::Statement s(pimpl_->db_, SQLITE_FROM_HERE, "SELECT bundle,COUNT(*),SUM(fileSize) FROM Cache GROUP BY bundle");
+    while (s.Step())
+    {
+      int index = s.ColumnInt(0);
+      Bundle bundle(static_cast<uint32_t>(s.ColumnInt(1)),
+                    static_cast<uint64_t>(s.ColumnInt64(2)));
+      pimpl_->bundles_[index] = bundle;
+    }
+  }
+
+
+
+  void CacheManager::SanityCheck()
+  {
+    if (!pimpl_->sanityCheck_)
+    {
+      return;
+    }
+
+    using namespace Orthanc;
+
+    SQLite::Statement s(pimpl_->db_, SQLITE_FROM_HERE, "SELECT bundle,COUNT(*),SUM(fileSize) FROM Cache GROUP BY bundle");
+    while (s.Step())
+    {
+      const Bundle& bundle = GetBundle(s.ColumnInt(0));
+      if (bundle.GetCount() != static_cast<uint32_t>(s.ColumnInt(1)) ||
+          bundle.GetSpace() != static_cast<uint64_t>(s.ColumnInt64(2)))
+      {
+        throw std::runtime_error("SANITY ERROR in cache: " + boost::lexical_cast<std::string>(bundle.GetCount()) 
+                                 + "/" + boost::lexical_cast<std::string>(bundle.GetSpace())
+                                 + " vs " + boost::lexical_cast<std::string>(s.ColumnInt(1)) + "/"
+                                 + boost::lexical_cast<std::string>(s.ColumnInt64(2)));
+      }
+    }
+  }
+
+
+
+  CacheManager::CacheManager(Orthanc::SQLite::Connection& db,
+                             Orthanc::FilesystemStorage& storage) :
+    pimpl_(new PImpl(db, storage))
+  {
+    Open();
+    ReadBundleStatistics();
+  }
+
+
+  void CacheManager::SetSanityCheckEnabled(bool enabled)
+  {
+    pimpl_->sanityCheck_ = enabled;
+  }
+
+
+  void CacheManager::Open()
+  {
+    if (!pimpl_->db_.DoesTableExist("Cache"))
+    {
+      pimpl_->db_.Execute("CREATE TABLE Cache(seq INTEGER PRIMARY KEY, bundle INTEGER, item TEXT, fileUuid TEXT, fileSize INT);");
+      pimpl_->db_.Execute("CREATE INDEX CacheBundles ON Cache(bundle);");
+      pimpl_->db_.Execute("CREATE INDEX CacheIndex ON Cache(bundle, item);");
+    }
+
+    // Performance tuning of SQLite with PRAGMAs
+    // http://www.sqlite.org/pragma.html
+    pimpl_->db_.Execute("PRAGMA SYNCHRONOUS=OFF;");
+    pimpl_->db_.Execute("PRAGMA JOURNAL_MODE=WAL;");
+    pimpl_->db_.Execute("PRAGMA LOCKING_MODE=EXCLUSIVE;");
+  }
+
+
+  void CacheManager::Store(int bundleIndex,
+                           const std::string& item,
+                           const std::string& content)
+  {
+    SanityCheck();
+
+    const BundleQuota quota = GetBundleQuota(bundleIndex);
+
+    if (quota.GetMaxSpace() > 0 &&
+        content.size() > quota.GetMaxSpace())
+    {
+      // Cannot store such a large instance into the cache, forget about it
+      return;
+    }
+
+    using namespace Orthanc;
+
+    std::auto_ptr<SQLite::Transaction> transaction(new SQLite::Transaction(pimpl_->db_));
+    transaction->Begin();
+
+    Bundle bundle = GetBundle(bundleIndex);
+
+    std::list<std::string>  toRemove;
+    bundle.Add(content.size());
+    MakeRoom(bundle, toRemove, bundleIndex, quota);
+
+    // Store the cached content on the disk
+    const char* data = content.size() ? &content[0] : NULL;
+    std::string uuid = Toolbox::GenerateUuid();
+    pimpl_->storage_.Create(uuid, data, content.size());
+
+    bool ok = true;
+
+    // Remove the previous cached value. This might happen if the same
+    // item is accessed very quickly twice: Another factory could have
+    // been cached a value before the check for existence in Access().
+    {
+      SQLite::Statement s(pimpl_->db_, SQLITE_FROM_HERE, "SELECT seq, fileUuid, fileSize FROM Cache WHERE bundle=? AND item=?");
+      s.BindInt(0, bundleIndex);
+      s.BindString(1, item);
+      if (s.Step())
+      {
+        SQLite::Statement t(pimpl_->db_, SQLITE_FROM_HERE, "DELETE FROM Cache WHERE seq=?");
+        t.BindInt64(0, s.ColumnInt64(0));
+        t.Run();
+
+        toRemove.push_back(s.ColumnString(1));
+        bundle.Remove(s.ColumnInt64(2));
+      }
+    }
+
+    if (ok)
+    {
+      SQLite::Statement s(pimpl_->db_, SQLITE_FROM_HERE, "INSERT INTO Cache VALUES(NULL, ?, ?, ?, ?)");
+      s.BindInt(0, bundleIndex);
+      s.BindString(1, item);
+      s.BindString(2, uuid);
+      s.BindInt64(3, content.size());
+
+      if (!s.Run())
+      {
+        ok = false;
+      }
+    }
+
+    if (!ok)
+    {
+      // Error: Remove the stored file
+      pimpl_->storage_.Remove(uuid);
+    }
+    else
+    {
+      transaction->Commit();
+
+      pimpl_->bundles_[bundleIndex] = bundle;
+    
+      for (std::list<std::string>::const_iterator
+             it = toRemove.begin(); it != toRemove.end(); it++)
+      {
+        pimpl_->storage_.Remove(*it);
+      }
+    }
+
+    SanityCheck();
+  }
+
+
+
+  bool CacheManager::LocateInCache(std::string& uuid,
+                                   uint64_t& size,
+                                   int bundle,
+                                   const std::string& item)
+  {
+    using namespace Orthanc;
+    SanityCheck();
+
+    std::auto_ptr<SQLite::Transaction> transaction(new SQLite::Transaction(pimpl_->db_));
+    transaction->Begin();
+
+    SQLite::Statement s(pimpl_->db_, SQLITE_FROM_HERE, "SELECT seq, fileUuid, fileSize FROM Cache WHERE bundle=? AND item=?");
+    s.BindInt(0, bundle);
+    s.BindString(1, item);
+    if (!s.Step())
+    {
+      return false;
+    }
+
+    int64_t seq = s.ColumnInt64(0);
+    uuid = s.ColumnString(1);
+    size = s.ColumnInt64(2);
+
+    // Touch the cache to fulfill the LRU scheme.
+    SQLite::Statement t(pimpl_->db_, SQLITE_FROM_HERE, "DELETE FROM Cache WHERE seq=?");
+    t.BindInt64(0, seq);
+    if (t.Run())
+    {
+      SQLite::Statement u(pimpl_->db_, SQLITE_FROM_HERE, "INSERT INTO Cache VALUES(NULL, ?, ?, ?, ?)");
+      u.BindInt(0, bundle);
+      u.BindString(1, item);
+      u.BindString(2, uuid);
+      u.BindInt64(3, size);
+      if (u.Run())
+      {
+        // Everything was OK. Commit the changes to the cache.
+        transaction->Commit();
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+
+  bool CacheManager::IsCached(int bundle,
+                              const std::string& item)
+  {
+    std::string uuid;
+    uint64_t size;
+    return LocateInCache(uuid, size, bundle, item);
+  }
+
+
+  bool CacheManager::Access(std::string& content,
+                            int bundle,
+                            const std::string& item)
+  {
+    std::string uuid;
+    uint64_t size;
+    if (!LocateInCache(uuid, size, bundle, item))
+    {
+      return false;
+    }
+
+    bool ok;
+    try
+    {
+      pimpl_->storage_.Read(content, uuid);
+      ok = (content.size() == size);
+    }
+    catch (std::runtime_error&)
+    {
+      ok = false;
+    }
+
+    if (ok)
+    {
+      return true;
+    }
+    else
+    {
+      throw std::runtime_error("Error in the filesystem");
+    }
+  }
+
+
+  void CacheManager::Invalidate(int bundleIndex,
+                                const std::string& item)
+  {
+    using namespace Orthanc;
+    SanityCheck();
+
+    std::auto_ptr<SQLite::Transaction> transaction(new SQLite::Transaction(pimpl_->db_));
+    transaction->Begin();
+
+    Bundle bundle = GetBundle(bundleIndex);
+
+    SQLite::Statement s(pimpl_->db_, SQLITE_FROM_HERE, "SELECT seq, fileUuid, fileSize FROM Cache WHERE bundle=? AND item=?");
+    s.BindInt(0, bundleIndex);
+    s.BindString(1, item);
+    if (s.Step())
+    {
+      int64_t seq = s.ColumnInt64(0);
+      const std::string uuid = s.ColumnString(1);
+      uint64_t expectedSize = s.ColumnInt64(2);
+      bundle.Remove(expectedSize);
+
+      SQLite::Statement t(pimpl_->db_, SQLITE_FROM_HERE, "DELETE FROM Cache WHERE seq=?");
+      t.BindInt64(0, seq);
+      if (t.Run())
+      {
+        transaction->Commit();
+        pimpl_->bundles_[bundleIndex] = bundle;
+        pimpl_->storage_.Remove(uuid);
+      }
+    }
+  }
+
+
+
+  void CacheManager::SetBundleQuota(int bundle,
+                                    uint32_t maxCount,
+                                    uint64_t maxSpace)
+  {
+    SanityCheck();
+
+    const BundleQuota quota(maxCount, maxSpace);
+    EnsureQuota(bundle, quota);
+    pimpl_->quotas_[bundle] = quota;
+
+    SanityCheck();
+  }
+
+  void CacheManager::SetDefaultQuota(uint32_t maxCount,
+                                     uint64_t maxSpace)
+  {
+    using namespace Orthanc;
+    SanityCheck();
+
+    pimpl_->defaultQuota_ = BundleQuota(maxCount, maxSpace);
+
+    SQLite::Statement s(pimpl_->db_, SQLITE_FROM_HERE, "SELECT DISTINCT bundle FROM Cache");
+    while (s.Step())
+    {
+      EnsureQuota(s.ColumnInt(0), pimpl_->defaultQuota_);
+    }
+
+    SanityCheck();
+  }
+
+
+  void CacheManager::Clear()
+  {
+    using namespace Orthanc;
+    SanityCheck();
+
+    SQLite::Statement s(pimpl_->db_, SQLITE_FROM_HERE, "SELECT fileUuid FROM Cache");
+    while (s.Step())
+    {
+      pimpl_->storage_.Remove(s.ColumnString(0));    
+    }  
+
+    SQLite::Statement t(pimpl_->db_, SQLITE_FROM_HERE, "DELETE FROM Cache");
+    t.Run();
+
+    ReadBundleStatistics();
+    SanityCheck();
+  }
+
+
+
+  void CacheManager::Clear(int bundle)
+  {
+    using namespace Orthanc;
+    SanityCheck();
+
+    SQLite::Statement s(pimpl_->db_, SQLITE_FROM_HERE, "SELECT fileUuid FROM Cache WHERE bundle=?");
+    s.BindInt(0, bundle);
+    while (s.Step())
+    {
+      pimpl_->storage_.Remove(s.ColumnString(0));
+    }  
+
+    SQLite::Statement t(pimpl_->db_, SQLITE_FROM_HERE, "DELETE FROM Cache WHERE bundle=?");
+    t.BindInt(0, bundle);
+    t.Run();
+
+    ReadBundleStatistics();
+    SanityCheck();
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/Cache/CacheManager.h	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,96 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero 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
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../../Orthanc/SQLite/Connection.h"
+#include "../../Orthanc/FileStorage/FilesystemStorage.h"
+
+namespace OrthancPlugins
+{
+  class CacheManager : public boost::noncopyable
+  {
+  private:
+    struct PImpl;
+    boost::shared_ptr<PImpl> pimpl_;
+
+    class Bundle;
+    class BundleQuota;
+
+    typedef std::map<int, Bundle>  Bundles;
+    typedef std::map<int, BundleQuota>  BundleQuotas;
+
+    const BundleQuota& GetBundleQuota(int bundleIndex) const;
+
+    Bundle GetBundle(int bundleIndex) const;
+
+    void MakeRoom(Bundle& bundle,
+                  std::list<std::string>& toRemove,
+                  int bundleIndex,
+                  const BundleQuota& quota);
+
+    void EnsureQuota(int bundleIndex,
+                     const BundleQuota& quota);
+
+    void ReadBundleStatistics();
+
+    void Open();
+
+    bool LocateInCache(std::string& uuid,
+                       uint64_t& size,
+                       int bundle,
+                       const std::string& item);
+
+    void SanityCheck();  // Only for debug
+
+
+  public:
+    CacheManager(Orthanc::SQLite::Connection& db,
+                 Orthanc::FilesystemStorage& storage);
+
+    void SetSanityCheckEnabled(bool enabled);
+
+    void Clear();
+
+    void Clear(int bundle);
+
+    void SetBundleQuota(int bundle,
+                        uint32_t maxCount,
+                        uint64_t maxSpace);
+
+    void SetDefaultQuota(uint32_t maxCount,
+                         uint64_t maxSpace);
+
+    bool IsCached(int bundle,
+                  const std::string& item);
+
+    bool Access(std::string& content,
+                int bundle,
+                const std::string& item);
+
+    void Invalidate(int bundle,
+                    const std::string& item);
+
+    void Store(int bundle,
+               const std::string& item,
+               const std::string& content);
+
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/Cache/CacheScheduler.cpp	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,385 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero 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
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "CacheScheduler.h"
+
+#include "CacheIndex.h"
+
+#include "../../Orthanc/OrthancException.h"
+#include <stdio.h>
+
+namespace OrthancPlugins
+{
+  class DynamicString : public Orthanc::IDynamicObject
+  {
+  private:
+    std::string   value_;
+
+  public:
+    DynamicString(const std::string& value) : value_(value)
+    {
+    }
+
+    const std::string& GetValue() const
+    {
+      return value_;
+    }
+  };
+
+
+  class CacheScheduler::PrefetchQueue : public boost::noncopyable
+  {
+  private:
+    boost::mutex                 mutex_;
+    Orthanc::SharedMessageQueue  queue_;
+    std::set<std::string>        content_;
+
+  public:
+    PrefetchQueue(size_t maxSize) : queue_(maxSize)
+    {
+      queue_.SetLifoPolicy();
+    }
+
+    void Enqueue(const std::string& item)
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+
+      if (content_.find(item) != content_.end())
+      {
+        // This cache index is already pending in the queue
+        return;
+      }
+
+      content_.insert(item);
+      queue_.Enqueue(new DynamicString(item));
+    }
+
+    DynamicString* Dequeue(int32_t msTimeout)
+    {
+      std::auto_ptr<Orthanc::IDynamicObject> message(queue_.Dequeue(msTimeout));
+      if (message.get() == NULL)
+      {
+        return NULL;
+      }
+
+      const DynamicString& index = dynamic_cast<const DynamicString&>(*message);
+
+      {
+        boost::mutex::scoped_lock lock(mutex_);
+        content_.erase(index.GetValue());
+      }
+
+      return dynamic_cast<DynamicString*>(message.release());
+    }
+  };
+
+
+  class CacheScheduler::Prefetcher : public boost::noncopyable
+  {
+  private:
+    int             bundleIndex_;
+    ICacheFactory&  factory_;
+    CacheManager&   cache_;
+    boost::mutex&   cacheMutex_;
+    PrefetchQueue&  queue_;
+
+    bool            done_;
+    boost::thread   thread_;
+    boost::mutex    invalidatedMutex_;
+    bool            invalidated_;
+    std::string     prefetching_;
+
+    static void Worker(Prefetcher* that)
+    {
+      while (!(that->done_))
+      {
+        std::auto_ptr<DynamicString> prefetch(that->queue_.Dequeue(500));
+
+        if (prefetch.get() != NULL)
+        {
+          {
+            boost::mutex::scoped_lock lock(that->invalidatedMutex_);
+            that->invalidated_ = false;
+            that->prefetching_ = prefetch->GetValue();
+          }
+
+          {
+            boost::mutex::scoped_lock lock(that->cacheMutex_);
+            if (that->cache_.IsCached(that->bundleIndex_, prefetch->GetValue()))
+            {
+              // This item is already cached
+              continue;
+            }
+          }
+
+          std::string content;
+          if (!that->factory_.Create(content, prefetch->GetValue()))
+          {
+            // The factory cannot generate this item
+            continue;
+          }
+
+          {
+            boost::mutex::scoped_lock lock(that->invalidatedMutex_);
+            if (that->invalidated_)
+            {
+              // This item has been invalidated
+              continue;
+            }
+              
+            {
+              boost::mutex::scoped_lock lock2(that->cacheMutex_);
+              that->cache_.Store(that->bundleIndex_, prefetch->GetValue(), content);
+            }
+          }
+        }
+      }
+    }
+
+
+  public:
+    Prefetcher(int             bundleIndex,
+               ICacheFactory&  factory,
+               CacheManager&   cache,
+               boost::mutex&   cacheMutex,
+               PrefetchQueue&  queue) :
+      bundleIndex_(bundleIndex),
+      factory_(factory),
+      cache_(cache),
+      cacheMutex_(cacheMutex),
+      queue_(queue)
+    {
+      done_ = false;
+      thread_ = boost::thread(Worker, this);
+    }
+
+    ~Prefetcher()
+    {
+      done_ = true;
+      if (thread_.joinable())
+      {
+        thread_.join();
+      }
+    }
+
+    void SignalInvalidated(const std::string& item)
+    {
+      boost::mutex::scoped_lock lock(invalidatedMutex_);
+
+      if (prefetching_ == item)
+      {
+        invalidated_ = true;
+      }
+    }
+  };
+
+
+
+  class CacheScheduler::BundleScheduler
+  {
+  private:
+    std::auto_ptr<ICacheFactory>   factory_;
+    PrefetchQueue                  queue_;
+    std::vector<Prefetcher*>       prefetchers_;
+
+  public:
+    BundleScheduler(int bundleIndex,
+                    ICacheFactory* factory,
+                    CacheManager&   cache,
+                    boost::mutex&   cacheMutex,
+                    size_t numThreads,
+                    size_t queueSize) :
+      factory_(factory),
+      queue_(queueSize)
+    {
+      prefetchers_.resize(numThreads, NULL);
+
+      for (size_t i = 0; i < numThreads; i++)
+      {
+        prefetchers_[i] = new Prefetcher(bundleIndex, *factory_, cache, cacheMutex, queue_);
+      }
+    }
+
+    ~BundleScheduler()
+    {
+      for (size_t i = 0; i < prefetchers_.size(); i++)
+      {
+        if (prefetchers_[i] != NULL)
+          delete prefetchers_[i];
+      }
+    }
+
+    void Invalidate(const std::string& item)
+    {
+      for (size_t i = 0; i < prefetchers_.size(); i++)
+      {
+        prefetchers_[i]->SignalInvalidated(item);
+      }
+    }
+
+    void Prefetch(const std::string& item)
+    {
+      queue_.Enqueue(item);
+    }
+
+    bool CallFactory(std::string& content,
+                     const std::string& item)
+    {
+      content.clear();
+      return factory_->Create(content, item);
+    }
+  };
+
+
+
+  CacheScheduler::BundleScheduler&  CacheScheduler::GetBundleScheduler(unsigned int bundleIndex)
+  {
+    boost::mutex::scoped_lock lock(factoryMutex_);
+
+    BundleSchedulers::iterator it = bundles_.find(bundleIndex);
+    if (it == bundles_.end())
+    {
+      throw Orthanc::OrthancException("No factory associated with this bundle");
+    }
+
+    return *(it->second);
+  }
+
+
+  
+  CacheScheduler::CacheScheduler(CacheManager& cache,
+                                 unsigned int maxPrefetchSize) :
+    maxPrefetchSize_(maxPrefetchSize),
+    cache_(cache),
+    policy_(NULL)
+  {
+  }
+
+
+  CacheScheduler::~CacheScheduler()
+  {
+    for (BundleSchedulers::iterator it = bundles_.begin(); 
+         it != bundles_.end(); it++)
+    {
+      delete it->second;
+    }
+  }
+
+
+  void CacheScheduler::Register(int bundle, 
+                                ICacheFactory* factory /* takes ownership */,
+                                size_t  numThreads)
+  {
+    boost::mutex::scoped_lock lock(factoryMutex_);
+
+    BundleSchedulers::iterator it = bundles_.find(bundle);
+    if (it != bundles_.end())
+    {
+      // This bundle is already registered
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+
+    bundles_[bundle] = new BundleScheduler(bundle, factory, cache_, cacheMutex_, numThreads, maxPrefetchSize_);
+  }
+
+
+  void CacheScheduler::Invalidate(int bundle,
+                                  const std::string& item)
+  {
+    {
+      boost::mutex::scoped_lock lock(cacheMutex_);
+      cache_.Invalidate(bundle, item);
+    }
+
+    GetBundleScheduler(bundle).Invalidate(item);
+  }
+
+
+  void CacheScheduler::ApplyPrefetchPolicy(int bundle,
+                                           const std::string& item,
+                                           const std::string& content)
+  {
+    boost::recursive_mutex::scoped_lock lock(policyMutex_);
+
+    if (policy_.get() != NULL)
+    {
+      std::list<CacheIndex> toPrefetch;
+
+      {
+        policy_->Apply(toPrefetch, *this, CacheIndex(bundle, item), content);
+      }
+
+      for (std::list<CacheIndex>::const_reverse_iterator
+             it = toPrefetch.rbegin(); it != toPrefetch.rend(); ++it)
+      {
+        Prefetch(it->GetBundle(), it->GetItem());
+      }
+    }
+  }
+
+
+  bool CacheScheduler::Access(std::string& content,
+                              int bundle,
+                              const std::string& item)
+  {
+    bool existing;
+
+    {
+      boost::mutex::scoped_lock lock(cacheMutex_);
+      existing = cache_.Access(content, bundle, item);
+    }
+
+    if (existing)
+    {
+      ApplyPrefetchPolicy(bundle, item, content);
+      return true;
+    }
+
+    if (!GetBundleScheduler(bundle).CallFactory(content, item))
+    {
+      // This item cannot be generated by the factory
+      return false;
+    }
+
+    {
+      boost::mutex::scoped_lock lock(cacheMutex_);
+      cache_.Store(bundle, item, content);
+    }
+
+    ApplyPrefetchPolicy(bundle, item, content);
+
+    return true;
+  }
+
+
+  void CacheScheduler::Prefetch(int bundle,
+                                const std::string& item)
+  {
+    GetBundleScheduler(bundle).Prefetch(item);
+  }
+
+
+  void CacheScheduler::RegisterPolicy(IPrefetchPolicy* policy)
+  {
+    boost::recursive_mutex::scoped_lock lock(policyMutex_);
+    policy_.reset(policy);
+  }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/Cache/CacheScheduler.h	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,79 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero 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
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "CacheManager.h"
+#include "ICacheFactory.h"
+#include "IPrefetchPolicy.h"
+#include "../../Orthanc/MultiThreading/SharedMessageQueue.h"
+
+#include <boost/thread.hpp>
+#include <stdio.h>
+
+namespace OrthancPlugins
+{
+  class CacheScheduler : public boost::noncopyable
+  {
+  private:
+    class Prefetcher;
+    class PrefetchQueue;
+    class BundleScheduler;
+
+    typedef std::map<int, BundleScheduler*>  BundleSchedulers;
+
+    size_t   maxPrefetchSize_;
+
+    boost::mutex                    cacheMutex_;
+    boost::mutex                    factoryMutex_;
+    boost::recursive_mutex          policyMutex_;
+    CacheManager&                   cache_;
+    std::auto_ptr<IPrefetchPolicy>  policy_;
+    BundleSchedulers                bundles_;
+
+    void ApplyPrefetchPolicy(int bundle,
+                             const std::string& item,
+                             const std::string& content);
+
+    BundleScheduler&  GetBundleScheduler(unsigned int bundleIndex);
+
+  public:
+    CacheScheduler(CacheManager& cache,
+                   unsigned int maxPrefetchSize);
+
+    ~CacheScheduler();
+
+    void Register(int bundle,
+                  ICacheFactory* factory /* takes ownership */,
+                  size_t  numThreads);
+
+    void RegisterPolicy(IPrefetchPolicy* policy /* takes ownership */);
+
+    void Invalidate(int bundle,
+                    const std::string& item);
+
+    bool Access(std::string& content,
+                int bundle,
+                const std::string& item);
+
+    void Prefetch(int bundle,
+                  const std::string& item);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/Cache/ICacheFactory.h	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,41 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero 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
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include <string>
+#include <boost/noncopyable.hpp>
+
+
+namespace OrthancPlugins
+{
+  class ICacheFactory : public boost::noncopyable
+  {
+  public:
+    virtual ~ICacheFactory()
+    {
+    }
+
+    // WARNING: No mutual exclusion is enforced! Several threads could
+    // call this method at the same time.
+    virtual bool Create(std::string& content,
+                        const std::string& key) = 0;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/Cache/IPrefetchPolicy.h	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,46 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero 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
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "CacheIndex.h"
+
+#include <boost/noncopyable.hpp>
+#include <list>
+
+namespace OrthancPlugins
+{
+  class CacheScheduler;
+
+  class IPrefetchPolicy : public boost::noncopyable
+  {
+  public:
+    virtual ~IPrefetchPolicy()
+    {
+    }
+
+    // Mutual exclusion is enforced when calling this method.
+    // "toPrefetch" must be listed from top-priority to low-priority.
+    virtual void Apply(std::list<CacheIndex>& toPrefetch,
+                       CacheScheduler& cache,
+                       const CacheIndex& index,
+                       const std::string& content) = 0;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/DecodedImageAdapter.cpp	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,122 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero 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
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "DecodedImageAdapter.h"
+
+#include "ViewerToolbox.h"
+#include "ParsedDicomImage.h"
+
+#include <boost/lexical_cast.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+#include <json/writer.h>
+
+namespace OrthancPlugins
+{
+  bool DecodedImageAdapter::ParseUri(CompressionType& type,
+                                     uint8_t& compressionLevel,
+                                     std::string& instanceId,
+                                     const std::string& uri)
+  {
+    size_t separator = uri.find('-');
+    if (separator == std::string::npos &&
+        separator >= 1)
+    {
+      return false;
+    }
+  
+    std::string compression = uri.substr(0, separator);
+    instanceId = uri.substr(separator + 1);
+
+    if (compression == "deflate")
+    {
+      type = CompressionType_Deflate;
+    }
+    else if (boost::starts_with(compression, "jpeg"))
+    {
+      type = CompressionType_Jpeg;
+      int level = boost::lexical_cast<int>(compression.substr(4));
+      if (level <= 0 || level > 100)
+      {
+        return false;
+      }
+
+      compressionLevel = static_cast<uint8_t>(level);
+    }
+    else
+    {
+      return false;
+    }
+
+    return true;
+  }
+
+
+
+  bool DecodedImageAdapter::Create(std::string& content,
+                                   const std::string& uri)
+  {
+    std::string message = "Decoding DICOM instance: " + uri;
+    OrthancPluginLogInfo(context_, message.c_str());
+
+    CompressionType type;
+    uint8_t level;
+    std::string instanceId;
+    
+    if (!ParseUri(type, level, instanceId, uri))
+    {
+      return false;
+    }
+
+    std::string file = "/instances/" + instanceId + "/file";
+
+    std::string dicom;
+    if (!GetStringFromOrthanc(dicom, context_, file))
+    {
+      return false;
+    }
+
+    ParsedDicomImage image(dicom);
+
+    Json::Value json;
+
+    if (type == CompressionType_Deflate)
+    {
+      if (!image.EncodeUsingDeflate(json, 9))
+      {
+        return false;
+      }
+    }
+    else if (type == CompressionType_Jpeg)
+    {
+      if (!image.EncodeUsingJpeg(json, level))
+      {
+        return false;
+      }
+    }
+    else
+    {
+      return false;
+    }
+
+    Json::FastWriter writer;
+    content = writer.write(json);
+    return true;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/DecodedImageAdapter.h	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,54 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero 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
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "Cache/ICacheFactory.h"
+
+#include <orthanc/OrthancCPlugin.h>
+#include <stdint.h>
+
+namespace OrthancPlugins
+{
+  class DecodedImageAdapter : public ICacheFactory
+  {
+  private:
+    enum CompressionType
+    {
+      CompressionType_Jpeg,
+      CompressionType_Deflate
+    };
+
+    static bool ParseUri(CompressionType& type,
+                         uint8_t& compressionLevel,
+                         std::string& instanceId,
+                         const std::string& uri);
+
+    OrthancPluginContext* context_;
+
+  public:
+    DecodedImageAdapter(OrthancPluginContext* context) : context_(context)
+    {
+    }
+
+    virtual bool Create(std::string& content,
+                        const std::string& uri);  
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/InstanceInformation.cpp	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,130 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero 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
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "InstanceInformation.h"
+
+#include "../Orthanc/OrthancException.h"
+
+#include <cassert>
+#include <string.h>
+#include <json/value.h>
+#include <json/reader.h>
+#include <json/writer.h>
+
+
+namespace OrthancPlugins
+{
+  void InstanceInformation::SetPosition(const std::vector<float>& normal,
+                                        const std::vector<float>& position)
+  {
+    if (normal.size() != 3 ||
+        position.size() != 3)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadParameterType);
+    }
+
+    hasPosition_ = true;
+    memcpy(normal_, &normal[0], sizeof(float) * 3);
+    memcpy(position_, &position[0], sizeof(float) * 3);
+  }
+
+  void InstanceInformation::SetIndexInSeries(int index)
+  {
+    hasIndex_ = true;
+    index_ = index;
+  }
+
+  float InstanceInformation::GetPosition(size_t i) const
+  {
+    assert(hasPosition_ && i < 3);
+    return position_[i];
+  }
+
+  float InstanceInformation::GetNormal(size_t i) const
+  {
+    assert(hasPosition_ && i < 3);
+    return normal_[i];
+  }
+
+  int InstanceInformation::GetIndexInSeries() const
+  {
+    assert(hasIndex_);
+    return index_;
+  }
+
+  void InstanceInformation::Serialize(std::string& result) const
+  {
+    Json::Value value = Json::objectValue;
+
+    if (hasPosition_)
+    {
+      value["Normal"] = Json::arrayValue;
+      value["Normal"][0] = normal_[0];
+      value["Normal"][1] = normal_[1];
+      value["Normal"][2] = normal_[2];
+
+      value["Position"] = Json::arrayValue;
+      value["Position"][0] = position_[0];
+      value["Position"][1] = position_[1];
+      value["Position"][2] = position_[2];
+    }
+
+    if (hasIndex_)
+    {
+      value["Index"] = index_;
+    }
+
+    Json::FastWriter writer;
+    result = writer.write(value);
+  }
+
+
+  void InstanceInformation::Deserialize(InstanceInformation& result,
+                                        const std::string& serialized)
+  {
+    result = InstanceInformation();
+
+    Json::Reader reader;
+    Json::Value value;
+
+    if (!reader.parse(serialized, value) ||
+        value.type() != Json::objectValue)
+    {
+      return;
+    }
+
+    if (value.isMember("Normal"))
+    {
+      std::vector<float> normal(3), position(3);
+      normal[0] = value["Normal"][0].asFloat();
+      normal[1] = value["Normal"][1].asFloat();
+      normal[2] = value["Normal"][2].asFloat();
+      position[0] = value["Position"][0].asFloat();
+      position[1] = value["Position"][1].asFloat();
+      position[2] = value["Position"][2].asFloat();
+      result.SetPosition(normal, position);
+    }
+
+    if (value.isMember("Index"))
+    {
+      result.SetIndexInSeries(value["Index"].asInt());
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/InstanceInformation.h	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,73 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero 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
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include <vector>
+#include <string>
+
+namespace OrthancPlugins
+{
+  class InstanceInformation
+  {
+  private:
+    bool  hasPosition_;
+    float normal_[3];
+    float position_[3];
+    bool  hasIndex_;
+    int   index_;
+
+  public:
+    InstanceInformation() : hasPosition_(false), hasIndex_(false)
+    {
+    }
+
+    InstanceInformation(const std::string& serialized)
+    {
+      Deserialize(*this, serialized);
+    }
+
+    void SetPosition(const std::vector<float>& normal,
+                     const std::vector<float>& position);
+
+    void SetIndexInSeries(int index);
+
+    bool HasPosition() const
+    {
+      return hasPosition_;
+    }
+
+    bool HasIndexInSeries() const
+    {
+      return hasIndex_;
+    }
+
+    float GetPosition(size_t i) const;
+
+    float GetNormal(size_t i) const;
+
+    int GetIndexInSeries() const;
+
+    void Serialize(std::string& result) const;
+
+    static void Deserialize(InstanceInformation& result,
+                            const std::string& serialized);  
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/InstanceInformationAdapter.cpp	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,81 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero 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
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "InstanceInformationAdapter.h"
+
+#include "ViewerToolbox.h"
+#include "InstanceInformation.h"
+
+#include <json/value.h>
+
+static const char* IMAGE_ORIENTATION_PATIENT = "ImageOrientationPatient";
+static const char* IMAGE_POSITION_PATIENT = "ImagePositionPatient";
+static const char* INDEX_IN_SERIES = "IndexInSeries";
+
+
+namespace OrthancPlugins
+{
+  bool InstanceInformationAdapter::Create(std::string& content,
+                                          const std::string& instanceId)
+  {
+    std::string message = "Creating spatial information for instance: " + instanceId;
+    OrthancPluginLogInfo(context_, message.c_str());
+
+    std::string uri = "/instances/" + instanceId;
+
+    Json::Value instance, tags;
+    if (!GetJsonFromOrthanc(instance, context_, uri) ||
+        !GetJsonFromOrthanc(tags, context_, uri + "/tags?simplify") ||
+        instance.type() != Json::objectValue ||
+        tags.type() != Json::objectValue)
+    {
+      return false;
+    }
+
+    InstanceInformation info;
+
+    if (tags.isMember(IMAGE_ORIENTATION_PATIENT) &&
+        tags.isMember(IMAGE_POSITION_PATIENT) &&
+        tags[IMAGE_ORIENTATION_PATIENT].type() == Json::stringValue &&
+        tags[IMAGE_POSITION_PATIENT].type() == Json::stringValue)
+    {
+      std::vector<float> cosines, position;
+      if (TokenizeVector(cosines, tags[IMAGE_ORIENTATION_PATIENT].asString(), 6) &&
+          TokenizeVector(position, tags[IMAGE_POSITION_PATIENT].asString(), 3))
+      {
+        std::vector<float> normal(3);
+        normal[0] = cosines[1] * cosines[5] - cosines[2] * cosines[4];
+        normal[1] = cosines[2] * cosines[3] - cosines[0] * cosines[5];
+        normal[2] = cosines[0] * cosines[4] - cosines[1] * cosines[3];
+
+        info.SetPosition(normal, position);
+      }
+    }
+
+    if (instance.isMember(INDEX_IN_SERIES) &&
+        instance[INDEX_IN_SERIES].type() == Json::intValue)
+    {
+      info.SetIndexInSeries(instance[INDEX_IN_SERIES].asInt());
+    }
+
+    info.Serialize(content);
+    return true;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/InstanceInformationAdapter.h	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,42 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero 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
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "Cache/ICacheFactory.h"
+
+#include <orthanc/OrthancCPlugin.h>
+
+namespace OrthancPlugins
+{
+  class InstanceInformationAdapter : public ICacheFactory
+  {
+  private:
+    OrthancPluginContext* context_;
+
+  public:
+    InstanceInformationAdapter(OrthancPluginContext* context) : context_(context)
+    {
+    }
+
+    virtual bool Create(std::string& content,
+                        const std::string& instanceId);  
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/JpegWriter.cpp	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,246 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero 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
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "JpegWriter.h"
+
+#include "../Orthanc/OrthancException.h"
+
+#include <jpeglib.h>
+#include <setjmp.h>
+#include <stdio.h>
+#include <vector>
+#include <string.h>
+#include <stdlib.h>
+
+namespace OrthancPlugins
+{
+  namespace
+  {
+    class ErrorManager 
+    {
+    private:
+      struct jpeg_error_mgr pub;  /* "public" fields */
+      jmp_buf setjmp_buffer;      /* for return to caller */
+      std::string message;
+
+      static void OutputMessage(j_common_ptr cinfo)
+      {
+        char message[JMSG_LENGTH_MAX];
+        (*cinfo->err->format_message) (cinfo, message);
+
+        ErrorManager* that = reinterpret_cast<ErrorManager*>(cinfo->err);
+        that->message = std::string(message);
+      }
+
+
+      static void ErrorExit(j_common_ptr cinfo)
+      {
+        (*cinfo->err->output_message) (cinfo);
+
+        ErrorManager* that = reinterpret_cast<ErrorManager*>(cinfo->err);
+        longjmp(that->setjmp_buffer, 1);
+      }
+      
+
+    public:
+      ErrorManager()
+      {
+        memset(&pub, 0, sizeof(struct jpeg_error_mgr));
+        memset(&setjmp_buffer, 0, sizeof(jmp_buf));
+
+        jpeg_std_error(&pub);
+        pub.error_exit = ErrorExit;
+        pub.output_message = OutputMessage;
+      }
+
+      struct jpeg_error_mgr* GetPublic()
+      {
+        return &pub;
+      }
+
+      jmp_buf& GetJumpBuffer()
+      {
+        return setjmp_buffer;
+      }
+
+      const std::string& GetMessage() const
+      {
+        return message;
+      }
+    };
+  }
+
+
+  static void GetLines(std::vector<uint8_t*>& lines,
+                       unsigned int height,
+                       unsigned int pitch,
+                       Orthanc::PixelFormat format,
+                       const void* buffer)
+  {
+    if (format != Orthanc::PixelFormat_Grayscale8 &&
+        format != Orthanc::PixelFormat_RGB24)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    lines.resize(height);
+
+    uint8_t* base = const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(buffer));
+    for (unsigned int y = 0; y < height; y++)
+    {
+      lines[y] = base + static_cast<intptr_t>(y) * static_cast<intptr_t>(pitch);
+    }
+  }
+
+
+  static void Compress(struct jpeg_compress_struct& cinfo,
+                       std::vector<uint8_t*>& lines,
+                       unsigned int width,
+                       unsigned int height,
+                       Orthanc::PixelFormat format,
+                       int quality)
+  {
+    cinfo.image_width = width;
+    cinfo.image_height = height;
+
+    switch (format)
+    {
+      case Orthanc::PixelFormat_Grayscale8:
+        cinfo.input_components = 1;
+        cinfo.in_color_space = JCS_GRAYSCALE;
+        break;
+
+      case Orthanc::PixelFormat_RGB24:
+        cinfo.input_components = 3;
+        cinfo.in_color_space = JCS_RGB;
+        break;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+
+    jpeg_set_defaults(&cinfo);
+    jpeg_set_quality(&cinfo, quality, TRUE);
+    jpeg_start_compress(&cinfo, TRUE);
+    jpeg_write_scanlines(&cinfo, &lines[0], height);
+    jpeg_finish_compress(&cinfo);
+    jpeg_destroy_compress(&cinfo);
+  }
+                       
+
+  void JpegWriter::SetQuality(uint8_t quality)
+  {
+    if (quality <= 0 || quality > 100)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    quality_ = quality;
+  }
+
+
+  void JpegWriter::WriteToFile(const char* filename,
+                               unsigned int width,
+                               unsigned int height,
+                               unsigned int pitch,
+                               Orthanc::PixelFormat format,
+                               const void* buffer)
+  {
+    FILE* fp = fopen(filename, "wb");
+    if (fp == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_FullStorage);
+    }
+
+    std::vector<uint8_t*> lines;
+    GetLines(lines, height, pitch, format, buffer);
+
+    struct jpeg_compress_struct cinfo;
+    memset(&cinfo, 0, sizeof(struct jpeg_compress_struct));
+
+    ErrorManager jerr;
+    cinfo.err = jerr.GetPublic();
+
+    if (setjmp(jerr.GetJumpBuffer())) 
+    {
+      /* If we get here, the JPEG code has signaled an error.
+       * We need to clean up the JPEG object, close the input file, and return.
+       */
+      jpeg_destroy_compress(&cinfo);
+      fclose(fp);
+      throw Orthanc::OrthancException("Error during JPEG encoding: " + jerr.GetMessage());
+    }
+
+    // Do not allocate data on the stack below this line!
+
+    jpeg_create_compress(&cinfo);
+    jpeg_stdio_dest(&cinfo, fp);
+    Compress(cinfo, lines, width, height, format, quality_);
+
+    // Everything went fine, "setjmp()" didn't get called
+
+    fclose(fp);
+  }
+
+
+  void JpegWriter::WriteToMemory(std::string& jpeg,
+                                 unsigned int width,
+                                 unsigned int height,
+                                 unsigned int pitch,
+                                 Orthanc::PixelFormat format,
+                                 const void* buffer)
+  {
+    std::vector<uint8_t*> lines;
+    GetLines(lines, height, pitch, format, buffer);
+
+    struct jpeg_compress_struct cinfo;
+    memset(&cinfo, 0, sizeof(struct jpeg_compress_struct));
+
+    ErrorManager jerr;
+
+    unsigned char* data = NULL;
+    unsigned long size;
+
+    if (setjmp(jerr.GetJumpBuffer())) 
+    {
+      jpeg_destroy_compress(&cinfo);
+
+      if (data != NULL)
+      {
+        free(data);
+      }
+
+      throw Orthanc::OrthancException("Error during JPEG encoding: " + jerr.GetMessage());
+    }
+
+    // Do not allocate data on the stack below this line!
+
+    jpeg_create_compress(&cinfo);
+    cinfo.err = jerr.GetPublic();
+    jpeg_mem_dest(&cinfo, &data, &size);
+
+    Compress(cinfo, lines, width, height, format, quality_);
+
+    // Everything went fine, "setjmp()" didn't get called
+
+    jpeg.assign(reinterpret_cast<const char*>(data), size);
+    free(data);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/JpegWriter.h	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,75 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero 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
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../Orthanc/ImageFormats/ImageAccessor.h"
+
+#include <string>
+#include <stdint.h>
+
+namespace OrthancPlugins
+{
+  class JpegWriter
+  {
+  private:
+    int  quality_;
+
+  public:
+    JpegWriter() : quality_(90)
+    {
+    }
+
+    void SetQuality(uint8_t quality);
+
+    uint8_t GetQuality() const
+    {
+      return quality_;
+    }
+
+    void WriteToFile(const char* filename,
+                     unsigned int width,
+                     unsigned int height,
+                     unsigned int pitch,
+                     Orthanc::PixelFormat format,
+                     const void* buffer);
+
+    void WriteToMemory(std::string& jpeg,
+                       unsigned int width,
+                       unsigned int height,
+                       unsigned int pitch,
+                       Orthanc::PixelFormat format,
+                       const void* buffer);
+
+    void WriteToFile(const char* filename,
+                     const Orthanc::ImageAccessor& accessor)
+    {
+      WriteToFile(filename, accessor.GetWidth(), accessor.GetHeight(),
+                  accessor.GetPitch(), accessor.GetFormat(), accessor.GetConstBuffer());
+    }
+
+    void WriteToMemory(std::string& jpeg,
+                       const Orthanc::ImageAccessor& accessor)
+    {
+      WriteToMemory(jpeg, accessor.GetWidth(), accessor.GetHeight(),
+                    accessor.GetPitch(), accessor.GetFormat(), accessor.GetConstBuffer());
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/ParsedDicomImage.cpp	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,477 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero 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
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "ParsedDicomImage.h"
+
+#include "../Orthanc/OrthancException.h"
+#include "../Orthanc/Toolbox.h"
+#include "../Orthanc/ImageFormats/ImageProcessing.h"
+#include "../Orthanc/ImageFormats/ImageBuffer.h"
+#include "JpegWriter.h"
+#include "ViewerToolbox.h"
+
+#include <gdcmImageReader.h>
+#include <gdcmImageChangePlanarConfiguration.h>
+#include <gdcmImageChangePhotometricInterpretation.h>
+#include <boost/lexical_cast.hpp>
+#include <boost/math/special_functions/round.hpp>
+
+#include "../Resources/ThirdParty/base64/base64.h"
+
+
+namespace OrthancPlugins
+{
+  struct ParsedDicomImage::PImpl
+  {
+    gdcm::ImageReader reader_;
+    std::auto_ptr<gdcm::ImageChangePhotometricInterpretation> photometric_;
+    std::auto_ptr<gdcm::ImageChangePlanarConfiguration> interleaved_;
+    std::string decoded_;
+
+    const gdcm::Image& GetImage() const
+    {
+      if (interleaved_.get() != NULL)
+      {
+        return interleaved_->GetOutput();
+      }
+
+      if (photometric_.get() != NULL)
+      {
+        return photometric_->GetOutput();
+      }
+
+      return reader_.GetImage();
+    }
+
+
+    const gdcm::DataSet& GetDataSet() const
+    {
+      return reader_.GetFile().GetDataSet();
+    }
+  };
+
+
+  template <typename TargetType, typename SourceType>
+  static void ChangeDynamics(Orthanc::ImageAccessor& target,
+                             const Orthanc::ImageAccessor& source,
+                             SourceType source1, TargetType target1,
+                             SourceType source2, TargetType target2)
+  {
+    if (source.GetWidth() != target.GetWidth() ||
+        source.GetHeight() != target.GetHeight())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize);
+    }
+
+    float scale = static_cast<float>(target2 - target1) / static_cast<float>(source2 - source1);
+    float offset = static_cast<float>(target1) - scale * static_cast<float>(source1);
+
+    const float minValue = static_cast<float>(std::numeric_limits<TargetType>::min());
+    const float maxValue = static_cast<float>(std::numeric_limits<TargetType>::max());
+
+    for (unsigned int y = 0; y < source.GetHeight(); y++)
+    {
+      const SourceType* p = reinterpret_cast<const SourceType*>(source.GetConstRow(y));
+      TargetType* q = reinterpret_cast<TargetType*>(target.GetRow(y));
+
+      for (unsigned int x = 0; x < source.GetWidth(); x++, p++, q++)
+      {
+        float v = (scale * static_cast<float>(*p)) + offset;
+
+        if (v > maxValue)
+        {
+          *q = std::numeric_limits<TargetType>::max();
+        }
+        else if (v < minValue)
+        {
+          *q = std::numeric_limits<TargetType>::min();
+        }
+        else
+        {
+          *q = static_cast<TargetType>(boost::math::iround(v));
+        }
+      }
+    }
+  }
+
+
+  void ParsedDicomImage::Setup(const std::string& dicom)
+  {
+    // Prepare a memory stream over the DICOM instance
+    std::stringstream stream(dicom);
+
+    // Parse the DICOM instance using GDCM
+    pimpl_->reader_.SetStream(stream);
+    if (!pimpl_->reader_.Read())
+    {
+      throw Orthanc::OrthancException("GDCM cannot extract an image from this DICOM instance");
+    }
+
+    // Change photometric interpretation, if required
+    {
+      const gdcm::Image& image = pimpl_->GetImage();
+      if (image.GetPixelFormat().GetSamplesPerPixel() == 1)
+      {
+        if (image.GetPhotometricInterpretation() != gdcm::PhotometricInterpretation::MONOCHROME1 &&
+            image.GetPhotometricInterpretation() != gdcm::PhotometricInterpretation::MONOCHROME2)
+        {
+          pimpl_->photometric_.reset(new gdcm::ImageChangePhotometricInterpretation());
+          pimpl_->photometric_->SetInput(image);
+          pimpl_->photometric_->SetPhotometricInterpretation(gdcm::PhotometricInterpretation::MONOCHROME2);
+          if (!pimpl_->photometric_->Change())
+          {
+            throw Orthanc::OrthancException("GDCM cannot change the photometric interpretation");
+          }
+        }      
+      }
+      else 
+      {
+        if (image.GetPixelFormat().GetSamplesPerPixel() == 3 &&
+            image.GetPhotometricInterpretation() != gdcm::PhotometricInterpretation::RGB)
+        {
+          pimpl_->photometric_.reset(new gdcm::ImageChangePhotometricInterpretation());
+          pimpl_->photometric_->SetInput(image);
+          pimpl_->photometric_->SetPhotometricInterpretation(gdcm::PhotometricInterpretation::RGB);
+          if (!pimpl_->photometric_->Change())
+          {
+            throw Orthanc::OrthancException("GDCM cannot change the photometric interpretation");
+          }
+        }
+      }
+    }
+
+    // Possibly convert planar configuration to interleaved
+    {
+      const gdcm::Image& image = pimpl_->GetImage();
+      if (image.GetPlanarConfiguration() != 0 && 
+          image.GetPixelFormat().GetSamplesPerPixel() != 1)
+      {
+        pimpl_->interleaved_.reset(new gdcm::ImageChangePlanarConfiguration());
+        pimpl_->interleaved_->SetInput(image);
+        if (!pimpl_->interleaved_->Change())
+        {
+          throw Orthanc::OrthancException("GDCM cannot change the planar configuration to interleaved");
+        }
+      }
+    }
+
+    // Decode the image to the memory buffer
+    {
+      const gdcm::Image& image = pimpl_->GetImage();
+      pimpl_->decoded_.resize(image.GetBufferLength());
+      
+      if (pimpl_->decoded_.size() > 0)
+      {
+        image.GetBuffer(&pimpl_->decoded_[0]);
+      }
+    }
+  }
+
+
+  ParsedDicomImage::ParsedDicomImage(const std::string& dicom) : pimpl_(new PImpl)
+  {
+    Setup(dicom);
+  }
+
+
+  bool ParsedDicomImage::GetTag(std::string& result,
+                                uint16_t group,
+                                uint16_t element,
+                                bool stripSpaces)
+  {
+    const gdcm::Tag tag(group, element);
+
+    if (pimpl_->GetDataSet().FindDataElement(tag))
+    {
+      const gdcm::ByteValue* value = pimpl_->GetDataSet().GetDataElement(tag).GetByteValue();
+      if (value)
+      {
+        result = std::string(value->GetPointer(), value->GetLength());
+
+        if (stripSpaces)
+        {
+          result = Orthanc::Toolbox::StripSpaces(result);
+        }
+
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+
+  bool ParsedDicomImage::GetAccessor(Orthanc::ImageAccessor& accessor)
+  {
+    const gdcm::Image& image = pimpl_->GetImage();
+
+    size_t size = pimpl_->decoded_.size();
+    void* buffer = (size ? &pimpl_->decoded_[0] : NULL);    
+    unsigned int height = image.GetRows();
+    unsigned int width = image.GetColumns();
+
+    if (image.GetPixelFormat().GetSamplesPerPixel() == 1 &&
+        (image.GetPhotometricInterpretation() == gdcm::PhotometricInterpretation::MONOCHROME1 ||
+         image.GetPhotometricInterpretation() == gdcm::PhotometricInterpretation::MONOCHROME2))
+    {
+      switch (image.GetPixelFormat())
+      {
+        case gdcm::PixelFormat::UINT16:
+          accessor.AssignWritable(Orthanc::PixelFormat_Grayscale16, width, height, 2 * width, buffer);
+          return true;
+
+        case gdcm::PixelFormat::INT16:
+          accessor.AssignWritable(Orthanc::PixelFormat_SignedGrayscale16, width, height, 2 * width, buffer);
+          return true;
+
+        case gdcm::PixelFormat::UINT8:
+          accessor.AssignWritable(Orthanc::PixelFormat_Grayscale8, width, height, width, buffer);
+          return true;
+      }
+    }
+    else if (image.GetPixelFormat().GetSamplesPerPixel() == 3 &&
+             image.GetPhotometricInterpretation() == gdcm::PhotometricInterpretation::RGB)
+    {
+      switch (image.GetPixelFormat())
+      {
+        case gdcm::PixelFormat::UINT8:
+          accessor.AssignWritable(Orthanc::PixelFormat_RGB24, width, height, 3 * width, buffer);
+          return true;
+      }      
+    }
+
+    return false;
+  }
+
+  bool ParsedDicomImage::GetCornerstoneMetadata(Json::Value& json)
+  {
+    using namespace Orthanc;
+
+    ImageAccessor accessor;
+    if (!GetAccessor(accessor))
+    {
+      return false;
+    }
+
+    float windowCenter, windowWidth;
+
+    switch (accessor.GetFormat())
+    {
+      case PixelFormat_Grayscale8:
+      case PixelFormat_Grayscale16:
+      case PixelFormat_SignedGrayscale16:
+      {
+        int64_t a, b;
+        Orthanc::ImageProcessing::GetMinMaxValue(a, b, accessor);
+        json["minPixelValue"] = (a < 0 ? static_cast<int32_t>(a) : 0);
+        json["maxPixelValue"] = (b > 0 ? static_cast<int32_t>(b) : 1);
+        json["color"] = false;
+        
+        windowCenter = static_cast<float>(a + b) / 2.0f;
+        
+        if (a == b)
+        {
+          windowWidth = 127.5f;  // Arbitrary value
+        }
+        else
+        {
+          windowWidth = static_cast<float>(b - a) / 2.0f;
+        }
+
+        break;
+      }
+
+      case PixelFormat_RGB24:
+        json["minPixelValue"] = 0;
+        json["maxPixelValue"] = 255;
+        json["color"] = true;
+        windowCenter = 127.5f;
+        windowWidth = 127.5f;
+        break;
+
+      default:
+        return false;
+    }
+  
+    const gdcm::Image& image = pimpl_->GetImage();
+    json["slope"] = image.GetSlope();
+    json["intercept"] = image.GetIntercept();
+    json["rows"] = image.GetRows();
+    json["columns"] = image.GetColumns();
+    json["height"] = image.GetRows();
+    json["width"] = image.GetColumns();
+    json["columnPixelSpacing"] = image.GetSpacing(1);
+    json["rowPixelSpacing"] = image.GetSpacing(0);
+
+    json["windowCenter"] = windowCenter * image.GetSlope() + image.GetIntercept();
+    json["windowWidth"] = windowWidth * image.GetSlope();
+
+    try
+    {
+      std::string width, center;
+      if (GetTag(center, 0x0028, 0x1050 /*DICOM_TAG_WINDOW_CENTER*/) &&
+          GetTag(width, 0x0028, 0x1051 /*DICOM_TAG_WINDOW_WIDTH*/))
+      {
+        float a = boost::lexical_cast<float>(width);
+        float b = boost::lexical_cast<float>(center);
+        json["windowWidth"] = a;
+        json["windowCenter"] = b;
+      }
+    }
+    catch (boost::bad_lexical_cast&)
+    {
+    }
+
+    return true;
+  }
+
+
+  bool  ParsedDicomImage::EncodeUsingDeflate(Json::Value& result,
+                                             uint8_t compressionLevel  /* between 0 and 9 */)
+  {
+    using namespace Orthanc;
+
+    ImageAccessor accessor;
+    if (!GetAccessor(accessor))
+    {
+      return false;
+    }
+
+    result = Json::objectValue;
+    result["Orthanc"] = Json::objectValue;
+    if (!GetCornerstoneMetadata(result))
+    {
+      return false;
+    }
+
+    ImageBuffer buffer;
+    buffer.SetMinimalPitchForced(true);
+
+    ImageAccessor converted;
+
+
+    switch (accessor.GetFormat())
+    {
+      case Orthanc::PixelFormat_RGB24:
+        converted = accessor;
+        break;
+
+      case Orthanc::PixelFormat_Grayscale8:
+      case Orthanc::PixelFormat_Grayscale16:
+        buffer.SetFormat(Orthanc::PixelFormat_SignedGrayscale16);
+        buffer.SetWidth(accessor.GetWidth());
+        buffer.SetHeight(accessor.GetHeight());
+        converted = buffer.GetAccessor();
+        ImageProcessing::Convert(converted, accessor);
+        break;
+
+      case Orthanc::PixelFormat_SignedGrayscale16:
+        converted = accessor;
+        break;
+
+      default:
+        // Unsupported pixel format
+        return false;
+    }
+
+    // Sanity check: The pitch must be minimal
+    assert(converted.GetSize() == converted.GetWidth() * converted.GetHeight() * 
+           GetBytesPerPixel(converted.GetFormat()));
+    result["Orthanc"]["Compression"] = "Deflate";
+    result["sizeInBytes"] = converted.GetSize();
+
+    std::string z;
+    if (!CompressUsingDeflate(z, converted.GetConstBuffer(), converted.GetSize(), compressionLevel))
+    {
+      return false;
+    }
+
+    result["Orthanc"]["PixelData"] = base64_encode(z);  
+
+    return true;
+  }
+
+
+
+  bool  ParsedDicomImage::EncodeUsingJpeg(Json::Value& result,
+                                          uint8_t quality /* between 0 and 100 */)
+  {
+    using namespace Orthanc;
+
+    ImageAccessor accessor;
+    if (!GetAccessor(accessor))
+    {
+      return false;
+    }
+
+    result = Json::objectValue;
+    result["Orthanc"] = Json::objectValue;
+    GetCornerstoneMetadata(result);
+
+    ImageBuffer buffer;
+    buffer.SetMinimalPitchForced(true);
+
+    ImageAccessor converted;
+
+    if (accessor.GetFormat() == Orthanc::PixelFormat_Grayscale8 ||
+        accessor.GetFormat() == Orthanc::PixelFormat_RGB24)
+    {
+      result["Orthanc"]["Stretched"] = false;
+      converted = accessor;
+    }
+    else if (accessor.GetFormat() == Orthanc::PixelFormat_Grayscale16 ||
+             accessor.GetFormat() == Orthanc::PixelFormat_SignedGrayscale16)
+    {
+      result["Orthanc"]["Stretched"] = true;
+      buffer.SetFormat(Orthanc::PixelFormat_Grayscale8);
+      buffer.SetWidth(accessor.GetWidth());
+      buffer.SetHeight(accessor.GetHeight());
+      converted = buffer.GetAccessor();
+
+      int64_t a, b;
+      Orthanc::ImageProcessing::GetMinMaxValue(a, b, accessor);
+      result["Orthanc"]["StretchLow"] = static_cast<int32_t>(a);
+      result["Orthanc"]["StretchHigh"] = static_cast<int32_t>(b);
+
+      if (accessor.GetFormat() == Orthanc::PixelFormat_Grayscale16)
+      {
+        ChangeDynamics<uint8_t, uint16_t>(converted, accessor, a, 0, b, 255);
+      }
+      else
+      {
+        ChangeDynamics<uint8_t, int16_t>(converted, accessor, a, 0, b, 255);
+      }
+    }
+    else
+    {
+      return false;
+    }
+    
+    result["Orthanc"]["Compression"] = "Jpeg";
+    result["sizeInBytes"] = converted.GetSize();
+
+    std::string jpeg;
+    OrthancPlugins::JpegWriter writer;
+    writer.SetQuality(quality);
+    writer.WriteToMemory(jpeg, converted);
+    result["Orthanc"]["PixelData"] = base64_encode(jpeg);  
+    return true;
+  }
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/ParsedDicomImage.h	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,58 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero 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
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../Orthanc/ImageFormats/ImageAccessor.h"
+
+#include <stdint.h>
+#include <boost/noncopyable.hpp>
+#include <boost/shared_ptr.hpp>
+#include <json/value.h>
+
+namespace OrthancPlugins
+{
+  class ParsedDicomImage : public boost::noncopyable
+  {
+  private:
+    struct PImpl;
+    boost::shared_ptr<PImpl> pimpl_;
+  
+    void Setup(const std::string& dicom);
+
+  public:
+    ParsedDicomImage(const std::string& dicom);
+
+    bool GetTag(std::string& result,
+                uint16_t group,
+                uint16_t element,
+                bool stripSpaces = true);
+
+    bool GetAccessor(Orthanc::ImageAccessor& accessor);
+
+    bool GetCornerstoneMetadata(Json::Value& json);
+
+    bool EncodeUsingDeflate(Json::Value& result,
+                            uint8_t compressionLevel  /* between 0 and 9 */);
+
+    bool EncodeUsingJpeg(Json::Value& result,
+                         uint8_t quality /* between 0 and 100 */);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/Plugin.cpp	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,350 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero 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
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include <boost/thread.hpp>
+#include <boost/lexical_cast.hpp>
+#include <EmbeddedResources.h>
+
+#include "../Orthanc/OrthancException.h"
+#include "ViewerToolbox.h"
+#include "ViewerPrefetchPolicy.h"
+#include "DecodedImageAdapter.h"
+#include "InstanceInformationAdapter.h"
+#include "SeriesInformationAdapter.h"
+
+
+
+class CacheContext
+{
+private:
+  std::auto_ptr<Orthanc::FilesystemStorage>  storage_;
+  std::auto_ptr<Orthanc::SQLite::Connection>  db_;
+  std::auto_ptr<OrthancPlugins::CacheManager>  cache_;
+  std::auto_ptr<OrthancPlugins::CacheScheduler>  scheduler_;
+
+public:
+  CacheContext(const std::string& path)
+  {
+    boost::filesystem::path p(path);
+
+    storage_.reset(new Orthanc::FilesystemStorage(path));
+    db_.reset(new Orthanc::SQLite::Connection());
+    db_->Open((p / "cache.db").string());
+
+    cache_.reset(new OrthancPlugins::CacheManager(*db_, *storage_));
+    //cache_->SetSanityCheckEnabled(true);  // For debug
+
+    scheduler_.reset(new OrthancPlugins::CacheScheduler(*cache_, 100));
+  }
+
+  OrthancPlugins::CacheScheduler& GetScheduler()
+  {
+    return *scheduler_;
+  }
+};
+
+
+static OrthancPluginContext* context_ = NULL;
+static CacheContext* cache_ = NULL;
+
+
+
+static int32_t OnChangeCallback(OrthancPluginChangeType changeType,
+                                OrthancPluginResourceType resourceType,
+                                const char* resourceId)
+{
+  try
+  {
+    if (changeType == OrthancPluginChangeType_NewInstance &&
+        resourceType == OrthancPluginResourceType_Instance)
+    {
+      // On the reception of a new instance, precompute its spatial position
+      cache_->GetScheduler().Prefetch(OrthancPlugins::CacheBundle_InstanceInformation, resourceId);
+     
+      // Indalidate the parent series of the instance
+      std::string uri = "/instances/" + std::string(resourceId);
+      Json::Value instance;
+      if (OrthancPlugins::GetJsonFromOrthanc(instance, context_, uri))
+      {
+        std::string seriesId = instance["ParentSeries"].asString();
+        cache_->GetScheduler().Invalidate(OrthancPlugins::CacheBundle_SeriesInformation, seriesId);
+      }
+    }
+
+    return 0;
+  }
+  catch (std::runtime_error& e)
+  {
+    OrthancPluginLogError(context_, e.what());
+    return 0;  // Ignore error
+  }
+}
+
+
+
+template <enum OrthancPlugins::CacheBundle bundle>
+int32_t ServeCache(OrthancPluginRestOutput* output,
+                   const char* url,
+                   const OrthancPluginHttpRequest* request)
+{
+  try
+  {
+    if (request->method != OrthancPluginHttpMethod_Get)
+    {
+      OrthancPluginSendMethodNotAllowed(context_, output, "GET");
+      return 0;
+    }
+
+    const std::string id = request->groups[0];
+    std::string content;
+
+    if (cache_->GetScheduler().Access(content, bundle, id))
+    {
+      OrthancPluginAnswerBuffer(context_, output, content.c_str(), content.size(), "application/json");
+    }
+    else
+    {
+      OrthancPluginSendHttpStatusCode(context_, output, 404);
+    }
+
+    return 0;
+  }
+  catch (Orthanc::OrthancException& e)
+  {
+    OrthancPluginLogError(context_, e.What());
+    return -1;
+  }
+  catch (std::runtime_error& e)
+  {
+    OrthancPluginLogError(context_, e.what());
+    return -1;
+  }
+  catch (boost::bad_lexical_cast&)
+  {
+    OrthancPluginLogError(context_, "Bad lexical cast");
+    return -1;
+  }
+}
+
+
+
+
+#if ORTHANC_STANDALONE == 0
+static int32_t ServeWebViewer(OrthancPluginRestOutput* output,
+                              const char* url,
+                              const OrthancPluginHttpRequest* request)
+{
+  if (request->method != OrthancPluginHttpMethod_Get)
+  {
+    OrthancPluginSendMethodNotAllowed(context_, output, "GET");
+    return 0;
+  }
+
+  const std::string path = std::string(WEB_VIEWER_PATH) + std::string(request->groups[0]);
+  const char* mime = OrthancPlugins::GetMimeType(path);
+
+  std::string s;
+  if (OrthancPlugins::ReadFile(s, path))
+  {
+    const char* resource = s.size() ? s.c_str() : NULL;
+    OrthancPluginAnswerBuffer(context_, output, resource, s.size(), mime);
+  }
+  else
+  {
+    std::string s = "Inexistent file in served folder: " + path;
+    OrthancPluginLogError(context_, s.c_str());
+    OrthancPluginSendHttpStatusCode(context_, output, 404);
+  }
+
+  return 0;
+}
+#endif
+
+
+
+template <enum OrthancPlugins::EmbeddedResources::DirectoryResourceId folder>
+static int32_t ServeEmbeddedFolder(OrthancPluginRestOutput* output,
+                                   const char* url,
+                                   const OrthancPluginHttpRequest* request)
+{
+  if (request->method != OrthancPluginHttpMethod_Get)
+  {
+    OrthancPluginSendMethodNotAllowed(context_, output, "GET");
+    return 0;
+  }
+
+  std::string path = "/" + std::string(request->groups[0]);
+  const char* mime = OrthancPlugins::GetMimeType(path);
+
+  try
+  {
+    std::string s;
+    OrthancPlugins::EmbeddedResources::GetDirectoryResource(s, folder, path.c_str());
+
+    const char* resource = s.size() ? s.c_str() : NULL;
+    OrthancPluginAnswerBuffer(context_, output, resource, s.size(), mime);
+
+    return 0;
+  }
+  catch (std::runtime_error&)
+  {
+    std::string s = "Unknown static resource in plugin: " + std::string(request->groups[0]);
+    OrthancPluginLogError(context_, s.c_str());
+    OrthancPluginSendHttpStatusCode(context_, output, 404);
+    return 0;
+  }
+}
+
+
+
+
+extern "C"
+{
+  ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* context)
+  {
+    using namespace OrthancPlugins;
+
+    context_ = context;
+    OrthancPluginLogWarning(context_, "Initializing the Web viewer");
+
+
+    /* Check the version of the Orthanc core */
+    if (OrthancPluginCheckVersion(context_) == 0)
+    {
+      char info[1024];
+      sprintf(info, "Your version of Orthanc (%s) must be above %d.%d.%d to run this plugin",
+              context_->orthancVersion,
+              ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER,
+              ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER,
+              ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER);
+      OrthancPluginLogError(context_, info);
+      return -1;
+    }
+
+    OrthancPluginSetDescription(context_, "Provides a Web viewer of DICOM series within Orthanc.");
+
+
+    /* By default, use half of the available processing cores for the decoding of DICOM images */
+    int decodingThreads = boost::thread::hardware_concurrency() / 2;
+    if (decodingThreads == 0)
+    {
+      decodingThreads = 1;
+    }
+
+
+    try
+    {
+      /* Read the configuration of the Web viewer */
+      Json::Value configuration;
+      if (!ReadConfiguration(configuration, context))
+      {
+        OrthancPluginLogError(context_, "Unable to read the configuration file of Orthanc");
+        return -1;
+      }
+
+      std::string cachePath = "WebViewerCache";
+    
+      if (configuration.isMember("WebViewer"))
+      {
+        cachePath = GetStringValue(configuration["WebViewer"], "Cache", cachePath);
+        decodingThreads = GetIntegerValue(configuration["WebViewer"], "Threads", decodingThreads);
+      }
+
+      std::string message = ("Web viewer using " + boost::lexical_cast<std::string>(decodingThreads) + 
+                             " threads for the decoding of the DICOM images");
+      OrthancPluginLogWarning(context_, message.c_str());
+
+      if (decodingThreads <= 0)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+      }
+
+      message = "Storing the cache of the Web viewer in folder: " + cachePath;
+      OrthancPluginLogWarning(context_, message.c_str());
+
+   
+      /* Create the cache */
+      cache_ = new CacheContext(cachePath);
+      cache_->GetScheduler().RegisterPolicy(new ViewerPrefetchPolicy(context_));
+      cache_->GetScheduler().Register(CacheBundle_SeriesInformation, 
+                                      new SeriesInformationAdapter(context_, cache_->GetScheduler()), 1);
+      cache_->GetScheduler().Register(CacheBundle_InstanceInformation, 
+                                      new InstanceInformationAdapter(context_), 1);
+      cache_->GetScheduler().Register(CacheBundle_DecodedImage, 
+                                      new DecodedImageAdapter(context_), decodingThreads);
+    }
+    catch (std::runtime_error& e)
+    {
+      OrthancPluginLogError(context_, e.what());
+      return -1;
+    }
+    catch (Orthanc::OrthancException& e)
+    {
+      OrthancPluginLogError(context_, e.What());
+      return -1;
+    }
+
+
+    /* Install the callbacks */
+    OrthancPluginRegisterRestCallback(context_, "/web-viewer/series/(.*)", ServeCache<CacheBundle_SeriesInformation>);
+    OrthancPluginRegisterRestCallback(context_, "/web-viewer/instances/(.*)", ServeCache<CacheBundle_DecodedImage>);
+    OrthancPluginRegisterRestCallback(context, "/web-viewer/libs/(.*)", ServeEmbeddedFolder<EmbeddedResources::JAVASCRIPT_LIBS>);
+
+#if ORTHANC_STANDALONE == 1
+    OrthancPluginRegisterRestCallback(context, "/web-viewer/app/(.*)", ServeEmbeddedFolder<EmbeddedResources::WEB_VIEWER>);
+#else
+    OrthancPluginRegisterRestCallback(context, "/web-viewer/app/(.*)", ServeWebViewer);
+#endif
+
+    OrthancPluginRegisterOnChangeCallback(context, OnChangeCallback);
+
+
+    /* Extend the default Orthanc Explorer with custom JavaScript */
+    std::string explorer;
+    EmbeddedResources::GetFileResource(explorer, EmbeddedResources::ORTHANC_EXPLORER);
+    OrthancPluginExtendOrthancExplorer(context_, explorer.c_str());
+
+    return 0;
+  }
+
+
+  ORTHANC_PLUGINS_API void OrthancPluginFinalize()
+  {
+    OrthancPluginLogWarning(context_, "Finalizing the Web viewer");
+
+    if (cache_ != NULL)
+    {
+      delete cache_;
+      cache_ = NULL;
+    }
+  }
+
+
+  ORTHANC_PLUGINS_API const char* OrthancPluginGetName()
+  {
+    return "web-viewer";
+  }
+
+
+  ORTHANC_PLUGINS_API const char* OrthancPluginGetVersion()
+  {
+    return "1.0";
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/SeriesInformationAdapter.cpp	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,80 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero 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
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "SeriesInformationAdapter.h"
+
+#include "ViewerToolbox.h"
+#include "SeriesVolumeSorter.h"
+
+#include "../Orthanc/OrthancException.h"
+
+namespace OrthancPlugins
+{
+  bool SeriesInformationAdapter::Create(std::string& content,
+                                        const std::string& seriesId)
+  {
+    std::string message = "Ordering instances of series: " + seriesId;
+    OrthancPluginLogInfo(context_, message.c_str());
+
+    Json::Value series, study, patient;
+    if (!GetJsonFromOrthanc(series, context_, "/series/" + seriesId) ||
+        !GetJsonFromOrthanc(study, context_, "/studies/" + series["ID"].asString() + "/module?simplify") ||
+        !GetJsonFromOrthanc(patient, context_, "/studies/" + series["ID"].asString() + "/module-patient?simplify") ||
+        !series.isMember("Instances") ||
+        series["Instances"].type() != Json::arrayValue)
+    {
+      return false;
+    }
+
+    Json::Value result;
+    result["ID"] = seriesId;
+    result["SeriesDescription"] = series["MainDicomTags"]["SeriesDescription"].asString();
+    result["StudyDescription"] = study["StudyDescription"].asString();
+    result["PatientID"] = patient["PatientID"].asString();
+    result["PatientName"] = patient["PatientName"].asString();
+    result["SortedInstances"] = Json::arrayValue;
+
+    SeriesVolumeSorter sorter;
+    sorter.Reserve(series["Instances"].size());
+
+    for (Json::Value::ArrayIndex i = 0; i < series["Instances"].size(); i++)
+    {
+      const std::string instanceId = series["Instances"][i].asString();
+      std::string tmp;
+
+      if (!cache_.Access(tmp, CacheBundle_InstanceInformation, instanceId))
+      {
+        throw Orthanc::OrthancException("The cache is corrupted. Delete it to reconstruct it.");
+      }
+
+      InstanceInformation instance(tmp);
+      sorter.AddInstance(instanceId, instance);
+    }
+
+    for (size_t i = 0; i < sorter.GetSize(); i++)
+    {
+      result["SortedInstances"].append(sorter.GetInstance(i));
+    }
+
+    content = result.toStyledString();
+
+    return true;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/SeriesInformationAdapter.h	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,47 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero 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
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "Cache/ICacheFactory.h"
+#include "Cache/CacheScheduler.h"
+
+#include <orthanc/OrthancCPlugin.h>
+
+namespace OrthancPlugins
+{
+  class SeriesInformationAdapter : public ICacheFactory
+  {
+  private:
+    OrthancPluginContext* context_;
+    CacheScheduler&       cache_;
+
+  public:
+    SeriesInformationAdapter(OrthancPluginContext* context,
+                             CacheScheduler&       cache) : 
+      context_(context),
+      cache_(cache)
+    {
+    }
+
+    virtual bool Create(std::string& content,
+                        const std::string& seriesId);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/SeriesVolumeSorter.cpp	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,131 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero 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
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "SeriesVolumeSorter.h"
+
+#include <algorithm>
+#include <limits>
+#include <math.h>
+#include <cassert>
+
+namespace OrthancPlugins
+{
+  SeriesVolumeSorter::SeriesVolumeSorter() : 
+    isVolume_(true), 
+    sorted_(true)
+  {
+  }
+
+
+  void SeriesVolumeSorter::Reserve(size_t countInstances)
+  {
+    positions_.reserve(countInstances);
+    indexes_.reserve(countInstances);
+  }
+
+
+  void SeriesVolumeSorter::AddInstance(const std::string& instanceId,
+                                       const InstanceInformation& instance)
+  {
+    if (instance.HasIndexInSeries())
+    {
+      indexes_.push_back(std::make_pair(instanceId, instance.GetIndexInSeries()));
+    }
+
+    if (!isVolume_ ||
+        !instance.HasPosition())
+    {
+      isVolume_ = false;
+    }
+    else
+    {
+      if (positions_.size() == 0)
+      {
+        // This is the first slice in a possible 3D volume. Remember its normal.
+        normal_[0] = instance.GetNormal(0);
+        normal_[1] = instance.GetNormal(1);
+        normal_[2] = instance.GetNormal(2);
+      }
+      else
+      {
+        static const float THRESHOLD = 10.0f * std::numeric_limits<float>::epsilon();
+
+        // This is still a possible 3D volume. Check whether the normal
+        // is constant wrt. the previous slices.
+        if (fabs(normal_[0] - instance.GetNormal(0)) > THRESHOLD ||
+            fabs(normal_[1] - instance.GetNormal(1)) > THRESHOLD ||
+            fabs(normal_[2] - instance.GetNormal(2)) > THRESHOLD)
+        {
+          // The normal is not constant, not a 3D volume.
+          isVolume_ = false;
+          positions_.clear();
+        }
+      }
+
+      if (isVolume_)
+      {
+        float distance = (normal_[0] * instance.GetPosition(0) + 
+                          normal_[1] * instance.GetPosition(1) +
+                          normal_[2] * instance.GetPosition(2));
+        positions_.push_back(std::make_pair(instanceId, distance));
+      }
+    }
+
+    sorted_ = false;
+  }
+
+
+  std::string  SeriesVolumeSorter::GetInstance(size_t index)
+  {
+    if (!sorted_)
+    {
+      if (isVolume_)
+      {
+        assert(indexes_.size() == positions_.size());
+        std::sort(positions_.begin(), positions_.end(), ComparePosition);
+
+        float a = positions_.front().second;
+        float b = positions_.back().second;
+        assert(a <= b);
+        
+        if (fabs(b - a) <= 10.0f * std::numeric_limits<float>::epsilon())
+        {
+          // Not enough difference between the minimum and maximum
+          // positions along the normal of the volume
+          isVolume_ = false;
+        }
+      }
+
+      if (!isVolume_)
+      {
+        std::sort(indexes_.begin(), indexes_.end(), CompareIndex);
+      }
+    }
+
+    if (isVolume_)
+    {
+      return positions_[index].first;
+    }
+    else
+    {
+      return indexes_[index].first;
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/SeriesVolumeSorter.h	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,70 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero 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
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "InstanceInformation.h"
+
+#include <json/reader.h>
+#include <boost/noncopyable.hpp>
+
+namespace OrthancPlugins
+{
+  class SeriesVolumeSorter : public boost::noncopyable
+  {
+  private:
+    typedef std::pair<std::string, float>  InstanceWithPosition;
+    typedef std::pair<std::string, int>  InstanceWithIndex;
+
+    static bool ComparePosition(const InstanceWithPosition& a,
+                                const InstanceWithPosition& b)
+    {
+      return a.second < b.second;
+    }
+
+    static bool CompareIndex(const InstanceWithIndex& a,
+                             const InstanceWithIndex& b)
+    {
+      return a.second < b.second;
+    }  
+
+    bool            sorted_;
+    bool            isVolume_;
+    float           normal_[3];
+
+    std::vector<InstanceWithPosition>  positions_;
+    std::vector<InstanceWithIndex>  indexes_;
+
+  public:
+    SeriesVolumeSorter();
+
+    void Reserve(size_t countInstances);
+
+    void AddInstance(const std::string& instanceId,
+                     const InstanceInformation& instance);
+
+    size_t GetSize() const
+    {
+      return indexes_.size();
+    }
+
+    std::string  GetInstance(size_t index); 
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/ViewerPrefetchPolicy.cpp	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,160 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero 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
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "ViewerPrefetchPolicy.h"
+
+#include "ViewerToolbox.h"
+#include "Cache/CacheScheduler.h"
+
+#include <json/value.h>
+#include <json/reader.h>
+
+
+
+static const Json::Value::ArrayIndex PREFETCH_FORWARD = 10;
+static const Json::Value::ArrayIndex PREFETCH_BACKWARD = 3;
+
+
+namespace OrthancPlugins
+{
+  void ViewerPrefetchPolicy::ApplySeries(std::list<CacheIndex>& toPrefetch,
+                                         CacheScheduler& cache,
+                                         const std::string& series,
+                                         const std::string& content)
+  {
+    Json::Value json;
+    Json::Reader reader;
+    if (!reader.parse(content, json) ||
+        !json.isMember("SortedInstances"))
+    {
+      return;
+    }
+
+    const Json::Value& instances = json["SortedInstances"];
+    if (instances.type() != Json::arrayValue)
+    {
+      return;
+    }
+
+    for (Json::Value::ArrayIndex i = 0; 
+         i < instances.size() && i < PREFETCH_FORWARD; 
+         i++)
+    {
+      std::string item = "jpeg95-" + instances[i].asString();
+      toPrefetch.push_back(CacheIndex(CacheBundle_DecodedImage, item));
+    }
+  }
+
+
+  void ViewerPrefetchPolicy::ApplyInstance(std::list<CacheIndex>& toPrefetch,
+                                           CacheScheduler& cache,
+                                           const std::string& path)
+  {
+    size_t separator = path.find('-');
+    if (separator == std::string::npos)
+    {
+      return;
+    }
+
+    std::string compression = path.substr(0, separator + 1);
+    std::string instanceId = path.substr(separator + 1);
+
+    Json::Value instance;
+    if (!GetJsonFromOrthanc(instance, context_, "/instances/" + instanceId) ||
+        !instance.isMember("ParentSeries"))
+    {
+      return;
+    }
+
+    std::string tmp;
+    if (!cache.Access(tmp, CacheBundle_SeriesInformation, instance["ParentSeries"].asString()))
+    {
+      return;
+    }
+    
+    Json::Value series;
+    Json::Reader reader;
+    if (!reader.parse(tmp, series) ||
+        !series.isMember("SortedInstances"))
+    {
+      return;
+    }
+
+    const Json::Value& instances = series["SortedInstances"];
+    if (instances.type() != Json::arrayValue)
+    {
+      return;
+    }
+
+    Json::Value::ArrayIndex position = 0;
+    while (position < instances.size())
+    {
+      if (instances[position] == instanceId)
+      {
+        break;
+      }
+
+      position++;
+    }
+
+    if (position == instances.size())
+    {
+      return;
+    }
+
+    for (Json::Value::ArrayIndex i = position;
+         i < instances.size() && i < position + PREFETCH_FORWARD; 
+         i++)
+    {
+      std::string item = compression + instances[i].asString();
+      toPrefetch.push_back(CacheIndex(CacheBundle_DecodedImage, item));
+    }
+
+    for (Json::Value::ArrayIndex i = position;
+         i >= 0 && i > position - PREFETCH_BACKWARD; )
+    {
+      i--;
+      std::string item = compression + instances[i].asString();
+      toPrefetch.push_back(CacheIndex(CacheBundle_DecodedImage, item));
+    }
+  }
+
+
+  void ViewerPrefetchPolicy::Apply(std::list<CacheIndex>& toPrefetch,
+                                   CacheScheduler& cache,
+                                   const CacheIndex& accessed,
+                                   const std::string& content)
+  {
+    switch (accessed.GetBundle())
+    {
+      case CacheBundle_SeriesInformation:
+        ApplySeries(toPrefetch, cache, accessed.GetItem(), content);
+        return;
+
+      case CacheBundle_DecodedImage:
+        ApplyInstance(toPrefetch, cache, accessed.GetItem());
+        return;
+
+      default:
+        return;
+    }
+  }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/ViewerPrefetchPolicy.h	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,53 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero 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
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "Cache/IPrefetchPolicy.h"
+
+#include <orthanc/OrthancCPlugin.h>
+
+namespace OrthancPlugins
+{
+  class ViewerPrefetchPolicy : public IPrefetchPolicy
+  {
+  private:
+    OrthancPluginContext* context_;
+
+    void ApplySeries(std::list<CacheIndex>& toPrefetch,
+                     CacheScheduler& cache,
+                     const std::string& series,
+                     const std::string& content);
+
+    void ApplyInstance(std::list<CacheIndex>& toPrefetch,
+                       CacheScheduler& cache,
+                       const std::string& path);
+
+  public:
+    ViewerPrefetchPolicy(OrthancPluginContext* context) : context_(context)
+    {
+    }
+
+    virtual void Apply(std::list<CacheIndex>& toPrefetch,
+                       CacheScheduler& cache,
+                       const CacheIndex& accessed,
+                       const std::string& content);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/ViewerToolbox.cpp	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,329 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero 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
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "ViewerToolbox.h"
+
+#include "../Orthanc/OrthancException.h"
+#include "../Orthanc/Toolbox.h"
+
+#include <json/reader.h>
+#include <zlib.h>
+#include <stdexcept>
+#include <boost/lexical_cast.hpp>
+#include <fstream>
+#include <sys/stat.h>
+
+namespace OrthancPlugins
+{
+  bool GetStringFromOrthanc(std::string& content,
+                            OrthancPluginContext* context,
+                            const std::string& uri)
+  {
+    OrthancPluginMemoryBuffer answer;
+
+    if (OrthancPluginRestApiGet(context, &answer, uri.c_str()))
+    {
+      return false;
+    }
+
+    if (answer.size)
+    {
+      try
+      {
+        content.assign(reinterpret_cast<const char*>(answer.data), answer.size);
+      }
+      catch (std::bad_alloc&)
+      {
+        OrthancPluginFreeMemoryBuffer(context, &answer);
+        throw Orthanc::OrthancException("Not enough memory");
+      }
+    }
+
+    OrthancPluginFreeMemoryBuffer(context, &answer);
+    return true;
+  }
+
+
+  bool GetJsonFromOrthanc(Json::Value& json,
+                          OrthancPluginContext* context,
+                          const std::string& uri)
+  {
+    OrthancPluginMemoryBuffer answer;
+
+    if (OrthancPluginRestApiGet(context, &answer, uri.c_str()))
+    {
+      return false;
+    }
+
+    if (answer.size)
+    {
+      try
+      {
+        const char* data = reinterpret_cast<const char*>(answer.data);
+        Json::Reader reader;
+        if (!reader.parse(data, data + answer.size, json, 
+                          false /* don't collect comments */))
+        {
+          return false;
+        }
+      }
+      catch (std::runtime_error&)
+      {
+        OrthancPluginFreeMemoryBuffer(context, &answer);
+        return false;
+      }
+    }
+
+    OrthancPluginFreeMemoryBuffer(context, &answer);
+    return true;
+  }
+
+
+
+
+  bool TokenizeVector(std::vector<float>& result,
+                      const std::string& value,
+                      unsigned int expectedSize)
+  {
+    std::vector<std::string> tokens;
+    Orthanc::Toolbox::TokenizeString(tokens, value, '\\');
+
+    if (tokens.size() != expectedSize)
+    {
+      return false;
+    }
+
+    result.resize(tokens.size());
+
+    for (size_t i = 0; i < tokens.size(); i++)
+    {
+      try
+      {
+        result[i] = boost::lexical_cast<float>(tokens[i]);
+      }
+      catch (boost::bad_lexical_cast&)
+      {
+        return false;
+      }
+    }
+
+    return true;
+  }
+
+
+  bool CompressUsingDeflate(std::string& compressed,
+                            const void* uncompressed,
+                            size_t uncompressedSize,
+                            uint8_t compressionLevel)
+  {
+    if (uncompressedSize == 0)
+    {
+      compressed.clear();
+      return true;
+    }
+
+    uLongf compressedSize = compressBound(uncompressedSize);
+    compressed.resize(compressedSize);
+
+    int error = compress2
+      (reinterpret_cast<uint8_t*>(&compressed[0]),
+       &compressedSize,
+       const_cast<Bytef *>(static_cast<const Bytef *>(uncompressed)), 
+       uncompressedSize,
+       compressionLevel);
+
+    if (error == Z_OK)
+    {
+      compressed.resize(compressedSize);
+      return true;
+    }
+    else
+    {
+      compressed.clear();
+      return false;
+    }
+  }
+
+
+
+  const char* GetMimeType(const std::string& path)
+  {
+    size_t dot = path.find_last_of('.');
+
+    std::string extension = (dot == std::string::npos) ? "" : path.substr(dot);
+    std::transform(extension.begin(), extension.end(), extension.begin(), tolower);
+
+    if (extension == ".html")
+    {
+      return "text/html";
+    }
+    else if (extension == ".css")
+    {
+      return "text/css";
+    }
+    else if (extension == ".js")
+    {
+      return "application/javascript";
+    }
+    else if (extension == ".gif")
+    {
+      return "image/gif";
+    }
+    else if (extension == ".svg")
+    {
+      return "image/svg+xml";
+    }
+    else if (extension == ".json")
+    {
+      return "application/json";
+    }
+    else if (extension == ".xml")
+    {
+      return "application/xml";
+    }
+    else if (extension == ".png")
+    {
+      return "image/png";
+    }
+    else if (extension == ".jpg" || extension == ".jpeg")
+    {
+      return "image/jpeg";
+    }
+    else
+    {
+      return "application/octet-stream";
+    }
+  }
+
+
+  bool ReadConfiguration(Json::Value& configuration,
+                         OrthancPluginContext* context)
+  {
+    std::string path;
+
+    {
+      char* pathTmp = OrthancPluginGetConfigurationPath(context);
+      if (pathTmp == NULL)
+      {
+        OrthancPluginLogError(context, "No configuration file is provided");
+        return false;
+      }
+
+      path = std::string(pathTmp);
+
+      OrthancPluginFreeString(context, pathTmp);
+    }
+
+    std::ifstream f(path.c_str());
+
+    Json::Reader reader;
+    if (!reader.parse(f, configuration) ||
+        configuration.type() != Json::objectValue)
+    {
+      std::string s = "Unable to parse the configuration file: " + std::string(path);
+      OrthancPluginLogError(context, s.c_str());
+      return false;
+    }
+
+    return true;
+  }
+
+
+  std::string GetStringValue(const Json::Value& configuration,
+                             const std::string& key,
+                             const std::string& defaultValue)
+  {
+    if (configuration.type() != Json::objectValue ||
+        !configuration.isMember(key) ||
+        configuration[key].type() != Json::stringValue)
+    {
+      return defaultValue;
+    }
+    else
+    {
+      return configuration[key].asString();
+    }
+  }  
+
+
+  int GetIntegerValue(const Json::Value& configuration,
+                      const std::string& key,
+                      int defaultValue)
+  {
+    if (configuration.type() != Json::objectValue ||
+        !configuration.isMember(key) ||
+        configuration[key].type() != Json::intValue)
+    {
+      return defaultValue;
+    }
+    else
+    {
+      return configuration[key].asInt();
+    }
+  }
+
+
+  bool ReadFile(std::string& content,
+                const std::string& path)
+  {
+    struct stat s;
+    if (stat(path.c_str(), &s) != 0 ||
+        !(s.st_mode & S_IFREG))
+    {
+      // Either the path does not exist, or it is not a regular file
+      return false;
+    }
+
+    FILE* fp = fopen(path.c_str(), "rb");
+    if (fp == NULL)
+    {
+      return false;
+    }
+
+    long size;
+
+    if (fseek(fp, 0, SEEK_END) == -1 ||
+        (size = ftell(fp)) < 0)
+    {
+      fclose(fp);
+      return false;
+    }
+
+    content.resize(size);
+      
+    if (fseek(fp, 0, SEEK_SET) == -1)
+    {
+      fclose(fp);
+      return false;
+    }
+
+    bool ok = true;
+
+    if (size > 0 &&
+        fread(&content[0], size, 1, fp) != 1)
+    {
+      ok = false;
+    }
+
+    fclose(fp);
+
+    return ok;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/ViewerToolbox.h	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,68 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero 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
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include <string>
+#include <json/value.h>
+#include <orthanc/OrthancCPlugin.h>
+
+namespace OrthancPlugins
+{
+  enum CacheBundle
+  {
+    CacheBundle_DecodedImage = 1,
+    CacheBundle_InstanceInformation = 2,
+    CacheBundle_SeriesInformation = 3
+  };
+
+  bool GetStringFromOrthanc(std::string& content,
+                            OrthancPluginContext* context,
+                            const std::string& uri);
+
+  bool GetJsonFromOrthanc(Json::Value& json,
+                          OrthancPluginContext* context,
+                          const std::string& uri);
+
+  bool TokenizeVector(std::vector<float>& result,
+                      const std::string& value,
+                      unsigned int expectedSize);
+
+  bool CompressUsingDeflate(std::string& compressed,
+                            const void* uncompressed,
+                            size_t uncompressedSize,
+                            uint8_t compressionLevel);
+
+  const char* GetMimeType(const std::string& path);
+
+  bool ReadConfiguration(Json::Value& configuration,
+                         OrthancPluginContext* context);
+
+  std::string GetStringValue(const Json::Value& configuration,
+                             const std::string& key,
+                             const std::string& defaultValue);
+
+  int GetIntegerValue(const Json::Value& configuration,
+                      const std::string& key,
+                      int defaultValue);
+
+  bool ReadFile(std::string& content,
+                const std::string& path);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/README	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,64 @@
+Web Viewer plugin for Orthanc
+=============================
+
+
+General Information
+-------------------
+
+This repository contains the source code of a plugin implementing a
+Web viewer for Orthanc, the lightweight, RESTful DICOM server.
+
+
+Dependencies
+------------
+
+The Web viewer is based upon the following projects:
+
+* Cornerstone, a client-side JavaScript library to display medical
+  images in Web browsers, by Chris Hafey <chafey@gmail.com>:
+  https://github.com/chafey/cornerstone
+
+* GDCM, an open-source implementation of the DICOM standard with
+  advanced features for image decoding, by Mathieu Malaterre
+  <mathieu.malaterre@gmail.com>:
+  http://sourceforge.net/projects/gdcm/
+
+
+Supported Platforms
+-------------------
+
+Currently, the supported platforms are:
+
+* Linux 32bit.
+* Linux 64bit.
+* Windows 32bit.
+
+
+Build Instructions
+------------------
+
+See: ./Resources/BuildInstructions.txt
+
+
+Licensing
+---------
+
+The Web viewer plugin for Orthanc is licensed under the AGPL 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:
+
+@inproceedings{Jodogne:ISBI2013,
+  author = {Jodogne, S. and Bernard, C. and Devillers, M. and Lenaerts, E. and Coucke, P.},
+  title = {Orthanc -- {A} Lightweight, {REST}ful {DICOM} Server for Healthcare and Medical Research},
+  booktitle={Biomedical Imaging ({ISBI}), {IEEE} 10th International Symposium on}, 
+  year={2013}, 
+  pages={190-193}, 
+  ISSN={1945-7928},
+  month=apr,
+  url={http://ieeexplore.ieee.org/xpl/articleDetails.jsp?tp=&arnumber=6556444},
+  address={San Francisco, {CA}, {USA}}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/BuildInstructions.txt	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,29 @@
+Generic Linux (static linking)
+==============================
+
+# mkdir Build
+# cd Build
+# cmake .. -DCMAKE_BUILD_TYPE=Debug -DALLOW_DOWNLOADS=ON -DSTATIC_BUILD=ON
+# make
+
+
+Debian Sid (dynamic linking)
+============================
+
+# sudo apt-get install build-essential unzip cmake libgdcm2-dev libjpeg-dev \
+                       uuid-dev libgtest-dev libpng-dev libsqlite3-dev \
+                       zlib1g-dev libboost-all-dev libjsoncpp-dev 
+
+# mkdir Build
+# cd Build
+# cmake .. -DCMAKE_BUILD_TYPE=Debug -DALLOW_DOWNLOADS=ON -DUSE_SYSTEM_GOOGLE_TEST=OFF -DSTANDALONE_BUILD=ON
+# make
+
+
+Cross-compiling for Windows from Linux using MinGW
+==================================================
+
+# mkdir Build
+# cd Build
+# cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TOOLCHAIN_FILE=../Resources/MinGWToolchain.cmake
+# make
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/CMake/AutoGeneratedCode.cmake	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,59 @@
+# Orthanc - A Lightweight, RESTful DICOM Store
+# Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+# Department, University Hospital of Liege, Belgium
+#
+# This program is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Affero 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
+# Affero General Public License for more details.
+# 
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+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_ARGUMENTS)
+  set(DEPENDENCIES)
+  set(IS_PATH_NAME false)
+  foreach(arg ${ARGN})
+    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()
+  endforeach()
+
+  set(TARGET_BASE "${AUTOGENERATED_DIR}/EmbeddedResources")
+  add_custom_command(
+    OUTPUT
+    "${TARGET_BASE}.h"
+    "${TARGET_BASE}.cpp"
+    COMMAND 
+    python
+    "${CMAKE_CURRENT_SOURCE_DIR}/Resources/EmbedResources.py"
+    "${AUTOGENERATED_DIR}/EmbeddedResources"
+    ${SCRIPT_ARGUMENTS}
+    DEPENDS
+    "${CMAKE_CURRENT_SOURCE_DIR}/Resources/EmbedResources.py"
+    ${DEPENDENCIES}
+    )
+
+  list(APPEND AUTOGENERATED_SOURCES
+    "${AUTOGENERATED_DIR}/EmbeddedResources.cpp"
+    ) 
+endmacro()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/CMake/BoostConfiguration.cmake	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,124 @@
+# Orthanc - A Lightweight, RESTful DICOM Store
+# Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+# Department, University Hospital of Liege, Belgium
+#
+# This program is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Affero 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
+# Affero General Public License for more details.
+# 
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+if (STATIC_BUILD OR NOT USE_SYSTEM_BOOST)
+  set(BOOST_STATIC 1)
+else()
+  include(FindBoost)
+  set(BOOST_STATIC 0)
+  find_package(Boost COMPONENTS system thread filesystem)
+
+  if (NOT Boost_FOUND)
+    message(FATAL_ERROR "Unable to locate Boost on this system")
+  endif()
+
+  include_directories(${Boost_INCLUDE_DIRS})
+  link_libraries(${Boost_LIBRARIES})
+endif()
+
+
+if (BOOST_STATIC)
+  # Parameters for Boost 1.55.0
+  set(BOOST_NAME boost_1_55_0)
+  set(BOOST_BCP_SUFFIX bcpdigest-0.7.4)
+  set(BOOST_MD5 "409f7a0e4fb1f5659d07114f3133b67b")
+  set(BOOST_FILESYSTEM_SOURCES_DIR "${BOOST_NAME}/libs/filesystem/src")
+  
+  set(BOOST_SOURCES_DIR ${CMAKE_BINARY_DIR}/${BOOST_NAME})
+  DownloadPackage(
+    "${BOOST_MD5}"
+    "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/${BOOST_NAME}_${BOOST_BCP_SUFFIX}.tar.gz"
+    "${BOOST_SOURCES_DIR}"
+    )
+
+  add_definitions(
+    # Static build of Boost
+    -DBOOST_ALL_NO_LIB 
+    -DBOOST_ALL_NOLIB 
+    -DBOOST_DATE_TIME_NO_LIB 
+    -DBOOST_THREAD_BUILD_LIB
+    -DBOOST_PROGRAM_OPTIONS_NO_LIB
+    -DBOOST_REGEX_NO_LIB
+    -DBOOST_SYSTEM_NO_LIB
+    -DBOOST_LOCALE_NO_LIB
+    )
+
+  if (${CMAKE_COMPILER_IS_GNUCXX})
+    add_definitions(-isystem ${BOOST_SOURCES_DIR})
+  endif()
+
+  include_directories(
+    ${BOOST_SOURCES_DIR}
+    )
+
+  list(APPEND BOOST_SOURCES
+    ${BOOST_SOURCES_DIR}/libs/system/src/error_code.cpp
+    )
+
+
+  ## Boost::thread
+
+  if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR
+      ${CMAKE_SYSTEM_NAME} STREQUAL "Darwin" OR
+      ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD")
+    list(APPEND BOOST_SOURCES
+      ${BOOST_SOURCES_DIR}/libs/thread/src/pthread/once.cpp
+      ${BOOST_SOURCES_DIR}/libs/thread/src/pthread/thread.cpp
+      )
+
+    if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase")
+      add_definitions(-DBOOST_HAS_SCHED_YIELD=1)
+    endif()
+
+  elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
+    list(APPEND BOOST_SOURCES
+      ${BOOST_SOURCES_DIR}/libs/thread/src/win32/tss_dll.cpp
+      ${BOOST_SOURCES_DIR}/libs/thread/src/win32/thread.cpp
+      ${BOOST_SOURCES_DIR}/libs/thread/src/win32/tss_pe.cpp
+      )
+  endif()
+
+
+  ## Boost::filesystem
+
+  list(APPEND BOOST_SOURCES
+    ${BOOST_FILESYSTEM_SOURCES_DIR}/codecvt_error_category.cpp
+    ${BOOST_FILESYSTEM_SOURCES_DIR}/operations.cpp
+    ${BOOST_FILESYSTEM_SOURCES_DIR}/path.cpp
+    ${BOOST_FILESYSTEM_SOURCES_DIR}/path_traits.cpp
+    )
+
+  if (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin")
+    list(APPEND BOOST_SOURCES
+      ${BOOST_SOURCES_DIR}/libs/filesystem/src/utf8_codecvt_facet.cpp
+      )
+  elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
+    list(APPEND BOOST_SOURCES
+      ${BOOST_FILESYSTEM_SOURCES_DIR}/windows_file_codecvt.cpp
+      )
+  endif()
+
+
+  source_group(ThirdParty\\Boost REGULAR_EXPRESSION ${BOOST_SOURCES_DIR}/.*)
+endif()
+
+
+add_definitions(
+  -DBOOST_HAS_FILESYSTEM_V3=1
+  -DBOOST_HAS_LOCALE=1
+  )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/CMake/DownloadPackage.cmake	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,156 @@
+# Orthanc - A Lightweight, RESTful DICOM Store
+# Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+# Department, University Hospital of Liege, Belgium
+#
+# This program is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Affero 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
+# Affero General Public License for more details.
+# 
+# You should have received a copy of the GNU Affero 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()
+
+
+##
+## 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()
+endif()
+
+
+macro(DownloadPackage MD5 Url TargetDirectory)
+  if (NOT IS_DIRECTORY "${TargetDirectory}")
+    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()
+
+      file(DOWNLOAD "${Url}" "${TMP_PATH}" SHOW_PROGRESS EXPECTED_MD5 "${MD5}")
+    else()
+      message("Using local copy of ${Url}")
+    endif()
+
+    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"))
+        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}")
+        else()
+          string(REGEX REPLACE ".gz$" "" 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 "Support your platform here")
+      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
+          )
+      else()
+        message(FATAL_ERROR "Unknown package format.")
+      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()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/CMake/GdcmConfiguration.cmake	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,86 @@
+# Orthanc - A Lightweight, RESTful DICOM Store
+# Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+# Department, University Hospital of Liege, Belgium
+#
+# This program is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Affero 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
+# Affero General Public License for more details.
+# 
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+if (STATIC_BUILD OR NOT USE_SYSTEM_GDCM)
+  # If using gcc, build GDCM with the "-fPIC" argument to allow its
+  # embedding into the shared library containing the Orthanc plugin
+  if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
+    set(Flags -DCMAKE_CXX_FLAGS:STRING=-fPIC -DCMAKE_C_FLAGS:STRING=-fPIC)
+  else()
+  endif()
+
+  if (CMAKE_TOOLCHAIN_FILE)
+    list(APPEND Flags -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE})
+  endif()
+
+  include(ExternalProject)
+  externalproject_add(GDCM
+    URL "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/gdcm-2.4.4.tar.gz"
+    URL_MD5 "5dca87a061c536b6fa377263b7839dcb"
+    CMAKE_ARGS -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE} ${Flags}
+    #-DLIBRARY_OUTPUT_PATH=${CMAKE_CURRENT_BINARY_DIR}
+    INSTALL_COMMAND ""  # Skip the install step
+    )
+
+  if(MSVC)
+    set(Suffix ".lib")
+  else()
+    set(Suffix ".a")
+  endif()
+
+  list(GET CMAKE_FIND_LIBRARY_PREFIXES 0 Prefix)
+  set(GDCM_LIBRARIES 
+    ${Prefix}gdcmMSFF${Suffix}
+    ${Prefix}gdcmcharls${Suffix}
+    ${Prefix}gdcmDICT${Suffix}
+    ${Prefix}gdcmDSED${Suffix}
+    ${Prefix}gdcmIOD${Suffix}
+    ${Prefix}gdcmjpeg8${Suffix}
+    ${Prefix}gdcmjpeg12${Suffix}
+    ${Prefix}gdcmjpeg16${Suffix}
+    ${Prefix}gdcmMEXD${Suffix}
+    ${Prefix}gdcmopenjpeg${Suffix}
+    ${Prefix}gdcmzlib${Suffix}
+    ${Prefix}socketxx${Suffix}
+    ${Prefix}gdcmCommon${Suffix}
+    ${Prefix}gdcmexpat${Suffix}
+
+    #${Prefix}gdcmgetopt${Suffix}
+    #${Prefix}gdcmuuid${Suffix}
+    )
+
+  ExternalProject_Get_Property(GDCM binary_dir)
+  include_directories(${binary_dir}/Source/Common)
+  link_directories(${binary_dir}/bin)
+
+  ExternalProject_Get_Property(GDCM source_dir)
+  include_directories(
+    ${source_dir}/Source/Common
+    ${source_dir}/Source/MediaStorageAndFileFormat
+    ${source_dir}/Source/DataStructureAndEncodingDefinition
+    )
+
+else()
+  find_package(GDCM REQUIRED)
+  if (GDCM_FOUND)
+    include(${GDCM_USE_FILE})
+    set(GDCM_LIBRARIES gdcmCommon gdcmMSFF)
+  else(GDCM_FOUND)
+    message(FATAL_ERROR "Cannot find GDCM, did you set GDCM_DIR?")
+  endif(GDCM_FOUND)
+endif()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/CMake/GoogleTestConfiguration.cmake	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,57 @@
+# Orthanc - A Lightweight, RESTful DICOM Store
+# Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+# Department, University Hospital of Liege, Belgium
+#
+# This program is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Affero 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
+# Affero General Public License for more details.
+# 
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+if (USE_GTEST_DEBIAN_SOURCE_PACKAGE)
+  set(GTEST_SOURCES /usr/src/gtest/src/gtest-all.cc)
+  include_directories(/usr/src/gtest)
+
+  if (NOT EXISTS /usr/include/gtest/gtest.h OR
+      NOT EXISTS ${GTEST_SOURCES})
+    message(FATAL_ERROR "Please install the libgtest-dev package")
+  endif()
+
+elseif (STATIC_BUILD OR NOT USE_SYSTEM_GOOGLE_TEST)
+  set(GTEST_SOURCES_DIR ${CMAKE_BINARY_DIR}/gtest-1.7.0)
+  DownloadPackage(
+    "2d6ec8ccdf5c46b05ba54a9fd1d130d7"
+    "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/gtest-1.7.0.zip"
+    "${GTEST_SOURCES_DIR}")
+
+  include_directories(
+    ${GTEST_SOURCES_DIR}/include
+    ${GTEST_SOURCES_DIR}
+    )
+
+  set(GTEST_SOURCES
+    ${GTEST_SOURCES_DIR}/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()
+
+else()
+  include(FindGTest)
+  if (NOT GTEST_FOUND)
+    message(FATAL_ERROR "Unable to find GoogleTest")
+  endif()
+
+  include_directories(${GTEST_INCLUDE_DIRS})
+  link_libraries(${GTEST_LIBRARIES})
+endif()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/CMake/JavaScriptLibraries.cmake	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,68 @@
+# Orthanc - A Lightweight, RESTful DICOM Store
+# Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+# Department, University Hospital of Liege, Belgium
+#
+# This program is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Affero 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
+# Affero General Public License for more details.
+# 
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+set(BASE_URL "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/WebViewer")
+
+DownloadPackage(
+  "eef2d2e8c057d33c20ab55fdd06a73c7"
+  "${BASE_URL}/cornerstone-0.7.1.zip"
+  "cornerstone-0.7.1")
+
+DownloadPackage(
+  "cb943ac26be9ee755e8741ea232389e2"
+  "${BASE_URL}/jquery-ui-1.11.3.zip"
+  "jquery-ui-1.11.3")
+
+DownloadPackage(
+  "8f27231a78218b959159e37daa3d86b3"
+  "${BASE_URL}/jsPanel-2.3.3.zip"
+  "jspanel")
+
+DownloadPackage(
+  "8392ad105d913c3a83a7787c8f148055"
+  "${BASE_URL}/pako-0.2.5.zip"
+  "pako-0.2.5")
+
+DownloadPackage(
+  "7ebea0b624cd62445a124d264dfa2a53"
+  "${BASE_URL}/js-url-1.8.6.zip"
+  "js-url-1.8.6")
+
+
+set(JAVASCRIPT_LIBS_DIR  ${CMAKE_CURRENT_BINARY_DIR}/javascript-libs)
+file(MAKE_DIRECTORY ${JAVASCRIPT_LIBS_DIR})
+
+file(COPY
+  ${CMAKE_CURRENT_BINARY_DIR}/cornerstone-0.7.1/dist/cornerstone.min.css
+  ${CMAKE_CURRENT_BINARY_DIR}/cornerstone-0.7.1/dist/cornerstone.min.js
+  ${CMAKE_CURRENT_BINARY_DIR}/jquery-ui-1.11.3/external/jquery/jquery.js
+  ${CMAKE_CURRENT_BINARY_DIR}/jquery-ui-1.11.3/images
+  ${CMAKE_CURRENT_BINARY_DIR}/jquery-ui-1.11.3/jquery-ui.min.css
+  ${CMAKE_CURRENT_BINARY_DIR}/jquery-ui-1.11.3/jquery-ui.min.js
+  ${CMAKE_CURRENT_BINARY_DIR}/jquery-ui-1.11.3/jquery-ui.theme.min.css
+  ${CMAKE_CURRENT_BINARY_DIR}/js-url-1.8.6/url.min.js
+  ${CMAKE_CURRENT_BINARY_DIR}/jspanel/fonts
+  ${CMAKE_CURRENT_BINARY_DIR}/jspanel/images
+  ${CMAKE_CURRENT_BINARY_DIR}/jspanel/jquery.jspanel.min.css
+  ${CMAKE_CURRENT_BINARY_DIR}/jspanel/jquery.jspanel.min.js
+  ${CMAKE_CURRENT_BINARY_DIR}/vendor/jquery.ui.touch-punch.min.js
+  ${CMAKE_CURRENT_BINARY_DIR}/vendor/mobile-detect.min.js
+  ${CMAKE_CURRENT_BINARY_DIR}/pako-0.2.5/dist/pako_inflate.min.js
+  DESTINATION
+  ${JAVASCRIPT_LIBS_DIR}
+  )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/CMake/JsonCppConfiguration.cmake	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,47 @@
+# Orthanc - A Lightweight, RESTful DICOM Store
+# Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+# Department, University Hospital of Liege, Belgium
+#
+# This program is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Affero 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
+# Affero General Public License for more details.
+# 
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+if (STATIC_BUILD OR NOT USE_SYSTEM_JSONCPP)
+  set(JSONCPP_SOURCES_DIR ${CMAKE_BINARY_DIR}/jsoncpp-src-0.6.0-rc2)
+  DownloadPackage(
+    "363e2f4cbd3aeb63bf4e571f377400fb"
+    "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/jsoncpp-src-0.6.0-rc2.tar.gz"
+    "${JSONCPP_SOURCES_DIR}")
+
+  list(APPEND JSONCPP_SOURCES
+    ${JSONCPP_SOURCES_DIR}/src/lib_json/json_reader.cpp
+    ${JSONCPP_SOURCES_DIR}/src/lib_json/json_value.cpp
+    ${JSONCPP_SOURCES_DIR}/src/lib_json/json_writer.cpp
+    )
+
+  include_directories(
+    ${JSONCPP_SOURCES_DIR}/include
+    )
+
+  source_group(ThirdParty\\JsonCpp REGULAR_EXPRESSION ${JSONCPP_SOURCES_DIR}/.*)
+
+else()
+  CHECK_INCLUDE_FILE_CXX(jsoncpp/json/reader.h HAVE_JSONCPP_H)
+  if (NOT HAVE_JSONCPP_H)
+    message(FATAL_ERROR "Please install the libjsoncpp-dev package")
+  endif()
+
+  include_directories(/usr/include/jsoncpp)
+  link_libraries(jsoncpp)
+
+endif()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/CMake/LibJpegConfiguration.cmake	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,104 @@
+# Orthanc - A Lightweight, RESTful DICOM Store
+# Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+# Department, University Hospital of Liege, Belgium
+#
+# This program is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Affero 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
+# Affero General Public License for more details.
+# 
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+if (STATIC_BUILD OR NOT USE_SYSTEM_LIBJPEG)
+  set(LIBJPEG_SOURCES_DIR ${CMAKE_BINARY_DIR}/jpeg-9a)
+  DownloadPackage(
+    "3353992aecaee1805ef4109aadd433e7"
+    "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/jpegsrc.v9a.tar.gz"
+    "${LIBJPEG_SOURCES_DIR}")
+
+  include_directories(
+    ${LIBJPEG_SOURCES_DIR}/
+    )
+
+  list(APPEND LIBJPEG_SOURCES 
+    ${LIBJPEG_SOURCES_DIR}/jaricom.c
+    ${LIBJPEG_SOURCES_DIR}/jcapimin.c
+    ${LIBJPEG_SOURCES_DIR}/jcapistd.c
+    ${LIBJPEG_SOURCES_DIR}/jcarith.c
+    ${LIBJPEG_SOURCES_DIR}/jccoefct.c
+    ${LIBJPEG_SOURCES_DIR}/jccolor.c
+    ${LIBJPEG_SOURCES_DIR}/jcdctmgr.c
+    ${LIBJPEG_SOURCES_DIR}/jchuff.c
+    ${LIBJPEG_SOURCES_DIR}/jcinit.c
+    ${LIBJPEG_SOURCES_DIR}/jcmarker.c
+    ${LIBJPEG_SOURCES_DIR}/jcmaster.c
+    ${LIBJPEG_SOURCES_DIR}/jcomapi.c
+    ${LIBJPEG_SOURCES_DIR}/jcparam.c
+    ${LIBJPEG_SOURCES_DIR}/jcprepct.c
+    ${LIBJPEG_SOURCES_DIR}/jcsample.c
+    ${LIBJPEG_SOURCES_DIR}/jctrans.c
+    ${LIBJPEG_SOURCES_DIR}/jdapimin.c
+    ${LIBJPEG_SOURCES_DIR}/jdapistd.c
+    ${LIBJPEG_SOURCES_DIR}/jdarith.c
+    ${LIBJPEG_SOURCES_DIR}/jdatadst.c
+    ${LIBJPEG_SOURCES_DIR}/jdatasrc.c
+    ${LIBJPEG_SOURCES_DIR}/jdcoefct.c
+    ${LIBJPEG_SOURCES_DIR}/jdcolor.c
+    ${LIBJPEG_SOURCES_DIR}/jddctmgr.c
+    ${LIBJPEG_SOURCES_DIR}/jdhuff.c
+    ${LIBJPEG_SOURCES_DIR}/jdinput.c
+    ${LIBJPEG_SOURCES_DIR}/jcmainct.c
+    ${LIBJPEG_SOURCES_DIR}/jdmainct.c
+    ${LIBJPEG_SOURCES_DIR}/jdmarker.c
+    ${LIBJPEG_SOURCES_DIR}/jdmaster.c
+    ${LIBJPEG_SOURCES_DIR}/jdmerge.c
+    ${LIBJPEG_SOURCES_DIR}/jdpostct.c
+    ${LIBJPEG_SOURCES_DIR}/jdsample.c
+    ${LIBJPEG_SOURCES_DIR}/jdtrans.c
+    ${LIBJPEG_SOURCES_DIR}/jerror.c
+    ${LIBJPEG_SOURCES_DIR}/jfdctflt.c
+    ${LIBJPEG_SOURCES_DIR}/jfdctfst.c
+    ${LIBJPEG_SOURCES_DIR}/jfdctint.c
+    ${LIBJPEG_SOURCES_DIR}/jidctflt.c
+    ${LIBJPEG_SOURCES_DIR}/jidctfst.c
+    ${LIBJPEG_SOURCES_DIR}/jidctint.c
+    #${LIBJPEG_SOURCES_DIR}/jmemansi.c
+    #${LIBJPEG_SOURCES_DIR}/jmemdos.c
+    #${LIBJPEG_SOURCES_DIR}/jmemmac.c
+    ${LIBJPEG_SOURCES_DIR}/jmemmgr.c
+    #${LIBJPEG_SOURCES_DIR}/jmemname.c
+    ${LIBJPEG_SOURCES_DIR}/jmemnobs.c
+    ${LIBJPEG_SOURCES_DIR}/jquant1.c
+    ${LIBJPEG_SOURCES_DIR}/jquant2.c
+    ${LIBJPEG_SOURCES_DIR}/jutils.c
+
+    # ${LIBJPEG_SOURCES_DIR}/rdbmp.c
+    # ${LIBJPEG_SOURCES_DIR}/rdcolmap.c
+    # ${LIBJPEG_SOURCES_DIR}/rdgif.c
+    # ${LIBJPEG_SOURCES_DIR}/rdppm.c
+    # ${LIBJPEG_SOURCES_DIR}/rdrle.c
+    # ${LIBJPEG_SOURCES_DIR}/rdswitch.c
+    # ${LIBJPEG_SOURCES_DIR}/rdtarga.c
+    # ${LIBJPEG_SOURCES_DIR}/transupp.c
+    # ${LIBJPEG_SOURCES_DIR}/wrbmp.c
+    # ${LIBJPEG_SOURCES_DIR}/wrgif.c
+    # ${LIBJPEG_SOURCES_DIR}/wrppm.c
+    # ${LIBJPEG_SOURCES_DIR}/wrrle.c
+    # ${LIBJPEG_SOURCES_DIR}/wrtarga.c
+    )
+
+  configure_file(
+    ${LIBJPEG_SOURCES_DIR}/jconfig.txt
+    ${LIBJPEG_SOURCES_DIR}/jconfig.h COPYONLY
+    )
+
+else()
+  link_libraries(jpeg)
+endif()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/CMake/LibPngConfiguration.cmake	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,78 @@
+# Orthanc - A Lightweight, RESTful DICOM Store
+# Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+# Department, University Hospital of Liege, Belgium
+#
+# This program is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Affero 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
+# Affero General Public License for more details.
+# 
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+if (STATIC_BUILD OR NOT USE_SYSTEM_LIBPNG)
+  SET(LIBPNG_SOURCES_DIR ${CMAKE_BINARY_DIR}/libpng-1.5.12)
+  DownloadPackage(
+    "8ea7f60347a306c5faf70b977fa80e28"
+    "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/libpng-1.5.12.tar.gz"
+    "${LIBPNG_SOURCES_DIR}")
+
+  include_directories(
+    ${LIBPNG_SOURCES_DIR}
+    )
+
+  configure_file(
+    ${LIBPNG_SOURCES_DIR}/scripts/pnglibconf.h.prebuilt
+    ${LIBPNG_SOURCES_DIR}/pnglibconf.h
+    COPY_ONLY)
+
+  set(LIBPNG_SOURCES
+    #${LIBPNG_SOURCES_DIR}/example.c
+    ${LIBPNG_SOURCES_DIR}/png.c
+    ${LIBPNG_SOURCES_DIR}/pngerror.c
+    ${LIBPNG_SOURCES_DIR}/pngget.c
+    ${LIBPNG_SOURCES_DIR}/pngmem.c
+    ${LIBPNG_SOURCES_DIR}/pngpread.c
+    ${LIBPNG_SOURCES_DIR}/pngread.c
+    ${LIBPNG_SOURCES_DIR}/pngrio.c
+    ${LIBPNG_SOURCES_DIR}/pngrtran.c
+    ${LIBPNG_SOURCES_DIR}/pngrutil.c
+    ${LIBPNG_SOURCES_DIR}/pngset.c
+    #${LIBPNG_SOURCES_DIR}/pngtest.c
+    ${LIBPNG_SOURCES_DIR}/pngtrans.c
+    ${LIBPNG_SOURCES_DIR}/pngwio.c
+    ${LIBPNG_SOURCES_DIR}/pngwrite.c
+    ${LIBPNG_SOURCES_DIR}/pngwtran.c
+    ${LIBPNG_SOURCES_DIR}/pngwutil.c
+    )
+
+  #set_property(
+  #  SOURCE ${LIBPNG_SOURCES}
+  #  PROPERTY COMPILE_FLAGS -UHAVE_CONFIG_H)
+
+  list(APPEND THIRD_PARTY_SOURCES ${LIBPNG_SOURCES})
+
+  add_definitions(
+    -DPNG_NO_CONSOLE_IO=1
+    -DPNG_NO_STDIO=1
+    )
+
+  source_group(ThirdParty\\Libpng REGULAR_EXPRESSION ${LIBPNG_SOURCES_DIR}/.*)
+
+else()
+  include(FindPNG)
+
+  if (NOT ${PNG_FOUND})
+    message(FATAL_ERROR "Unable to find LibPNG")
+  endif()
+
+  include_directories(${PNG_INCLUDE_DIRS})
+  link_libraries(${PNG_LIBRARIES})
+  add_definitions(${PNG_DEFINITIONS})
+endif()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/CMake/SQLiteConfiguration.cmake	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,61 @@
+# Orthanc - A Lightweight, RESTful DICOM Store
+# Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+# Department, University Hospital of Liege, Belgium
+#
+# This program is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Affero 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
+# Affero General Public License for more details.
+# 
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+if (STATIC_BUILD OR NOT USE_SYSTEM_SQLITE)
+  SET(SQLITE_SOURCES_DIR ${CMAKE_BINARY_DIR}/sqlite-amalgamation-3071300)
+  DownloadPackage(
+    "5fbeff9645ab035a1f580e90b279a16d"
+    "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/sqlite-amalgamation-3071300.zip"
+    "${SQLITE_SOURCES_DIR}")
+
+  list(APPEND SQLITE_SOURCES
+    ${SQLITE_SOURCES_DIR}/sqlite3.c
+    )
+
+  add_definitions(
+    # For SQLite to run in the "Serialized" thread-safe mode
+    # http://www.sqlite.org/threadsafe.html
+    -DSQLITE_THREADSAFE=1  
+    -DSQLITE_OMIT_LOAD_EXTENSION  # Disable SQLite plugins
+    )
+
+  include_directories(
+    ${SQLITE_SOURCES_DIR}
+    )
+
+  source_group(ThirdParty\\SQLite REGULAR_EXPRESSION ${SQLITE_SOURCES_DIR}/.*)
+
+else()
+  CHECK_INCLUDE_FILE_CXX(sqlite3.h HAVE_SQLITE_H)
+  if (NOT HAVE_SQLITE_H)
+    message(FATAL_ERROR "Please install the libsqlite3-dev package")
+  endif()
+
+  # Autodetection of the version of SQLite
+  file(STRINGS "/usr/include/sqlite3.h" SQLITE_VERSION_NUMBER1 REGEX "#define SQLITE_VERSION_NUMBER.*$")    
+  string(REGEX REPLACE "#define SQLITE_VERSION_NUMBER(.*)$" "\\1" SQLITE_VERSION_NUMBER ${SQLITE_VERSION_NUMBER1})
+
+  message("Detected version of SQLite: ${SQLITE_VERSION_NUMBER}")
+
+  IF (${SQLITE_VERSION_NUMBER} LESS 3007000)
+    # "sqlite3_create_function_v2" is not defined in SQLite < 3.7.0
+    message(FATAL_ERROR "SQLite version must be above 3.7.0. Please set the CMake variable USE_SYSTEM_SQLITE to OFF.")
+  ENDIF()
+
+  link_libraries(sqlite3)
+endif()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/CMake/ZlibConfiguration.cmake	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,60 @@
+# Orthanc - A Lightweight, RESTful DICOM Store
+# Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+# Department, University Hospital of Liege, Belgium
+#
+# This program is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Affero 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
+# Affero General Public License for more details.
+# 
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+# This is the minizip distribution to create ZIP files
+list(APPEND THIRD_PARTY_SOURCES 
+  ${ORTHANC_ROOT}/Resources/ThirdParty/minizip/ioapi.c
+  ${ORTHANC_ROOT}/Resources/ThirdParty/minizip/zip.c
+  )
+
+if (STATIC_BUILD OR NOT USE_SYSTEM_ZLIB)
+  SET(ZLIB_SOURCES_DIR ${CMAKE_BINARY_DIR}/zlib-1.2.7)
+  DownloadPackage(
+    "60df6a37c56e7c1366cca812414f7b85"
+    "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/zlib-1.2.7.tar.gz"
+    "${ZLIB_SOURCES_DIR}")
+
+  include_directories(
+    ${ZLIB_SOURCES_DIR}
+    )
+
+  list(APPEND ZLIB_SOURCES 
+    ${ZLIB_SOURCES_DIR}/adler32.c
+    ${ZLIB_SOURCES_DIR}/compress.c
+    ${ZLIB_SOURCES_DIR}/crc32.c 
+    ${ZLIB_SOURCES_DIR}/deflate.c 
+    ${ZLIB_SOURCES_DIR}/gzclose.c 
+    ${ZLIB_SOURCES_DIR}/gzlib.c 
+    ${ZLIB_SOURCES_DIR}/gzread.c 
+    ${ZLIB_SOURCES_DIR}/gzwrite.c 
+    ${ZLIB_SOURCES_DIR}/infback.c 
+    ${ZLIB_SOURCES_DIR}/inffast.c 
+    ${ZLIB_SOURCES_DIR}/inflate.c 
+    ${ZLIB_SOURCES_DIR}/inftrees.c 
+    ${ZLIB_SOURCES_DIR}/trees.c 
+    ${ZLIB_SOURCES_DIR}/uncompr.c 
+    ${ZLIB_SOURCES_DIR}/zutil.c
+    )
+
+else()
+  include(FindZLIB)
+  include_directories(${ZLIB_INCLUDE_DIRS})
+  link_libraries(${ZLIB_LIBRARIES})
+endif()
+
+source_group(ThirdParty\\ZLib REGULAR_EXPRESSION ${ZLIB_SOURCES_DIR}/.*)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/EmbedResources.py	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,391 @@
+# Orthanc - A Lightweight, RESTful DICOM Store
+# Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+# Department, University Hospital of Liege, Belgium
+#
+# This program is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Affero 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
+# Affero General Public License for more details.
+# 
+# You should have received a copy of the GNU Affero 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
+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
+
+if len(ARGS) < 2 or len(ARGS) % 2 != 0:
+    print ('Usage:')
+    print ('python %s [--no-upcase-check] <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):
+            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>
+
+namespace OrthancPlugins
+{
+  namespace EmbeddedResources
+  {
+    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);
+  }
+}
+""")
+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
+    for b in content:
+        if PYTHON_MAJOR_VERSION == 2:
+            c = ord(b[0])
+        else:
+            c = b
+
+        if pos > 0:
+            cpp.write(', ')
+
+        if (pos % 16) == 0:
+            cpp.write('\n    ')
+
+        if c < 0:
+            raise Exception("Internal error")
+
+        cpp.write("0x%02x" % c)
+        pos += 1
+
+    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"
+
+#include <stdexcept>
+#include <stdint.h>
+#include <string.h>
+
+namespace OrthancPlugins
+{
+  namespace EmbeddedResources
+  {
+""" % (os.path.basename(TARGET_BASE_FILENAME)))
+
+
+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 std::runtime_error("Parameter out of range");
+      }
+    }
+
+    size_t GetFileResourceSize(FileResourceId id)
+    {
+      switch (id)
+      {
+""")
+
+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 std::runtime_error("Parameter out of range");
+      }
+    }
+""")
+
+
+
+#####################################################################
+## 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 std::runtime_error("Unknown path in a directory resource");\n\n')
+
+cpp.write("""      default:
+        throw std::runtime_error("Parameter out of range");
+      }
+    }
+
+    size_t GetDirectoryResourceSize(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%dSize;\n' % resources[name]['Files'][path]['Index'])
+        cpp.write('        throw std::runtime_error("Unknown path in a directory resource");\n\n')
+
+cpp.write("""      default:
+        throw std::runtime_error("Parameter out of range");
+      }
+    }
+""")
+
+
+
+
+#####################################################################
+## 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 std::runtime_error("Parameter out of range");
+      }
+    }
+""")
+
+
+
+
+#####################################################################
+## 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);
+    }
+  }
+}
+""")
+cpp.close()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/ImplementationNotes.txt	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,17 @@
+Possible combinations returned by the plugin:
+
+Compression | Color | Depth (bpp) | Colorspace | Stretched
+------------+-------+-------------+------------+-----------
+Deflate     | False | 16          | int16_t    | Never
+JPEG        | False | 8           | uint8_t    | Possible
+Deflate     | True  | 8           | RGB24      | Never
+JPEG        | True  | 8           | RGB24      | Never
+
+
+In the viewer, grayscale images are always converted to int16_t.
+
+
+
+Note for Cornerstone < 0.7.1: 1 must be added to "maxPixelValue", as
+the range [minPixelValue, maxPixelValue[ is taken into consideration
+by Cornerstone (i.e. "maxPixelValue" is not inclusive).
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/MinGWToolchain.cmake	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,37 @@
+# Orthanc - A Lightweight, RESTful DICOM Store
+# Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+# Department, University Hospital of Liege, Belgium
+#
+# This program is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Affero 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
+# Affero General Public License for more details.
+# 
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+# http://www.vtk.org/Wiki/CmakeMingw
+
+# 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)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/OrthancExplorer.js	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,18 @@
+$('#series').live('pagebeforecreate', function() {
+  $('#series-preview').parent().remove();
+
+  var b = $('<a>')
+    .attr('data-role', 'button')
+    .attr('href', '#')
+    .attr('data-icon', 'search')
+    .attr('data-theme', 'e')
+    .text('Orthanc Web Viewer');
+
+  b.insertBefore($('#series-delete').parent().parent());
+  b.click(function() {
+    if ($.mobile.pageData) {
+      var series = $.mobile.pageData.uuid;
+      window.open('/web-viewer/app/viewer.html?series=' + series);
+    }
+  });
+});
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/ThirdParty/base64/base64.cpp	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,128 @@
+/* 
+   base64.cpp and base64.h
+
+   Copyright (C) 2004-2008 René Nyffenegger
+
+   This source code is provided 'as-is', without any express or implied
+   warranty. In no event will the author be held liable for any damages
+   arising from the use of this software.
+
+   Permission is granted to anyone to use this software for any purpose,
+   including commercial applications, and to alter it and redistribute it
+   freely, subject to the following restrictions:
+
+   1. The origin of this source code must not be misrepresented; you must not
+      claim that you wrote the original source code. If you use this source code
+      in a product, an acknowledgment in the product documentation would be
+      appreciated but is not required.
+
+   2. Altered source versions must be plainly marked as such, and must not be
+      misrepresented as being the original source code.
+
+   3. This notice may not be removed or altered from any source distribution.
+
+   René Nyffenegger rene.nyffenegger@adp-gmbh.ch
+
+*/
+
+#include "base64.h"
+#include <string.h>
+
+static const std::string base64_chars = 
+             "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+             "abcdefghijklmnopqrstuvwxyz"
+             "0123456789+/";
+
+
+static inline bool is_base64(unsigned char c) {
+  return (isalnum(c) || (c == '+') || (c == '/'));
+}
+
+std::string base64_encode(const std::string& stringToEncode) 
+{
+  const unsigned char* bytes_to_encode = reinterpret_cast<const unsigned char*>
+    (stringToEncode.size() > 0 ? &stringToEncode[0] : NULL);
+  unsigned int in_len = stringToEncode.size();
+  
+  std::string ret;
+  int i = 0;
+  int j = 0;
+  unsigned char char_array_3[3];
+  unsigned char char_array_4[4];
+
+  while (in_len--) {
+    char_array_3[i++] = *(bytes_to_encode++);
+    if (i == 3) {
+      char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
+      char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
+      char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
+      char_array_4[3] = char_array_3[2] & 0x3f;
+
+      for(i = 0; (i <4) ; i++)
+        ret += base64_chars[char_array_4[i]];
+      i = 0;
+    }
+  }
+
+  if (i)
+  {
+    for(j = i; j < 3; j++)
+      char_array_3[j] = '\0';
+
+    char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
+    char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
+    char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
+    char_array_4[3] = char_array_3[2] & 0x3f;
+
+    for (j = 0; (j < i + 1); j++)
+      ret += base64_chars[char_array_4[j]];
+
+    while((i++ < 3))
+      ret += '=';
+
+  }
+
+  return ret;
+}
+
+
+std::string base64_decode(const std::string& encoded_string) {
+  int in_len = encoded_string.size();
+  int i = 0;
+  int j = 0;
+  int in_ = 0;
+  unsigned char char_array_4[4], char_array_3[3];
+  std::string ret;
+
+  while (in_len-- && ( encoded_string[in_] != '=') && is_base64(encoded_string[in_])) {
+    char_array_4[i++] = encoded_string[in_]; in_++;
+    if (i ==4) {
+      for (i = 0; i <4; i++)
+        char_array_4[i] = base64_chars.find(char_array_4[i]);
+
+      char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
+      char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
+      char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
+
+      for (i = 0; (i < 3); i++)
+        ret += char_array_3[i];
+      i = 0;
+    }
+  }
+
+  if (i) {
+    for (j = i; j <4; j++)
+      char_array_4[j] = 0;
+
+    for (j = 0; j <4; j++)
+      char_array_4[j] = base64_chars.find(char_array_4[j]);
+
+    char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
+    char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
+    char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
+
+    for (j = 0; (j < i - 1); j++) ret += char_array_3[j];
+  }
+
+  return ret;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/ThirdParty/base64/base64.h	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,4 @@
+#include <string>
+
+std::string base64_encode(const std::string& stringToEncode);
+std::string base64_decode(const std::string& s);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/VersionScript.map	Wed Feb 25 13:45:35 2015 +0100
@@ -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/UnitTestsSources/UnitTestsMain.cpp	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,223 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero 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
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include <gtest/gtest.h>
+#include <boost/lexical_cast.hpp>
+
+static int argc_;
+static char** argv_;
+
+#include "../Orthanc/OrthancException.h"
+#include "../Orthanc/Toolbox.h"
+#include "../Orthanc/ImageFormats/ImageBuffer.h"
+#include "../Orthanc/ImageFormats/PngWriter.h"
+#include "../Plugin/Cache/CacheManager.h"
+#include "../Plugin/Cache/CacheScheduler.h"
+#include "../Plugin/Cache/ICacheFactory.h"
+#include "../Plugin/Cache/ICacheFactory.h"
+#include "../Plugin/JpegWriter.h"
+
+using namespace OrthancPlugins;
+
+
+class CacheManagerTest : public testing::Test
+{
+private:
+  std::auto_ptr<Orthanc::FilesystemStorage>  storage_;
+  std::auto_ptr<Orthanc::SQLite::Connection>  db_;
+  std::auto_ptr<CacheManager>  cache_;
+
+public:
+  virtual void SetUp()
+  {
+    storage_.reset(new Orthanc::FilesystemStorage("UnitTestsResults"));
+    storage_->Clear();
+    Orthanc::Toolbox::RemoveFile("UnitTestsResults/cache.db");
+
+    db_.reset(new Orthanc::SQLite::Connection());
+    db_->Open("UnitTestsResults/cache.db");
+
+    cache_.reset(new CacheManager(*db_, *storage_));
+    cache_->SetSanityCheckEnabled(true);
+  }
+
+  virtual void TearDown()
+  {
+    cache_.reset(NULL);
+    db_.reset(NULL);
+    storage_.reset(NULL);
+  }
+
+  CacheManager& GetCache() 
+  {
+    return *cache_;
+  }
+
+  Orthanc::FilesystemStorage& GetStorage() 
+  {
+    return *storage_;
+  }
+};
+
+
+
+class TestF : public ICacheFactory
+{
+private:
+  int bundle_;
+  
+public:
+  TestF(int bundle) : bundle_(bundle)
+  {
+  }
+
+  virtual bool Create(std::string& content,
+                      const std::string& key)
+  {
+    content = "Bundle " + boost::lexical_cast<std::string>(bundle_) + ", item " + key;
+    return true;
+  }
+};
+
+
+TEST_F(CacheManagerTest, DefaultQuota)
+{
+  std::set<std::string> f;
+  GetStorage().ListAllFiles(f);
+  ASSERT_EQ(0, f.size());
+  
+  GetCache().SetDefaultQuota(10, 0);
+  for (int i = 0; i < 30; i++)
+  {
+    GetStorage().ListAllFiles(f);
+    ASSERT_EQ(i >= 10 ? 10 : i, f.size());
+    std::string s = boost::lexical_cast<std::string>(i);
+    GetCache().Store(0, s, "Test " + s);
+  }
+
+  GetStorage().ListAllFiles(f);
+  ASSERT_EQ(10, f.size());
+
+  for (int i = 0; i < 30; i++)
+  {
+    ASSERT_EQ(i >= 20, GetCache().IsCached(0, boost::lexical_cast<std::string>(i)));
+  }
+
+  GetCache().SetDefaultQuota(5, 0);
+  GetStorage().ListAllFiles(f);
+  ASSERT_EQ(5, f.size());
+  for (int i = 0; i < 30; i++)
+  {
+    ASSERT_EQ(i >= 25, GetCache().IsCached(0, boost::lexical_cast<std::string>(i)));
+  }
+
+  for (int i = 0; i < 15; i++)
+  {
+    std::string s = boost::lexical_cast<std::string>(i);
+    GetCache().Store(0, s, "Test " + s);
+  }
+
+  GetStorage().ListAllFiles(f);
+  ASSERT_EQ(5, f.size());
+
+  for (int i = 0; i < 50; i++)
+  {
+    std::string s = boost::lexical_cast<std::string>(i);
+    if (i >= 10 && i < 15)
+    {
+      std::string tmp;
+      ASSERT_TRUE(GetCache().IsCached(0, s));
+      ASSERT_TRUE(GetCache().Access(tmp, 0, s));
+      ASSERT_EQ("Test " + s, tmp);
+    }
+    else
+    {
+      ASSERT_FALSE(GetCache().IsCached(0, s));
+    }
+  }
+}
+
+
+
+TEST_F(CacheManagerTest, Invalidate)
+{
+  GetCache().SetDefaultQuota(10, 0);
+  for (int i = 0; i < 30; i++)
+  {
+    std::string s = boost::lexical_cast<std::string>(i);
+    GetCache().Store(0, s, "Test " + s);
+  }
+
+  std::set<std::string> f;
+  GetStorage().ListAllFiles(f);
+  ASSERT_EQ(10, f.size());
+
+  GetCache().Invalidate(0, "25");
+  GetStorage().ListAllFiles(f);
+  ASSERT_EQ(9, f.size());
+  for (int i = 0; i < 50; i++)
+  {
+    std::string s = boost::lexical_cast<std::string>(i);
+    ASSERT_EQ((i >= 20 && i < 30 && i != 25), GetCache().IsCached(0, s));
+  }
+
+  for (int i = 0; i < 50; i++)
+  {
+    GetCache().Invalidate(0, boost::lexical_cast<std::string>(i));
+  }
+
+  GetStorage().ListAllFiles(f);
+  ASSERT_EQ(0, f.size());
+}
+
+
+
+TEST(JpegWriter, Basic)
+{
+  Orthanc::ImageBuffer img(16, 16, Orthanc::PixelFormat_Grayscale8);
+  Orthanc::ImageAccessor accessor = img.GetAccessor();
+  for (int y = 0, value = 0; y < img.GetHeight(); y++)
+  {
+    uint8_t* p = reinterpret_cast<uint8_t*>(accessor.GetRow(y));
+    for (int x = 0; x < img.GetWidth(); x++, p++)
+    {
+      *p = value++;
+    }
+  }
+
+  JpegWriter w;
+  w.WriteToFile("UnitTestsResults/hello.jpg", accessor);
+
+  std::string s;
+  w.WriteToMemory(s, accessor);
+  Orthanc::Toolbox::WriteFile(s, "UnitTestsResults/hello2.jpg");
+}
+
+
+
+int main(int argc, char **argv)
+{
+  argc_ = argc;
+  argv_ = argv;  
+
+  ::testing::InitGoogleTest(&argc, argv);
+
+  return RUN_ALL_TESTS();
+}
Binary file WebApplication/images/bone.png has changed
Binary file WebApplication/images/default.png has changed
Binary file WebApplication/images/interpolation.png has changed
Binary file WebApplication/images/inversion.png has changed
Binary file WebApplication/images/lung.png has changed
Binary file WebApplication/images/orthanc-icon.png has changed
Binary file WebApplication/images/orthanc-logo.png has changed
Binary file WebApplication/images/stretch.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/WebApplication/jpeg-decoder.js	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,984 @@
+/**
+ * SOURCE: https://github.com/notmasteryet/jpgjs/blob/master/jpg.js
+ **/
+
+
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+/*
+   Copyright 2011 notmasteryet
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+// - The JPEG specification can be found in the ITU CCITT Recommendation T.81
+//   (www.w3.org/Graphics/JPEG/itu-t81.pdf)
+// - The JFIF specification can be found in the JPEG File Interchange Format
+//   (www.w3.org/Graphics/JPEG/jfif3.pdf)
+// - The Adobe Application-Specific JPEG markers in the Supporting the DCT Filters
+//   in PostScript Level 2, Technical Note #5116
+//   (partners.adobe.com/public/developer/en/ps/sdk/5116.DCT_Filter.pdf)
+
+var JpegImage = (function jpegImage() {
+  "use strict";
+  var dctZigZag = new Int32Array([
+     0,
+     1,  8,
+    16,  9,  2,
+     3, 10, 17, 24,
+    32, 25, 18, 11, 4,
+     5, 12, 19, 26, 33, 40,
+    48, 41, 34, 27, 20, 13,  6,
+     7, 14, 21, 28, 35, 42, 49, 56,
+    57, 50, 43, 36, 29, 22, 15,
+    23, 30, 37, 44, 51, 58,
+    59, 52, 45, 38, 31,
+    39, 46, 53, 60,
+    61, 54, 47,
+    55, 62,
+    63
+  ]);
+
+  var dctCos1  =  4017   // cos(pi/16)
+  var dctSin1  =   799   // sin(pi/16)
+  var dctCos3  =  3406   // cos(3*pi/16)
+  var dctSin3  =  2276   // sin(3*pi/16)
+  var dctCos6  =  1567   // cos(6*pi/16)
+  var dctSin6  =  3784   // sin(6*pi/16)
+  var dctSqrt2 =  5793   // sqrt(2)
+  var dctSqrt1d2 = 2896  // sqrt(2) / 2
+
+  function constructor() {
+  }
+
+  function buildHuffmanTable(codeLengths, values) {
+    var k = 0, code = [], i, j, length = 16;
+    while (length > 0 && !codeLengths[length - 1])
+      length--;
+    code.push({children: [], index: 0});
+    var p = code[0], q;
+    for (i = 0; i < length; i++) {
+      for (j = 0; j < codeLengths[i]; j++) {
+        p = code.pop();
+        p.children[p.index] = values[k];
+        while (p.index > 0) {
+          p = code.pop();
+        }
+        p.index++;
+        code.push(p);
+        while (code.length <= i) {
+          code.push(q = {children: [], index: 0});
+          p.children[p.index] = q.children;
+          p = q;
+        }
+        k++;
+      }
+      if (i + 1 < length) {
+        // p here points to last code
+        code.push(q = {children: [], index: 0});
+        p.children[p.index] = q.children;
+        p = q;
+      }
+    }
+    return code[0].children;
+  }
+
+  function getBlockBufferOffset(component, row, col) {
+    return 64 * ((component.blocksPerLine + 1) * row + col);
+  }
+
+  function decodeScan(data, offset,
+                      frame, components, resetInterval,
+                      spectralStart, spectralEnd,
+                      successivePrev, successive) {
+    var precision = frame.precision;
+    var samplesPerLine = frame.samplesPerLine;
+    var scanLines = frame.scanLines;
+    var mcusPerLine = frame.mcusPerLine;
+    var progressive = frame.progressive;
+    var maxH = frame.maxH, maxV = frame.maxV;
+
+    var startOffset = offset, bitsData = 0, bitsCount = 0;
+
+    function readBit() {
+      if (bitsCount > 0) {
+        bitsCount--;
+        return (bitsData >> bitsCount) & 1;
+      }
+      bitsData = data[offset++];
+      if (bitsData == 0xFF) {
+        var nextByte = data[offset++];
+        if (nextByte) {
+          throw "unexpected marker: " + ((bitsData << 8) | nextByte).toString(16);
+        }
+        // unstuff 0
+      }
+      bitsCount = 7;
+      return bitsData >>> 7;
+    }
+
+    function decodeHuffman(tree) {
+      var node = tree;
+      var bit;
+      while ((bit = readBit()) !== null) {
+        node = node[bit];
+        if (typeof node === 'number')
+          return node;
+        if (typeof node !== 'object')
+          throw "invalid huffman sequence";
+      }
+      return null;
+    }
+
+    function receive(length) {
+      var n = 0;
+      while (length > 0) {
+        var bit = readBit();
+        if (bit === null) return;
+        n = (n << 1) | bit;
+        length--;
+      }
+      return n;
+    }
+
+    function receiveAndExtend(length) {
+      var n = receive(length);
+      if (n >= 1 << (length - 1))
+        return n;
+      return n + (-1 << length) + 1;
+    }
+
+    function decodeBaseline(component, offset) {
+      var t = decodeHuffman(component.huffmanTableDC);
+      var diff = t === 0 ? 0 : receiveAndExtend(t);
+      component.blockData[offset] = (component.pred += diff);
+      var k = 1;
+      while (k < 64) {
+        var rs = decodeHuffman(component.huffmanTableAC);
+        var s = rs & 15, r = rs >> 4;
+        if (s === 0) {
+          if (r < 15)
+            break;
+          k += 16;
+          continue;
+        }
+        k += r;
+        var z = dctZigZag[k];
+        component.blockData[offset + z] = receiveAndExtend(s);
+        k++;
+      }
+    }
+
+    function decodeDCFirst(component, offset) {
+      var t = decodeHuffman(component.huffmanTableDC);
+      var diff = t === 0 ? 0 : (receiveAndExtend(t) << successive);
+      component.blockData[offset] = (component.pred += diff);
+    }
+
+    function decodeDCSuccessive(component, offset) {
+      component.blockData[offset] |= readBit() << successive;
+    }
+
+    var eobrun = 0;
+    function decodeACFirst(component, offset) {
+      if (eobrun > 0) {
+        eobrun--;
+        return;
+      }
+      var k = spectralStart, e = spectralEnd;
+      while (k <= e) {
+        var rs = decodeHuffman(component.huffmanTableAC);
+        var s = rs & 15, r = rs >> 4;
+        if (s === 0) {
+          if (r < 15) {
+            eobrun = receive(r) + (1 << r) - 1;
+            break;
+          }
+          k += 16;
+          continue;
+        }
+        k += r;
+        var z = dctZigZag[k];
+        component.blockData[offset + z] = receiveAndExtend(s) * (1 << successive);
+        k++;
+      }
+    }
+
+    var successiveACState = 0, successiveACNextValue;
+    function decodeACSuccessive(component, offset) {
+      var k = spectralStart, e = spectralEnd, r = 0;
+      while (k <= e) {
+        var z = dctZigZag[k];
+        switch (successiveACState) {
+        case 0: // initial state
+          var rs = decodeHuffman(component.huffmanTableAC);
+          var s = rs & 15, r = rs >> 4;
+          if (s === 0) {
+            if (r < 15) {
+              eobrun = receive(r) + (1 << r);
+              successiveACState = 4;
+            } else {
+              r = 16;
+              successiveACState = 1;
+            }
+          } else {
+            if (s !== 1)
+              throw "invalid ACn encoding";
+            successiveACNextValue = receiveAndExtend(s);
+            successiveACState = r ? 2 : 3;
+          }
+          continue;
+        case 1: // skipping r zero items
+        case 2:
+          if (component.blockData[offset + z]) {
+            component.blockData[offset + z] += (readBit() << successive);
+          } else {
+            r--;
+            if (r === 0)
+              successiveACState = successiveACState == 2 ? 3 : 0;
+          }
+          break;
+        case 3: // set value for a zero item
+          if (component.blockData[offset + z]) {
+            component.blockData[offset + z] += (readBit() << successive);
+          } else {
+            component.blockData[offset + z] = successiveACNextValue << successive;
+            successiveACState = 0;
+          }
+          break;
+        case 4: // eob
+          if (component.blockData[offset + z]) {
+            component.blockData[offset + z] += (readBit() << successive);
+          }
+          break;
+        }
+        k++;
+      }
+      if (successiveACState === 4) {
+        eobrun--;
+        if (eobrun === 0)
+          successiveACState = 0;
+      }
+    }
+
+    function decodeMcu(component, decode, mcu, row, col) {
+      var mcuRow = (mcu / mcusPerLine) | 0;
+      var mcuCol = mcu % mcusPerLine;
+      var blockRow = mcuRow * component.v + row;
+      var blockCol = mcuCol * component.h + col;
+      var offset = getBlockBufferOffset(component, blockRow, blockCol);
+      decode(component, offset);
+    }
+
+    function decodeBlock(component, decode, mcu) {
+      var blockRow = (mcu / component.blocksPerLine) | 0;
+      var blockCol = mcu % component.blocksPerLine;
+      var offset = getBlockBufferOffset(component, blockRow, blockCol);
+      decode(component, offset);
+    }
+
+    var componentsLength = components.length;
+    var component, i, j, k, n;
+    var decodeFn;
+    if (progressive) {
+      if (spectralStart === 0)
+        decodeFn = successivePrev === 0 ? decodeDCFirst : decodeDCSuccessive;
+      else
+        decodeFn = successivePrev === 0 ? decodeACFirst : decodeACSuccessive;
+    } else {
+      decodeFn = decodeBaseline;
+    }
+
+    var mcu = 0, marker;
+    var mcuExpected;
+    if (componentsLength == 1) {
+      mcuExpected = components[0].blocksPerLine * components[0].blocksPerColumn;
+    } else {
+      mcuExpected = mcusPerLine * frame.mcusPerColumn;
+    }
+    if (!resetInterval) {
+      resetInterval = mcuExpected;
+    }
+
+    var h, v;
+    while (mcu < mcuExpected) {
+      // reset interval stuff
+      for (i = 0; i < componentsLength; i++) {
+        components[i].pred = 0;
+      }
+      eobrun = 0;
+
+      if (componentsLength == 1) {
+        component = components[0];
+        for (n = 0; n < resetInterval; n++) {
+          decodeBlock(component, decodeFn, mcu);
+          mcu++;
+        }
+      } else {
+        for (n = 0; n < resetInterval; n++) {
+          for (i = 0; i < componentsLength; i++) {
+            component = components[i];
+            h = component.h;
+            v = component.v;
+            for (j = 0; j < v; j++) {
+              for (k = 0; k < h; k++) {
+                decodeMcu(component, decodeFn, mcu, j, k);
+              }
+            }
+          }
+          mcu++;
+        }
+      }
+
+      // find marker
+      bitsCount = 0;
+      marker = (data[offset] << 8) | data[offset + 1];
+      if (marker <= 0xFF00) {
+        throw "marker was not found";
+      }
+
+      if (marker >= 0xFFD0 && marker <= 0xFFD7) { // RSTx
+        offset += 2;
+      } else {
+        break;
+      }
+    }
+
+    return offset - startOffset;
+  }
+
+  // A port of poppler's IDCT method which in turn is taken from:
+  //   Christoph Loeffler, Adriaan Ligtenberg, George S. Moschytz,
+  //   "Practical Fast 1-D DCT Algorithms with 11 Multiplications",
+  //   IEEE Intl. Conf. on Acoustics, Speech & Signal Processing, 1989,
+  //   988-991.
+  function quantizeAndInverse(component, blockBufferOffset, p) {
+    var qt = component.quantizationTable;
+    var v0, v1, v2, v3, v4, v5, v6, v7, t;
+    var i;
+
+    // dequant
+    for (i = 0; i < 64; i++) {
+      p[i] = component.blockData[blockBufferOffset + i] * qt[i];
+    }
+
+    // inverse DCT on rows
+    for (i = 0; i < 8; ++i) {
+      var row = 8 * i;
+
+      // check for all-zero AC coefficients
+      if (p[1 + row] == 0 && p[2 + row] == 0 && p[3 + row] == 0 &&
+          p[4 + row] == 0 && p[5 + row] == 0 && p[6 + row] == 0 &&
+          p[7 + row] == 0) {
+        t = (dctSqrt2 * p[0 + row] + 512) >> 10;
+        p[0 + row] = t;
+        p[1 + row] = t;
+        p[2 + row] = t;
+        p[3 + row] = t;
+        p[4 + row] = t;
+        p[5 + row] = t;
+        p[6 + row] = t;
+        p[7 + row] = t;
+        continue;
+      }
+
+      // stage 4
+      v0 = (dctSqrt2 * p[0 + row] + 128) >> 8;
+      v1 = (dctSqrt2 * p[4 + row] + 128) >> 8;
+      v2 = p[2 + row];
+      v3 = p[6 + row];
+      v4 = (dctSqrt1d2 * (p[1 + row] - p[7 + row]) + 128) >> 8;
+      v7 = (dctSqrt1d2 * (p[1 + row] + p[7 + row]) + 128) >> 8;
+      v5 = p[3 + row] << 4;
+      v6 = p[5 + row] << 4;
+
+      // stage 3
+      t = (v0 - v1+ 1) >> 1;
+      v0 = (v0 + v1 + 1) >> 1;
+      v1 = t;
+      t = (v2 * dctSin6 + v3 * dctCos6 + 128) >> 8;
+      v2 = (v2 * dctCos6 - v3 * dctSin6 + 128) >> 8;
+      v3 = t;
+      t = (v4 - v6 + 1) >> 1;
+      v4 = (v4 + v6 + 1) >> 1;
+      v6 = t;
+      t = (v7 + v5 + 1) >> 1;
+      v5 = (v7 - v5 + 1) >> 1;
+      v7 = t;
+
+      // stage 2
+      t = (v0 - v3 + 1) >> 1;
+      v0 = (v0 + v3 + 1) >> 1;
+      v3 = t;
+      t = (v1 - v2 + 1) >> 1;
+      v1 = (v1 + v2 + 1) >> 1;
+      v2 = t;
+      t = (v4 * dctSin3 + v7 * dctCos3 + 2048) >> 12;
+      v4 = (v4 * dctCos3 - v7 * dctSin3 + 2048) >> 12;
+      v7 = t;
+      t = (v5 * dctSin1 + v6 * dctCos1 + 2048) >> 12;
+      v5 = (v5 * dctCos1 - v6 * dctSin1 + 2048) >> 12;
+      v6 = t;
+
+      // stage 1
+      p[0 + row] = v0 + v7;
+      p[7 + row] = v0 - v7;
+      p[1 + row] = v1 + v6;
+      p[6 + row] = v1 - v6;
+      p[2 + row] = v2 + v5;
+      p[5 + row] = v2 - v5;
+      p[3 + row] = v3 + v4;
+      p[4 + row] = v3 - v4;
+    }
+
+    // inverse DCT on columns
+    for (i = 0; i < 8; ++i) {
+      var col = i;
+
+      // check for all-zero AC coefficients
+      if (p[1*8 + col] == 0 && p[2*8 + col] == 0 && p[3*8 + col] == 0 &&
+          p[4*8 + col] == 0 && p[5*8 + col] == 0 && p[6*8 + col] == 0 &&
+          p[7*8 + col] == 0) {
+        t = (dctSqrt2 * p[i+0] + 8192) >> 14;
+        p[0*8 + col] = t;
+        p[1*8 + col] = t;
+        p[2*8 + col] = t;
+        p[3*8 + col] = t;
+        p[4*8 + col] = t;
+        p[5*8 + col] = t;
+        p[6*8 + col] = t;
+        p[7*8 + col] = t;
+        continue;
+      }
+
+      // stage 4
+      v0 = (dctSqrt2 * p[0*8 + col] + 2048) >> 12;
+      v1 = (dctSqrt2 * p[4*8 + col] + 2048) >> 12;
+      v2 = p[2*8 + col];
+      v3 = p[6*8 + col];
+      v4 = (dctSqrt1d2 * (p[1*8 + col] - p[7*8 + col]) + 2048) >> 12;
+      v7 = (dctSqrt1d2 * (p[1*8 + col] + p[7*8 + col]) + 2048) >> 12;
+      v5 = p[3*8 + col];
+      v6 = p[5*8 + col];
+
+      // stage 3
+      t = (v0 - v1 + 1) >> 1;
+      v0 = (v0 + v1 + 1) >> 1;
+      v1 = t;
+      t = (v2 * dctSin6 + v3 * dctCos6 + 2048) >> 12;
+      v2 = (v2 * dctCos6 - v3 * dctSin6 + 2048) >> 12;
+      v3 = t;
+      t = (v4 - v6 + 1) >> 1;
+      v4 = (v4 + v6 + 1) >> 1;
+      v6 = t;
+      t = (v7 + v5 + 1) >> 1;
+      v5 = (v7 - v5 + 1) >> 1;
+      v7 = t;
+
+      // stage 2
+      t = (v0 - v3 + 1) >> 1;
+      v0 = (v0 + v3 + 1) >> 1;
+      v3 = t;
+      t = (v1 - v2 + 1) >> 1;
+      v1 = (v1 + v2 + 1) >> 1;
+      v2 = t;
+      t = (v4 * dctSin3 + v7 * dctCos3 + 2048) >> 12;
+      v4 = (v4 * dctCos3 - v7 * dctSin3 + 2048) >> 12;
+      v7 = t;
+      t = (v5 * dctSin1 + v6 * dctCos1 + 2048) >> 12;
+      v5 = (v5 * dctCos1 - v6 * dctSin1 + 2048) >> 12;
+      v6 = t;
+
+      // stage 1
+      p[0*8 + col] = v0 + v7;
+      p[7*8 + col] = v0 - v7;
+      p[1*8 + col] = v1 + v6;
+      p[6*8 + col] = v1 - v6;
+      p[2*8 + col] = v2 + v5;
+      p[5*8 + col] = v2 - v5;
+      p[3*8 + col] = v3 + v4;
+      p[4*8 + col] = v3 - v4;
+    }
+
+    // convert to 8-bit integers
+    for (i = 0; i < 64; ++i) {
+      var index = blockBufferOffset + i;
+      var q = p[i];
+      q = (q <= -2056) ? 0 : (q >= 2024) ? 255 : (q + 2056) >> 4;
+      component.blockData[index] = q;
+    }
+  }
+
+  function buildComponentData(frame, component) {
+    var lines = [];
+    var blocksPerLine = component.blocksPerLine;
+    var blocksPerColumn = component.blocksPerColumn;
+    var samplesPerLine = blocksPerLine << 3;
+    var computationBuffer = new Int32Array(64);
+
+    var i, j, ll = 0;
+    for (var blockRow = 0; blockRow < blocksPerColumn; blockRow++) {
+      for (var blockCol = 0; blockCol < blocksPerLine; blockCol++) {
+        var offset = getBlockBufferOffset(component, blockRow, blockCol)
+        quantizeAndInverse(component, offset, computationBuffer);
+      }
+    }
+    return component.blockData;
+  }
+
+  function clampToUint8(a) {
+    return a <= 0 ? 0 : a >= 255 ? 255 : a | 0;
+  }
+
+  constructor.prototype = {
+    load: function load(path) {
+      var handleData = (function(data) {
+        this.parse(data);
+        if (this.onload)
+          this.onload();
+      }).bind(this);
+
+      if (path.indexOf("data:") > -1) {
+        var offset = path.indexOf("base64,")+7;
+        var data = atob(path.substring(offset));
+        var arr = new Uint8Array(data.length);
+        for (var i = data.length - 1; i >= 0; i--) {
+          arr[i] = data.charCodeAt(i);
+        }
+        handleData(data);
+      } else {
+        var xhr = new XMLHttpRequest();
+        xhr.open("GET", path, true);
+        xhr.responseType = "arraybuffer";
+        xhr.onload = (function() {
+          // TODO catch parse error
+          var data = new Uint8Array(xhr.response);
+          handleData(data);
+        }).bind(this);
+        xhr.send(null);
+      }
+    },
+
+    parse: function parse(data) {
+
+      function readUint16() {
+        var value = (data[offset] << 8) | data[offset + 1];
+        offset += 2;
+        return value;
+      }
+
+      function readDataBlock() {
+        var length = readUint16();
+        var array = data.subarray(offset, offset + length - 2);
+        offset += array.length;
+        return array;
+      }
+
+      function prepareComponents(frame) {
+        var mcusPerLine = Math.ceil(frame.samplesPerLine / 8 / frame.maxH);
+        var mcusPerColumn = Math.ceil(frame.scanLines / 8 / frame.maxV);
+        for (var i = 0; i < frame.components.length; i++) {
+          component = frame.components[i];
+          var blocksPerLine = Math.ceil(Math.ceil(frame.samplesPerLine / 8) * component.h / frame.maxH);
+          var blocksPerColumn = Math.ceil(Math.ceil(frame.scanLines  / 8) * component.v / frame.maxV);
+          var blocksPerLineForMcu = mcusPerLine * component.h;
+          var blocksPerColumnForMcu = mcusPerColumn * component.v;
+
+          var blocksBufferSize = 64 * blocksPerColumnForMcu
+                                    * (blocksPerLineForMcu + 1);
+          component.blockData = new Int16Array(blocksBufferSize);
+          component.blocksPerLine = blocksPerLine;
+          component.blocksPerColumn = blocksPerColumn;
+        }
+        frame.mcusPerLine = mcusPerLine;
+        frame.mcusPerColumn = mcusPerColumn;
+      }
+
+      var offset = 0, length = data.length;
+      var jfif = null;
+      var adobe = null;
+      var pixels = null;
+      var frame, resetInterval;
+      var quantizationTables = [];
+      var huffmanTablesAC = [], huffmanTablesDC = [];
+      var fileMarker = readUint16();
+      if (fileMarker != 0xFFD8) { // SOI (Start of Image)
+        throw "SOI not found";
+      }
+
+      fileMarker = readUint16();
+      while (fileMarker != 0xFFD9) { // EOI (End of image)
+        var i, j, l;
+        switch(fileMarker) {
+          case 0xFFE0: // APP0 (Application Specific)
+          case 0xFFE1: // APP1
+          case 0xFFE2: // APP2
+          case 0xFFE3: // APP3
+          case 0xFFE4: // APP4
+          case 0xFFE5: // APP5
+          case 0xFFE6: // APP6
+          case 0xFFE7: // APP7
+          case 0xFFE8: // APP8
+          case 0xFFE9: // APP9
+          case 0xFFEA: // APP10
+          case 0xFFEB: // APP11
+          case 0xFFEC: // APP12
+          case 0xFFED: // APP13
+          case 0xFFEE: // APP14
+          case 0xFFEF: // APP15
+          case 0xFFFE: // COM (Comment)
+            var appData = readDataBlock();
+
+            if (fileMarker === 0xFFE0) {
+              if (appData[0] === 0x4A && appData[1] === 0x46 && appData[2] === 0x49 &&
+                appData[3] === 0x46 && appData[4] === 0) { // 'JFIF\x00'
+                jfif = {
+                  version: { major: appData[5], minor: appData[6] },
+                  densityUnits: appData[7],
+                  xDensity: (appData[8] << 8) | appData[9],
+                  yDensity: (appData[10] << 8) | appData[11],
+                  thumbWidth: appData[12],
+                  thumbHeight: appData[13],
+                  thumbData: appData.subarray(14, 14 + 3 * appData[12] * appData[13])
+                };
+              }
+            }
+            // TODO APP1 - Exif
+            if (fileMarker === 0xFFEE) {
+              if (appData[0] === 0x41 && appData[1] === 0x64 && appData[2] === 0x6F &&
+                appData[3] === 0x62 && appData[4] === 0x65 && appData[5] === 0) { // 'Adobe\x00'
+                adobe = {
+                  version: appData[6],
+                  flags0: (appData[7] << 8) | appData[8],
+                  flags1: (appData[9] << 8) | appData[10],
+                  transformCode: appData[11]
+                };
+              }
+            }
+            break;
+
+          case 0xFFDB: // DQT (Define Quantization Tables)
+            var quantizationTablesLength = readUint16();
+            var quantizationTablesEnd = quantizationTablesLength + offset - 2;
+            while (offset < quantizationTablesEnd) {
+              var quantizationTableSpec = data[offset++];
+              var tableData = new Int32Array(64);
+              if ((quantizationTableSpec >> 4) === 0) { // 8 bit values
+                for (j = 0; j < 64; j++) {
+                  var z = dctZigZag[j];
+                  tableData[z] = data[offset++];
+                }
+              } else if ((quantizationTableSpec >> 4) === 1) { //16 bit
+                for (j = 0; j < 64; j++) {
+                  var z = dctZigZag[j];
+                  tableData[z] = readUint16();
+                }
+              } else
+                throw "DQT: invalid table spec";
+              quantizationTables[quantizationTableSpec & 15] = tableData;
+            }
+            break;
+
+          case 0xFFC0: // SOF0 (Start of Frame, Baseline DCT)
+          case 0xFFC1: // SOF1 (Start of Frame, Extended DCT)
+          case 0xFFC2: // SOF2 (Start of Frame, Progressive DCT)
+            if (frame) {
+              throw "Only single frame JPEGs supported";
+            }
+            readUint16(); // skip data length
+            frame = {};
+            frame.extended = (fileMarker === 0xFFC1);
+            frame.progressive = (fileMarker === 0xFFC2);
+            frame.precision = data[offset++];
+            frame.scanLines = readUint16();
+            frame.samplesPerLine = readUint16();
+            frame.components = [];
+            frame.componentIds = {};
+            var componentsCount = data[offset++], componentId;
+            var maxH = 0, maxV = 0;
+            for (i = 0; i < componentsCount; i++) {
+              componentId = data[offset];
+              var h = data[offset + 1] >> 4;
+              var v = data[offset + 1] & 15;
+              if (maxH < h) maxH = h;
+              if (maxV < v) maxV = v;
+              var qId = data[offset + 2];
+              var l = frame.components.push({
+                h: h,
+                v: v,
+                quantizationTable: quantizationTables[qId]
+              });
+              frame.componentIds[componentId] = l - 1;
+              offset += 3;
+            }
+            frame.maxH = maxH;
+            frame.maxV = maxV;
+            prepareComponents(frame);
+            break;
+
+          case 0xFFC4: // DHT (Define Huffman Tables)
+            var huffmanLength = readUint16();
+            for (i = 2; i < huffmanLength;) {
+              var huffmanTableSpec = data[offset++];
+              var codeLengths = new Uint8Array(16);
+              var codeLengthSum = 0;
+              for (j = 0; j < 16; j++, offset++)
+                codeLengthSum += (codeLengths[j] = data[offset]);
+              var huffmanValues = new Uint8Array(codeLengthSum);
+              for (j = 0; j < codeLengthSum; j++, offset++)
+                huffmanValues[j] = data[offset];
+              i += 17 + codeLengthSum;
+
+              ((huffmanTableSpec >> 4) === 0 ?
+                huffmanTablesDC : huffmanTablesAC)[huffmanTableSpec & 15] =
+                buildHuffmanTable(codeLengths, huffmanValues);
+            }
+            break;
+
+          case 0xFFDD: // DRI (Define Restart Interval)
+            readUint16(); // skip data length
+            resetInterval = readUint16();
+            break;
+
+          case 0xFFDA: // SOS (Start of Scan)
+            var scanLength = readUint16();
+            var selectorsCount = data[offset++];
+            var components = [], component;
+            for (i = 0; i < selectorsCount; i++) {
+              var componentIndex = frame.componentIds[data[offset++]];
+              component = frame.components[componentIndex];
+              var tableSpec = data[offset++];
+              component.huffmanTableDC = huffmanTablesDC[tableSpec >> 4];
+              component.huffmanTableAC = huffmanTablesAC[tableSpec & 15];
+              components.push(component);
+            }
+            var spectralStart = data[offset++];
+            var spectralEnd = data[offset++];
+            var successiveApproximation = data[offset++];
+            var processed = decodeScan(data, offset,
+              frame, components, resetInterval,
+              spectralStart, spectralEnd,
+              successiveApproximation >> 4, successiveApproximation & 15);
+            offset += processed;
+            break;
+          default:
+            if (data[offset - 3] == 0xFF &&
+                data[offset - 2] >= 0xC0 && data[offset - 2] <= 0xFE) {
+              // could be incorrect encoding -- last 0xFF byte of the previous
+              // block was eaten by the encoder
+              offset -= 3;
+              break;
+            }
+            throw "unknown JPEG marker " + fileMarker.toString(16);
+        }
+        fileMarker = readUint16();
+      }
+
+      this.width = frame.samplesPerLine;
+      this.height = frame.scanLines;
+      this.jfif = jfif;
+      this.adobe = adobe;
+      this.components = [];
+      for (var i = 0; i < frame.components.length; i++) {
+        var component = frame.components[i];
+        this.components.push({
+          output: buildComponentData(frame, component),
+          scaleX: component.h / frame.maxH,
+          scaleY: component.v / frame.maxV,
+          blocksPerLine: component.blocksPerLine,
+          blocksPerColumn: component.blocksPerColumn
+        });
+      }
+    },
+
+    getData: function getData(width, height) {
+      var scaleX = this.width / width, scaleY = this.height / height;
+
+      var component, componentScaleX, componentScaleY;
+      var x, y, i;
+      var offset = 0;
+      var Y, Cb, Cr, K, C, M, Ye, R, G, B;
+      var colorTransform;
+      var numComponents = this.components.length;
+      var dataLength = width * height * numComponents;
+      var data = new Uint8Array(dataLength);
+      var componentLine;
+
+      // lineData is reused for all components. Assume first component is
+      // the biggest
+      var lineData = new Uint8Array((this.components[0].blocksPerLine << 3) *
+                                    this.components[0].blocksPerColumn * 8);
+
+      // First construct image data ...
+      for (i = 0; i < numComponents; i++) {
+        component = this.components[i];
+        var blocksPerLine = component.blocksPerLine;
+        var blocksPerColumn = component.blocksPerColumn;
+        var samplesPerLine = blocksPerLine << 3;
+
+        var j, k, ll = 0;
+        var lineOffset = 0;
+        for (var blockRow = 0; blockRow < blocksPerColumn; blockRow++) {
+          var scanLine = blockRow << 3;
+          for (var blockCol = 0; blockCol < blocksPerLine; blockCol++) {
+            var bufferOffset = getBlockBufferOffset(component, blockRow, blockCol);
+            var offset = 0, sample = blockCol << 3;
+            for (j = 0; j < 8; j++) {
+              var lineOffset = (scanLine + j) * samplesPerLine;
+              for (k = 0; k < 8; k++) {
+                lineData[lineOffset + sample + k] =
+                  component.output[bufferOffset + offset++];
+              }
+            }
+          }
+        }
+
+        componentScaleX = component.scaleX * scaleX;
+        componentScaleY = component.scaleY * scaleY;
+        offset = i;
+
+        var cx, cy;
+        var index;
+        for (y = 0; y < height; y++) {
+          for (x = 0; x < width; x++) {
+            cy = 0 | (y * componentScaleY);
+            cx = 0 | (x * componentScaleX);
+            index = cy * samplesPerLine + cx;
+            data[offset] = lineData[index];
+            offset += numComponents;
+          }
+        }
+      }
+
+      // ... then transform colors, if necessary
+      switch (numComponents) {
+        case 1: case 2: break;
+        // no color conversion for one or two compoenents
+
+        case 3:
+          // The default transform for three components is true
+          colorTransform = true;
+          // The adobe transform marker overrides any previous setting
+          if (this.adobe && this.adobe.transformCode)
+            colorTransform = true;
+          else if (typeof this.colorTransform !== 'undefined')
+            colorTransform = !!this.colorTransform;
+
+          if (colorTransform) {
+            for (i = 0; i < dataLength; i += numComponents) {
+              Y  = data[i    ];
+              Cb = data[i + 1];
+              Cr = data[i + 2];
+
+              R = clampToUint8(Y - 179.456 + 1.402 * Cr);
+              G = clampToUint8(Y + 135.459 - 0.344 * Cb - 0.714 * Cr);
+              B = clampToUint8(Y - 226.816 + 1.772 * Cb);
+
+              data[i    ] = R;
+              data[i + 1] = G;
+              data[i + 2] = B;
+            }
+          }
+          break;
+        case 4:
+          if (!this.adobe)
+            throw 'Unsupported color mode (4 components)';
+          // The default transform for four components is false
+          colorTransform = false;
+          // The adobe transform marker overrides any previous setting
+          if (this.adobe && this.adobe.transformCode)
+            colorTransform = true;
+          else if (typeof this.colorTransform !== 'undefined')
+            colorTransform = !!this.colorTransform;
+
+          if (colorTransform) {
+            for (i = 0; i < dataLength; i += numComponents) {
+              Y  = data[i];
+              Cb = data[i + 1];
+              Cr = data[i + 2];
+
+              C = clampToUint8(434.456 - Y - 1.402 * Cr);
+              M = clampToUint8(119.541 - Y + 0.344 * Cb + 0.714 * Cr);
+              Y = clampToUint8(481.816 - Y - 1.772 * Cb);
+
+              data[i    ] = C;
+              data[i + 1] = M;
+              data[i + 2] = Y;
+              // K is unchanged
+            }
+          }
+          break;
+        default:
+          throw 'Unsupported color mode';
+      }
+      return data;
+    },
+    copyToImageData: function copyToImageData(imageData) {
+      var width = imageData.width, height = imageData.height;
+      var imageDataBytes = width * height * 4;
+      var imageDataArray = imageData.data;
+      var data = this.getData(width, height);
+      var i = 0, j = 0, k0, k1;
+      var Y, K, C, M, R, G, B;
+      switch (this.components.length) {
+        case 1:
+          while (j < imageDataBytes) {
+            Y = data[i++];
+
+            imageDataArray[j++] = Y;
+            imageDataArray[j++] = Y;
+            imageDataArray[j++] = Y;
+            imageDataArray[j++] = 255;
+          }
+          break;
+        case 3:
+          while (j < imageDataBytes) {
+            R = data[i++];
+            G = data[i++];
+            B = data[i++];
+
+            imageDataArray[j++] = R;
+            imageDataArray[j++] = G;
+            imageDataArray[j++] = B;
+            imageDataArray[j++] = 255;
+          }
+          break;
+        case 4:
+          while (j < imageDataBytes) {
+            C = data[i++];
+            M = data[i++];
+            Y = data[i++];
+            K = data[i++];
+
+            k0 = 255 - K;
+            k1 = k0 / 255;
+
+
+            R = clampToUint8(k0 - C * k1);
+            G = clampToUint8(k0 - M * k1);
+            B = clampToUint8(k0 - Y * k1);
+
+            imageDataArray[j++] = R;
+            imageDataArray[j++] = G;
+            imageDataArray[j++] = B;
+            imageDataArray[j++] = 255;
+          }
+          break;
+        default:
+          throw 'Unsupported color mode';
+      }
+    }
+  };
+
+  return constructor;
+})();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/WebApplication/viewer.css	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,121 @@
+.jsPanel-hdr-r {
+    display: none;
+}
+
+.ui-button-icon-only {
+    padding-top: 6px;
+    padding-bottom: 6px;
+}
+
+.jsPanel-content {
+    padding: 4px;
+    overflow-y: visible !important;
+}
+
+.jsPanel-hdr img {
+    padding-top: 4px;
+    padding-bottom: 4px;
+    display: block;
+    margin-left: auto;
+    margin-right: auto;
+}
+
+.ui-slider .ui-slider-handle {
+    height: 20px;
+}
+
+.ui-icon-custom-default { 
+    background-image: url(images/default.png) !important; 
+}
+
+.ui-icon-custom-orthanc { 
+    background-image: url(images/orthanc-icon.png) !important; 
+}
+
+.ui-icon-custom-stretch { 
+    background-image: url(images/stretch.png) !important; 
+}
+
+.ui-icon-custom-bone { 
+    background-image: url(images/bone.png) !important; 
+}
+
+.ui-icon-custom-lung { 
+    background-image: url(images/lung.png) !important; 
+}
+
+.ui-icon-custom-interpolation { 
+    background-image: url(images/interpolation.png) !important; 
+}
+
+.ui-icon-custom-inversion { 
+    background-image: url(images/inversion.png) !important; 
+}
+
+#dicomImageWrapper {
+    top: 0px;
+    left: 0px;
+    width: 100%;
+    height: 100%;
+    position: absolute;
+    background-color:  black;
+    color: white;
+    overflow: hidden; 
+    font-family:  Arial, Helvetica, sans-serif;
+}
+
+#dicomImage {
+    width: 100%;
+    height: 100%;
+    top: 0px;
+    left: 0px; 
+    position: absolute;
+}
+
+#topleft {
+    position: absolute;
+    top: 10px; 
+    left: 10px; 
+}
+
+#topright {
+    position: absolute;
+    top: 10px; 
+    right: 10px; 
+    text-align: right;
+}
+
+#bottomright {
+    position: absolute;
+    bottom:25px; 
+    right:10px;
+}
+
+#bottomleft {
+    position: absolute;
+    bottom: 25px; 
+    left: 10px;
+}
+
+#bottomcenter {
+    position: absolute;
+    bottom: 25px; 
+    left: 10px;
+    right: 10px; 
+    width: 100%;
+    text-align: center;
+}
+
+#bottom {
+    position: absolute;
+    bottom: 0px;
+    left: 0px;
+    height: 20px;
+    width: 100%;
+}
+
+.alert {
+    color: #f00;
+    font-weight: bold;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/WebApplication/viewer.html	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,81 @@
+<!doctype html>
+
+<html lang="us">
+  <head>
+    <meta charset="utf-8" />
+    <title>Orthanc Web Viewer</title>
+    <link href="../libs/jquery-ui.min.css" rel="stylesheet" />
+    <link href="../libs/jquery-ui.theme.min.css" rel="stylesheet" />
+    <link href="../libs/cornerstone.min.css" rel="stylesheet" />
+    <link href="../libs/jquery.jspanel.min.css" rel="stylesheet" />
+    <link href="viewer.css" rel="stylesheet" />
+  </head>
+  <body>
+    <div id="dicomImageWrapper"
+         class="cornerstone-enabled-image"
+         oncontextmenu="return false"
+         unselectable="on"
+         onselectstart="return false;"
+         onmousedown="return false;">
+      <div id="dicomImage" oncontextmenu="return false" />
+    </div>
+
+    <div id="topleft">
+      <button id="open-toolbar"></button>
+    </div>
+
+    <div id="topright">Render Time:</div>
+    <div id="bottomright">Zoom:</div>
+    <div id="bottomleft">WW/WC:</div>
+    <div id="bottomcenter" class="alert">Not for diagnostic purpose</div>
+    <div id="bottom">
+      <div id="slider"></div>
+    </div>
+
+    <div id="toolbar-content" style="display:none">
+      View<br/>
+      <div class="toolbar-view">
+        <button title="Series information"></button>
+        <button title="Reverse contrast"></button>
+        <button title="Interpolation"></button>
+        <button title="Download DICOM"></button>
+      </div>
+      Zoom<br/>
+      <div class="toolbar-zoom">
+        <button title="Adjust"></button>
+        <button title="Zoom in"></button>
+        <button title="Zoom out"></button>
+      </div>
+      Windowing<br/>
+      <div class="toolbar-windowing">
+        <button title="Default windowing"></button>
+        <button title="Stretch contrast"></button>
+        <button title="Lung"></button>
+        <button title="Bone"></button>
+      </div>
+      Quality<br/>
+      <div class="toolbar-quality">
+        <button title="Low quality"></button>
+        <button title="Medium quality"></button>
+        <button title="Best quality (lossless)"></button>
+      </div>
+    </div>
+
+    <!-- jQuery, jQuery UI and jsPanel -->
+    <script src="../libs/jquery.js" type="text/javascript"></script>
+    <script src="../libs/jquery-ui.min.js" type="text/javascript"></script>
+    <script src="../libs/mobile-detect.min.js" type="text/javascript"></script>
+    <script src="../libs/jquery.ui.touch-punch.min.js" type="text/javascript"></script>
+    <script src="../libs/jquery.jspanel.min.js" type="text/javascript"></script>
+
+    <!-- Cornerstone -->
+    <script src="../libs/cornerstone.min.js" type="text/javascript"></script>
+
+    <!-- For zlib (deflate) and JPEG decompression -->
+    <script src="../libs/pako_inflate.min.js" type="text/javascript"></script>
+    <script src="jpeg-decoder.js" type="text/javascript"></script>
+
+    <script src="../libs/url.min.js" type="text/javascript"></script>
+    <script src="viewer.js" type="text/javascript"></script>
+  </body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/WebApplication/viewer.js	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,578 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero 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
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+var compression = 'jpeg95';
+
+
+
+// Prevent the access to IE
+if(navigator.appVersion.indexOf("MSIE ") != -1)
+{
+  alert("Please use Mozilla Firefox or Google Chrome. Microsoft Internet Explorer is not supported.");
+}
+
+
+function ResizeCornerstone()
+{
+  $('#dicomImage').height($(window).height() - $('#slider').parent().height());
+  var element = $('#dicomImage').get(0);
+  cornerstone.resize(element, true);
+}
+
+
+function SetWindowing(center, width)
+{
+  var element = $('#dicomImage').get(0);
+  var viewport = cornerstone.getViewport(element);
+  viewport.voi.windowCenter = center;
+  viewport.voi.windowWidth = width;
+  cornerstone.setViewport(element, viewport);
+  UpdateViewportInformation();
+}
+
+
+function SetFullWindowing()
+{
+  var element = $('#dicomImage').get(0);
+  var viewport = cornerstone.getViewport(element);
+  var image = cornerstone.getEnabledElement(element).image;
+
+  if (image.color) {
+    // Ignore color images
+    return;
+  }
+
+  var minValue = image.minPixelValue;
+  var maxValue = image.maxPixelValue;
+  if (minValue == undefined ||
+      maxValue == undefined ||
+      minValue == maxValue) {
+    return; 
+  }
+
+  if (image.slope != undefined && 
+      image.intercept != undefined) {
+    minValue = minValue * image.slope + image.intercept;
+    maxValue = maxValue * image.slope + image.intercept;
+  }
+
+  viewport.voi.windowCenter = (minValue + maxValue) / 2.0;
+  viewport.voi.windowWidth = (maxValue - minValue) / 2.0;
+  cornerstone.setViewport(element, viewport);
+  UpdateViewportInformation();
+}
+
+
+function SetDefaultWindowing()
+{
+  var element = $('#dicomImage').get(0);
+  var viewport = cornerstone.getViewport(element);
+  var image = cornerstone.getEnabledElement(element).image;
+
+  viewport.voi.windowCenter = image.windowCenter;
+  viewport.voi.windowWidth = image.windowWidth;
+  cornerstone.setViewport(element, viewport);
+  UpdateViewportInformation();
+}
+
+
+function SetBoneWindowing()
+{
+  SetWindowing(300, 2000);
+}
+
+
+function SetLungWindowing()
+{
+  SetWindowing(-600, 1600);
+}
+
+
+function UpdateViewportInformation()
+{
+  var element = $('#dicomImage').get(0);
+  var viewport = cornerstone.getViewport(element);
+
+  $('#bottomleft').text('WW/WL:' + Math.round(viewport.voi.windowWidth) + '/' + Math.round(viewport.voi.windowCenter));
+  $('#bottomright').text('Zoom: ' + viewport.scale.toFixed(2) + 'x');
+}
+
+
+function ToggleSeriesInformation()
+{
+  $('#topright').toggle();
+}
+
+
+function ToggleInterpolation()
+{
+  var element = $('#dicomImage').get(0);
+  var viewport = cornerstone.getViewport(element);
+  if (viewport.pixelReplication === true) {
+    viewport.pixelReplication = false;
+  } else {
+    viewport.pixelReplication = true;
+  }
+  cornerstone.setViewport(element, viewport);
+}
+
+
+function ToggleInversion()
+{
+  var element = $('#dicomImage').get(0);
+  var viewport = cornerstone.getViewport(element);
+  if (viewport.invert === true) {
+    viewport.invert = false;
+  } else {
+    viewport.invert = true;
+  }
+  cornerstone.setViewport(element, viewport);
+}
+
+
+function DownloadInstance(instance)
+{
+  // http://stackoverflow.com/a/3749395/881731
+  var hiddenIFrameID = 'hiddenDownloader',
+  iframe = document.getElementById(hiddenIFrameID);
+  if (iframe === null) {
+    iframe = document.createElement('iframe');
+    iframe.id = hiddenIFrameID;
+    iframe.style.display = 'none';
+    document.body.appendChild(iframe);
+  }
+  iframe.src = '../../instances/' + instance + '/file';
+}
+
+
+function AdjustZoom()
+{
+  var element = $('#dicomImage').get(0);
+  cornerstone.fitToWindow(element);
+}
+
+
+function ZoomIn()
+{
+  var element = $('#dicomImage').get(0);
+  var viewport = cornerstone.getViewport(element);
+  viewport.scale /= 0.5;
+  cornerstone.setViewport(element, viewport);
+  UpdateViewportInformation();
+}
+
+
+function ZoomOut()
+{
+  var element = $('#dicomImage').get(0);
+  var viewport = cornerstone.getViewport(element);
+  viewport.scale *= 0.5;
+  cornerstone.setViewport(element, viewport);
+  UpdateViewportInformation();
+}
+
+
+
+(function (cornerstone) {
+  'use strict';
+
+  function PrintRange(pixels)
+  {
+    var a = Infinity;
+    var b = -Infinity;
+
+    for (var i = 0, length = pixels.length; i < length; i++) {
+      if (pixels[i] < a)
+        a = pixels[i];
+      if (pixels[i] > b)
+        b = pixels[i];
+    }    
+
+    console.log(a + ' ' + b);
+  }
+
+  function ChangeDynamics(pixels, source1, target1, source2, target2)
+  {
+    var scale = (target2 - target1) / (source2 - source1);
+    var offset = (target1) - scale * source1;
+
+    for (var i = 0, length = pixels.length; i < length; i++) {
+      pixels[i] = scale * pixels[i] + offset;
+    }    
+  }
+
+
+  function getPixelDataDeflate(image) {
+    // Decompresses the base64 buffer that was compressed with Deflate
+    var s = pako.inflate(window.atob(image.Orthanc.PixelData));
+    var pixels = null;
+
+    if (image.color) {
+      var buf = new ArrayBuffer(s.length / 3 * 4); // RGB32
+      pixels = new Uint8Array(buf);
+      var index = 0;
+      for (var i = 0, length = s.length; i < length; i += 3) {
+        pixels[index++] = s[i];
+        pixels[index++] = s[i + 1];
+        pixels[index++] = s[i + 2];
+        pixels[index++] = 255;  // Alpha channel
+      }
+    } else {
+      var buf = new ArrayBuffer(s.length * 2); // int16_t
+      pixels = new Int16Array(buf);
+      var index = 0;
+      for (var i = 0, length = s.length; i < length; i += 2) {
+        var lower = s[i];
+        var upper = s[i + 1];
+        pixels[index] = lower + upper * 256;
+        index++;
+      }
+    }
+
+    return pixels;
+  }
+
+
+  // http://stackoverflow.com/a/11058858/881731
+  function str2ab(str) {
+    var buf = new ArrayBuffer(str.length);
+    var pixels = new Uint8Array(buf);
+    for (var i = 0, strLen=str.length; i<strLen; i++) {
+      pixels[i] = str.charCodeAt(i);
+    }
+    return pixels;
+  }
+
+  function getPixelDataJpeg(image) {
+    var jpegReader = new JpegImage();
+    var jpeg = str2ab(window.atob(image.Orthanc.PixelData));
+    jpegReader.parse(jpeg);
+    var s = jpegReader.getData(image.width, image.height);
+    var pixels = null;
+
+    if (image.color) {
+      var buf = new ArrayBuffer(s.length / 3 * 4); // RGB32
+      pixels = new Uint8Array(buf);
+      var index = 0;
+      for (var i = 0, length = s.length; i < length; i += 3) {
+        pixels[index++] = s[i];
+        pixels[index++] = s[i + 1];
+        pixels[index++] = s[i + 2];
+        pixels[index++] = 255;  // Alpha channel
+      }
+    } else {
+      var buf = new ArrayBuffer(s.length * 2); // uint8_t
+      pixels = new Int16Array(buf);
+      var index = 0;
+      for (var i = 0, length = s.length; i < length; i++) {
+        pixels[index] = s[i];
+        index++;
+      }
+
+      if (image.Orthanc.Stretched) {
+        ChangeDynamics(pixels, 0, image.Orthanc.StretchLow, 255, image.Orthanc.StretchHigh);
+      }
+    }
+
+    return pixels;
+  }
+  
+
+  function getOrthancImage(imageId) {
+    var result = null;
+
+    $.ajax({
+      type: 'GET',
+      url: '../instances/' + compression + '-' + imageId,
+      dataType: 'json',
+      cache: true,
+      async: false,
+      success: function(image) {
+        image.imageId = imageId;
+        if (image.color)
+          image.render = cornerstone.renderColorImage;
+        else
+          image.render = cornerstone.renderGrayscaleImage;
+
+        image.getPixelData = function() {
+          if (image.Orthanc.Compression == 'Deflate')
+            return getPixelDataDeflate(this);
+
+          if (image.Orthanc.Compression == 'Jpeg')
+            return getPixelDataJpeg(this);
+
+          // Unknown compression
+          return null;
+        }
+
+        result = image;
+      },
+      error: function() {
+        return null;
+      }
+    });
+    
+    var deferred = $.Deferred();
+    deferred.resolve(result);
+    return deferred;
+  }
+
+  // register our imageLoader plugin with cornerstone
+  cornerstone.registerImageLoader('', getOrthancImage);
+
+}(cornerstone));
+
+
+$(document).ready(function() {
+  $('#open-toolbar').button({
+    icons: { primary: 'ui-icon-custom-orthanc' },
+    text: false
+  });
+
+  var series = window.url('?series', window.location.search);
+  if (series == null)
+    return;
+
+  console.log('Displaying series: ' + series);
+  var instances = [ ];
+
+  $.ajax({
+    type: 'GET',
+    url: '../series/' + series,
+    dataType: 'json',
+    cache: false,
+    async: false,
+    success: function(volume) {
+      if (volume.SortedInstances.length != 0) {
+        instances = volume.SortedInstances;
+        $('#topright').html([
+          $('<span>').text(volume.PatientID),
+          $('<br>'),
+          $('<span>').text(volume.PatientName),
+          $('<br>'),
+          $('<span>').text(volume.StudyDescription),
+          $('<br>'),
+          $('<span>').text(volume.SeriesDescription)
+        ]);
+      }
+    }
+  });
+  
+  if (instances.length == 0)
+  {
+    console.log('No image in this series');
+    return;
+  }
+
+
+  var currentImageIndex = 0;
+
+  // updates the image display
+  function updateTheImage(imageIndex) {
+    return cornerstone.loadAndCacheImage(instances[imageIndex]).then(function(image) {
+      currentImageIndex = imageIndex;
+      var viewport = cornerstone.getViewport(element);
+      cornerstone.displayImage(element, image, viewport);
+    });
+  }
+
+  // image enable the element
+  var element = $('#dicomImage').get(0);
+  cornerstone.enable(element);
+
+  // set event handlers
+  /*function onImageRendered(e, eventData) {
+    $('#topright').text('Render Time:' + eventData.renderTimeInMs + ' ms');
+  }
+  $(element).on('CornerstoneImageRendered', onImageRendered);*/
+
+  // load and display the image
+  var imagePromise = updateTheImage(0);
+
+  // add handlers for mouse events once the image is loaded.
+  imagePromise.then(function() {
+    viewport = cornerstone.getViewport(element);
+    UpdateViewportInformation();
+
+    // add event handlers to pan image on mouse move
+    $('#dicomImage').mousedown(function (e) {
+      var lastX = e.pageX;
+      var lastY = e.pageY;
+      var mouseButton = e.which;
+
+      $(toolbar).hide();
+
+      $(document).mousemove(function (e) {
+        var deltaX = e.pageX - lastX,
+        deltaY = e.pageY - lastY;
+        lastX = e.pageX;
+        lastY = e.pageY;
+
+        if (mouseButton == 1) {
+          var viewport = cornerstone.getViewport(element);
+          viewport.voi.windowWidth += (deltaX / viewport.scale);
+          viewport.voi.windowCenter += (deltaY / viewport.scale);
+          cornerstone.setViewport(element, viewport);
+          UpdateViewportInformation();
+        }
+        else if (mouseButton == 2) {
+          var viewport = cornerstone.getViewport(element);
+          viewport.translation.x += (deltaX / viewport.scale);
+          viewport.translation.y += (deltaY / viewport.scale);
+          cornerstone.setViewport(element, viewport);
+        }
+        else if (mouseButton == 3) {
+          var viewport = cornerstone.getViewport(element);
+          viewport.scale += (deltaY / 100);
+          cornerstone.setViewport(element, viewport);
+          UpdateViewportInformation();
+        }
+      });
+
+      $(document).mouseup(function (e) {
+        $(document).unbind('mousemove');
+        $(document).unbind('mouseup');
+      });
+    });
+
+    $('#dicomImage').on('mousewheel DOMMouseScroll', function (e) {
+      // Firefox e.originalEvent.detail > 0 scroll back, < 0 scroll forward
+      // chrome/safari e.originalEvent.wheelDelta < 0 scroll back, > 0 scroll forward
+      if (e.originalEvent.wheelDelta < 0 || e.originalEvent.detail > 0) {
+        currentImageIndex ++;
+        if (currentImageIndex >= instances.length) {
+          currentImageIndex = instances.length - 1; 
+        }         
+      } else {
+        currentImageIndex --;
+        if (currentImageIndex < 0) {
+          currentImageIndex = 0;
+        }         
+      }
+
+      updateTheImage(currentImageIndex);
+      $('#slider').slider("option", "value", currentImageIndex);
+
+      //prevent page fom scrolling
+      return false;
+    });
+  });
+
+
+  $('#slider').slider({
+    min: 0,
+    max: instances.length - 1,
+    slide: function(event, ui) {
+      updateTheImage(ui.value);
+    }
+  });
+
+  var toolbar = $.jsPanel({
+    position: { top: 50, left: 10 },
+    size: { width: 155, height: 200 },
+    content: $('#toolbar-content').clone().show(),
+    controls: { buttons: 'none' },
+    title: '<a target="_blank" href="http://www.orthanc-server.com/"><img src="images/orthanc-logo.png" /></a>'
+  });
+
+  $('#open-toolbar').click(function() {
+    toolbar.toggle();
+  });
+
+  $(toolbar).hide();
+
+  $('.toolbar-view', toolbar).buttonset()
+    .children().first().button({
+      icons: { primary: 'ui-icon-info' },
+      text: false
+    }).click(ToggleSeriesInformation).next().button({
+      icons: { primary: 'ui-icon-custom-inversion' },
+      text: false
+    }).click(ToggleInversion).next().button({
+      icons: { primary: 'ui-icon-custom-interpolation' },
+      text: false
+    }).click(ToggleInterpolation).next().button({
+      icons: { primary: 'ui-icon-circle-triangle-s' },
+      text: false
+    }).click(function() {
+      DownloadInstance(instances[currentImageIndex]);
+    });
+
+  $('.toolbar-zoom', toolbar).buttonset()
+    .children().first().button({
+      icons: { primary: 'ui-icon-image' },
+      text: false
+    }).click(AdjustZoom).next().button({
+      icons: { primary: 'ui-icon-zoomin' },
+      text: false
+    }).click(ZoomIn).next().button({
+      icons: { primary: 'ui-icon-zoomout' },
+      text: false
+    }).click(ZoomOut);
+
+  $('.toolbar-windowing', toolbar).buttonset()
+    .children().first().button({
+      icons: { primary: 'ui-icon-custom-default' },
+      text: false
+    }).click(SetDefaultWindowing).next().button({
+      icons: { primary: 'ui-icon-custom-stretch' },
+      text: false
+    }).click(SetFullWindowing).next().button({
+      icons: { primary: 'ui-icon-custom-lung' },
+      text: false
+    }).click(SetLungWindowing).next().button({
+      icons: { primary: 'ui-icon-custom-bone' },
+      text: false
+    }).click(SetBoneWindowing);
+
+
+  function SetCompression(c)
+  {
+    compression = c;
+    cornerstone.imageCache.purgeCache();
+    updateTheImage(currentImageIndex);
+    cornerstone.invalidateImageId(instances[currentImageIndex]);
+  }
+
+  $('.toolbar-quality', toolbar).buttonset()
+    .children().first().button({
+      label: 'L'
+    }).click(function() {
+      SetCompression('jpeg80');
+    }).next().button({
+      label: 'M'
+    }).click(function() {
+      SetCompression('jpeg95');
+    }).next().button({
+      label: 'H'
+    }).click(function() {
+      SetCompression('deflate');
+    });
+
+
+  ResizeCornerstone();
+  $(window).resize(function(e) {
+    if (!$(e.target).hasClass('jsPanel'))  // Ignore toolbar resizing
+    {
+      ResizeCornerstone();
+    }
+  });
+
+});