changeset 0:351ab0da0150

initial commit
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 14 Oct 2016 15:34:11 +0200
parents
children 2dbe613f6c93
files AUTHORS CMakeLists.txt COPYING Framework/Applications/BasicApplicationContext.cpp Framework/Applications/BasicApplicationContext.h Framework/Applications/IBasicApplication.cpp Framework/Applications/IBasicApplication.h Framework/Applications/Sdl/SdlBuffering.cpp Framework/Applications/Sdl/SdlBuffering.h Framework/Applications/Sdl/SdlEngine.cpp Framework/Applications/Sdl/SdlEngine.h Framework/Applications/Sdl/SdlWindow.cpp Framework/Applications/Sdl/SdlWindow.h Framework/Enumerations.h Framework/Layers/CircleMeasureTracker.cpp Framework/Layers/CircleMeasureTracker.h Framework/Layers/ColorFrameRenderer.cpp Framework/Layers/ColorFrameRenderer.h Framework/Layers/DicomStructureSetRendererFactory.cpp Framework/Layers/DicomStructureSetRendererFactory.h Framework/Layers/FrameRenderer.cpp Framework/Layers/FrameRenderer.h Framework/Layers/GrayscaleFrameRenderer.cpp Framework/Layers/GrayscaleFrameRenderer.h Framework/Layers/ILayerRenderer.h Framework/Layers/ILayerRendererFactory.h Framework/Layers/LineLayerRenderer.cpp Framework/Layers/LineLayerRenderer.h Framework/Layers/LineMeasureTracker.cpp Framework/Layers/LineMeasureTracker.h Framework/Layers/RenderStyle.cpp Framework/Layers/RenderStyle.h Framework/Layers/SeriesFrameRendererFactory.cpp Framework/Layers/SeriesFrameRendererFactory.h Framework/Layers/SiblingSliceLocationFactory.cpp Framework/Layers/SiblingSliceLocationFactory.h Framework/Layers/SingleFrameRendererFactory.cpp Framework/Layers/SingleFrameRendererFactory.h Framework/Messaging/CurlOrthancConnection.cpp Framework/Messaging/CurlOrthancConnection.h Framework/Messaging/IOrthancConnection.h Framework/Messaging/MessagingToolbox.cpp Framework/Messaging/MessagingToolbox.h Framework/Orthanc/README.txt Framework/Toolbox/BinarySemaphore.cpp Framework/Toolbox/BinarySemaphore.h Framework/Toolbox/DicomDataset.cpp Framework/Toolbox/DicomDataset.h Framework/Toolbox/DicomFrameConverter.cpp Framework/Toolbox/DicomFrameConverter.h Framework/Toolbox/DicomStructureSet.cpp Framework/Toolbox/DicomStructureSet.h Framework/Toolbox/DownloadStack.cpp Framework/Toolbox/DownloadStack.h Framework/Toolbox/GeometryToolbox.cpp Framework/Toolbox/GeometryToolbox.h Framework/Toolbox/ISeriesLoader.h Framework/Toolbox/IThreadSafety.h Framework/Toolbox/ObserversRegistry.h Framework/Toolbox/OrthancSeriesLoader.cpp Framework/Toolbox/OrthancSeriesLoader.h Framework/Toolbox/ParallelSlices.cpp Framework/Toolbox/ParallelSlices.h Framework/Toolbox/ParallelSlicesCursor.cpp Framework/Toolbox/ParallelSlicesCursor.h Framework/Toolbox/SharedValue.h Framework/Toolbox/SliceGeometry.cpp Framework/Toolbox/SliceGeometry.h Framework/Toolbox/ViewportGeometry.cpp Framework/Toolbox/ViewportGeometry.h Framework/Viewport/CairoContext.cpp Framework/Viewport/CairoContext.h Framework/Viewport/CairoFont.cpp Framework/Viewport/CairoFont.h Framework/Viewport/CairoSurface.cpp Framework/Viewport/CairoSurface.h Framework/Viewport/IMouseTracker.h Framework/Viewport/IStatusBar.h Framework/Viewport/IViewport.h Framework/Viewport/WidgetViewport.cpp Framework/Viewport/WidgetViewport.h Framework/Volumes/ISliceableVolume.h Framework/Volumes/ImageBuffer3D.cpp Framework/Volumes/ImageBuffer3D.h Framework/Volumes/VolumeImage.cpp Framework/Volumes/VolumeImage.h Framework/Volumes/VolumeImagePolicyBase.cpp Framework/Volumes/VolumeImagePolicyBase.h Framework/Volumes/VolumeImageProgressivePolicy.cpp Framework/Volumes/VolumeImageProgressivePolicy.h Framework/Volumes/VolumeImageSimplePolicy.cpp Framework/Volumes/VolumeImageSimplePolicy.h Framework/Widgets/CairoWidget.cpp Framework/Widgets/CairoWidget.h Framework/Widgets/EmptyWidget.cpp Framework/Widgets/EmptyWidget.h Framework/Widgets/IWidget.h Framework/Widgets/IWorldSceneInteractor.h Framework/Widgets/IWorldSceneMouseTracker.h Framework/Widgets/LayeredSceneWidget.cpp Framework/Widgets/LayeredSceneWidget.h Framework/Widgets/LayoutWidget.cpp Framework/Widgets/LayoutWidget.h Framework/Widgets/TestCairoWidget.cpp Framework/Widgets/TestCairoWidget.h Framework/Widgets/TestWorldSceneWidget.cpp Framework/Widgets/TestWorldSceneWidget.h Framework/Widgets/WidgetBase.cpp Framework/Widgets/WidgetBase.h Framework/Widgets/WorldSceneWidget.cpp Framework/Widgets/WorldSceneWidget.h NEWS README Resources/CMake/BoostExtendedConfiguration.cmake Resources/CMake/CairoConfiguration.cmake Resources/CMake/OrthancStone.cmake Resources/CMake/PixmanConfiguration.cmake Resources/CMake/PixmanConfiguration.patch Resources/CMake/SdlConfiguration.cmake Resources/CMake/cairo-features.h Resources/Colormaps/GenerateColormaps.py Resources/Colormaps/blue.lut Resources/Colormaps/green.lut Resources/Colormaps/hot.lut Resources/Colormaps/jet.lut Resources/Colormaps/red.lut Resources/OrthancLogoDocumentation.png Resources/OrthancStone.doxygen Resources/SyncOrthancFolder.py Samples/BasicPetCtFusionApplication.h Samples/EmptyApplication.h Samples/LayoutPetCtFusionApplication.h Samples/SampleApplicationBase.h Samples/SampleInteractor.h Samples/SampleMainSdl.cpp Samples/SingleFrameApplication.h Samples/SingleVolumeApplication.h Samples/SynchronizedSeriesApplication.h Samples/TestPatternApplication.h TODO
diffstat 140 files changed, 20136 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/AUTHORS	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,13 @@
+Stone of 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	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,20 @@
+cmake_minimum_required(VERSION 2.8)
+project(OrthancStone)
+
+include(Resources/CMake/OrthancStone.cmake)
+
+add_library(OrthancStone ${ORTHANC_STONE_SOURCES})
+
+macro(BuildSample Target Sample)
+  add_executable(${Target} Samples/SampleMainSdl.cpp)
+  set_target_properties(${Target} PROPERTIES COMPILE_DEFINITIONS ORTHANC_STONE_SAMPLE=${Sample})
+  target_link_libraries(${Target} OrthancStone)
+endmacro()
+
+BuildSample(OrthancStoneEmpty 1)
+BuildSample(OrthancStoneTestPattern 2)
+BuildSample(OrthancStoneSingleFrame 3)
+BuildSample(OrthancStoneSingleVolume 4)
+BuildSample(OrthancStoneBasicPetCtFusion 5)
+BuildSample(OrthancStoneSynchronizedSeries 6)
+BuildSample(OrthancStoneLayoutPetCtFusion 7)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/COPYING	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,674 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  You must make sure that they, too, receive
+or can get the source code.  And you must show them these terms so they
+know their rights.
+
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Use with the GNU Affero General Public License.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+    <program>  Copyright (C) <year>  <name of author>
+    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+  The GNU General Public License does not permit incorporating your program
+into proprietary programs.  If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.  But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Applications/BasicApplicationContext.cpp	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,146 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "BasicApplicationContext.h"
+
+#include "../Toolbox/OrthancSeriesLoader.h"
+#include "../Volumes/VolumeImageSimplePolicy.h"
+#include "../Volumes/VolumeImageProgressivePolicy.h"
+
+namespace OrthancStone
+{
+  BasicApplicationContext::BasicApplicationContext(IOrthancConnection& orthanc) :
+    orthanc_(orthanc)
+  {
+  }
+
+
+  BasicApplicationContext::~BasicApplicationContext()
+  {
+    for (Interactors::iterator it = interactors_.begin(); it != interactors_.end(); ++it)
+    {
+      assert(*it != NULL);
+      delete *it;
+    }
+
+    for (Volumes::iterator it = volumes_.begin(); it != volumes_.end(); ++it)
+    {
+      assert(*it != NULL);
+      delete *it;
+    }
+
+    for (StructureSets::iterator it = structureSets_.begin(); it != structureSets_.end(); ++it)
+    {
+      assert(*it != NULL);
+      delete *it;
+    }
+  }
+
+
+  IWidget& BasicApplicationContext::SetCentralWidget(IWidget* widget)   // Takes ownership
+  {
+    viewport_.SetCentralWidget(widget);
+    return *widget;
+  }
+
+
+  VolumeImage& BasicApplicationContext::AddSeriesVolume(const std::string& series,
+                                                        bool isProgressiveDownload,
+                                                        size_t downloadThreadCount)
+  {
+    std::auto_ptr<VolumeImage> volume(new VolumeImage(new OrthancSeriesLoader(orthanc_, series)));
+
+    if (isProgressiveDownload)
+    {
+      volume->SetDownloadPolicy(new VolumeImageProgressivePolicy);
+    }
+    else
+    {
+      volume->SetDownloadPolicy(new VolumeImageSimplePolicy);
+    }
+
+    volume->SetThreadCount(downloadThreadCount);
+
+    VolumeImage& result = *volume;
+    volumes_.push_back(volume.release());
+
+    return result;
+  }
+
+
+  DicomStructureSet& BasicApplicationContext::AddStructureSet(const std::string& instance)
+  {
+    std::auto_ptr<DicomStructureSet> structureSet(new DicomStructureSet(orthanc_, instance));
+
+    DicomStructureSet& result = *structureSet;
+    structureSets_.push_back(structureSet.release());
+
+    return result;
+  }
+
+
+  IWorldSceneInteractor& BasicApplicationContext::AddInteractor(IWorldSceneInteractor* interactor)
+  {
+    if (interactor == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    interactors_.push_back(interactor);
+
+    return *interactor;
+  }
+
+
+  void BasicApplicationContext::Start()
+  {
+    for (Volumes::iterator it = volumes_.begin(); it != volumes_.end(); ++it)
+    {
+      assert(*it != NULL);
+      (*it)->Start();
+    }
+
+    viewport_.Start();
+  }
+
+
+  void BasicApplicationContext::Stop()
+  {
+    viewport_.Stop();
+
+    for (Volumes::iterator it = volumes_.begin(); it != volumes_.end(); ++it)
+    {
+      assert(*it != NULL);
+      (*it)->Stop();
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Applications/BasicApplicationContext.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,86 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "../Volumes/VolumeImage.h"
+#include "../Viewport/WidgetViewport.h"
+#include "../Widgets/IWorldSceneInteractor.h"
+#include "../Toolbox/DicomStructureSet.h"
+
+#include <list>
+
+namespace OrthancStone
+{
+  class BasicApplicationContext : public boost::noncopyable
+  {
+  private:
+    typedef std::list<ISliceableVolume*>       Volumes;
+    typedef std::list<IWorldSceneInteractor*>  Interactors;
+    typedef std::list<DicomStructureSet*>      StructureSets;
+
+    IOrthancConnection&  orthanc_;
+    WidgetViewport       viewport_;
+    Volumes              volumes_;
+    Interactors          interactors_;
+    StructureSets        structureSets_;
+
+  public:
+    BasicApplicationContext(IOrthancConnection& orthanc);
+
+    ~BasicApplicationContext();
+
+    IWidget& SetCentralWidget(IWidget* widget);   // Takes ownership
+
+    IViewport& GetViewport()
+    {
+      return viewport_;
+    }
+
+    IOrthancConnection& GetOrthancConnection()
+    {
+      return orthanc_;
+    }
+
+    VolumeImage& AddSeriesVolume(const std::string& series,
+                                 bool isProgressiveDownload,
+                                 size_t downloadThreadCount);
+
+    DicomStructureSet& AddStructureSet(const std::string& instance);
+
+    IWorldSceneInteractor& AddInteractor(IWorldSceneInteractor* interactor);
+
+    void Start();
+
+    void Stop();
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Applications/IBasicApplication.cpp	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,284 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "IBasicApplication.h"
+
+#include "../Orthanc/Core/Logging.h"
+#include "../Orthanc/Core/HttpClient.h"
+#include "../Messaging/CurlOrthancConnection.h"
+#include "Sdl/SdlEngine.h"
+
+namespace OrthancStone
+{
+  // Anonymous namespace to avoid clashes against other compilation modules
+  namespace
+  {
+    class LogStatusBar : public IStatusBar
+    {
+    public:
+      virtual void ClearMessage()
+      {
+      }
+
+      virtual void SetMessage(const std::string& message)
+      {
+        LOG(WARNING) << message;
+      }
+    };
+  }
+
+
+#if ORTHANC_ENABLE_SDL == 1
+  static void DeclareSdlCommandLineOptions(boost::program_options::options_description& options)
+  {
+    // Declare the supported parameters
+    boost::program_options::options_description generic("Generic options");
+    generic.add_options()
+      ("help", "Display this help and exit")
+      ("verbose", "Be verbose in logs")
+      ("orthanc", boost::program_options::value<std::string>()->default_value("http://localhost:8042/"),
+       "URL to the Orthanc server")
+      ("username", "Username for the Orthanc server")
+      ("password", "Password for the Orthanc server")
+      ("https-verify", boost::program_options::value<bool>()->default_value(true), "Check HTTPS certificates")
+      ;
+
+    options.add(generic);
+
+    boost::program_options::options_description sdl("SDL options");
+    sdl.add_options()
+      ("width", boost::program_options::value<int>()->default_value(1024), "Initial width of the SDL window")
+      ("height", boost::program_options::value<int>()->default_value(768), "Initial height of the SDL window")
+      ("opengl", boost::program_options::value<bool>()->default_value(true), "Enable OpenGL in SDL")
+      ;
+
+    options.add(sdl);
+  }
+
+
+  int IBasicApplication::ExecuteWithSdl(IBasicApplication& application,
+                                        int argc, 
+                                        char* argv[])
+  {
+    /******************************************************************
+     * Initialize all the subcomponents of Orthanc Stone
+     ******************************************************************/
+
+    Orthanc::Logging::Initialize();
+    Orthanc::HttpClient::InitializeOpenSsl();
+    Orthanc::HttpClient::GlobalInitialize();
+    SdlEngine::GlobalInitialize();
+
+
+    /******************************************************************
+     * Declare and parse the command-line options of the application
+     ******************************************************************/
+
+    boost::program_options::options_description options;
+    DeclareSdlCommandLineOptions(options);   
+    application.DeclareCommandLineOptions(options);
+
+    boost::program_options::variables_map parameters;
+    bool error = false;
+
+    try
+    {
+      boost::program_options::store(boost::program_options::command_line_parser(argc, argv).
+                                    options(options).run(), parameters);
+      boost::program_options::notify(parameters);    
+    }
+    catch (boost::program_options::error& e)
+    {
+      LOG(ERROR) << "Error while parsing the command-line arguments: " << e.what();
+      error = true;
+    }
+
+
+    /******************************************************************
+     * Configure the application with the command-line parameters
+     ******************************************************************/
+
+    if (error || parameters.count("help")) 
+    {
+      std::cout << std::endl
+                << "Usage: " << argv[0] << " [OPTION]..."
+                << std::endl
+                << "Orthanc, lightweight, RESTful DICOM server for healthcare and medical research."
+                << std::endl << std::endl
+                << "Demonstration application of Orthanc Stone using SDL."
+                << std::endl;
+
+      std::cout << options << "\n";
+      return error ? -1 : 0;
+    }
+
+    if (parameters.count("https-verify") &&
+        !parameters["https-verify"].as<bool>())
+    {
+      LOG(WARNING) << "Turning off verification of HTTPS certificates (unsafe)";
+      Orthanc::HttpClient::ConfigureSsl(false, "");
+    }
+
+    if (parameters.count("verbose"))
+    {
+      Orthanc::Logging::EnableInfoLevel(true);
+    }
+
+    if (!parameters.count("width") ||
+        !parameters.count("height") ||
+        !parameters.count("opengl"))
+    {
+      LOG(ERROR) << "Parameter \"width\", \"height\" or \"opengl\" is missing";
+      return -1;
+    }
+
+    int w = parameters["width"].as<int>();
+    int h = parameters["height"].as<int>();
+    if (w <= 0 || h <= 0)
+    {
+      LOG(ERROR) << "Parameters \"width\" and \"height\" must be positive";
+      return -1;
+    }
+
+    unsigned int width = static_cast<unsigned int>(w);
+    unsigned int height = static_cast<unsigned int>(h);
+    LOG(WARNING) << "Initial display size: " << width << "x" << height;
+
+    bool opengl = parameters["opengl"].as<bool>();
+    if (opengl)
+    {
+      LOG(WARNING) << "OpenGL is enabled, disable it with option \"--opengl=off\" if the application crashes";
+    }
+    else
+    {
+      LOG(WARNING) << "OpenGL is disabled, enable it with option \"--opengl=on\" for best performance";
+    }
+
+    bool success = true;
+    try
+    {
+      /****************************************************************
+       * Initialize the connection to the Orthanc server
+       ****************************************************************/
+
+      Orthanc::WebServiceParameters webService;
+
+      if (parameters.count("orthanc"))
+      {
+        webService.SetUrl(parameters["orthanc"].as<std::string>());
+      }
+
+      if (parameters.count("username"))
+      {
+        webService.SetUsername(parameters["username"].as<std::string>());
+      }
+
+      if (parameters.count("password"))
+      {
+        webService.SetPassword(parameters["password"].as<std::string>());
+      }
+
+      LOG(WARNING) << "URL to the Orthanc REST API: " << webService.GetUrl();
+      CurlOrthancConnection orthanc(webService);
+
+      if (!MessagingToolbox::CheckOrthancVersion(orthanc))
+      {
+        LOG(ERROR) << "Your version of Orthanc is incompatible with Orthanc Stone, please upgrade";
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+      }
+
+
+      /****************************************************************
+       * Initialize the application
+       ****************************************************************/
+
+      LogStatusBar statusBar;
+      BasicApplicationContext context(orthanc);
+
+      application.Initialize(context, statusBar, parameters);
+      context.GetViewport().SetStatusBar(statusBar);
+
+      std::string title = application.GetTitle();
+      if (title.empty())
+      {
+        title = "Stone of Orthanc";
+      }
+
+      context.Start();
+
+      {
+        /**************************************************************
+         * Run the application inside a SDL window
+         **************************************************************/
+
+        LOG(WARNING) << "Starting the application";
+
+        SdlWindow window(title.c_str(), width, height, opengl);
+        SdlEngine sdl(window, context.GetViewport());
+
+        sdl.Run();
+
+        LOG(WARNING) << "Stopping the application";
+      }
+
+
+      /****************************************************************
+       * Finalize the application
+       ****************************************************************/
+
+      context.Stop();
+
+      LOG(WARNING) << "The application has stopped";
+
+      context.GetViewport().ResetStatusBar();
+      application.Finalize();
+    }
+    catch (Orthanc::OrthancException& e)
+    {
+      LOG(ERROR) << "EXCEPTION: " << e.What();
+      success = false;
+    }
+
+
+    /******************************************************************
+     * Finalize all the subcomponents of Orthanc Stone
+     ******************************************************************/
+
+    SdlEngine::GlobalFinalize();
+    Orthanc::HttpClient::GlobalFinalize();
+    Orthanc::HttpClient::FinalizeOpenSsl();
+
+    return (success ? 0 : -1);
+  }
+#endif
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Applications/IBasicApplication.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,68 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "BasicApplicationContext.h"
+
+#include <boost/program_options.hpp>
+
+#if ORTHANC_ENABLE_SDL == 1
+#  include <SDL.h>   // Necessary to avoid undefined reference to `SDL_main'
+#endif
+
+namespace OrthancStone
+{
+  class IBasicApplication : public boost::noncopyable
+  {
+  public:
+    virtual ~IBasicApplication()
+    {
+    }
+
+    virtual void DeclareCommandLineOptions(boost::program_options::options_description& options) = 0;
+
+    virtual std::string GetTitle() const = 0;
+
+    virtual void Initialize(BasicApplicationContext& context,
+                            IStatusBar& statusBar,
+                            const boost::program_options::variables_map& parameters) = 0;
+
+    virtual void Finalize() = 0;
+
+#if ORTHANC_ENABLE_SDL == 1
+    static int ExecuteWithSdl(IBasicApplication& application,
+                              int argc, 
+                              char* argv[]);
+#endif
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Applications/Sdl/SdlBuffering.cpp	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,145 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "SdlBuffering.h"
+
+#if ORTHANC_ENABLE_SDL == 1
+
+#include "../../Orthanc/Core/Logging.h"
+#include "../../Orthanc/Core/OrthancException.h"
+
+namespace OrthancStone
+{
+  SdlBuffering::SdlBuffering() :
+    sdlSurface_(NULL),
+    pendingFrame_(false)
+  {
+  }
+
+
+  SdlBuffering::~SdlBuffering()
+  {
+    if (sdlSurface_)
+    {
+      SDL_FreeSurface(sdlSurface_);
+    }
+  }
+
+
+  void SdlBuffering::SetSize(unsigned int width,
+                             unsigned int height,
+                             IViewport& viewport)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    viewport.SetSize(width, height);
+
+    if (offscreenSurface_.get() == NULL ||
+        offscreenSurface_->GetWidth() != width ||
+        offscreenSurface_->GetHeight() != height)
+    {
+      offscreenSurface_.reset(new CairoSurface(width, height));
+    }
+
+    if (onscreenSurface_.get() == NULL ||
+        onscreenSurface_->GetWidth() != width ||
+        onscreenSurface_->GetHeight() != height)
+    {
+      onscreenSurface_.reset(new CairoSurface(width, height));
+
+      // TODO Big endian?
+      static const uint32_t rmask = 0x00ff0000;
+      static const uint32_t gmask = 0x0000ff00;
+      static const uint32_t bmask = 0x000000ff;
+
+      if (sdlSurface_)
+      {
+        SDL_FreeSurface(sdlSurface_);
+      }
+
+      sdlSurface_ = SDL_CreateRGBSurfaceFrom(onscreenSurface_->GetBuffer(), width, height, 32,
+                                             onscreenSurface_->GetPitch(), rmask, gmask, bmask, 0);
+      if (!sdlSurface_)
+      {
+        LOG(ERROR) << "Cannot create a SDL surface from a Cairo surface";
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }    
+    }
+
+    pendingFrame_ = false;
+  }
+
+
+  bool SdlBuffering::RenderOffscreen(IViewport& viewport)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    if (offscreenSurface_.get() == NULL)
+    {
+      return false;
+    }
+
+    Orthanc::ImageAccessor target = offscreenSurface_->GetAccessor();
+
+    if (viewport.Render(target) &&
+        !pendingFrame_)
+    {
+      pendingFrame_ = true;
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  void SdlBuffering::SwapToScreen(SdlWindow& window)
+  {
+    if (!pendingFrame_ ||
+        offscreenSurface_.get() == NULL ||
+        onscreenSurface_.get() == NULL)
+    {
+      return;
+    }
+
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+      onscreenSurface_->Copy(*offscreenSurface_);
+    }
+    
+    window.Render(sdlSurface_);
+    pendingFrame_ = false;
+  }
+}
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Applications/Sdl/SdlBuffering.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,71 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 ORTHANC_ENABLE_SDL == 1
+
+#include "SdlWindow.h"
+#include "../../Viewport/CairoSurface.h"
+#include "../../Viewport/IViewport.h"
+
+#include <boost/thread/mutex.hpp>
+
+namespace OrthancStone
+{
+  class SdlBuffering : public boost::noncopyable
+  {
+  private:
+    boost::mutex                 mutex_;
+    std::auto_ptr<CairoSurface>  offscreenSurface_;
+    std::auto_ptr<CairoSurface>  onscreenSurface_;
+    SDL_Surface*                 sdlSurface_;
+    bool                         pendingFrame_;
+
+  public:
+    SdlBuffering();
+
+    ~SdlBuffering();
+
+    void SetSize(unsigned int width,
+                 unsigned int height,
+                 IViewport& viewport);
+
+    // Returns "true" if a new refresh of the display should be
+    // triggered afterwards
+    bool RenderOffscreen(IViewport& viewport);
+
+    void SwapToScreen(SdlWindow& window);
+  };
+}
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Applications/Sdl/SdlEngine.cpp	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,330 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "SdlEngine.h"
+
+#if ORTHANC_ENABLE_SDL == 1
+
+#include "../../Orthanc/Core/Logging.h"
+
+#include <SDL.h>
+
+namespace OrthancStone
+{
+  void SdlEngine::RenderFrame()
+  {
+    if (!viewportChanged_)
+    {
+      return;
+    }
+
+    viewportChanged_ = false;
+
+    if (buffering_.RenderOffscreen(viewport_))
+    {
+      // Do not notify twice when a new frame was rendered, to avoid
+      // spoiling the SDL event queue
+      SDL_Event event;
+      SDL_memset(&event, 0, sizeof(event));
+      event.type = refreshEvent_;
+      event.user.code = 0;
+      event.user.data1 = 0;
+      event.user.data2 = 0;
+      SDL_PushEvent(&event);
+    }
+  }
+
+
+  void SdlEngine::RenderThread(SdlEngine* that)
+  {
+    for (;;)
+    {
+      that->renderFrame_.Wait();
+
+      if (that->continue_)
+      {
+        that->RenderFrame();
+      }
+      else
+      {
+        return;
+      }
+    }
+  }             
+
+
+  KeyboardModifiers SdlEngine::GetKeyboardModifiers(const uint8_t* keyboardState,
+                                                    const int scancodeCount)
+  {
+    int result = KeyboardModifiers_None;
+
+    if (keyboardState != NULL)
+    {
+      if (SDL_SCANCODE_LSHIFT < scancodeCount &&
+          keyboardState[SDL_SCANCODE_LSHIFT])
+      {
+        result |= KeyboardModifiers_Shift;
+      }
+
+      if (SDL_SCANCODE_RSHIFT < scancodeCount &&
+          keyboardState[SDL_SCANCODE_RSHIFT])
+      {
+        result |= KeyboardModifiers_Shift;
+      }
+
+      if (SDL_SCANCODE_LCTRL < scancodeCount &&
+          keyboardState[SDL_SCANCODE_LCTRL])
+      {
+        result |= KeyboardModifiers_Control;
+      }
+
+      if (SDL_SCANCODE_RCTRL < scancodeCount &&
+          keyboardState[SDL_SCANCODE_RCTRL])
+      {
+        result |= KeyboardModifiers_Control;
+      }
+
+      if (SDL_SCANCODE_LALT < scancodeCount &&
+          keyboardState[SDL_SCANCODE_LALT])
+      {
+        result |= KeyboardModifiers_Alt;
+      }
+
+      if (SDL_SCANCODE_RALT < scancodeCount &&
+          keyboardState[SDL_SCANCODE_RALT])
+      {
+        result |= KeyboardModifiers_Alt;
+      }
+    }
+
+    return static_cast<KeyboardModifiers>(result);
+  }
+
+
+  void SdlEngine::SetSize(unsigned int width,
+                          unsigned int height)
+  {
+    buffering_.SetSize(width, height, viewport_);
+    viewportChanged_ = true;
+    Refresh();
+  }
+
+
+  void SdlEngine::Stop()
+  {
+    if (continue_)
+    {
+      continue_ = false;
+      renderFrame_.Signal();  // Unlock the render thread
+      renderThread_.join();
+    }
+  }
+
+
+  void SdlEngine::Refresh()
+  {
+    renderFrame_.Signal();
+  }
+
+
+  SdlEngine::SdlEngine(SdlWindow& window,
+                       IViewport& viewport) :
+    window_(window),
+    viewport_(viewport),
+    continue_(true)
+  {
+    refreshEvent_ = SDL_RegisterEvents(1);
+
+    SetSize(window_.GetWidth(), window_.GetHeight());
+
+    viewport_.Register(*this);
+
+    renderThread_ = boost::thread(RenderThread, this);
+  }
+  
+
+  SdlEngine::~SdlEngine()
+  {
+    Stop();
+
+    viewport_.Unregister(*this);
+  }
+
+
+  void SdlEngine::Run()
+  {
+    int scancodeCount = 0;
+    const uint8_t* keyboardState = SDL_GetKeyboardState(&scancodeCount);
+
+    bool stop = false;
+    while (!stop)
+    {
+      Refresh();
+
+      SDL_Event event;
+
+      while (SDL_PollEvent(&event))
+      {
+        if (event.type == SDL_QUIT) 
+        {
+          stop = true;
+          break;
+        }
+        else if (event.type == refreshEvent_)
+        {
+          buffering_.SwapToScreen(window_);
+        }
+        else if (event.type == SDL_MOUSEBUTTONDOWN)
+        {
+          KeyboardModifiers modifiers = GetKeyboardModifiers(keyboardState, scancodeCount);
+
+          switch (event.button.button)
+          {
+            case SDL_BUTTON_LEFT:
+              viewport_.MouseDown(MouseButton_Left, event.button.x, event.button.y, modifiers);
+              break;
+            
+            case SDL_BUTTON_RIGHT:
+              viewport_.MouseDown(MouseButton_Right, event.button.x, event.button.y, modifiers);
+              break;
+            
+            case SDL_BUTTON_MIDDLE:
+              viewport_.MouseDown(MouseButton_Middle, event.button.x, event.button.y, modifiers);
+              break;
+
+            default:
+              break;
+          }
+        }
+        else if (event.type == SDL_MOUSEMOTION)
+        {
+          viewport_.MouseMove(event.button.x, event.button.y);
+        }
+        else if (event.type == SDL_MOUSEBUTTONUP)
+        {
+          viewport_.MouseUp();
+        }
+        else if (event.type == SDL_WINDOWEVENT)
+        {
+          switch (event.window.event)
+          {
+            case SDL_WINDOWEVENT_LEAVE:
+              viewport_.MouseLeave();
+              break;
+
+            case SDL_WINDOWEVENT_ENTER:
+              viewport_.MouseEnter();
+              break;
+
+            case SDL_WINDOWEVENT_SIZE_CHANGED:
+              SetSize(event.window.data1, event.window.data2);
+              break;
+
+            default:
+              break;
+          }
+        }
+        else if (event.type == SDL_MOUSEWHEEL)
+        {
+          KeyboardModifiers modifiers = GetKeyboardModifiers(keyboardState, scancodeCount);
+
+          int x, y;
+          SDL_GetMouseState(&x, &y);
+
+          if (event.wheel.y > 0)
+          {
+            viewport_.MouseWheel(MouseWheelDirection_Up, x, y, modifiers);
+          }
+          else if (event.wheel.y < 0)
+          {
+            viewport_.MouseWheel(MouseWheelDirection_Down, x, y, modifiers);
+          }
+        }
+        else if (event.type == SDL_KEYDOWN)
+        {
+          KeyboardModifiers modifiers = GetKeyboardModifiers(keyboardState, scancodeCount);
+
+          switch (event.key.keysym.sym)
+          {
+            case SDLK_a:  viewport_.KeyPressed('a', modifiers);  break;
+            case SDLK_b:  viewport_.KeyPressed('b', modifiers);  break;
+            case SDLK_c:  viewport_.KeyPressed('c', modifiers);  break;
+            case SDLK_d:  viewport_.KeyPressed('d', modifiers);  break;
+            case SDLK_e:  viewport_.KeyPressed('e', modifiers);  break;
+            case SDLK_f:  window_.ToggleMaximize();              break;
+            case SDLK_g:  viewport_.KeyPressed('g', modifiers);  break;
+            case SDLK_h:  viewport_.KeyPressed('h', modifiers);  break;
+            case SDLK_i:  viewport_.KeyPressed('i', modifiers);  break;
+            case SDLK_j:  viewport_.KeyPressed('j', modifiers);  break;
+            case SDLK_k:  viewport_.KeyPressed('k', modifiers);  break;
+            case SDLK_l:  viewport_.KeyPressed('l', modifiers);  break;
+            case SDLK_m:  viewport_.KeyPressed('m', modifiers);  break;
+            case SDLK_n:  viewport_.KeyPressed('n', modifiers);  break;
+            case SDLK_o:  viewport_.KeyPressed('o', modifiers);  break;
+            case SDLK_p:  viewport_.KeyPressed('p', modifiers);  break;
+            case SDLK_q:  stop = true;                           break;
+            case SDLK_r:  viewport_.KeyPressed('r', modifiers);  break;
+            case SDLK_s:  viewport_.KeyPressed('s', modifiers);  break;
+            case SDLK_t:  viewport_.KeyPressed('t', modifiers);  break;
+            case SDLK_u:  viewport_.KeyPressed('u', modifiers);  break;
+            case SDLK_v:  viewport_.KeyPressed('v', modifiers);  break;
+            case SDLK_w:  viewport_.KeyPressed('w', modifiers);  break;
+            case SDLK_x:  viewport_.KeyPressed('x', modifiers);  break;
+            case SDLK_y:  viewport_.KeyPressed('y', modifiers);  break;
+            case SDLK_z:  viewport_.KeyPressed('z', modifiers);  break;
+
+            default:
+              break;
+          }
+        }
+      }
+
+      SDL_Delay(10);   // Necessary for mouse wheel events to work
+    }
+
+    Stop();
+  }
+
+
+  void SdlEngine::GlobalInitialize()
+  {
+    SDL_Init(SDL_INIT_VIDEO);
+  }
+
+
+  void SdlEngine::GlobalFinalize()
+  {
+    SDL_Quit();
+  }
+}
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Applications/Sdl/SdlEngine.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,89 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 ORTHANC_ENABLE_SDL == 1
+
+#include "SdlBuffering.h"
+#include "../../Toolbox/BinarySemaphore.h"
+
+#include <boost/thread.hpp>
+
+namespace OrthancStone
+{
+  class SdlEngine : public IViewport::IChangeObserver
+  {
+  private:
+    SdlWindow&        window_;
+    IViewport&        viewport_;
+    SdlBuffering      buffering_;
+    boost::thread     renderThread_;
+    bool              continue_;
+    BinarySemaphore   renderFrame_;
+    uint32_t          refreshEvent_;
+    bool              viewportChanged_;
+
+    void RenderFrame();
+
+    static void RenderThread(SdlEngine* that);
+
+    static KeyboardModifiers GetKeyboardModifiers(const uint8_t* keyboardState,
+                                                  const int scancodeCount);
+
+    void SetSize(unsigned int width,
+                 unsigned int height);
+
+    void Stop();
+
+    void Refresh();
+
+  public:
+    SdlEngine(SdlWindow& window,
+              IViewport& viewport);
+  
+    virtual ~SdlEngine();
+
+    virtual void NotifyChange(const IViewport& viewport)
+    {
+      viewportChanged_ = true;
+    }
+
+    void Run();
+
+    static void GlobalInitialize();
+
+    static void GlobalFinalize();
+  };
+}
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Applications/Sdl/SdlWindow.cpp	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,161 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "SdlWindow.h"
+
+#if ORTHANC_ENABLE_SDL == 1
+
+#include "../../Orthanc/Core/Logging.h"
+#include "../../Orthanc/Core/OrthancException.h"
+
+namespace OrthancStone
+{
+  SdlWindow::SdlWindow(const char* title,
+                       unsigned int width,
+                       unsigned int height,
+                       bool enableOpenGl) :
+    maximized_(false)
+  {
+    // TODO Understand why, with SDL_WINDOW_OPENGL + MinGW32 + Release
+    // build mode, the application crashes whenever the SDL window is
+    // resized or maximized
+
+    uint32_t windowFlags, rendererFlags;
+    if (enableOpenGl)
+    {
+      windowFlags = SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE;
+      rendererFlags = SDL_RENDERER_ACCELERATED;
+    }
+    else
+    {
+      windowFlags = SDL_WINDOW_RESIZABLE;
+      rendererFlags = SDL_RENDERER_SOFTWARE;
+    }
+    
+    window_ = SDL_CreateWindow(title,
+                               SDL_WINDOWPOS_UNDEFINED,
+                               SDL_WINDOWPOS_UNDEFINED,
+                               width, height, windowFlags);
+
+    if (window_ == NULL) 
+    {
+      LOG(ERROR) << "Cannot create the SDL window: " << SDL_GetError();
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+
+    renderer_ = SDL_CreateRenderer(window_, -1, rendererFlags);
+    if (!renderer_)
+    {
+      LOG(ERROR) << "Cannot create the SDL renderer: " << SDL_GetError();
+      SDL_DestroyWindow(window_);
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+  }
+
+
+  SdlWindow::~SdlWindow()
+  {
+    if (renderer_ != NULL)
+    { 
+      SDL_DestroyRenderer(renderer_);
+    }
+
+    if (window_ != NULL)
+    { 
+      SDL_DestroyWindow(window_);
+    }
+  }
+
+
+  unsigned int SdlWindow::GetWidth() const
+  {
+    int w = -1;
+    SDL_GetWindowSize(window_, &w, NULL);
+
+    if (w < 0)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+    else
+    {
+      return static_cast<unsigned int>(w);
+    }
+  }
+
+
+  unsigned int SdlWindow::GetHeight() const
+  {
+    int h = -1;
+    SDL_GetWindowSize(window_, NULL, &h);
+
+    if (h < 0)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+    else
+    {
+      return static_cast<unsigned int>(h);
+    }
+  }
+
+
+  void SdlWindow::Render(SDL_Surface* surface)
+  {
+    //SDL_RenderClear(renderer_);
+
+    SDL_Texture *texture = SDL_CreateTextureFromSurface(renderer_, surface);
+    if (texture != NULL)
+    {
+      SDL_RenderCopy(renderer_, texture, NULL, NULL);
+      SDL_DestroyTexture(texture);
+    }
+
+    SDL_RenderPresent(renderer_);
+  }
+
+
+  void SdlWindow::ToggleMaximize()
+  {
+    if (maximized_)
+    {
+      SDL_RestoreWindow(window_);
+      maximized_ = false;
+    }
+    else
+    {
+      SDL_MaximizeWindow(window_);
+      maximized_ = true;
+    }
+  }
+}
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Applications/Sdl/SdlWindow.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,68 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 ORTHANC_ENABLE_SDL == 1
+
+#include <SDL_render.h>
+#include <SDL_video.h>
+#include <boost/noncopyable.hpp>
+
+namespace OrthancStone
+{
+  class SdlWindow : public boost::noncopyable
+  {
+  private:
+    SDL_Window    *window_;
+    SDL_Renderer  *renderer_;
+    bool           maximized_;
+
+  public:
+    SdlWindow(const char* title,
+              unsigned int width,
+              unsigned int height,
+              bool enableOpenGl);
+
+    ~SdlWindow();
+
+    unsigned int GetWidth() const;
+
+    unsigned int GetHeight() const;
+
+    void Render(SDL_Surface* surface);
+
+    void ToggleMaximize();
+  };
+}
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Enumerations.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,85 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 OrthancStone
+{
+  enum SliceOffsetMode
+  {
+    SliceOffsetMode_Absolute,
+    SliceOffsetMode_Relative,
+    SliceOffsetMode_Loop
+  };
+
+  enum ImageWindowing
+  {
+    ImageWindowing_Default,
+    ImageWindowing_Bone,
+    ImageWindowing_Lung,
+    ImageWindowing_Custom
+  };
+
+  enum MouseButton
+  {
+    MouseButton_Left,
+    MouseButton_Right,
+    MouseButton_Middle
+  };
+
+  enum MouseWheelDirection
+  {
+    MouseWheelDirection_Up,
+    MouseWheelDirection_Down
+  };
+
+  enum VolumeProjection
+  {
+    VolumeProjection_Axial,
+    VolumeProjection_Coronal,
+    VolumeProjection_Sagittal
+  };
+
+  enum ImageInterpolation
+  {
+    ImageInterpolation_Nearest,
+    ImageInterpolation_Linear
+  };
+
+  enum KeyboardModifiers
+  {
+    KeyboardModifiers_None = 0,
+    KeyboardModifiers_Shift = (1 << 0),
+    KeyboardModifiers_Control = (1 << 1),
+    KeyboardModifiers_Alt = (1 << 2)
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Layers/CircleMeasureTracker.cpp	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,122 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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/>.
+ **/
+
+
+#define _USE_MATH_DEFINES  // To access M_PI in Visual Studio
+#include <cmath>
+
+#include "CircleMeasureTracker.h"
+
+#include "../Viewport/CairoFont.h"
+
+#include <stdio.h>
+
+namespace OrthancStone
+{
+  CircleMeasureTracker::CircleMeasureTracker(IStatusBar* statusBar,
+                                             const SliceGeometry& slice,
+                                             double x, 
+                                             double y,
+                                             uint8_t red,
+                                             uint8_t green,
+                                             uint8_t blue,
+                                             unsigned int fontSize) :
+    statusBar_(statusBar),
+    slice_(slice),
+    x1_(x),
+    y1_(y),
+    x2_(x),
+    y2_(y),
+    fontSize_(fontSize)
+  {
+    color_[0] = red;
+    color_[1] = green;
+    color_[2] = blue;
+  }
+    
+
+  void CircleMeasureTracker::Render(CairoContext& context,
+                                    double zoom)
+  {
+    double x = (x1_ + x2_) / 2.0;
+    double y = (y1_ + y2_) / 2.0;
+
+    Vector tmp;
+    GeometryToolbox::AssignVector(tmp, x2_ - x1_, y2_ - y1_);
+    double r = boost::numeric::ublas::norm_2(tmp) / 2.0;
+
+    context.SetSourceColor(color_[0], color_[1], color_[2]);
+
+    cairo_t* cr = context.GetObject();
+    cairo_save(cr);
+    cairo_set_line_width(cr, 2.0 / zoom);
+    cairo_translate(cr, x, y);
+    cairo_arc(cr, 0, 0, r, 0, 2 * M_PI);
+    cairo_stroke_preserve(cr);
+    cairo_stroke(cr);
+    cairo_restore(cr);
+
+    if (fontSize_ != 0)
+    {
+      cairo_move_to(cr, x, y);
+      CairoFont font("sans-serif", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
+      font.Draw(context, FormatRadius(), static_cast<double>(fontSize_) / zoom);
+    }
+  }
+    
+
+  double CircleMeasureTracker::GetRadius() const  // In millimeters
+  {
+    Vector a = slice_.MapSliceToWorldCoordinates(x1_, y1_);
+    Vector b = slice_.MapSliceToWorldCoordinates(x2_, y2_);
+    return boost::numeric::ublas::norm_2(b - a) / 2.0;
+  }
+
+
+  std::string CircleMeasureTracker::FormatRadius() const
+  {
+    char buf[64];
+    sprintf(buf, "%0.01f cm", GetRadius() / 10.0);
+    return buf;
+  }
+
+  void CircleMeasureTracker::MouseMove(double x,
+                                       double y)
+  {
+    x2_ = x;
+    y2_ = y;
+
+    if (statusBar_ != NULL)
+    {
+      statusBar_->SetMessage("Circle radius: " + FormatRadius());
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Layers/CircleMeasureTracker.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,79 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "../Widgets/IWorldSceneMouseTracker.h"
+
+#include "../Viewport/IStatusBar.h"
+#include "../Toolbox/SliceGeometry.h"
+
+namespace OrthancStone
+{
+  class CircleMeasureTracker : public IWorldSceneMouseTracker
+  {
+  private:
+    IStatusBar*    statusBar_;
+    SliceGeometry  slice_;
+    double         x1_;
+    double         y1_;
+    double         x2_;
+    double         y2_;
+    uint8_t        color_[3];
+    unsigned int   fontSize_;
+
+  public:
+    CircleMeasureTracker(IStatusBar* statusBar,
+                         const SliceGeometry& slice,
+                         double x, 
+                         double y,
+                         uint8_t red,
+                         uint8_t green,
+                         uint8_t blue,
+                         unsigned int fontSize);
+    
+    virtual void Render(CairoContext& context,
+                        double zoom);
+    
+    double GetRadius() const;  // In millimeters
+
+    std::string FormatRadius() const;
+
+    virtual void MouseUp()
+    {
+      // Possibly create a new landmark "volume" with the circle in subclasses
+    }
+
+    virtual void MouseMove(double x,
+                           double y);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Layers/ColorFrameRenderer.cpp	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,70 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "ColorFrameRenderer.h"
+
+#include "../Orthanc/Core/OrthancException.h"
+#include "../Orthanc/Core/Images/ImageProcessing.h"
+
+namespace OrthancStone
+{
+  CairoSurface* ColorFrameRenderer::GenerateDisplay(const RenderStyle& style)
+  {
+    std::auto_ptr<CairoSurface> display(new CairoSurface(frame_->GetWidth(), frame_->GetHeight()));
+
+    Orthanc::ImageAccessor target = display->GetAccessor();
+    Orthanc::ImageProcessing::Convert(target, *frame_);
+
+    return display.release();
+  }
+
+
+  ColorFrameRenderer::ColorFrameRenderer(Orthanc::ImageAccessor* frame,
+                                         const SliceGeometry& viewportSlice,
+                                         const SliceGeometry& frameSlice,
+                                         double pixelSpacingX,
+                                         double pixelSpacingY,
+                                         bool isFullQuality) :
+    FrameRenderer(viewportSlice, frameSlice, pixelSpacingX, pixelSpacingY, isFullQuality),
+    frame_(frame)
+  {
+    if (frame == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    if (frame_->GetFormat() != Orthanc::PixelFormat_RGB24)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Layers/ColorFrameRenderer.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,55 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "FrameRenderer.h"
+
+namespace OrthancStone
+{
+  class ColorFrameRenderer : public FrameRenderer
+  {
+  private:
+    std::auto_ptr<Orthanc::ImageAccessor>   frame_;  // In RGB24
+
+  protected:
+    virtual CairoSurface* GenerateDisplay(const RenderStyle& style);
+
+  public:
+    ColorFrameRenderer(Orthanc::ImageAccessor* frame,    // Takes ownership
+                       const SliceGeometry& viewportSlice,
+                       const SliceGeometry& frameSlice,
+                       double pixelSpacingX,
+                       double pixelSpacingY,
+                       bool isFullQuality);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Layers/DicomStructureSetRendererFactory.cpp	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,97 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "DicomStructureSetRendererFactory.h"
+
+#include "../Orthanc/Core/OrthancException.h"
+
+namespace OrthancStone
+{
+  class DicomStructureSetRendererFactory::Renderer : public ILayerRenderer
+  {
+  private:
+    const DicomStructureSet&  structureSet_;
+    SliceGeometry             slice_;
+    bool                      visible_;
+
+  public:
+    Renderer(const DicomStructureSet& structureSet,
+             const SliceGeometry& slice) :
+      structureSet_(structureSet),
+      slice_(slice),
+      visible_(true)
+    {
+    }
+
+    virtual bool RenderLayer(CairoContext& context,
+                             const ViewportGeometry& view)
+    {
+      if (visible_)
+      {
+        cairo_set_line_width(context.GetObject(), 3.0f / view.GetZoom());
+        structureSet_.Render(context, slice_);
+      }
+
+      return true;
+    }
+
+    virtual void SetLayerStyle(const RenderStyle& style)
+    {
+      visible_ = style.visible_;
+    }
+
+    virtual bool IsFullQuality()
+    {
+      return true;
+    }
+  };
+
+
+  ILayerRenderer* DicomStructureSetRendererFactory::CreateLayerRenderer(const SliceGeometry& displaySlice)
+  {
+    bool isOpposite;
+    if (GeometryToolbox::IsParallelOrOpposite(isOpposite, displaySlice.GetNormal(), structureSet_.GetNormal()))
+    {
+      return new Renderer(structureSet_, displaySlice);
+    }
+    else
+    {
+      return NULL;
+    }
+  }
+
+
+  ISliceableVolume& DicomStructureSetRendererFactory::GetSourceVolume() const
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Layers/DicomStructureSetRendererFactory.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,71 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "../Toolbox/DicomStructureSet.h"
+#include "ILayerRendererFactory.h"
+
+namespace OrthancStone
+{
+  class DicomStructureSetRendererFactory : public ILayerRendererFactory
+  {
+  private:
+    class Renderer;
+
+    const DicomStructureSet&  structureSet_;
+
+  public:
+    DicomStructureSetRendererFactory(const DicomStructureSet&  structureSet) :
+      structureSet_(structureSet)
+    {
+    }
+
+    virtual bool GetExtent(double& x1,
+                           double& y1,
+                           double& x2,
+                           double& y2,
+                           const SliceGeometry& displaySlice)
+    {
+      return false;
+    }
+
+    virtual ILayerRenderer* CreateLayerRenderer(const SliceGeometry& displaySlice);
+
+    virtual bool HasSourceVolume() const
+    {
+      return false;
+    }
+
+    virtual ISliceableVolume& GetSourceVolume() const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Layers/FrameRenderer.cpp	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,249 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "FrameRenderer.h"
+
+#include "GrayscaleFrameRenderer.h"
+#include "ColorFrameRenderer.h"
+
+#include "../Orthanc/Core/OrthancException.h"
+
+namespace OrthancStone
+{
+  static bool ComputePixelTransform(cairo_matrix_t& target,
+                                    const SliceGeometry& viewportSlice,
+                                    const SliceGeometry& frameSlice,
+                                    double pixelSpacingX,
+                                    double pixelSpacingY)
+  {
+    bool isOpposite;
+    if (!GeometryToolbox::IsParallelOrOpposite(isOpposite, viewportSlice.GetNormal(), frameSlice.GetNormal()))
+    {
+      return false;
+    }
+    else
+    {
+      double x0, y0, x1, y1, x2, y2;
+      viewportSlice.ProjectPoint(x0, y0, frameSlice.GetOrigin() 
+                                 - 0.5 * pixelSpacingX * frameSlice.GetAxisX()
+                                 - 0.5 * pixelSpacingY * frameSlice.GetAxisY());
+      viewportSlice.ProjectPoint(x1, y1, frameSlice.GetOrigin() 
+                                 + 0.5 * pixelSpacingX * frameSlice.GetAxisX()
+                                 - 0.5 * pixelSpacingY * frameSlice.GetAxisY());
+      viewportSlice.ProjectPoint(x2, y2, frameSlice.GetOrigin() 
+                                 - 0.5 * pixelSpacingX * frameSlice.GetAxisX()
+                                 + 0.5 * pixelSpacingY * frameSlice.GetAxisY());
+
+      /**
+       * Now we solve the system of linear equations Ax + b = x', given:
+       *   A [0 ; 0] + b = [x0 ; y0]
+       *   A [1 ; 0] + b = [x1 ; y1]
+       *   A [0 ; 1] + b = [x2 ; y2]
+       * <=>
+       *   b = [x0 ; y0]
+       *   A [1 ; 0] = [x1 ; y1] - b = [x1 - x0 ; y1 - y0]
+       *   A [0 ; 1] = [x2 ; y2] - b = [x2 - x0 ; y2 - y0]
+       * <=>
+       *   b = [x0 ; y0]
+       *   [a11 ; a21] = [x1 - x0 ; y1 - y0]
+       *   [a12 ; a22] = [x2 - x0 ; y2 - y0]
+       **/
+
+      cairo_matrix_init(&target, x1 - x0, y1 - y0, x2 - x0, y2 - y0, x0, y0);
+
+      return true;
+    }
+  }
+
+
+  FrameRenderer::FrameRenderer(const SliceGeometry& viewportSlice,
+                               const SliceGeometry& frameSlice,
+                               double pixelSpacingX,
+                               double pixelSpacingY,
+                               bool isFullQuality) :
+    viewportSlice_(viewportSlice),
+    frameSlice_(frameSlice),
+    pixelSpacingX_(pixelSpacingX),
+    pixelSpacingY_(pixelSpacingY),
+    isFullQuality_(isFullQuality)
+  {
+  }
+
+
+  bool FrameRenderer::ComputeFrameExtent(double& x1,
+                                         double& y1,
+                                         double& x2,
+                                         double& y2,
+                                         const SliceGeometry& viewportSlice,
+                                         const SliceGeometry& frameSlice,
+                                         unsigned int frameWidth,
+                                         unsigned int frameHeight,
+                                         double pixelSpacingX,
+                                         double pixelSpacingY)
+  {
+    bool isOpposite;
+    if (!GeometryToolbox::IsParallelOrOpposite(isOpposite, viewportSlice.GetNormal(), frameSlice.GetNormal()))
+    {
+      return false;
+    }
+    else
+    {
+      cairo_matrix_t transform;
+      if (!ComputePixelTransform(transform, viewportSlice, frameSlice, pixelSpacingX, pixelSpacingY))
+      {
+        return true;
+      }
+      
+      x1 = 0;
+      y1 = 0;
+      cairo_matrix_transform_point(&transform, &x1, &y1);
+      
+      x2 = frameWidth;
+      y2 = frameHeight;
+      cairo_matrix_transform_point(&transform, &x2, &y2);
+      
+      if (x1 > x2)
+      {
+        std::swap(x1, x2);
+      }
+
+      if (y1 > y2)
+      {
+        std::swap(y1, y2);
+      }
+
+      return true;
+    }
+  }
+
+
+  bool FrameRenderer::RenderLayer(CairoContext& context,
+                                  const ViewportGeometry& view)
+  {
+    if (!style_.visible_)
+    {
+      return true;
+    }
+
+    if (display_.get() == NULL)
+    {
+      if (!ComputePixelTransform(transform_, viewportSlice_, frameSlice_, pixelSpacingX_, pixelSpacingY_))
+      {
+        return true;
+      }
+
+      display_.reset(GenerateDisplay(style_));
+    }
+
+    assert(display_.get() != NULL);
+
+    cairo_t *cr = context.GetObject();
+
+    cairo_save(cr);
+
+    cairo_transform(cr, &transform_);
+    //cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
+    cairo_set_source_surface(cr, display_->GetObject(), 0, 0);
+
+    switch (style_.interpolation_)
+    {
+      case ImageInterpolation_Nearest:
+        cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_NEAREST);
+        break;
+
+      case ImageInterpolation_Linear:
+        cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_BILINEAR);
+        break;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    cairo_paint_with_alpha(cr, style_.alpha_);
+
+    if (style_.drawGrid_)
+    {
+      context.SetSourceColor(style_.drawColor_);
+      cairo_set_line_width(cr, 0.5 / view.GetZoom());
+
+      for (unsigned int x = 0; x <= display_->GetWidth(); x++)
+      {
+        cairo_move_to(cr, x, 0);
+        cairo_line_to(cr, x, display_->GetHeight());
+      }
+
+      for (unsigned int y = 0; y <= display_->GetHeight(); y++)
+      {
+        cairo_move_to(cr, 0, y);
+        cairo_line_to(cr, display_->GetWidth(), y);
+      }
+
+      cairo_stroke(cr);
+    }
+
+    cairo_restore(cr);
+
+    return true;
+  }
+
+
+  void FrameRenderer::SetLayerStyle(const RenderStyle& style)
+  {
+    style_ = style;
+    display_.reset(NULL);
+  }
+
+
+  ILayerRenderer* FrameRenderer::CreateRenderer(Orthanc::ImageAccessor* frame,
+                                                const SliceGeometry& viewportSlice,
+                                                const SliceGeometry& frameSlice,
+                                                const DicomDataset& dicom,
+                                                double pixelSpacingX,
+                                                double pixelSpacingY,
+                                                bool isFullQuality)
+  {  
+    std::auto_ptr<Orthanc::ImageAccessor> protect(frame);
+
+    if (frame->GetFormat() == Orthanc::PixelFormat_RGB24)
+    {
+      return new ColorFrameRenderer(protect.release(), viewportSlice, frameSlice, 
+                                    pixelSpacingX, pixelSpacingY, isFullQuality);
+    }
+    else
+    {
+      DicomFrameConverter converter;
+      converter.ReadParameters(dicom);
+      return new GrayscaleFrameRenderer(protect.release(), converter, viewportSlice, frameSlice, 
+                                        pixelSpacingX, pixelSpacingY, isFullQuality);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Layers/FrameRenderer.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,93 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "ILayerRenderer.h"
+
+#include "../Toolbox/SliceGeometry.h"
+
+namespace OrthancStone
+{
+  class FrameRenderer : public ILayerRenderer
+  {
+  private:
+    SliceGeometry                 viewportSlice_;
+    SliceGeometry                 frameSlice_;
+    double                        pixelSpacingX_;
+    double                        pixelSpacingY_;
+    RenderStyle                   style_;
+    bool                          isFullQuality_;
+
+    std::auto_ptr<CairoSurface>   display_;
+    cairo_matrix_t                transform_;
+
+  protected:
+    virtual CairoSurface* GenerateDisplay(const RenderStyle& style) = 0;
+
+  public:
+    FrameRenderer(const SliceGeometry& viewportSlice,
+                  const SliceGeometry& frameSlice,
+                  double pixelSpacingX,
+                  double pixelSpacingY,
+                  bool isFullQuality);
+
+    static bool ComputeFrameExtent(double& x1,
+                                   double& y1,
+                                   double& x2,
+                                   double& y2,
+                                   const SliceGeometry& viewportSlice,
+                                   const SliceGeometry& frameSlice,
+                                   unsigned int frameWidth,
+                                   unsigned int frameHeight,
+                                   double pixelSpacingX,
+                                   double pixelSpacingY);
+
+    virtual bool RenderLayer(CairoContext& context,
+                             const ViewportGeometry& view);
+
+    virtual void SetLayerStyle(const RenderStyle& style);
+
+    virtual bool IsFullQuality() 
+    {
+      return isFullQuality_;
+    }
+
+    static ILayerRenderer* CreateRenderer(Orthanc::ImageAccessor* frame,
+                                          const SliceGeometry& viewportSlice,
+                                          const SliceGeometry& frameSlice,
+                                          const DicomDataset& dicom,
+                                          double pixelSpacingX,
+                                          double pixelSpacingY,
+                                          bool isFullQuality);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Layers/GrayscaleFrameRenderer.cpp	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,144 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "GrayscaleFrameRenderer.h"
+
+#include "../Orthanc/Core/OrthancException.h"
+
+namespace OrthancStone
+{
+  CairoSurface* GrayscaleFrameRenderer::GenerateDisplay(const RenderStyle& style)
+  {
+    std::auto_ptr<CairoSurface> result;
+
+    float windowCenter, windowWidth;
+    style.ComputeWindowing(windowCenter, windowWidth,
+                           defaultWindowCenter_, defaultWindowWidth_);
+
+    float x0 = windowCenter - windowWidth / 2.0f;
+    float x1 = windowCenter + windowWidth / 2.0f;
+
+    //LOG(INFO) << "Window: " << x0 << " => " << x1;
+
+    result.reset(new CairoSurface(frame_->GetWidth(), frame_->GetHeight()));
+
+    const uint8_t* lut = NULL;
+    if (style.applyLut_)
+    {
+      if (Orthanc::EmbeddedResources::GetFileResourceSize(style.lut_) != 3 * 256)
+      {
+        // Invalid colormap
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+
+      lut = reinterpret_cast<const uint8_t*>(Orthanc::EmbeddedResources::GetFileResourceBuffer(style.lut_));
+    }
+      
+    Orthanc::ImageAccessor target = result->GetAccessor();
+    for (unsigned int y = 0; y < target.GetHeight(); y++)
+    {
+      const float* p = reinterpret_cast<const float*>(frame_->GetConstRow(y));
+      uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y));
+
+      for (unsigned int x = 0; x < target.GetWidth(); x++, p++, q += 4)
+      {
+        uint8_t v = 0;
+        if (windowWidth >= 0.001f)  // Avoid division by zero
+        {
+          if (*p >= x1)
+          {
+            v = 255;
+          }
+          else if (*p <= x0)
+          {
+            v = 0;
+          }
+          else
+          {
+            // https://en.wikipedia.org/wiki/Linear_interpolation
+            v = static_cast<uint8_t>(255.0f * (*p - x0) / (x1 - x0));
+          }
+
+          if (style.reverse_)
+          {
+            v = 255 - v;
+          }
+        }
+
+        if (style.applyLut_)
+        {
+          assert(lut != NULL);
+          q[3] = 255;
+          q[2] = lut[3 * v];
+          q[1] = lut[3 * v + 1];
+          q[0] = lut[3 * v + 2];
+        }
+        else
+        {
+          q[3] = 255;
+          q[2] = v;
+          q[1] = v;
+          q[0] = v;
+        }
+      }
+    }
+
+    return result.release();
+  }
+
+
+  GrayscaleFrameRenderer::GrayscaleFrameRenderer(Orthanc::ImageAccessor* frame,
+                                                 const DicomFrameConverter& converter,
+                                                 const SliceGeometry& viewportSlice,
+                                                 const SliceGeometry& frameSlice,
+                                                 double pixelSpacingX,
+                                                 double pixelSpacingY,
+                                                 bool isFullQuality) :
+    FrameRenderer(viewportSlice, frameSlice, pixelSpacingX, pixelSpacingY, isFullQuality),
+    frame_(frame),
+    defaultWindowCenter_(converter.GetDefaultWindowCenter()),
+    defaultWindowWidth_(converter.GetDefaultWindowWidth())
+  {
+    if (frame == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    converter.ConvertFrame(frame_);
+    assert(frame_.get() != NULL);
+
+    if (frame_->GetFormat() != Orthanc::PixelFormat_Float32)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Layers/GrayscaleFrameRenderer.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,59 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "FrameRenderer.h"
+#include "../Toolbox/DicomFrameConverter.h"
+
+namespace OrthancStone
+{
+  class GrayscaleFrameRenderer : public FrameRenderer
+  {
+  private:
+    std::auto_ptr<Orthanc::ImageAccessor>   frame_;  // In Float32
+    float                                   defaultWindowCenter_;
+    float                                   defaultWindowWidth_;
+
+  protected:
+    virtual CairoSurface* GenerateDisplay(const RenderStyle& style);
+
+  public:
+    GrayscaleFrameRenderer(Orthanc::ImageAccessor* frame,    // Takes ownership
+                           const DicomFrameConverter& converter,
+                           const SliceGeometry& viewportSlice,
+                           const SliceGeometry& frameSlice,
+                           double pixelSpacingX,
+                           double pixelSpacingY,
+                           bool isFullQuality);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Layers/ILayerRenderer.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,52 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "../Viewport/CairoContext.h"
+#include "../Toolbox/IThreadSafety.h"
+#include "../Toolbox/ViewportGeometry.h"
+#include "RenderStyle.h"
+
+namespace OrthancStone
+{
+  class ILayerRenderer : public IThreadUnsafe
+  {
+  public:
+    virtual bool RenderLayer(CairoContext& context,
+                             const ViewportGeometry& view) = 0;
+
+    virtual void SetLayerStyle(const RenderStyle& style) = 0;
+
+    virtual bool IsFullQuality() = 0;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Layers/ILayerRendererFactory.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,63 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "ILayerRenderer.h"
+#include "../Toolbox/SliceGeometry.h"
+#include "../Volumes/ISliceableVolume.h"
+
+namespace OrthancStone
+{
+  class ILayerRendererFactory : public IThreadUnsafe
+  {
+  public:
+    virtual ~ILayerRendererFactory()
+    {
+    }
+
+    virtual bool GetExtent(double& x1,
+                           double& y1,
+                           double& x2,
+                           double& y2,
+                           const SliceGeometry& displaySlice) = 0;
+
+    // This operation can be slow, as it might imply the download of a
+    // slice from Orthanc. The result might be NULL, if the slice is
+    // not compatible with the underlying source volume.
+    virtual ILayerRenderer* CreateLayerRenderer(const SliceGeometry& displaySlice) = 0;
+
+    virtual bool HasSourceVolume() const = 0;
+
+    virtual ISliceableVolume& GetSourceVolume() const = 0;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Layers/LineLayerRenderer.cpp	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,76 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "LineLayerRenderer.h"
+
+namespace OrthancStone
+{
+  LineLayerRenderer::LineLayerRenderer(double x1,
+                                       double y1,
+                                       double x2,
+                                       double y2) : 
+    x1_(x1),
+    y1_(y1),
+    x2_(x2),
+    y2_(y2)
+  {
+    RenderStyle style;
+    SetLayerStyle(style);
+  }
+
+
+  bool LineLayerRenderer::RenderLayer(CairoContext& context,
+                                      const ViewportGeometry& view)
+  {
+    if (visible_)
+    {
+      context.SetSourceColor(color_);
+
+      cairo_t *cr = context.GetObject();
+      cairo_set_line_width(cr, 1.0 / view.GetZoom());
+      cairo_move_to(cr, x1_, y1_);
+      cairo_line_to(cr, x2_, y2_);
+      cairo_stroke(cr);
+    }
+
+    return true;
+  }
+
+
+  void LineLayerRenderer::SetLayerStyle(const RenderStyle& style)
+  {
+    visible_ = style.visible_;
+    color_[0] = style.drawColor_[0];
+    color_[1] = style.drawColor_[1];
+    color_[2] = style.drawColor_[2];
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Layers/LineLayerRenderer.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,65 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "ILayerRenderer.h"
+
+namespace OrthancStone
+{
+  class LineLayerRenderer : public ILayerRenderer
+  {
+  private:
+    double   x1_;
+    double   y1_;
+    double   x2_;
+    double   y2_;
+    bool     visible_;
+    uint8_t  color_[3];
+
+  public:
+    LineLayerRenderer(double x1,
+                      double y1,
+                      double x2,
+                      double y2);
+
+    virtual bool RenderLayer(CairoContext& context,
+                             const ViewportGeometry& view);
+
+    virtual void SetLayerStyle(const RenderStyle& style);
+
+    virtual bool IsFullQuality()
+    {
+      return true;
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Layers/LineMeasureTracker.cpp	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,109 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "LineMeasureTracker.h"
+
+#include "../Viewport/CairoFont.h"
+
+#include <stdio.h>
+
+namespace OrthancStone
+{
+  LineMeasureTracker::LineMeasureTracker(IStatusBar* statusBar,
+                                         const SliceGeometry& slice,
+                                         double x, 
+                                         double y,
+                                         uint8_t red,
+                                         uint8_t green,
+                                         uint8_t blue,
+                                         unsigned int fontSize) :
+    statusBar_(statusBar),
+    slice_(slice),
+    x1_(x),
+    y1_(y),
+    x2_(x),
+    y2_(y),
+    fontSize_(fontSize)
+  {
+    color_[0] = red;
+    color_[1] = green;
+    color_[2] = blue;
+  }
+    
+
+  void LineMeasureTracker::Render(CairoContext& context,
+                                  double zoom)
+  {
+    context.SetSourceColor(color_[0], color_[1], color_[2]);
+
+    cairo_t* cr = context.GetObject();
+    cairo_set_line_width(cr, 2.0 / zoom);
+    cairo_move_to(cr, x1_, y1_);
+    cairo_line_to(cr, x2_, y2_);
+    cairo_stroke(cr);
+
+    if (fontSize_ != 0)
+    {
+      cairo_move_to(cr, x2_, y2_ - static_cast<double>(fontSize_) / zoom);
+      CairoFont font("sans-serif", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
+      font.Draw(context, FormatLength(), static_cast<double>(fontSize_) / zoom);
+    }
+  }
+    
+
+  double LineMeasureTracker::GetLength() const  // In millimeters
+  {
+    Vector a = slice_.MapSliceToWorldCoordinates(x1_, y1_);
+    Vector b = slice_.MapSliceToWorldCoordinates(x2_, y2_);
+    return boost::numeric::ublas::norm_2(b - a);
+  }
+
+
+  std::string LineMeasureTracker::FormatLength() const
+  {
+    char buf[64];
+    sprintf(buf, "%0.01f cm", GetLength() / 10.0);
+    return buf;
+  }
+
+  void LineMeasureTracker::MouseMove(double x,
+                                     double y)
+  {
+    x2_ = x;
+    y2_ = y;
+
+    if (statusBar_ != NULL)
+    {
+      statusBar_->SetMessage("Line length: " + FormatLength());
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Layers/LineMeasureTracker.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,79 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "../Widgets/IWorldSceneMouseTracker.h"
+
+#include "../Viewport/IStatusBar.h"
+#include "../Toolbox/SliceGeometry.h"
+
+namespace OrthancStone
+{
+  class LineMeasureTracker : public IWorldSceneMouseTracker
+  {
+  private:
+    IStatusBar*    statusBar_;
+    SliceGeometry  slice_;
+    double         x1_;
+    double         y1_;
+    double         x2_;
+    double         y2_;
+    uint8_t        color_[3];
+    unsigned int   fontSize_;
+
+  public:
+    LineMeasureTracker(IStatusBar* statusBar,
+                       const SliceGeometry& slice,
+                       double x, 
+                       double y,
+                       uint8_t red,
+                       uint8_t green,
+                       uint8_t blue,
+                       unsigned int fontSize);
+    
+    virtual void Render(CairoContext& context,
+                        double zoom);
+    
+    double GetLength() const;  // In millimeters
+
+    std::string FormatLength() const;
+
+    virtual void MouseUp()
+    {
+      // Possibly create a new landmark "volume" with the line in subclasses
+    }
+
+    virtual void MouseMove(double x,
+                           double y);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Layers/RenderStyle.cpp	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,99 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "RenderStyle.h"
+
+#include "../Orthanc/Core/OrthancException.h"
+
+namespace OrthancStone
+{
+  RenderStyle::RenderStyle()
+  {
+    visible_ = true;
+    reverse_ = false;
+    windowing_ = ImageWindowing_Default;
+    alpha_ = 1;
+    applyLut_ = false;
+    lut_ = Orthanc::EmbeddedResources::COLORMAP_HOT;
+    drawGrid_ = false;
+    drawColor_[0] = 255;
+    drawColor_[1] = 255;
+    drawColor_[2] = 255;
+    customWindowCenter_ = 128;
+    customWindowWidth_ = 256;
+    interpolation_ = ImageInterpolation_Nearest;
+    fontSize_ = 14;
+  }
+
+
+  void RenderStyle::ComputeWindowing(float& targetCenter,
+                                     float& targetWidth,
+                                     float defaultCenter,
+                                     float defaultWidth) const
+  {
+    switch (windowing_)
+    {
+      case ImageWindowing_Default:
+        targetCenter = defaultCenter;
+        targetWidth = defaultWidth;
+        break;
+
+      case ImageWindowing_Bone:
+        targetCenter = 300;
+        targetWidth = 2000;
+        break;
+
+      case ImageWindowing_Lung:
+        targetCenter = -600;
+        targetWidth = 1600;
+        break;
+
+      case ImageWindowing_Custom:
+        targetCenter = customWindowCenter_;
+        targetWidth = customWindowWidth_;
+        break;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+  
+  void RenderStyle::SetColor(uint8_t red,
+                             uint8_t green,
+                             uint8_t blue)
+  {
+    drawColor_[0] = red;
+    drawColor_[1] = green;
+    drawColor_[2] = blue;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Layers/RenderStyle.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,69 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 <EmbeddedResources.h>
+
+#include <stdint.h>
+
+namespace OrthancStone
+{
+  struct RenderStyle
+  {
+    bool visible_;
+    bool reverse_;
+    ImageWindowing windowing_;
+    float alpha_;   // In [0,1]
+    bool applyLut_;
+    Orthanc::EmbeddedResources::FileResourceId  lut_;
+    bool drawGrid_;
+    uint8_t drawColor_[3];
+    float customWindowCenter_;
+    float customWindowWidth_;
+    ImageInterpolation interpolation_;
+    unsigned int fontSize_;
+    
+    RenderStyle();
+
+    void ComputeWindowing(float& targetCenter,
+                          float& targetWidth,
+                          float defaultCenter,
+                          float defaultWidth) const;
+
+    void SetColor(uint8_t red,
+                  uint8_t green,
+                  uint8_t blue);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Layers/SeriesFrameRendererFactory.cpp	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,176 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "SeriesFrameRendererFactory.h"
+
+#include "FrameRenderer.h"
+#include "../Orthanc/Core/OrthancException.h"
+
+namespace OrthancStone
+{
+  void SeriesFrameRendererFactory::ReadCurrentFrameDataset(size_t frame)
+  {
+    if (currentDataset_.get() != NULL &&
+        (fast_ || currentFrame_ == frame))
+    {
+      // The frame has not changed since the previous call, no need to
+      // update the DICOM dataset
+      return; 
+    }
+      
+    currentDataset_.reset(loader_->DownloadDicom(frame));
+    currentFrame_ = frame;
+
+    if (currentDataset_.get() == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+  }
+
+
+  void SeriesFrameRendererFactory::GetCurrentPixelSpacing(double& spacingX,
+                                                          double& spacingY) const
+  {
+    if (currentDataset_.get() == NULL)
+    {
+      // There was no previous call "ReadCurrentFrameDataset()"
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+    
+    currentDataset_->GetPixelSpacing(spacingX, spacingY);
+  }
+
+
+  double SeriesFrameRendererFactory::GetCurrentSliceThickness() const
+  {
+    if (currentDataset_.get() == NULL)
+    {
+      // There was no previous call "ReadCurrentFrameDataset()"
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+      
+    if (currentDataset_->HasTag(DICOM_TAG_SLICE_THICKNESS))
+    {
+      return currentDataset_->GetFloatValue(DICOM_TAG_SLICE_THICKNESS);
+    }
+    else
+    {
+      // Some arbitrary large slice thickness
+      return std::numeric_limits<double>::infinity();
+    }
+  }
+
+
+  SeriesFrameRendererFactory::SeriesFrameRendererFactory(ISeriesLoader* loader,   // Takes ownership
+                                                         bool fast) :
+    loader_(loader),
+    currentFrame_(0),
+    fast_(fast)
+  {
+    if (loader == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+  }
+
+
+  bool SeriesFrameRendererFactory::GetExtent(double& x1,
+                                             double& y1,
+                                             double& x2,
+                                             double& y2,
+                                             const SliceGeometry& viewportSlice)
+  {
+    if (currentDataset_.get() == NULL)
+    {
+      // There has been no previous call to
+      // "CreateLayerRenderer". Read some arbitrary DICOM frame, the
+      // one at the middle of the series.
+      unsigned int depth = loader_->GetGeometry().GetSliceCount();
+      ReadCurrentFrameDataset(depth / 2);
+    }
+
+    double spacingX, spacingY;
+    GetCurrentPixelSpacing(spacingX, spacingY);
+
+    return FrameRenderer::ComputeFrameExtent(x1, y1, x2, y2, 
+                                             viewportSlice, 
+                                             loader_->GetGeometry().GetSlice(0), 
+                                             loader_->GetWidth(), 
+                                             loader_->GetHeight(),
+                                             spacingX, spacingY);
+  }
+
+
+  ILayerRenderer* SeriesFrameRendererFactory::CreateLayerRenderer(const SliceGeometry& viewportSlice)
+  {
+    size_t closest;
+    double distance;
+
+    bool isOpposite;
+    if (!GeometryToolbox::IsParallelOrOpposite(isOpposite, loader_->GetGeometry().GetNormal(), viewportSlice.GetNormal()) ||
+        !loader_->GetGeometry().ComputeClosestSlice(closest, distance, viewportSlice.GetOrigin()))
+    {
+      // Unable to compute the slice in the series that is the
+      // closest to the slice displayed by the viewport
+      return NULL;
+    }
+
+    ReadCurrentFrameDataset(closest);
+    assert(currentDataset_.get() != NULL);
+        
+    double spacingX, spacingY;
+    GetCurrentPixelSpacing(spacingX, spacingY);
+
+    if (distance <= GetCurrentSliceThickness() / 2.0)
+    {
+      SliceGeometry frameSlice(*currentDataset_);
+      return FrameRenderer::CreateRenderer(loader_->DownloadFrame(closest), 
+                                           viewportSlice, 
+                                           frameSlice,
+                                           *currentDataset_, 
+                                           spacingX, spacingY,
+                                           true);
+    }
+    else
+    {
+      // The closest slice of the series is too far away from the
+      // slice displayed by the viewport
+      return NULL;
+    }
+  }
+
+
+  ISliceableVolume& SeriesFrameRendererFactory::GetSourceVolume() const
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Layers/SeriesFrameRendererFactory.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,75 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "ILayerRendererFactory.h"
+
+#include "../Toolbox/ISeriesLoader.h"
+
+namespace OrthancStone
+{
+  class SeriesFrameRendererFactory : public ILayerRendererFactory
+  {
+  private:
+    std::auto_ptr<ISeriesLoader>  loader_;
+    std::auto_ptr<DicomDataset>   currentDataset_;
+    size_t                        currentFrame_;
+    bool                          fast_;
+
+    void ReadCurrentFrameDataset(size_t frame);
+
+    void GetCurrentPixelSpacing(double& spacingX,
+                                double& spacingY) const;
+
+    double GetCurrentSliceThickness() const;
+
+  public:
+    SeriesFrameRendererFactory(ISeriesLoader* loader,   // Takes ownership
+                               bool fast);
+
+    virtual bool GetExtent(double& x1,
+                           double& y1,
+                           double& x2,
+                           double& y2,
+                           const SliceGeometry& viewportSlice);
+
+    virtual ILayerRenderer* CreateLayerRenderer(const SliceGeometry& viewportSlice);
+
+    virtual bool HasSourceVolume() const
+    {
+      return false;
+    }
+
+    virtual ISliceableVolume& GetSourceVolume() const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Layers/SiblingSliceLocationFactory.cpp	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,159 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "SiblingSliceLocationFactory.h"
+
+#include "LineLayerRenderer.h"
+
+namespace OrthancStone
+{
+  SiblingSliceLocationFactory::SiblingSliceLocationFactory(LayeredSceneWidget& owner,
+                                                           LayeredSceneWidget& sibling) :
+    owner_(owner),
+    sibling_(sibling),
+    hasLayerIndex_(false)
+  {
+    style_.SetColor(0, 255, 0);
+    slice_ = sibling.GetSlice();
+    sibling_.Register(*this);
+  }
+
+
+  void SiblingSliceLocationFactory::NotifySliceChange(const LayeredSceneWidget& source,
+                                                      const SliceGeometry& slice)
+  {
+    if (&source == &sibling_)
+    {
+      SetSlice(slice);
+    }
+  }
+
+
+  void SiblingSliceLocationFactory::SetLayerIndex(size_t layerIndex)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+    hasLayerIndex_ = true;
+    layerIndex_ = layerIndex;
+  }
+
+
+  void SiblingSliceLocationFactory::SetStyle(const RenderStyle& style)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+    style_ = style;
+  }
+
+
+  RenderStyle SiblingSliceLocationFactory::GetRenderStyle()
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+    return style_;
+  }
+
+
+  void SiblingSliceLocationFactory::SetSlice(const SliceGeometry& slice)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+    slice_ = slice;
+
+    if (hasLayerIndex_)
+    {
+      owner_.InvalidateLayer(layerIndex_);
+    }
+  }
+
+
+  ILayerRenderer* SiblingSliceLocationFactory::CreateLayerRenderer(const SliceGeometry& viewportSlice)
+  {
+    Vector p, d;
+    RenderStyle style;
+
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+
+      style = style_;
+
+      // Compute the line of intersection between the two slices
+      if (!GeometryToolbox::IntersectTwoPlanes(p, d, 
+                                               slice_.GetOrigin(), slice_.GetNormal(),
+                                               viewportSlice.GetOrigin(), viewportSlice.GetNormal()))
+      {
+        // The two slice are parallel, don't try and display the intersection
+        return NULL;
+      }
+    }
+
+    double x1, y1, x2, y2;
+    viewportSlice.ProjectPoint(x1, y1, p);
+    viewportSlice.ProjectPoint(x2, y2, p + 1000.0 * d);
+
+    double sx1, sy1, sx2, sy2;
+    owner_.GetView().GetSceneExtent(sx1, sy1, sx2, sy2);
+        
+    if (GeometryToolbox::ClipLineToRectangle(x1, y1, x2, y2, 
+                                             x1, y1, x2, y2,
+                                             sx1, sy1, sx2, sy2))
+    {
+      std::auto_ptr<ILayerRenderer> layer(new LineLayerRenderer(x1, y1, x2, y2));
+      layer->SetLayerStyle(style);
+      return layer.release();
+    }
+    else
+    {
+      // Parallel slices
+      return NULL;
+    }
+  }
+
+
+  ISliceableVolume& SiblingSliceLocationFactory::GetSourceVolume() const
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+  }
+
+
+  void SiblingSliceLocationFactory::Configure(LayeredSceneWidget& a,
+                                              LayeredSceneWidget& b)
+  {
+    {
+      size_t layerIndex;
+      ILayerRendererFactory& factory = a.AddLayer(layerIndex, new SiblingSliceLocationFactory(a, b));
+      dynamic_cast<SiblingSliceLocationFactory&>(factory).SetLayerIndex(layerIndex);
+    }
+
+    {
+      size_t layerIndex;
+      ILayerRendererFactory& factory = b.AddLayer(layerIndex, new SiblingSliceLocationFactory(b, a));
+      dynamic_cast<SiblingSliceLocationFactory&>(factory).SetLayerIndex(layerIndex);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Layers/SiblingSliceLocationFactory.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,89 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "../Widgets/LayeredSceneWidget.h"
+
+namespace OrthancStone
+{
+  class SiblingSliceLocationFactory : 
+    public ILayerRendererFactory,
+    public LayeredSceneWidget::ISliceObserver
+  {
+  private:
+    boost::mutex          mutex_;
+    LayeredSceneWidget&   owner_;
+    LayeredSceneWidget&   sibling_;
+    SliceGeometry         slice_;
+    RenderStyle           style_;
+    bool                  hasLayerIndex_;
+    size_t                layerIndex_;
+
+      
+  public:
+    SiblingSliceLocationFactory(LayeredSceneWidget& owner,
+                                LayeredSceneWidget& sibling);
+
+    virtual void NotifySliceChange(const LayeredSceneWidget& source,
+                                   const SliceGeometry& slice);
+
+    void SetLayerIndex(size_t layerIndex);
+
+    void SetStyle(const RenderStyle& style);
+
+    RenderStyle GetRenderStyle();
+
+    void SetSlice(const SliceGeometry& slice);
+
+    virtual bool GetExtent(double& x1,
+                           double& y1,
+                           double& x2,
+                           double& y2,
+                           const SliceGeometry& viewportSlice)
+    {
+      return false;
+    }
+
+    virtual ILayerRenderer* CreateLayerRenderer(const SliceGeometry& viewportSlice);
+
+    virtual bool HasSourceVolume() const
+    {
+      return false;
+    }
+
+    virtual ISliceableVolume& GetSourceVolume() const;
+
+    static void Configure(LayeredSceneWidget& a,
+                          LayeredSceneWidget& b);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Layers/SingleFrameRendererFactory.cpp	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,102 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "SingleFrameRendererFactory.h"
+
+#include "FrameRenderer.h"
+#include "../Messaging/MessagingToolbox.h"
+#include "../Orthanc/Core/OrthancException.h"
+#include "../Toolbox/DicomFrameConverter.h"
+
+namespace OrthancStone
+{
+  SingleFrameRendererFactory::SingleFrameRendererFactory(IOrthancConnection& orthanc,
+                                                         const std::string& instanceId,
+                                                         unsigned int frame) :
+    orthanc_(orthanc),
+    dicom_(orthanc, instanceId),
+    instance_(instanceId),
+    frame_(frame)
+  {
+    DicomFrameConverter converter;
+    converter.ReadParameters(dicom_);
+    format_ = converter.GetExpectedPixelFormat();
+  }
+
+
+  SliceGeometry SingleFrameRendererFactory::GetSliceGeometry()
+  {
+    if (dicom_.HasTag(DICOM_TAG_IMAGE_POSITION_PATIENT) &&
+        dicom_.HasTag(DICOM_TAG_IMAGE_ORIENTATION_PATIENT))
+    {
+      return SliceGeometry(dicom_);
+    }
+    else
+    {
+      return SliceGeometry();
+    }
+  }
+
+
+  bool SingleFrameRendererFactory::GetExtent(double& x1,
+                                             double& y1,
+                                             double& x2,
+                                             double& y2,
+                                             const SliceGeometry& viewportSlice)
+  {
+    // Assume that PixelSpacingX == PixelSpacingY == 1
+
+    unsigned int width = dicom_.GetUnsignedIntegerValue(DICOM_TAG_COLUMNS);
+    unsigned int height = dicom_.GetUnsignedIntegerValue(DICOM_TAG_ROWS);
+
+    x1 = 0;
+    y1 = 0;
+    x2 = static_cast<double>(width);
+    y2 = static_cast<double>(height);
+
+    return true;
+  }
+
+
+  ILayerRenderer* SingleFrameRendererFactory::CreateLayerRenderer(const SliceGeometry& viewportSlice)
+  {
+    SliceGeometry frameSlice(dicom_);
+    return FrameRenderer::CreateRenderer(MessagingToolbox::DecodeFrame(orthanc_, instance_, frame_, format_), 
+                                         viewportSlice, frameSlice, dicom_, 1, 1, true);
+  }
+
+
+  ISliceableVolume& SingleFrameRendererFactory::GetSourceVolume() const
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Layers/SingleFrameRendererFactory.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,75 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "ILayerRendererFactory.h"
+
+namespace OrthancStone
+{
+  class SingleFrameRendererFactory : public ILayerRendererFactory
+  {
+  private:
+    IOrthancConnection&   orthanc_;
+    DicomDataset          dicom_;
+    std::string           instance_;
+    unsigned int          frame_;
+    Orthanc::PixelFormat  format_;
+
+  public:
+    SingleFrameRendererFactory(IOrthancConnection& orthanc,
+                               const std::string& instanceId,
+                               unsigned int frame);
+
+    const DicomDataset& GetDataset() const
+    {
+      return dicom_;
+    }
+
+    SliceGeometry GetSliceGeometry();
+
+    virtual bool GetExtent(double& x1,
+                           double& y1,
+                           double& x2,
+                           double& y2,
+                           const SliceGeometry& viewportSlice);
+
+    virtual ILayerRenderer* CreateLayerRenderer(const SliceGeometry& viewportSlice);
+
+    virtual bool HasSourceVolume() const
+    {
+      return false;
+    }
+
+    virtual ISliceableVolume& GetSourceVolume() const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Messaging/CurlOrthancConnection.cpp	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,83 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "CurlOrthancConnection.h"
+
+#if ORTHANC_ENABLE_CURL == 1
+
+#include "../Orthanc/Core/HttpClient.h"
+#include "../Orthanc/Core/OrthancException.h"
+
+namespace OrthancStone
+{
+  void CurlOrthancConnection::RestApiGet(std::string& result,
+                                         const std::string& uri)
+  {
+    /**
+     * TODO: This function sometimes crashes if compiled with
+     * MinGW-W64 (32 bit) in Release mode, on Windows XP. Introducing
+     * a mutex here fixes the issue. Not sure of what is the
+     * culprit. Maybe a bug in a old version of MinGW?
+     **/
+
+    Orthanc::HttpClient client(parameters_, uri);
+
+    // Don't follow 3xx HTTP (avoid redirections to "unsupported.png" in Orthanc)
+    client.SetRedirectionFollowed(false);  
+   
+    if (!client.Apply(result))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
+    }
+  }
+
+
+  void CurlOrthancConnection::RestApiPost(std::string& result,
+                                          const std::string& uri,
+                                          const std::string& body)
+  {
+    Orthanc::HttpClient client(parameters_, uri);
+
+    // Don't follow 3xx HTTP (avoid redirections to "unsupported.png" in Orthanc)
+    client.SetRedirectionFollowed(false);  
+
+    client.SetBody(body);
+    client.SetMethod(Orthanc::HttpMethod_Post);
+   
+    if (!client.Apply(result))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
+    }
+  }
+}
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Messaging/CurlOrthancConnection.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,68 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "IOrthancConnection.h"
+
+#if ORTHANC_ENABLE_CURL == 1
+
+#include "../Orthanc/Core/WebServiceParameters.h"
+
+namespace OrthancStone
+{
+  class CurlOrthancConnection : public IOrthancConnection
+  {
+  private:
+    Orthanc::WebServiceParameters  parameters_;
+
+  public:
+    CurlOrthancConnection(const Orthanc::WebServiceParameters& parameters) :
+      parameters_(parameters)
+    {
+    }
+
+    const Orthanc::WebServiceParameters& GetParameters() const
+    {
+      return parameters_;
+    }
+
+    virtual void RestApiGet(std::string& result,
+                            const std::string& uri);
+
+    virtual void RestApiPost(std::string& result,
+                             const std::string& uri,
+                             const std::string& body);
+  };
+}
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Messaging/IOrthancConnection.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,52 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "../Toolbox/IThreadSafety.h"
+
+#include <string>
+
+namespace OrthancStone
+{
+  // Derived classes must be thread-safe
+  class IOrthancConnection : public IThreadSafe
+  {
+  public:
+    virtual void RestApiGet(std::string& result,
+                            const std::string& uri) = 0;
+
+    virtual void RestApiPost(std::string& result,
+                             const std::string& uri,
+                             const std::string& body) = 0;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Messaging/MessagingToolbox.cpp	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,436 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "MessagingToolbox.h"
+
+#include "../Orthanc/Core/Images/Image.h"
+#include "../Orthanc/Core/Images/ImageProcessing.h"
+#include "../Orthanc/Core/Images/JpegReader.h"
+#include "../Orthanc/Core/Images/PngReader.h"
+#include "../Orthanc/Core/OrthancException.h"
+#include "../Orthanc/Core/Toolbox.h"
+#include "../Orthanc/Core/Logging.h"
+
+#include <boost/lexical_cast.hpp>
+#include <json/reader.h>
+
+#if defined(__native_client__)
+#  include <boost/math/special_functions/round.hpp>
+#else
+#  include <boost/date_time/microsec_time_clock.hpp>
+#endif
+
+namespace OrthancStone
+{
+  namespace MessagingToolbox
+  {
+#if defined(__native_client__)
+    static pp::Core* core_ = NULL;
+
+    void Timestamp::Initialize(pp::Core* core)
+    {
+      if (core == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+
+      core_ = core;
+    }
+
+    Timestamp::Timestamp()
+    {
+      if (core_ == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+
+      time_ = core_->GetTimeTicks();
+    }
+
+    int Timestamp::GetMillisecondsSince(const Timestamp& other)
+    {
+      double difference = time_ - other.time_;
+      return static_cast<int>(boost::math::iround(difference * 1000.0));
+    }
+#else
+    Timestamp::Timestamp()
+    {
+      time_ = boost::posix_time::microsec_clock::local_time();
+    }
+
+    int Timestamp::GetMillisecondsSince(const Timestamp& other)
+    {
+      boost::posix_time::time_duration difference = time_ - other.time_;
+      return static_cast<int>(difference.total_milliseconds());
+    }
+#endif
+
+    static bool ParseVersion(std::string& version,
+                             unsigned int& major,
+                             unsigned int& minor,
+                             unsigned int& patch,
+                             const Json::Value& info)
+    {
+      if (info.type() != Json::objectValue ||
+          !info.isMember("Version") ||
+          info["Version"].type() != Json::stringValue)
+      {
+        return false;
+      }
+
+      version = info["Version"].asString();
+      if (version == "mainline")
+      {
+        // Some arbitrary high values Orthanc versions will never reach ;)
+        major = 999;
+        minor = 999;
+        patch = 999;
+        return true;
+      }
+
+      std::vector<std::string> tokens;
+      Orthanc::Toolbox::TokenizeString(tokens, version, '.');
+      
+      if (tokens.size() != 2 &&
+          tokens.size() != 3)
+      {
+        return false;
+      }
+
+      int a, b, c;
+      try
+      {
+        a = boost::lexical_cast<int>(tokens[0]);
+        b = boost::lexical_cast<int>(tokens[1]);
+
+        if (tokens.size() == 3)
+        {
+          c = boost::lexical_cast<int>(tokens[2]);
+        }
+        else
+        {
+          c = 0;
+        }
+      }
+      catch (boost::bad_lexical_cast&)
+      {
+        return false;
+      }
+
+      if (a < 0 ||
+          b < 0 ||
+          c < 0)
+      {
+        return false;
+      }
+      else
+      {
+        major = static_cast<unsigned int>(a);
+        minor = static_cast<unsigned int>(b);
+        patch = static_cast<unsigned int>(c);
+        return true;
+      }         
+    }
+
+    void ParseJson(Json::Value& target,
+                   const std::string& source)
+    {
+      Json::Reader reader;
+      if (!reader.parse(source, target))
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+      }
+    }
+
+    void RestApiGet(Json::Value& target,
+                    IOrthancConnection& orthanc,
+                    const std::string& uri)
+    {
+      std::string tmp;
+      orthanc.RestApiGet(tmp, uri);
+      ParseJson(target, tmp);
+    }
+
+
+    bool HasWebViewerInstalled(IOrthancConnection& orthanc)
+    {
+      try
+      {
+        Json::Value json;
+        RestApiGet(json, orthanc, "/plugins/web-viewer");
+        return json.type() == Json::objectValue;
+      }
+      catch (Orthanc::OrthancException&)
+      {
+        return false;
+      }
+    }
+
+
+    bool CheckOrthancVersion(IOrthancConnection& orthanc)
+    {
+      Json::Value json;
+      std::string version;
+      unsigned int major, minor, patch;
+
+      try
+      {
+        RestApiGet(json, orthanc, "/system");
+      }
+      catch (Orthanc::OrthancException&)
+      {
+        LOG(ERROR) << "Cannot connect to your Orthanc server";
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+      }
+        
+      if (!ParseVersion(version, major, minor, patch, json))
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+      }
+
+      LOG(WARNING) << "Version of the Orthanc core (must be above 1.1.0): " << version;
+
+      // Stone is only compatible with Orthanc >= 1.1.0, otherwise deadlocks might occur
+      if (major < 1 ||
+          (major == 1 && minor < 1))
+      {
+        return false;
+      }
+
+      try
+      {
+        RestApiGet(json, orthanc, "/plugins/web-viewer");       
+      }
+      catch (Orthanc::OrthancException&)
+      {
+        // The Web viewer is not installed, this is OK
+        LOG(WARNING) << "The Web viewer plugin is not installed, progressive download is disabled";
+        return true;
+      }
+
+      if (!ParseVersion(version, major, minor, patch, json))
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+      }
+
+      LOG(WARNING) << "Version of the Web viewer plugin (must be above 2.2): " << version;
+
+      return (major >= 3 ||
+              (major == 2 && minor >= 2));
+    }
+
+
+    Orthanc::ImageAccessor* DecodeFrame(IOrthancConnection& orthanc,
+                                        const std::string& instance,
+                                        unsigned int frame,
+                                        Orthanc::PixelFormat targetFormat)
+    {
+      std::string uri = ("instances/" + instance + "/frames/" + 
+                         boost::lexical_cast<std::string>(frame));
+
+      std::string compressed;
+
+      switch (targetFormat)
+      {
+        case Orthanc::PixelFormat_RGB24:
+          orthanc.RestApiGet(compressed, uri + "/preview");
+          break;
+
+        case Orthanc::PixelFormat_Grayscale16:
+          orthanc.RestApiGet(compressed, uri + "/image-uint16");
+          break;
+
+        case Orthanc::PixelFormat_SignedGrayscale16:
+          orthanc.RestApiGet(compressed, uri + "/image-int16");
+          break;
+
+        default:
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+      }
+      
+      std::auto_ptr<Orthanc::PngReader> result(new Orthanc::PngReader);
+      result->ReadFromMemory(compressed);
+
+      if (targetFormat == Orthanc::PixelFormat_SignedGrayscale16)
+      {
+        if (result->GetFormat() == Orthanc::PixelFormat_Grayscale16)
+        {
+          result->SetFormat(Orthanc::PixelFormat_SignedGrayscale16);
+        }
+        else
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+        }
+      }
+
+      return result.release();
+    }
+
+
+    Orthanc::ImageAccessor* DecodeJpegFrame(IOrthancConnection& orthanc,
+                                            const std::string& instance,
+                                            unsigned int frame,
+                                            unsigned int quality,
+                                            Orthanc::PixelFormat targetFormat)
+    {
+      if (quality <= 0 || 
+          quality > 100)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+      }
+
+      // This requires the official Web viewer plugin to be installed!
+      std::string uri = ("web-viewer/instances/jpeg" + 
+                         boost::lexical_cast<std::string>(quality) + 
+                         "-" + instance + "_" + 
+                         boost::lexical_cast<std::string>(frame));
+
+      Json::Value encoded;
+      RestApiGet(encoded, orthanc, uri);
+
+      if (encoded.type() != Json::objectValue ||
+          !encoded.isMember("Orthanc") ||
+          encoded["Orthanc"].type() != Json::objectValue)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+      }
+
+      Json::Value& info = encoded["Orthanc"];
+      if (!info.isMember("PixelData") ||
+          !info.isMember("Stretched") ||
+          !info.isMember("Compression") ||
+          info["Compression"].type() != Json::stringValue ||
+          info["PixelData"].type() != Json::stringValue ||
+          info["Stretched"].type() != Json::booleanValue ||
+          info["Compression"].asString() != "Jpeg")
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+      }          
+
+      bool isSigned = false;
+      bool isStretched = info["Stretched"].asBool();
+
+      if (info.isMember("IsSigned"))
+      {
+        if (info["IsSigned"].type() != Json::booleanValue)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+        }          
+        else
+        {
+          isSigned = info["IsSigned"].asBool();
+        }
+      }
+
+      std::string jpeg;
+      Orthanc::Toolbox::DecodeBase64(jpeg, info["PixelData"].asString());
+
+      std::auto_ptr<Orthanc::JpegReader> reader(new Orthanc::JpegReader);
+      reader->ReadFromMemory(jpeg);
+
+      if (reader->GetFormat() == Orthanc::PixelFormat_RGB24)  // This is a color image
+      {
+        if (targetFormat != Orthanc::PixelFormat_RGB24)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+        }
+
+        if (isSigned || isStretched)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+        }
+        else
+        {
+          return reader.release();
+        }
+      }
+
+      if (reader->GetFormat() != Orthanc::PixelFormat_Grayscale8)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+      }
+
+      if (!isStretched)
+      {
+        if (targetFormat != reader->GetFormat())
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+        }
+
+        return reader.release();
+      }
+
+      int32_t stretchLow = 0;
+      int32_t stretchHigh = 0;
+
+      if (!info.isMember("StretchLow") ||
+          !info.isMember("StretchHigh") ||
+          info["StretchLow"].type() != Json::intValue ||
+          info["StretchHigh"].type() != Json::intValue)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+      }
+
+      stretchLow = info["StretchLow"].asInt();
+      stretchHigh = info["StretchHigh"].asInt();
+
+      if (stretchLow < -32768 ||
+          stretchHigh > 65535 ||
+          (stretchLow < 0 && stretchHigh > 32767))
+      {
+        // This range cannot be represented with a uint16_t or an int16_t
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);          
+      }
+
+      // Decode a grayscale JPEG 8bpp image coming from the Web viewer
+      std::auto_ptr<Orthanc::ImageAccessor> image
+        (new Orthanc::Image(targetFormat, reader->GetWidth(), reader->GetHeight()));
+
+      float scaling = static_cast<float>(stretchHigh - stretchLow) / 255.0f;
+      float offset = static_cast<float>(stretchLow) / scaling;
+      
+      Orthanc::ImageProcessing::Convert(*image, *reader);
+      Orthanc::ImageProcessing::ShiftScale(*image, offset, scaling);
+
+#if 0
+      /*info.removeMember("PixelData");
+        std::cout << info.toStyledString();*/
+      
+      int64_t a, b;
+      Orthanc::ImageProcessing::GetMinMaxValue(a, b, *image);
+      std::cout << stretchLow << "->" << stretchHigh << " = " << a << "->" << b << std::endl;
+#endif
+
+      return image.release();
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Messaging/MessagingToolbox.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,97 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "IOrthancConnection.h"
+
+#include "../Enumerations.h"
+#include "../Orthanc/Core/Images/ImageAccessor.h"
+
+#include <json/value.h>
+
+#if defined(__native_client__)
+#  include <ppapi/cpp/core.h>
+#else
+#  include <boost/date_time/posix_time/ptime.hpp>
+#endif
+
+namespace OrthancStone
+{
+  namespace MessagingToolbox
+  {
+    class Timestamp
+    {
+    private:
+#if defined(__native_client__)
+      PP_TimeTicks   time_;
+#else
+      boost::posix_time::ptime   time_;
+#endif
+
+    public:
+      Timestamp();
+
+#if defined(__native_client__)
+      static void Initialize(pp::Core* core);
+#endif
+
+      int GetMillisecondsSince(const Timestamp& other);
+    };
+
+
+    void ParseJson(Json::Value& target,
+                   const std::string& source);
+
+    void RestApiGet(Json::Value& target,
+                    IOrthancConnection& orthanc,
+                    const std::string& uri);
+
+    bool HasWebViewerInstalled(IOrthancConnection& orthanc);
+
+    bool CheckOrthancVersion(IOrthancConnection& orthanc);
+
+    // This downloads the image from Orthanc and keeps its pixel
+    // format unchanged (will be either Grayscale8, Grayscale16,
+    // SignedGrayscale16, or RGB24)
+    Orthanc::ImageAccessor* DecodeFrame(IOrthancConnection& orthanc,
+                                        const std::string& instance,
+                                        unsigned int frame,
+                                        Orthanc::PixelFormat targetFormat);
+
+    Orthanc::ImageAccessor* DecodeJpegFrame(IOrthancConnection& orthanc,
+                                            const std::string& instance,
+                                            unsigned int frame,
+                                            unsigned int quality,
+                                            Orthanc::PixelFormat targetFormat);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Orthanc/README.txt	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,3 @@
+This folder contains an excerpt of the source code of Orthanc. It is
+automatically generated using the "../Resources/SyncOrthancFolder.py"
+script.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/BinarySemaphore.cpp	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,61 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "BinarySemaphore.h"
+
+namespace OrthancStone
+{
+  BinarySemaphore::BinarySemaphore() : 
+    proceed_(false)
+  {
+  }
+
+  void BinarySemaphore::Signal()
+  {
+    //boost::mutex::scoped_lock lock(mutex_);
+
+    proceed_ = true;
+    condition_.notify_one(); 
+  }
+
+  void BinarySemaphore::Wait()
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    while (!proceed_)
+    {
+      condition_.wait(lock);
+    }
+
+    proceed_ = false;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/BinarySemaphore.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,54 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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/thread/mutex.hpp>
+#include <boost/thread/condition.hpp>
+
+namespace OrthancStone
+{
+  class BinarySemaphore : public boost::noncopyable
+  {
+  private:
+    bool proceed_;
+    boost::mutex mutex_;
+    boost::condition_variable condition_;
+
+  public:
+    explicit BinarySemaphore();
+
+    void Signal();
+
+    void Wait();
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/DicomDataset.cpp	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,315 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "DicomDataset.h"
+
+#include "../Orthanc/Core/OrthancException.h"
+#include "../Orthanc/Core/Logging.h"
+#include "../Orthanc/Core/Toolbox.h"
+
+#include <boost/lexical_cast.hpp>
+#include <json/value.h>
+#include <json/reader.h>
+
+namespace OrthancStone
+{
+  static uint16_t GetCharValue(char c)
+  {
+    if (c >= '0' && c <= '9')
+      return c - '0';
+    else if (c >= 'a' && c <= 'f')
+      return c - 'a' + 10;
+    else if (c >= 'A' && c <= 'F')
+      return c - 'A' + 10;
+    else
+      return 0;
+  }
+
+
+  static uint16_t GetHexadecimalValue(const char* c)
+  {
+    return ((GetCharValue(c[0]) << 12) + 
+            (GetCharValue(c[1]) << 8) + 
+            (GetCharValue(c[2]) << 4) + 
+            GetCharValue(c[3]));
+  }
+
+
+  static DicomDataset::Tag ParseTag(const std::string& tag)
+  {
+    if (tag.size() == 9 &&
+        isxdigit(tag[0]) &&
+        isxdigit(tag[1]) &&
+        isxdigit(tag[2]) &&
+        isxdigit(tag[3]) &&
+        (tag[4] == '-' || tag[4] == ',') &&
+        isxdigit(tag[5]) &&
+        isxdigit(tag[6]) &&
+        isxdigit(tag[7]) &&
+        isxdigit(tag[8]))        
+    {
+      uint16_t group = GetHexadecimalValue(tag.c_str());
+      uint16_t element = GetHexadecimalValue(tag.c_str() + 5);
+      return std::make_pair(group, element);
+    }
+    else if (tag.size() == 8 &&
+             isxdigit(tag[0]) &&
+             isxdigit(tag[1]) &&
+             isxdigit(tag[2]) &&
+             isxdigit(tag[3]) &&
+             isxdigit(tag[4]) &&
+             isxdigit(tag[5]) &&
+             isxdigit(tag[6]) &&
+             isxdigit(tag[7]))        
+    {
+      uint16_t group = GetHexadecimalValue(tag.c_str());
+      uint16_t element = GetHexadecimalValue(tag.c_str() + 4);
+      return std::make_pair(group, element);
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);        
+    }
+  }
+
+  void DicomDataset::Parse(const std::string& content)
+  {
+    Json::Value json;
+    Json::Reader reader;
+    if (!reader.parse(content, json))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);        
+    }
+
+    Parse(json);
+  }
+
+
+  void DicomDataset::Parse(const Json::Value& content)
+  {
+    if (content.type() != Json::objectValue)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);        
+    }
+
+    Json::Value::Members members = content.getMemberNames();
+    for (size_t i = 0; i < members.size(); i++)
+    {
+      Tag tag = ParseTag(members[i]);
+
+      const Json::Value& item = content[members[i]];
+
+      if (item.type() != Json::objectValue ||
+          !item.isMember("Type") ||
+          !item.isMember("Value") ||
+          !item.isMember("Name") ||
+          item["Type"].type() != Json::stringValue ||
+          item["Name"].type() != Json::stringValue)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);        
+      }
+
+      if (item["Type"].asString() == "String")
+      {
+        if (item["Value"].type() == Json::stringValue)
+        {
+          values_[tag] = item["Value"].asString();
+        }
+        else
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);        
+        }
+      }
+    }
+  }
+
+
+  DicomDataset::DicomDataset(IOrthancConnection& orthanc,
+                             const std::string& instanceId)
+  {
+    std::string content;
+    orthanc.RestApiGet(content, "/instances/" + instanceId + "/tags");
+
+    Parse(content);
+  }
+
+
+  std::string DicomDataset::GetStringValue(const Tag& tag) const
+  {
+    Values::const_iterator it = values_.find(tag);
+
+    if (it == values_.end())
+    {
+      LOG(ERROR) << "Trying to access a DICOM tag that is not set in a DICOM dataset";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentItem);
+    }
+    else
+    {
+      return it->second;
+    }
+  }
+
+
+  std::string DicomDataset::GetStringValue(const Tag& tag,
+                                           const std::string& defaultValue) const
+  {
+    Values::const_iterator it = values_.find(tag);
+
+    if (it == values_.end())
+    {
+      return defaultValue;
+    }
+    else
+    {
+      return it->second;
+    }
+  }
+
+
+  float DicomDataset::GetFloatValue(const Tag& tag) const
+  {
+    try 
+    {
+      return boost::lexical_cast<float>(Orthanc::Toolbox::StripSpaces(GetStringValue(tag)));
+    }
+    catch (boost::bad_lexical_cast&)
+    {
+      LOG(ERROR) << "Trying to access a DICOM tag that is not a float";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+  }
+
+
+  double DicomDataset::GetDoubleValue(const Tag& tag) const
+  {
+    try 
+    {
+      return boost::lexical_cast<double>(Orthanc::Toolbox::StripSpaces(GetStringValue(tag)));
+    }
+    catch (boost::bad_lexical_cast&)
+    {
+      LOG(ERROR) << "Trying to access a DICOM tag that is not a float";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+  }
+
+
+  int DicomDataset::GetIntegerValue(const Tag& tag) const
+  {
+    try 
+    {
+      return boost::lexical_cast<int>(Orthanc::Toolbox::StripSpaces(GetStringValue(tag)));
+    }
+    catch (boost::bad_lexical_cast&)
+    {
+      LOG(ERROR) << "Trying to access a DICOM tag that is not an integer";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+  }
+
+
+  unsigned int DicomDataset::GetUnsignedIntegerValue(const Tag& tag) const
+  {
+    int v = GetIntegerValue(tag);
+
+    if (v < 0)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+    else
+    {
+      return static_cast<unsigned int>(v);
+    }
+  }
+
+
+  void DicomDataset::GetVectorValue(Vector& vector, 
+                                    const Tag& tag) const
+  {
+    if (!GeometryToolbox::ParseVector(vector, Orthanc::Toolbox::StripSpaces(GetStringValue(tag))))
+    {
+      LOG(ERROR) << "Trying to access a DICOM tag that is not a vector";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+  }
+
+
+  void DicomDataset::GetVectorValue(Vector& vector, 
+                                    const Tag& tag,
+                                    size_t expectedSize) const
+  {
+    GetVectorValue(vector, tag);
+
+    if (vector.size() != expectedSize)
+    {
+      LOG(ERROR) << "A vector in a DICOM tag has a bad size";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+  }
+
+
+  void DicomDataset::Print() const
+  {
+    for (Values::const_iterator it = values_.begin(); it != values_.end(); ++it)
+    {
+      printf("%04x,%04x = [%s]\n", it->first.first, it->first.second, it->second.c_str());
+    }
+    printf("\n");
+  }
+
+
+  bool DicomDataset::IsGrayscale() const
+  {
+    std::string photometric = Orthanc::Toolbox::StripSpaces(GetStringValue(DICOM_TAG_PHOTOMETRIC_INTERPRETATION));
+
+    return (photometric == "MONOCHROME1" ||
+            photometric == "MONOCHROME2");
+  }
+
+
+  void DicomDataset::GetPixelSpacing(double& spacingX,
+                                     double& spacingY) const
+  {
+    if (HasTag(DICOM_TAG_PIXEL_SPACING))
+    {
+      Vector spacing;
+      GetVectorValue(spacing, DICOM_TAG_PIXEL_SPACING, 2);
+      spacingX = spacing[0];
+      spacingY = spacing[1];
+    }
+    else
+    {
+      spacingX = 1.0;
+      spacingY = 1.0;
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/DicomDataset.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,121 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "GeometryToolbox.h"
+#include "../Messaging/IOrthancConnection.h"
+
+#include <map>
+#include <stdint.h>
+#include <json/value.h>
+
+namespace OrthancStone
+{
+  // This class is NOT thread-safe
+  // This is a lightweight alternative to Orthanc::DicomMap
+  class DicomDataset : public boost::noncopyable
+  {
+  public:
+    typedef std::pair<uint16_t, uint16_t>  Tag;
+
+  private:
+    typedef std::map<Tag, std::string>  Values;
+
+    Values  values_;
+
+    void Parse(const std::string& content);
+
+    void Parse(const Json::Value& content);
+
+  public:
+    DicomDataset(const std::string& content)
+    {
+      Parse(content);
+    }
+
+    DicomDataset(const Json::Value& content)
+    {
+      Parse(content);
+    }
+
+    DicomDataset(IOrthancConnection& orthanc,
+                 const std::string& instanceId);
+
+    bool HasTag(const Tag& tag) const
+    {
+      return values_.find(tag) != values_.end();
+    }
+
+    std::string GetStringValue(const Tag& tag) const;
+
+    std::string GetStringValue(const Tag& tag,
+                               const std::string& defaultValue) const;
+
+    float GetFloatValue(const Tag& tag) const;
+
+    double GetDoubleValue(const Tag& tag) const;
+
+    int GetIntegerValue(const Tag& tag) const;
+
+    unsigned int GetUnsignedIntegerValue(const Tag& tag) const;
+
+    void GetVectorValue(Vector& vector, 
+                        const Tag& tag,
+                        size_t expectedSize) const;
+
+    void GetVectorValue(Vector& vector, 
+                        const Tag& tag) const;
+
+    void Print() const;
+
+    bool IsGrayscale() const;
+
+    void GetPixelSpacing(double& spacingX,
+                         double& spacingY) const;
+  };
+
+
+  static const DicomDataset::Tag DICOM_TAG_COLUMNS(0x0028, 0x0011);
+  static const DicomDataset::Tag DICOM_TAG_IMAGE_ORIENTATION_PATIENT(0x0020, 0x0037);
+  static const DicomDataset::Tag DICOM_TAG_IMAGE_POSITION_PATIENT(0x0020, 0x0032);
+  static const DicomDataset::Tag DICOM_TAG_NUMBER_OF_FRAMES(0x0028, 0x0008);
+  static const DicomDataset::Tag DICOM_TAG_PIXEL_REPRESENTATION(0x0028, 0x0103);
+  static const DicomDataset::Tag DICOM_TAG_PIXEL_SPACING(0x0028, 0x0030);
+  static const DicomDataset::Tag DICOM_TAG_RESCALE_INTERCEPT(0x0028, 0x1052);
+  static const DicomDataset::Tag DICOM_TAG_RESCALE_SLOPE(0x0028, 0x1053);
+  static const DicomDataset::Tag DICOM_TAG_ROWS(0x0028, 0x0010);
+  static const DicomDataset::Tag DICOM_TAG_SLICE_THICKNESS(0x0018, 0x0050);
+  static const DicomDataset::Tag DICOM_TAG_WINDOW_CENTER(0x0028, 0x1050);
+  static const DicomDataset::Tag DICOM_TAG_WINDOW_WIDTH(0x0028, 0x1051);
+  static const DicomDataset::Tag DICOM_TAG_PHOTOMETRIC_INTERPRETATION(0x0028, 0x0004);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/DicomFrameConverter.cpp	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,162 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "DicomFrameConverter.h"
+
+#include "../Orthanc/Core/Images/Image.h"
+#include "../Orthanc/Core/Images/ImageProcessing.h"
+#include "../Orthanc/Core/OrthancException.h"
+#include "../Orthanc/Core/Toolbox.h"
+
+namespace OrthancStone
+{
+  void DicomFrameConverter::SetDefaultParameters()
+  {
+    isSigned_ = true;
+    isColor_ = false;
+    hasRescale_ = false;
+    rescaleIntercept_ = 0;
+    rescaleSlope_ = 1;
+    defaultWindowCenter_ = 128;
+    defaultWindowWidth_ = 256;
+  }
+
+
+  Orthanc::PixelFormat DicomFrameConverter::GetExpectedPixelFormat() const
+  {
+    // TODO Add more checks, e.g. on the number of bytes per value
+    // (cf. DicomImageInformation.h in Orthanc)
+
+    if (isColor_)
+    {
+      return Orthanc::PixelFormat_RGB24;
+    }
+    else if (isSigned_)
+    {
+      return Orthanc::PixelFormat_SignedGrayscale16;
+    }
+    else
+    {
+      return Orthanc::PixelFormat_Grayscale16;
+    }
+  }
+
+
+  void DicomFrameConverter::ReadParameters(const DicomDataset& dicom)
+  {
+    SetDefaultParameters();
+
+    if (dicom.HasTag(DICOM_TAG_WINDOW_CENTER))
+    {
+      Vector c, w;
+      dicom.GetVectorValue(c, DICOM_TAG_WINDOW_CENTER);
+      dicom.GetVectorValue(w, DICOM_TAG_WINDOW_WIDTH);
+
+      if (c.size() > 0 && w.size() > 0)
+      {
+        defaultWindowCenter_ = static_cast<float>(c[0]);
+        defaultWindowWidth_ = static_cast<float>(w[0]);
+      }
+    }
+
+    isSigned_ = (dicom.GetIntegerValue(DICOM_TAG_PIXEL_REPRESENTATION) == 1);  // Type 1 tag, must be present
+
+    if (dicom.HasTag(DICOM_TAG_RESCALE_INTERCEPT))
+    {
+      rescaleIntercept_ = dicom.GetFloatValue(DICOM_TAG_RESCALE_INTERCEPT);
+      rescaleSlope_ = dicom.GetFloatValue(DICOM_TAG_RESCALE_SLOPE);
+      hasRescale_ = true;
+    }
+
+    std::string photometric = dicom.GetStringValue(DICOM_TAG_PHOTOMETRIC_INTERPRETATION);  // Type 1 tag, must be present
+    photometric = Orthanc::Toolbox::StripSpaces(photometric);
+    isColor_ = (photometric != "MONOCHROME1" &&
+                photometric != "MONOCHROME2");
+  }
+
+
+  void DicomFrameConverter::ConvertFrame(std::auto_ptr<Orthanc::ImageAccessor>& source) const
+  {
+    assert(sizeof(float) == 4);
+
+    if (source.get() == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    Orthanc::PixelFormat sourceFormat = source->GetFormat();
+
+    if (sourceFormat != GetExpectedPixelFormat())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
+    }
+
+    if (sourceFormat == Orthanc::PixelFormat_RGB24)
+    {
+      // No conversion has to be done
+      return;
+    }
+
+    assert(sourceFormat == Orthanc::PixelFormat_Grayscale16 ||
+           sourceFormat == Orthanc::PixelFormat_SignedGrayscale16);
+
+    // This is the case of a grayscale frame. Convert it to Float32.
+    std::auto_ptr<Orthanc::Image> converted(new Orthanc::Image(Orthanc::PixelFormat_Float32, 
+                                                               source->GetWidth(), 
+                                                               source->GetHeight()));
+    Orthanc::ImageProcessing::Convert(*converted, *source);
+
+    source.reset(NULL);  // We don't need the source frame anymore
+
+    // Correct rescale slope/intercept if need be
+    if (hasRescale_)
+    {
+      for (unsigned int y = 0; y < converted->GetHeight(); y++)
+      {
+        float* p = reinterpret_cast<float*>(converted->GetRow(y));
+        for (unsigned int x = 0; x < converted->GetWidth(); x++, p++)
+        {
+          float value = *p;
+
+          if (hasRescale_)
+          {
+            value = value * rescaleSlope_ + rescaleIntercept_;
+          }
+            
+          *p = value;
+        }
+      }
+    }
+      
+    source = converted;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/DicomFrameConverter.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,83 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "DicomDataset.h"
+
+#include "../Orthanc/Core/Images/ImageAccessor.h"
+
+namespace OrthancStone
+{
+  /**
+   * This class is responsible for converting the pixel format of a
+   * DICOM frame coming from Orthanc, into a pixel format that is
+   * suitable for Stone, given the relevant DICOM tags:
+   * - Color frames will stay in the RGB24 format.
+   * - Grayscale frames will be converted to the Float32 format.
+   **/
+  class DicomFrameConverter
+  {
+  private:
+    bool    isSigned_;
+    bool    isColor_;
+    bool    hasRescale_;
+    float   rescaleIntercept_;
+    float   rescaleSlope_;
+    float   defaultWindowCenter_;
+    float   defaultWindowWidth_;
+
+    void SetDefaultParameters();
+
+  public:
+    DicomFrameConverter()
+    {
+      SetDefaultParameters();
+    }
+
+    Orthanc::PixelFormat GetExpectedPixelFormat() const;
+
+    void ReadParameters(const DicomDataset& dicom);
+
+    float GetDefaultWindowCenter() const
+    {
+      return defaultWindowCenter_;
+    }
+    
+    float GetDefaultWindowWidth() const
+    {
+      return defaultWindowWidth_;
+    }
+
+    void ConvertFrame(std::auto_ptr<Orthanc::ImageAccessor>& source) const;  
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/DicomStructureSet.cpp	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,399 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "DicomStructureSet.h"
+
+#include "../Orthanc/Core/Logging.h"
+#include "../Orthanc/Core/OrthancException.h"
+#include "../Messaging/MessagingToolbox.h"
+
+#include <stdio.h>
+#include <boost/lexical_cast.hpp>
+
+namespace OrthancStone
+{
+  static const Json::Value& GetSequence(const Json::Value& instance,
+                                        uint16_t group,
+                                        uint16_t element)
+  {
+    char buf[16];
+    sprintf(buf, "%04x,%04x", group, element);
+
+    if (instance.type() != Json::objectValue ||
+        !instance.isMember(buf) ||
+        instance[buf].type() != Json::objectValue ||
+        !instance[buf].isMember("Type") ||
+        !instance[buf].isMember("Value") ||
+        instance[buf]["Type"].type() != Json::stringValue ||
+        instance[buf]["Value"].type() != Json::arrayValue ||
+        instance[buf]["Type"].asString() != "Sequence")
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+
+    return instance[buf]["Value"];
+  }
+
+
+  static uint8_t ConvertColor(double v)
+  {
+    if (v < 0)
+    {
+      return 0;
+    }
+    else if (v >= 255)
+    {
+      return 255;
+    }
+    else
+    {
+      return static_cast<uint8_t>(v);
+    }
+  }
+
+
+  SliceGeometry DicomStructureSet::ExtractSliceGeometry(double& sliceThickness,
+                                                        IOrthancConnection& orthanc,
+                                                        const Json::Value& contour)
+  {
+    const Json::Value& sequence = GetSequence(contour, 0x3006, 0x0016);
+
+    if (sequence.size() != 1)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);          
+    }
+
+    DicomDataset contourImageSequence(sequence[0]);
+    std::string parentUid = contourImageSequence.GetStringValue(std::make_pair(0x0008, 0x1155));
+
+    std::string post;
+    orthanc.RestApiPost(post, "/tools/lookup", parentUid);
+
+    Json::Value tmp;
+    MessagingToolbox::ParseJson(tmp, post);
+
+    if (tmp.type() != Json::arrayValue ||
+        tmp.size() != 1 ||
+        !tmp[0].isMember("Type") ||
+        !tmp[0].isMember("Path") ||
+        tmp[0]["Type"].type() != Json::stringValue ||
+        tmp[0]["ID"].type() != Json::stringValue ||
+        tmp[0]["Type"].asString() != "Instance")
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);          
+    }
+
+    Json::Value parentInstance;
+    MessagingToolbox::RestApiGet(parentInstance, orthanc, "/instances/" + tmp[0]["ID"].asString());
+
+    if (parentInstance.type() != Json::objectValue ||
+        !parentInstance.isMember("ParentSeries") ||
+        parentInstance["ParentSeries"].type() != Json::stringValue)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);          
+    }
+
+    std::string parentSeriesId = parentInstance["ParentSeries"].asString();
+    bool isFirst = parentSeriesId_.empty();
+
+    if (isFirst)
+    {
+      parentSeriesId_ = parentSeriesId;
+    }
+    else if (parentSeriesId_ != parentSeriesId)
+    {
+      LOG(ERROR) << "This RT-STRUCT refers to several different series";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+
+    Json::Value parentTags;
+    MessagingToolbox::RestApiGet(parentTags, orthanc, "/instances/" + tmp[0]["ID"].asString() + "/tags?simplify");
+      
+    if (parentTags.type() != Json::objectValue ||
+        !parentTags.isMember("ImageOrientationPatient") ||
+        !parentTags.isMember("ImagePositionPatient") ||
+        parentTags["ImageOrientationPatient"].type() != Json::stringValue ||
+        parentTags["ImagePositionPatient"].type() != Json::stringValue)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);          
+    }
+
+    SliceGeometry slice(parentTags["ImagePositionPatient"].asString(),
+                        parentTags["ImageOrientationPatient"].asString());
+                          
+    sliceThickness = 1;  // 1 mm by default
+
+    if (parentTags.isMember("SliceThickness") &&
+        parentTags["SliceThickness"].type() == Json::stringValue)
+    {
+      Vector tmp;
+      GeometryToolbox::ParseVector(tmp, parentTags["SliceThickness"].asString());
+      if (tmp.size() > 0)
+      {
+        sliceThickness = tmp[0];
+      }
+    }
+
+    if (isFirst)
+    {
+      normal_ = slice.GetNormal();
+    }
+    else if (!GeometryToolbox::IsParallel(normal_, slice.GetNormal()))
+    {
+      LOG(ERROR) << "Incompatible orientation of slices in this RT-STRUCT";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+
+    return slice;
+  }
+
+
+  const DicomStructureSet::Structure& DicomStructureSet::GetStructure(size_t index) const
+  {
+    if (index >= structures_.size())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+    }
+
+    return structures_[index];
+  }
+
+
+  bool DicomStructureSet::IsPolygonOnSlice(const Polygon& polygon,
+                                           const SliceGeometry& geometry) const
+  {
+    double d = boost::numeric::ublas::inner_prod(geometry.GetOrigin(), normal_);
+
+    return (GeometryToolbox::IsNear(d, polygon.projectionAlongNormal_, polygon.sliceThickness_ / 2.0) &&
+            !polygon.points_.empty());
+  }
+
+
+  DicomStructureSet::DicomStructureSet(IOrthancConnection& orthanc,
+                                       const std::string& instanceId)
+  {
+    Json::Value instance;
+    MessagingToolbox::RestApiGet(instance, orthanc, "/instances/" + instanceId + "/tags");
+ 
+    Json::Value rtRoiObservationSequence = GetSequence(instance, 0x3006, 0x0080);
+    Json::Value roiContourSequence = GetSequence(instance, 0x3006, 0x0039);
+    Json::Value structureSetRoiSequence = GetSequence(instance, 0x3006, 0x0020);
+
+    Json::Value::ArrayIndex count = rtRoiObservationSequence.size();
+    if (count != roiContourSequence.size() ||
+        count != structureSetRoiSequence.size())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+      
+    structures_.resize(count);
+    for (Json::Value::ArrayIndex i = 0; i < count; i++)
+    {
+      DicomDataset observation(rtRoiObservationSequence[i]);
+      DicomDataset roi(structureSetRoiSequence[i]);
+      DicomDataset content(roiContourSequence[i]);
+
+      structures_[i].interpretation_ = observation.GetStringValue(std::make_pair(0x3006, 0x00a4), "No interpretation");
+      structures_[i].name_ = roi.GetStringValue(std::make_pair(0x3006, 0x0026), "No name");
+      structures_[i].red_ = 255;
+      structures_[i].green_ = 0;
+      structures_[i].blue_ = 0;
+
+      DicomDataset::Tag tag(0x3006, 0x002a);
+
+      Vector color;
+      if (content.HasTag(tag))
+      {
+        content.GetVectorValue(color, tag);
+        if (color.size() == 3)
+        {
+          structures_[i].red_ = ConvertColor(color[0]);
+          structures_[i].green_ = ConvertColor(color[1]);
+          structures_[i].blue_ = ConvertColor(color[2]);
+        }
+      }
+
+      const Json::Value& slices = GetSequence(roiContourSequence[i], 0x3006, 0x0040);
+
+      LOG(WARNING) << "New RT structure: \"" << structures_[i].name_ 
+                   << "\" with interpretation \"" << structures_[i].interpretation_
+                   << "\" containing " << slices.size() << " slices (color: " 
+                   << static_cast<int>(structures_[i].red_) << "," 
+                   << static_cast<int>(structures_[i].green_) << ","
+                   << static_cast<int>(structures_[i].blue_) << ")";
+
+      for (Json::Value::ArrayIndex j = 0; j < slices.size(); j++)
+      {
+        DicomDataset slice(slices[j]);
+
+        unsigned int npoints = slice.GetUnsignedIntegerValue(std::make_pair(0x3006, 0x0046));
+        LOG(INFO) << "Parsing slice containing " << npoints << " vertices";
+
+        if (slice.GetStringValue(std::make_pair(0x3006, 0x0042)) != "CLOSED_PLANAR")
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);          
+        }
+
+        std::string slicesData;
+        orthanc.RestApiGet(slicesData, "/instances/" + instanceId + "/content/3006-0039/" +
+                           boost::lexical_cast<std::string>(i) + "/3006-0040/" +
+                           boost::lexical_cast<std::string>(j) + "/3006-0050");
+
+        Vector points;
+        if (!GeometryToolbox::ParseVector(points, slicesData) ||
+            points.size() != 3 * npoints)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);          
+        }
+
+        Polygon polygon;
+        SliceGeometry geometry = ExtractSliceGeometry(polygon.sliceThickness_, orthanc, slices[j]);
+        polygon.projectionAlongNormal_ = geometry.ProjectAlongNormal(geometry.GetOrigin());
+
+        for (size_t k = 0; k < npoints; k++)
+        {
+          Vector v(3);
+          v[0] = points[3 * k];
+          v[1] = points[3 * k + 1];
+          v[2] = points[3 * k + 2];
+
+          if (!GeometryToolbox::IsNear(geometry.ProjectAlongNormal(v), 
+                                       polygon.projectionAlongNormal_, 
+                                       polygon.sliceThickness_ / 2.0 /* in mm */))
+          {
+            LOG(ERROR) << "This RT-STRUCT contains a point that is off the slice of its instance";
+            throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);          
+          }
+
+          polygon.points_.push_back(v);
+        }
+
+        structures_[i].polygons_.push_back(polygon);
+      }
+    }
+
+    if (parentSeriesId_.empty() ||
+        normal_.size() != 3)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);          
+    }
+  }
+
+
+  Vector DicomStructureSet::GetStructureCenter(size_t index) const
+  {
+    const Structure& structure = GetStructure(index);
+
+    Vector center;
+    GeometryToolbox::AssignVector(center, 0, 0, 0);
+    if (structure.polygons_.empty())
+    {
+      return center;
+    }
+
+    double n = static_cast<double>(structure.polygons_.size());
+
+    for (Polygons::const_iterator polygon = structure.polygons_.begin();
+         polygon != structure.polygons_.end(); ++polygon)
+    {
+      if (!polygon->points_.empty())
+      {
+        center += polygon->points_.front() / n;
+      }
+    }
+
+    return center;
+  }
+
+
+  const std::string& DicomStructureSet::GetStructureName(size_t index) const
+  {
+    return GetStructure(index).name_;
+  }
+
+
+  const std::string& DicomStructureSet::GetStructureInterpretation(size_t index) const
+  {
+    return GetStructure(index).interpretation_;
+  }
+
+
+  void DicomStructureSet::GetStructureColor(uint8_t& red,
+                                            uint8_t& green,
+                                            uint8_t& blue,
+                                            size_t index) const
+  {
+    const Structure& s = GetStructure(index);
+    red = s.red_;
+    green = s.green_;
+    blue = s.blue_;
+  }
+
+
+  void DicomStructureSet::Render(CairoContext& context,
+                                 const SliceGeometry& slice) const
+  {
+    cairo_t* cr = context.GetObject();
+
+    for (Structures::const_iterator structure = structures_.begin();
+         structure != structures_.end(); ++structure)
+    {
+      for (Polygons::const_iterator polygon = structure->polygons_.begin();
+           polygon != structure->polygons_.end(); ++polygon)
+      {
+        if (IsPolygonOnSlice(*polygon, slice))
+        {
+          context.SetSourceColor(structure->red_, structure->green_, structure->blue_);
+
+          Points::const_iterator p = polygon->points_.begin();
+
+          double x, y;
+          slice.ProjectPoint(x, y, *p);
+          cairo_move_to(cr, x, y);
+          ++p;
+            
+          while (p != polygon->points_.end())
+          {
+            slice.ProjectPoint(x, y, *p);
+            cairo_line_to(cr, x, y);
+            ++p;
+          }
+
+          slice.ProjectPoint(x, y, *polygon->points_.begin());
+          cairo_line_to(cr, x, y);
+        }
+      }
+    }
+
+    cairo_stroke(cr);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/DicomStructureSet.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,110 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "SliceGeometry.h"
+#include "../Viewport/CairoContext.h"
+
+#include <list>
+
+namespace OrthancStone
+{
+  class DicomStructureSet : public boost::noncopyable
+  {
+  private:
+    typedef std::list<Vector>  Points;
+
+    struct Polygon
+    {
+      double  projectionAlongNormal_;
+      double  sliceThickness_;  // In millimeters
+      Points  points_;
+    };
+
+    typedef std::list<Polygon>  Polygons;
+
+    struct Structure
+    {
+      std::string   name_;
+      std::string   interpretation_;
+      Polygons      polygons_;
+      uint8_t       red_;
+      uint8_t       green_;
+      uint8_t       blue_;
+    };
+
+    typedef std::vector<Structure>  Structures;
+
+    Structures   structures_;
+    std::string  parentSeriesId_;
+    Vector       normal_;
+
+    SliceGeometry ExtractSliceGeometry(double& sliceThickness,
+                                       IOrthancConnection& orthanc,
+                                       const Json::Value& contour);
+
+    const Structure& GetStructure(size_t index) const;
+
+    bool IsPolygonOnSlice(const Polygon& polygon,
+                          const SliceGeometry& geometry) const;
+
+
+  public:
+    DicomStructureSet(IOrthancConnection& orthanc,
+                      const std::string& instanceId);
+
+    size_t GetStructureCount() const
+    {
+      return structures_.size();
+    }
+
+    Vector GetStructureCenter(size_t index) const;
+
+    const std::string& GetStructureName(size_t index) const;
+
+    const std::string& GetStructureInterpretation(size_t index) const;
+
+    void GetStructureColor(uint8_t& red,
+                           uint8_t& green,
+                           uint8_t& blue,
+                           size_t index) const;
+
+    const Vector& GetNormal() const
+    {
+      return normal_;
+    }
+
+    void Render(CairoContext& context,
+                const SliceGeometry& slice) const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/DownloadStack.cpp	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,209 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "DownloadStack.h"
+
+#include "../Orthanc/Core/OrthancException.h"
+
+#include <cassert>
+
+namespace OrthancStone
+{
+  bool DownloadStack::CheckInvariants() const
+  {
+    std::vector<bool> dequeued(nodes_.size(), true);
+
+    int i = firstNode_;
+    while (i != NIL)
+    {
+      const Node& node = nodes_[i];
+
+      dequeued[i] = false;
+
+      if (node.next_ != NIL &&
+          nodes_[node.next_].prev_ != i)
+      {
+        return false;
+      }
+
+      if (node.prev_ != NIL &&
+          nodes_[node.prev_].next_ != i)
+      {
+        return false;
+      }
+
+      i = nodes_[i].next_;
+    }
+
+    for (size_t i = 0; i < nodes_.size(); i++)
+    {
+      if (nodes_[i].dequeued_ != dequeued[i])
+      {
+        return false;
+      }
+    }
+
+    return true;
+  }
+
+
+  DownloadStack::DownloadStack(unsigned int size)
+  {
+    nodes_.resize(size);
+
+    if (size == 0)
+    {
+      firstNode_ = NIL;
+    }
+    else
+    {
+      for (size_t i = 0; i < size; i++)
+      {
+        nodes_[i].prev_ = i - 1;
+        nodes_[i].next_ = i + 1;
+        nodes_[i].dequeued_ = false;
+      }
+
+      nodes_.front().prev_ = NIL;
+      nodes_.back().next_ = NIL;
+      firstNode_ = 0;
+    }
+
+    assert(CheckInvariants());
+  }
+
+
+  DownloadStack::~DownloadStack()
+  {
+    assert(CheckInvariants());    
+  }
+
+
+  bool DownloadStack::Pop(unsigned int& value)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    assert(CheckInvariants());
+
+    if (firstNode_ == NIL)
+    {
+      for (size_t i = 0; i < nodes_.size(); i++)
+      {
+        assert(nodes_[i].dequeued_);
+      }
+
+      return false;
+    }
+    else
+    {
+      assert(firstNode_ >= 0 && firstNode_ < static_cast<int>(nodes_.size()));
+      value = firstNode_;
+
+      Node& node = nodes_[firstNode_];
+      assert(node.prev_ == NIL);
+      assert(!node.dequeued_);
+
+      node.dequeued_ = true;
+      firstNode_ = node.next_;
+
+      if (firstNode_ != NIL)
+      {
+        nodes_[firstNode_].prev_ = NIL;
+      }
+
+      return true;
+    }
+  }
+
+
+  void DownloadStack::SetTopNodeInternal(unsigned int value)
+  {
+    assert(CheckInvariants());
+
+    Node& node = nodes_[value];
+
+    if (node.dequeued_)
+    {
+      // This node has already been processed by the download thread, nothing to do
+      return;
+    }
+
+    // Remove the node from the list
+    if (node.prev_ == NIL)
+    {
+      assert(firstNode_ == static_cast<int>(value));
+      
+      // This is already the top node in the list, nothing to do
+      return;
+    }
+
+    nodes_[node.prev_].next_ = node.next_;
+
+    if (node.next_ != NIL)
+    {
+      nodes_[node.next_].prev_ = node.prev_;
+    }
+
+    // Add back the node at the top of the list
+    assert(firstNode_ != NIL);
+
+    Node& old = nodes_[firstNode_];
+    assert(old.prev_ == NIL);
+    assert(!old.dequeued_);
+    node.prev_ = NIL;
+    node.next_ = firstNode_;
+    old.prev_ = value;
+
+    firstNode_ = value;
+  }
+
+  
+  void DownloadStack::Writer::SetTopNode(unsigned int value)
+  {
+    if (value >= that_.nodes_.size())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    that_.SetTopNodeInternal(value);
+  }
+
+
+  void DownloadStack::Writer::SetTopNodePermissive(int value)
+  {
+    if (value >= 0 &&
+        value < static_cast<int>(that_.nodes_.size()))
+    {
+      that_.SetTopNodeInternal(value);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/DownloadStack.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,87 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 <vector>
+#include <boost/noncopyable.hpp>
+#include <boost/thread/mutex.hpp>
+
+namespace OrthancStone
+{
+  class DownloadStack : public boost::noncopyable
+  {
+  private:
+    static const int NIL = -1;
+
+    // This is a doubly-linked list
+    struct Node
+    {
+      int   next_;
+      int   prev_;
+      bool  dequeued_;
+    };
+
+    boost::mutex        mutex_;
+    std::vector<Node>   nodes_;
+    int                 firstNode_;
+
+    bool CheckInvariants() const;
+
+    void SetTopNodeInternal(unsigned int value);  
+
+  public:
+    DownloadStack(unsigned int size);
+
+    ~DownloadStack();
+
+    bool Pop(unsigned int& value);
+
+    class Writer : public boost::noncopyable
+    {
+    private:
+      DownloadStack&              that_;
+      boost::mutex::scoped_lock   lock_;
+      
+    public:
+      Writer(DownloadStack& that) :
+        that_(that),
+        lock_(that.mutex_)
+      {
+      }
+      
+      void SetTopNode(unsigned int value);  
+
+      void SetTopNodePermissive(int value);
+    };
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/GeometryToolbox.cpp	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,351 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "GeometryToolbox.h"
+
+#include "../Orthanc/Core/OrthancException.h"
+#include "../Orthanc/Core/Toolbox.h"
+
+#include <stdio.h>
+#include <boost/lexical_cast.hpp>
+
+namespace OrthancStone
+{
+  namespace GeometryToolbox
+  {
+    void Print(const Vector& v)
+    {
+      for (size_t i = 0; i < v.size(); i++)
+      {
+        printf("%8.2f\n", v[i]);
+      }
+      printf("\n");
+    }
+
+
+    bool ParseVector(Vector& target,
+                     const std::string& value)
+    {
+      std::vector<std::string> items;
+      Orthanc::Toolbox::TokenizeString(items, value, '\\');
+
+      target.resize(items.size());
+
+      for (size_t i = 0; i < items.size(); i++)
+      {
+        try
+        {
+          target[i] = boost::lexical_cast<double>(Orthanc::Toolbox::StripSpaces(items[i]));
+        }
+        catch (boost::bad_lexical_cast&)
+        {
+          target.clear();
+          return false;
+        }
+      }
+
+      return true;
+    }
+
+
+    void AssignVector(Vector& v,
+                      double v1,
+                      double v2)
+    {
+      v.resize(2);
+      v[0] = v1;
+      v[1] = v2;
+    }
+
+
+    void AssignVector(Vector& v,
+                      double v1,
+                      double v2,
+                      double v3)
+    {
+      v.resize(3);
+      v[0] = v1;
+      v[1] = v2;
+      v[2] = v3;
+    }
+
+
+    bool IsNear(double x,
+                double y)
+    {
+      // As most input is read as single-precision numbers, we take the
+      // epsilon machine for float32 into consideration to compare numbers
+      return IsNear(x, y, 10.0 * std::numeric_limits<float>::epsilon());
+    }
+
+
+    void NormalizeVector(Vector& u)
+    {
+      double norm = boost::numeric::ublas::norm_2(u);
+      if (!IsCloseToZero(norm))
+      {
+        u = u / norm;
+      }
+    }
+
+
+    void CrossProduct(Vector& result,
+                      const Vector& u,
+                      const Vector& v)
+    {
+      if (u.size() != 3 ||
+          v.size() != 3)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+      }
+
+      result.resize(3);
+
+      result[0] = u[1] * v[2] - u[2] * v[1];
+      result[1] = u[2] * v[0] - u[0] * v[2];
+      result[2] = u[0] * v[1] - u[1] * v[0];
+    }
+
+
+    void ProjectPointOntoPlane(Vector& result,
+                               const Vector& point,
+                               const Vector& planeNormal,
+                               const Vector& planeOrigin)
+    {
+      double norm =  boost::numeric::ublas::norm_2(planeNormal);
+      if (IsCloseToZero(norm))
+      {
+        // Division by zero
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+      }
+
+      // Make sure the norm of the normal is 1
+      Vector n;
+      n = planeNormal / norm;
+
+      // Algebraic form of line–plane intersection, where the line passes
+      // through "point" along the direction "normal" (thus, l == n)
+      // https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection#Algebraic_form
+      result = boost::numeric::ublas::inner_prod(planeOrigin - point, n) * n + point;
+    }
+
+
+
+    bool IsParallelOrOpposite(bool& isOpposite,
+                              const Vector& u,
+                              const Vector& v)
+    {
+      // The dot product of the two vectors gives the cosine of the angle
+      // between the vectors
+      // https://en.wikipedia.org/wiki/Dot_product
+
+      double normU = boost::numeric::ublas::norm_2(u);
+      double normV = boost::numeric::ublas::norm_2(v);
+
+      if (IsCloseToZero(normU) ||
+          IsCloseToZero(normV))
+      {
+        return false;
+      }
+
+      double cosAngle = boost::numeric::ublas::inner_prod(u, v) / (normU * normV);
+
+      // The angle must be zero, so the cosine must be almost equal to
+      // cos(0) == 1 (or cos(180) == -1 if allowOppositeDirection == true)
+
+      if (IsCloseToZero(cosAngle - 1.0))
+      {
+        isOpposite = false;
+        return true;
+      }
+      else if (IsCloseToZero(fabs(cosAngle) - 1.0))
+      {
+        isOpposite = true;
+        return true;
+      }
+      else
+      {
+        return false;
+      }
+    }
+
+
+    bool IsParallel(const Vector& u,
+                    const Vector& v)
+    {
+      bool isOpposite;
+      return (IsParallelOrOpposite(isOpposite, u, v) &&
+              !isOpposite);
+    }
+
+
+    bool IntersectTwoPlanes(Vector& p,
+                            Vector& direction,
+                            const Vector& origin1,
+                            const Vector& normal1,
+                            const Vector& origin2,
+                            const Vector& normal2)
+    {
+      // This is "Intersection of 2 Planes", possibility "(C) 3 Plane
+      // Intersect Point" of:
+      // http://geomalgorithms.com/a05-_intersect-1.html
+
+      // The direction of the line of intersection is orthogonal to the
+      // normal of both planes
+      CrossProduct(direction, normal1, normal2);
+
+      double norm = boost::numeric::ublas::norm_2(direction);
+      if (IsCloseToZero(norm))
+      {
+        // The two planes are parallel or coincident
+        return false;
+      }
+
+      double d1 = -boost::numeric::ublas::inner_prod(normal1, origin1);
+      double d2 = -boost::numeric::ublas::inner_prod(normal2, origin2);
+      Vector tmp = d2 * normal1 - d1 * normal2;
+
+      CrossProduct(p, tmp, direction);
+      p /= norm;
+
+      return true;
+    }
+
+
+    bool ClipLineToRectangle(double& x1,  // Coordinates of the clipped line (out)
+                             double& y1,
+                             double& x2,
+                             double& y2,
+                             const double ax,  // Two points defining the line (in)
+                             const double ay,
+                             const double bx,
+                             const double by,
+                             const double& xmin,   // Coordinates of the rectangle (in)
+                             const double& ymin,
+                             const double& xmax,
+                             const double& ymax)
+    {
+      // This is Skala algorithm for rectangles, "A new approach to line
+      // and line segment clipping in homogeneous coordinates"
+      // (2005). This is a direct, non-optimized translation of Algorithm
+      // 2 in the paper.
+
+      static uint8_t tab1[16] = { 255 /* none */,
+                                  0,
+                                  0,
+                                  1,
+                                  1,
+                                  255 /* na */,
+                                  0,
+                                  2,
+                                  2,
+                                  0,
+                                  255 /* na */,
+                                  1,
+                                  1,
+                                  0,
+                                  0,
+                                  255 /* none */ };
+
+
+      static uint8_t tab2[16] = { 255 /* none */,
+                                  3,
+                                  1,
+                                  3,
+                                  2,
+                                  255 /* na */,
+                                  2,
+                                  3,
+                                  3,
+                                  2,
+                                  255 /* na */,
+                                  2,
+                                  3,
+                                  1,
+                                  3,
+                                  255 /* none */ };
+
+      // Create the coordinates of the rectangle
+      Vector x[4];
+      AssignVector(x[0], xmin, ymin, 1.0);
+      AssignVector(x[1], xmax, ymin, 1.0);
+      AssignVector(x[2], xmax, ymax, 1.0);
+      AssignVector(x[3], xmin, ymax, 1.0);
+
+      // Move to homogoneous coordinates in 2D
+      Vector p;
+
+      {
+        Vector a, b;
+        AssignVector(a, ax, ay, 1.0);
+        AssignVector(b, bx, by, 1.0);
+        CrossProduct(p, a, b);
+      }
+
+      uint8_t c = 0;
+
+      for (unsigned int k = 0; k < 4; k++)
+      {
+        if (boost::numeric::ublas::inner_prod(p, x[k]) >= 0)
+        {
+          c |= (1 << k);
+        }
+      }
+
+      assert(c < 16);
+
+      uint8_t i = tab1[c];
+      uint8_t j = tab2[c];
+
+      if (i == 255 || j == 255)
+      {
+        return false;   // No intersection
+      }
+      else
+      {
+        Vector a, b, e;
+        CrossProduct(e, x[i], x[(i + 1) % 4]);
+        CrossProduct(a, p, e);
+        CrossProduct(e, x[j], x[(j + 1) % 4]);
+        CrossProduct(b, p, e);
+
+        // Go back to non-homogeneous coordinates
+        x1 = a[0] / a[2];
+        y1 = a[1] / a[2];
+        x2 = b[0] / b[2];
+        y2 = b[1] / b[2];
+
+        return true;
+      }
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/GeometryToolbox.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,111 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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/numeric/ublas/vector.hpp>
+
+
+namespace OrthancStone
+{
+  typedef boost::numeric::ublas::vector<double>   Vector;
+
+  namespace GeometryToolbox
+  {
+    void Print(const Vector& v);
+
+    bool ParseVector(Vector& target,
+                     const std::string& value);
+
+    void AssignVector(Vector& v,
+                      double v1,
+                      double v2);
+
+    void AssignVector(Vector& v,
+                      double v1,
+                      double v2,
+                      double v3);
+
+    inline bool IsNear(double x,
+                       double y,
+                       double threshold)
+    {
+      return fabs(x - y) < threshold;
+    }
+
+    bool IsNear(double x,
+                double y);
+
+    inline bool IsCloseToZero(double x)
+    {
+      return IsNear(x, 0.0);
+    }
+
+    void NormalizeVector(Vector& u);
+
+    void CrossProduct(Vector& result,
+                      const Vector& u,
+                      const Vector& v);
+
+    void ProjectPointOntoPlane(Vector& result,
+                               const Vector& point,
+                               const Vector& planeNormal,
+                               const Vector& planeOrigin);
+
+    bool IsParallel(const Vector& u,
+                    const Vector& v);
+
+    bool IsParallelOrOpposite(bool& isOpposite,
+                              const Vector& u,
+                              const Vector& v);
+
+    bool IntersectTwoPlanes(Vector& p,
+                            Vector& direction,
+                            const Vector& origin1,
+                            const Vector& normal1,
+                            const Vector& origin2,
+                            const Vector& normal2);
+
+    bool ClipLineToRectangle(double& x1,  // Coordinates of the clipped line (out)
+                             double& y1,
+                             double& x2,
+                             double& y2,
+                             const double ax,  // Two points defining the line (in)
+                             const double ay,
+                             const double bx,
+                             const double by,
+                             const double& xmin,   // Coordinates of the rectangle (in)
+                             const double& ymin,
+                             const double& xmax,
+                             const double& ymax);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/ISeriesLoader.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,66 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "ParallelSlices.h"
+
+#include "../Orthanc/Core/Images/ImageAccessor.h"
+
+namespace OrthancStone
+{
+  // This class is NOT thread-safe
+  class ISeriesLoader : public IThreadUnsafe
+  {
+  public:
+    virtual ParallelSlices& GetGeometry() = 0;
+
+    virtual Orthanc::PixelFormat GetPixelFormat() = 0;
+
+    virtual unsigned int GetWidth() = 0;
+
+    virtual unsigned int GetHeight() = 0;
+
+    virtual DicomDataset* DownloadDicom(size_t index) = 0;
+
+    // This downloads the frame from Orthanc. The resulting pixel
+    // format must be Grayscale8, Grayscale16, SignedGrayscale16 or
+    // RGB24. Orthanc Stone assumes the conversion of the photometric
+    // interpretation is done by Orthanc.
+    virtual Orthanc::ImageAccessor* DownloadFrame(size_t index) = 0;
+
+    virtual Orthanc::ImageAccessor* DownloadJpegFrame(size_t index,
+                                                      unsigned int quality) = 0;
+
+    virtual bool IsJpegAvailable() = 0;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/IThreadSafety.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,68 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 OrthancStone
+{
+  /**
+   * Dummy interface to explicitely tag the interfaces whose derived
+   * class must be thread-safe. The different methods of such classes
+   * might be simlultaneously invoked by several threads, and should
+   * be properly protected by mutexes.
+   **/
+  class IThreadSafe : public boost::noncopyable
+  {
+  public:
+    virtual ~IThreadSafe()
+    {
+    }
+  };
+
+
+  /**
+   * Dummy interface to explicitely tag the interfaces that are NOT
+   * expected to be thread-safe. The Orthanc Stone framework ensures
+   * that at most one method of such classes will be invoked at a
+   * given time. Such classes are automatically protected by the
+   * Orthanc Stone framework wherever required.
+   **/
+  class IThreadUnsafe : public boost::noncopyable
+  {
+  public:
+    virtual ~IThreadUnsafe()
+    {
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/ObserversRegistry.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,136 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "../Orthanc/Core/OrthancException.h"
+
+#include <boost/noncopyable.hpp>
+#include <boost/thread/mutex.hpp>
+#include <set>
+
+namespace OrthancStone
+{
+  template <
+    typename Source,
+    typename Observer = typename Source::IChangeObserver
+    >
+  class ObserversRegistry : public boost::noncopyable
+  {
+  private:
+    struct ChangeFunctor : public boost::noncopyable
+    {
+      void operator() (Observer& observer,
+                       const Source& source)
+      {
+        observer.NotifyChange(source);
+      }
+    };
+
+    typedef std::set<Observer*>  Observers;
+
+    boost::mutex  mutex_;
+    Observers     observers_;
+    bool          empty_;
+
+  public:
+    ObserversRegistry() : empty_(true)
+    {
+    }
+
+    template <typename Functor>
+    void Notify(const Source* source,
+                Functor& functor)
+    {
+      if (empty_)
+      {
+        // Optimization to avoid the unnecessary locking of the mutex
+        return;
+      }
+
+      if (source == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+      }
+
+      boost::mutex::scoped_lock lock(mutex_);
+
+      for (typename Observers::const_iterator observer = observers_.begin();
+           observer != observers_.end(); ++observer)
+      {
+        functor(**observer, *source);
+      }
+    }
+
+    template <typename Functor>
+    void Notify(const Source* source)
+    {
+      // Use the default functor
+      Functor functor;
+      Notify(source, functor);
+    }
+
+    void NotifyChange(const Source* source)
+    {
+      Notify<ChangeFunctor>(source);
+    }
+
+    void Register(Observer& observer)
+    {
+      empty_ = false;
+
+      boost::mutex::scoped_lock lock(mutex_);
+      observers_.insert(&observer);
+    }
+
+    void Unregister(Observer& observer)
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+      observers_.erase(&observer);
+
+      if (observers_.empty())
+      {
+        empty_ = true;
+      }
+    }
+
+    bool IsEmpty()
+    {
+#if 0
+      boost::mutex::scoped_lock lock(mutex_);
+      return observers_.empty();
+#else
+      return empty_;
+#endif
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/OrthancSeriesLoader.cpp	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,444 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "OrthancSeriesLoader.h"
+
+#include "../Messaging/MessagingToolbox.h"
+#include "../Orthanc/Core/Images/Image.h"
+#include "../Orthanc/Core/Images/ImageProcessing.h"
+#include "../Orthanc/Core/Logging.h"
+#include "../Orthanc/Core/OrthancException.h"
+#include "DicomFrameConverter.h"
+
+namespace OrthancStone
+{
+  class OrthancSeriesLoader::Slice : public boost::noncopyable
+  {
+  private:
+    std::string     instanceId_;
+    SliceGeometry   geometry_;
+    double          projectionAlongNormal_;
+
+  public:
+    Slice(const std::string& instanceId,
+          const std::string& imagePositionPatient,
+          const std::string& imageOrientationPatient) :
+      instanceId_(instanceId),
+      geometry_(imagePositionPatient, imageOrientationPatient)
+    {
+    }
+
+    const std::string GetInstanceId() const
+    {
+      return instanceId_;
+    }
+
+    const SliceGeometry& GetGeometry() const
+    {
+      return geometry_;
+    }
+
+    void SetNormal(const Vector& normal)
+    {
+      projectionAlongNormal_ = boost::numeric::ublas::inner_prod(geometry_.GetOrigin(), normal);
+    }
+
+    double GetProjectionAlongNormal() const
+    {
+      return projectionAlongNormal_;
+    }
+  };
+
+
+  class OrthancSeriesLoader::SetOfSlices : public boost::noncopyable
+  {
+  private:
+    std::vector<Slice*>  slices_;
+
+    struct Comparator
+    {
+      bool operator() (const Slice* const a,
+                       const Slice* const b) const
+      {
+        return a->GetProjectionAlongNormal() < b->GetProjectionAlongNormal();
+      }
+    };
+
+  public:
+    ~SetOfSlices()
+    {
+      for (size_t i = 0; i < slices_.size(); i++)
+      {
+        assert(slices_[i] != NULL);
+        delete slices_[i];
+      }
+    }
+
+    void Reserve(size_t size)
+    {
+      slices_.reserve(size);
+    }
+
+    void AddSlice(const std::string& instanceId,
+                  const std::string& imagePositionPatient,
+                  const std::string& imageOrientationPatient)
+    {
+      slices_.push_back(new Slice(instanceId, imagePositionPatient, imageOrientationPatient));
+    }
+
+    size_t GetSliceCount() const
+    {
+      return slices_.size();
+    }
+
+    const Slice& GetSlice(size_t index) const
+    {
+      assert(slices_[index] != NULL);
+      return *slices_[index];
+    }
+      
+    void Sort(const Vector& normal)
+    {
+      for (size_t i = 0; i < slices_.size(); i++)
+      {
+        slices_[i]->SetNormal(normal);
+      }
+
+      Comparator comparator;
+      std::sort(slices_.begin(), slices_.end(), comparator);
+    }
+
+    void LoadSeriesFast(IOrthancConnection&  orthanc,
+                        const std::string& series)
+    {
+      // Retrieve the orientation of this series
+      Json::Value info;
+      MessagingToolbox::RestApiGet(info, orthanc, "/series/" + series);
+
+      if (info.type() != Json::objectValue ||
+          !info.isMember("MainDicomTags") ||
+          info["MainDicomTags"].type() != Json::objectValue ||
+          !info["MainDicomTags"].isMember("ImageOrientationPatient") ||
+          info["MainDicomTags"]["ImageOrientationPatient"].type() != Json::stringValue)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+      }
+
+      std::string imageOrientationPatient = info["MainDicomTags"]["ImageOrientationPatient"].asString();
+
+
+      // Retrieve the Orthanc ID of all the instances of this series
+      Json::Value instances;
+      MessagingToolbox::RestApiGet(instances, orthanc, "/series/" + series + "/instances");
+
+      if (instances.type() != Json::arrayValue)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+      }
+
+      if (instances.size() == 0)
+      {
+        LOG(ERROR) << "This series is empty";
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
+      }
+
+
+      // Retrieve the DICOM tags of all the instances
+      std::vector<std::string> instancesId;
+
+      instancesId.resize(instances.size());
+      Reserve(instances.size());
+
+      for (Json::Value::ArrayIndex i = 0; i < instances.size(); i++)
+      {
+        if (instances[i].type() != Json::objectValue ||
+            !instances[i].isMember("ID") ||
+            !instances[i].isMember("MainDicomTags") ||
+            instances[i]["ID"].type() != Json::stringValue ||
+            instances[i]["MainDicomTags"].type() != Json::objectValue ||
+            !instances[i]["MainDicomTags"].isMember("ImagePositionPatient") ||
+            instances[i]["MainDicomTags"]["ImagePositionPatient"].type() != Json::stringValue)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+        }
+        else
+        {
+          instancesId[i] = instances[i]["ID"].asString();
+          AddSlice(instancesId[i], 
+                   instances[i]["MainDicomTags"]["ImagePositionPatient"].asString(),
+                   imageOrientationPatient);
+        }
+      }
+
+      assert(GetSliceCount() == instances.size());
+    }
+
+      
+    void LoadSeriesSafe(IOrthancConnection& orthanc,
+                        const std::string& seriesId)
+    {
+      Json::Value series;
+      MessagingToolbox::RestApiGet(series, orthanc, "/series/" + seriesId + "/instances-tags?simplify");
+
+      if (series.type() != Json::objectValue)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+      }
+
+      if (series.size() == 0)
+      {
+        LOG(ERROR) << "This series is empty";
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
+      }
+
+      Json::Value::Members instances = series.getMemberNames();
+
+      Reserve(instances.size());
+
+      for (Json::Value::ArrayIndex i = 0; i < instances.size(); i++)
+      {
+        const Json::Value& tags = series[instances[i]];
+
+        if (tags.type() != Json::objectValue ||
+            !tags.isMember("ImagePositionPatient") ||
+            !tags.isMember("ImageOrientationPatient") ||
+            tags["ImagePositionPatient"].type() != Json::stringValue ||
+            tags["ImageOrientationPatient"].type() != Json::stringValue)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+        }
+        else
+        {
+          AddSlice(instances[i], 
+                   tags["ImagePositionPatient"].asString(),
+                   tags["ImageOrientationPatient"].asString());
+        }
+      }
+
+      assert(GetSliceCount() == instances.size());
+    }
+
+      
+    void SelectNormal(Vector& normal) const
+    {
+      std::vector<Vector>  normalCandidates;
+      std::vector<unsigned int>  normalCount;
+
+      bool found = false;
+
+      for (size_t i = 0; !found && i < GetSliceCount(); i++)
+      {
+        const Vector& normal = GetSlice(i).GetGeometry().GetNormal();
+
+        bool add = true;
+        for (size_t j = 0; add && j < normalCandidates.size(); j++)  // (*)
+        {
+          if (GeometryToolbox::IsParallel(normal, normalCandidates[j]))
+          {
+            normalCount[j] += 1;
+            add = false;
+          }
+        }
+
+        if (add)
+        {
+          if (normalCount.size() > 2)
+          {
+            // To get linear-time complexity in (*). This heuristics
+            // allows the series to have one single frame that is
+            // not parallel to the others (such a frame could be a
+            // generated preview)
+            found = false;
+          }
+          else
+          {
+            normalCandidates.push_back(normal);
+            normalCount.push_back(1);
+          }
+        }
+      }
+
+      for (size_t i = 0; !found && i < normalCandidates.size(); i++)
+      {
+        unsigned int count = normalCount[i];
+        if (count == GetSliceCount() ||
+            count + 1 == GetSliceCount())
+        {
+          normal = normalCandidates[i];
+          found = true;
+        }
+      }
+
+      if (!found)
+      {
+        LOG(ERROR) << "Cannot select a normal that is shared by most of the slices of this series";
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);          
+      }
+    }
+
+
+    void FilterNormal(const Vector& normal)
+    {
+      size_t pos = 0;
+
+      for (size_t i = 0; i < slices_.size(); i++)
+      {
+        if (GeometryToolbox::IsParallel(normal, slices_[i]->GetGeometry().GetNormal()))
+        {
+          // This slice is compatible with the selected normal
+          slices_[pos] = slices_[i];
+          pos += 1;
+        }
+        else
+        {
+          delete slices_[i];
+          slices_[i] = NULL;
+        }
+      }
+
+      slices_.resize(pos);
+    }
+  };
+
+
+
+  OrthancSeriesLoader::OrthancSeriesLoader(IOrthancConnection& orthanc,
+                                           const std::string& series) :
+    orthanc_(orthanc),
+    slices_(new SetOfSlices)
+  {
+    /**
+     * The function "LoadSeriesFast()" might now behave properly if
+     * some slice has some outsider value for its normal, which
+     * happens sometimes on reprojected series (e.g. coronal and
+     * sagittal of Delphine). Don't use it.
+     **/
+
+    slices_->LoadSeriesSafe(orthanc, series);
+    
+    Vector normal;
+    slices_->SelectNormal(normal);
+    slices_->FilterNormal(normal);
+    slices_->Sort(normal);
+
+    if (slices_->GetSliceCount() == 0)  // Sanity check
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+
+    for (size_t i = 0; i < slices_->GetSliceCount(); i++)
+    {
+      assert(GeometryToolbox::IsParallel(normal, slices_->GetSlice(i).GetGeometry().GetNormal()));
+      geometry_.AddSlice(slices_->GetSlice(i).GetGeometry());
+    }
+
+    std::auto_ptr<DicomDataset> dataset(new DicomDataset(orthanc_, slices_->GetSlice(0).GetInstanceId()));
+    if (!dataset->HasTag(DICOM_TAG_ROWS) ||
+        !dataset->HasTag(DICOM_TAG_COLUMNS))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentTag);
+    }
+
+    DicomFrameConverter converter;
+    converter.ReadParameters(*dataset);
+
+    format_ = converter.GetExpectedPixelFormat();
+    width_ = dataset->GetUnsignedIntegerValue(DICOM_TAG_COLUMNS);
+    height_ = dataset->GetUnsignedIntegerValue(DICOM_TAG_ROWS);
+  }
+    
+
+  DicomDataset* OrthancSeriesLoader::DownloadDicom(size_t index)
+  {
+    std::auto_ptr<DicomDataset> dataset(new DicomDataset(orthanc_, slices_->GetSlice(index).GetInstanceId()));
+
+    if (dataset->HasTag(DICOM_TAG_NUMBER_OF_FRAMES) &&
+        dataset->GetUnsignedIntegerValue(DICOM_TAG_NUMBER_OF_FRAMES) != 1)
+    {
+      LOG(ERROR) << "One instance in this series has more than 1 frame";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);          
+    }
+
+    return dataset.release();
+  }
+
+
+  void OrthancSeriesLoader::CheckFrame(const Orthanc::ImageAccessor& frame) const
+  {
+    if (frame.GetFormat() != format_ ||
+        frame.GetWidth() != width_ ||
+        frame.GetHeight() != height_)
+    {
+      LOG(ERROR) << "The parameters of this series vary accross its slices";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);          
+    }
+  }
+
+
+  Orthanc::ImageAccessor* OrthancSeriesLoader::DownloadFrame(size_t index)
+  {
+    const Slice& slice = slices_->GetSlice(index);
+
+    std::auto_ptr<Orthanc::ImageAccessor> frame
+      (MessagingToolbox::DecodeFrame(orthanc_, slice.GetInstanceId(), 0, format_));
+
+    if (frame.get() != NULL)
+    {
+      CheckFrame(*frame);
+    }
+
+    return frame.release();
+  }
+
+
+  Orthanc::ImageAccessor* OrthancSeriesLoader::DownloadJpegFrame(size_t index,
+                                                                 unsigned int quality)
+  {
+    const Slice& slice = slices_->GetSlice(index);
+
+    std::auto_ptr<Orthanc::ImageAccessor> frame
+      (MessagingToolbox::DecodeJpegFrame(orthanc_, slice.GetInstanceId(), 0, quality, format_));
+
+    if (frame.get() != NULL)
+    {
+      CheckFrame(*frame);
+    }
+
+    return frame.release();
+  }
+
+
+  bool OrthancSeriesLoader::IsJpegAvailable()
+  {
+    return MessagingToolbox::HasWebViewerInstalled(orthanc_);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/OrthancSeriesLoader.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,93 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "ISeriesLoader.h"
+
+#include <boost/shared_ptr.hpp>
+
+namespace OrthancStone
+{
+  // This class is NOT thread-safe
+  // It sorts the slices from a given series, give access to their
+  // geometry and individual frames, making the assumption that there
+  // is a single frame in each instance of the series
+  class OrthancSeriesLoader : public ISeriesLoader
+  {
+  private:
+    class Slice;
+    class SetOfSlices;
+
+    IOrthancConnection&             orthanc_;
+    boost::shared_ptr<SetOfSlices>  slices_;
+    ParallelSlices                  geometry_;
+    Orthanc::PixelFormat            format_;
+    unsigned int                    width_;
+    unsigned int                    height_;
+
+    void CheckFrame(const Orthanc::ImageAccessor& frame) const;
+
+  public:
+    OrthancSeriesLoader(IOrthancConnection& orthanc,
+                        const std::string& series);
+    
+    virtual Orthanc::PixelFormat GetPixelFormat()
+    {
+      return format_;
+    }
+
+    virtual ParallelSlices& GetGeometry()
+    {
+      return geometry_;
+    }
+
+    virtual unsigned int GetWidth()
+    {
+      return width_;
+    }
+
+    virtual unsigned int GetHeight()
+    {
+      return height_;
+    }
+
+    virtual DicomDataset* DownloadDicom(size_t index);
+
+    virtual Orthanc::ImageAccessor* DownloadFrame(size_t index);
+
+    virtual Orthanc::ImageAccessor* DownloadJpegFrame(size_t index,
+                                                      unsigned int quality);
+
+    virtual bool IsJpegAvailable();
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/ParallelSlices.cpp	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,158 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "ParallelSlices.h"
+
+#include "../Orthanc/Core/Logging.h"
+#include "../Orthanc/Core/OrthancException.h"
+
+namespace OrthancStone
+{
+  ParallelSlices::ParallelSlices()
+  {
+    GeometryToolbox::AssignVector(normal_, 0, 0, 1);
+  }
+
+
+  ParallelSlices::ParallelSlices(const ParallelSlices& other)
+  {
+    normal_ = other.normal_;
+
+    slices_.resize(other.slices_.size());
+
+    for (size_t i = 0; i < slices_.size(); i++)
+    {
+      assert(other.slices_[i] != NULL);
+      slices_[i] = new SliceGeometry(*other.slices_[i]);
+    }
+  }
+
+
+  ParallelSlices::~ParallelSlices()
+  {
+    for (size_t i = 0; i < slices_.size(); i++)
+    {
+      if (slices_[i] != NULL)
+      {
+        delete slices_[i];
+        slices_[i] = NULL;
+      }
+    }
+  }
+
+
+  void ParallelSlices::AddSlice(const SliceGeometry& slice)
+  {
+    if (slices_.empty())
+    {
+      normal_ = slice.GetNormal();
+      slices_.push_back(new SliceGeometry(slice));
+    }
+    else if (GeometryToolbox::IsParallel(slice.GetNormal(), normal_))
+    {
+      slices_.push_back(new SliceGeometry(slice));
+    }
+    else
+    {
+      LOG(ERROR) << "Trying to add a slice that is not parallel to the previous ones";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+  }
+
+
+  void ParallelSlices::AddSlice(const Vector& origin,
+                                const Vector& axisX,
+                                const Vector& axisY)
+  {
+    SliceGeometry slice(origin, axisX, axisY);
+    AddSlice(slice);
+  }
+
+
+  const SliceGeometry& ParallelSlices::GetSlice(size_t index) const
+  {
+    if (index >= slices_.size())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      return *slices_[index];
+    }
+  }
+
+
+  bool ParallelSlices::ComputeClosestSlice(size_t& closestSlice,
+                                           double& closestDistance,
+                                           const Vector& origin) const
+  {
+    if (slices_.empty())
+    {
+      return false;
+    }
+
+    double reference = boost::numeric::ublas::inner_prod(origin, normal_);
+
+    closestSlice = 0;
+    closestDistance = std::numeric_limits<double>::infinity();
+
+    for (size_t i = 0; i < slices_.size(); i++)
+    {
+      double distance = fabs(boost::numeric::ublas::inner_prod(slices_[i]->GetOrigin(), normal_) - reference);
+
+      if (distance < closestDistance)
+      {
+        closestSlice = i;
+        closestDistance = distance;
+      }
+    }
+
+    return true;
+  }
+
+
+  ParallelSlices* ParallelSlices::Reverse() const
+  {
+    std::auto_ptr<ParallelSlices> reversed(new ParallelSlices);
+
+    for (size_t i = slices_.size(); i > 0; i--)
+    {
+      const SliceGeometry& slice = *slices_[i - 1];
+
+      reversed->AddSlice(slice.GetOrigin(),
+                         -slice.GetAxisX(),
+                         slice.GetAxisY());
+    }
+
+    return reversed.release();
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/ParallelSlices.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,79 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "SliceGeometry.h"
+
+namespace OrthancStone
+{
+  // This class is NOT thread-safe
+  class ParallelSlices
+  {
+  private:
+    Vector                       normal_;
+    std::vector<SliceGeometry*>  slices_;
+    
+    ParallelSlices& operator= (const ParallelSlices& other);  // Forbidden
+
+  public:
+    ParallelSlices();
+
+    ParallelSlices(const ParallelSlices& other);
+
+    ~ParallelSlices();
+
+    const Vector& GetNormal() const
+    {
+      return normal_;
+    }
+
+    void AddSlice(const SliceGeometry& slice);
+
+    void AddSlice(const Vector& origin,
+                  const Vector& axisX,
+                  const Vector& axisY);
+
+    size_t GetSliceCount() const
+    {
+      return slices_.size();
+    }
+
+    const SliceGeometry& GetSlice(size_t index) const;
+
+    bool ComputeClosestSlice(size_t& closestSlice,
+                             double& closestDistance,
+                             const Vector& origin) const;
+
+    ParallelSlices* Reverse() const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/ParallelSlicesCursor.cpp	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,246 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "ParallelSlicesCursor.h"
+
+#include "../Orthanc/Core/OrthancException.h"
+
+namespace OrthancStone
+{
+  size_t ParallelSlicesCursor::GetDefaultSlice()
+  {
+    if (slices_.get() == NULL)
+    {
+      return 0;
+    }
+    else
+    {
+      return slices_->GetSliceCount() / 2;
+    }
+  }
+
+
+  size_t ParallelSlicesCursor::GetSliceCount()
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    if (slices_.get() == NULL)
+    {
+      return 0;
+    }
+    else
+    {
+      return slices_->GetSliceCount();
+    }
+  }
+
+
+  SliceGeometry ParallelSlicesCursor::GetSlice(size_t slice)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    if (slices_.get() == NULL)
+    {
+      return SliceGeometry();
+    }
+    else
+    {
+      return slices_->GetSlice(slice);
+    }
+  }
+
+
+  void ParallelSlicesCursor::SetGeometry(const ParallelSlices& slices)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    slices_.reset(new ParallelSlices(slices));
+
+    currentSlice_ = GetDefaultSlice();
+  }
+
+
+  SliceGeometry ParallelSlicesCursor::GetCurrentSlice()
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    if (slices_.get() != NULL &&
+        currentSlice_ < slices_->GetSliceCount())
+    {
+      return slices_->GetSlice(currentSlice_);
+    }
+    else
+    {
+      return SliceGeometry();  // No slice is available, return the canonical geometry
+    }
+  }
+
+
+  bool ParallelSlicesCursor::SetDefaultSlice()
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    size_t slice = GetDefaultSlice();
+
+    if (currentSlice_ != slice)
+    {
+      currentSlice_ = slice;
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  bool ParallelSlicesCursor::ApplyOffset(SliceOffsetMode mode,
+                                         int offset)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    if (slices_.get() == NULL)
+    {
+      return false;
+    }
+
+    int count = slices_->GetSliceCount();
+    if (count == 0)
+    {
+      return false;
+    }
+
+    int slice;
+    if (static_cast<int>(currentSlice_) >= count)
+    {
+      slice = count - 1;
+    }
+    else
+    {
+      slice = currentSlice_;
+    }
+
+    switch (mode)
+    {
+      case SliceOffsetMode_Absolute:
+      {
+        slice = offset;
+        break;
+      }
+
+      case SliceOffsetMode_Relative:
+      {
+        slice += offset;
+        break;
+      }
+
+      case SliceOffsetMode_Loop:
+      {
+        slice += offset;
+        while (slice < 0)
+        {
+          slice += count;
+        }
+
+        while (slice >= count)
+        {
+          slice -= count;
+        }
+
+        break;
+      }
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    if (slice < 0)
+    {
+      slice = 0;
+    }
+
+    if (slice >= count)
+    {
+      slice = count - 1;
+    }
+
+    if (slice != static_cast<int>(currentSlice_))
+    {
+      currentSlice_ = static_cast<int>(slice);
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  bool ParallelSlicesCursor::ApplyWheelEvent(MouseWheelDirection direction,
+                                             KeyboardModifiers modifiers)
+  {
+    int offset = (modifiers & KeyboardModifiers_Control ? 10 : 1);
+
+    switch (direction)
+    {
+      case MouseWheelDirection_Down:
+        return ApplyOffset(SliceOffsetMode_Relative, -offset);
+
+      case MouseWheelDirection_Up:
+        return ApplyOffset(SliceOffsetMode_Relative, offset);
+
+      default:
+        return false;
+    }
+  }
+
+
+  bool ParallelSlicesCursor::LookupSliceContainingPoint(const Vector& p)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    size_t slice;
+    double distance;
+
+    if (slices_.get() != NULL &&
+        slices_->ComputeClosestSlice(slice, distance, p))
+    {
+      if (currentSlice_ != slice)
+      {
+        currentSlice_ = slice;
+        return true;
+      }
+    }
+
+    return false;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/ParallelSlicesCursor.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,80 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "ParallelSlices.h"
+#include "../Enumerations.h"
+
+#include <boost/thread/mutex.hpp>
+
+namespace OrthancStone
+{
+  // This class is thread-safe
+  class ParallelSlicesCursor : public boost::noncopyable
+  {
+  private:
+    boost::mutex                   mutex_;
+    std::auto_ptr<ParallelSlices>  slices_;
+    size_t                         currentSlice_;
+
+    size_t GetDefaultSlice();
+
+  public:
+    ParallelSlicesCursor() :
+      currentSlice_(0)
+    {
+    }
+
+    void SetGeometry(const ParallelSlices& slices);
+
+    size_t GetSliceCount();
+
+    SliceGeometry GetSlice(size_t slice);
+
+    SliceGeometry GetCurrentSlice();
+
+    // Returns "true" iff. the slice has actually changed
+    bool SetDefaultSlice();
+
+    // Returns "true" iff. the slice has actually changed
+    bool ApplyOffset(SliceOffsetMode mode,
+                     int offset);
+
+    // Returns "true" iff. the slice has actually changed
+    bool ApplyWheelEvent(MouseWheelDirection direction,
+                         KeyboardModifiers modifiers);
+
+    // Returns "true" iff. the slice has actually changed
+    bool LookupSliceContainingPoint(const Vector& p);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/SharedValue.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,69 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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>
+#include <boost/thread/mutex.hpp>
+
+namespace OrthancStone
+{
+  // A value that is protected by a mutex, in order to be shared by
+  // multiple threads
+  template <typename T>
+  class SharedValue : public boost::noncopyable
+  {
+  private:
+    boost::mutex   mutex_;
+    T              value_;
+    
+  public:
+    class Locker : public boost::noncopyable
+    {
+    private:
+      boost::mutex::scoped_lock  lock_;
+      T&                         value_;
+
+    public:
+      Locker(SharedValue& shared) :
+        lock_(shared.mutex_),
+        value_(shared.value_)
+      {
+      }
+
+      T& GetValue() const
+      {
+        return value_;
+      }
+    };
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/SliceGeometry.cpp	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,165 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "SliceGeometry.h"
+
+#include "GeometryToolbox.h"
+
+#include "../Orthanc/Core/Logging.h"
+#include "../Orthanc/Core/Toolbox.h"
+#include "../Orthanc/Core/OrthancException.h"
+
+namespace OrthancStone
+{
+  void SliceGeometry::CheckAndComputeNormal()
+  {
+    // DICOM expects normal vectors to define the axes: "The row and
+    // column direction cosine vectors shall be normal, i.e., the dot
+    // product of each direction cosine vector with itself shall be
+    // unity."
+    // http://dicom.nema.org/medical/dicom/current/output/chtml/part03/sect_C.7.6.2.html
+    if (!GeometryToolbox::IsNear(boost::numeric::ublas::norm_2(axisX_), 1.0) ||
+        !GeometryToolbox::IsNear(boost::numeric::ublas::norm_2(axisY_), 1.0))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+
+    // The vectors within "Image Orientation Patient" must be
+    // orthogonal, according to the DICOM specification: "The row and
+    // column direction cosine vectors shall be orthogonal, i.e.,
+    // their dot product shall be zero."
+    // http://dicom.nema.org/medical/dicom/current/output/chtml/part03/sect_C.7.6.2.html
+    if (!GeometryToolbox::IsCloseToZero(boost::numeric::ublas::inner_prod(axisX_, axisY_)))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+
+    GeometryToolbox::CrossProduct(normal_, axisX_, axisY_);
+
+    // Just a sanity check, it should be useless by construction
+    assert(GeometryToolbox::IsNear(boost::numeric::ublas::norm_2(normal_), 1.0));
+  }
+
+
+  void SliceGeometry::SetupCanonical()
+  {
+    GeometryToolbox::AssignVector(origin_, 0, 0, 0);
+    GeometryToolbox::AssignVector(axisX_, 1, 0, 0);
+    GeometryToolbox::AssignVector(axisY_, 0, 1, 0);
+    CheckAndComputeNormal();
+  }
+
+
+  SliceGeometry::SliceGeometry(const Vector& origin,
+                               const Vector& axisX,
+                               const Vector& axisY) :
+    origin_(origin),
+    axisX_(axisX),
+    axisY_(axisY)
+  {
+    CheckAndComputeNormal();
+  }
+
+
+  void SliceGeometry::Setup(const std::string& imagePositionPatient,
+                            const std::string& imageOrientationPatient)
+  {
+    std::string tmpPosition = Orthanc::Toolbox::StripSpaces(imagePositionPatient);
+    std::string tmpOrientation = Orthanc::Toolbox::StripSpaces(imageOrientationPatient);
+
+    Vector orientation;
+    if (!GeometryToolbox::ParseVector(origin_, tmpPosition) ||
+        !GeometryToolbox::ParseVector(orientation, tmpOrientation) ||
+        origin_.size() != 3 ||
+        orientation.size() != 6)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+
+    axisX_.resize(3);
+    axisX_[0] = orientation[0];
+    axisX_[1] = orientation[1];
+    axisX_[2] = orientation[2];
+
+    axisY_.resize(3);
+    axisY_[0] = orientation[3];
+    axisY_[1] = orientation[4];
+    axisY_[2] = orientation[5];
+
+    CheckAndComputeNormal();
+  }   
+
+
+  SliceGeometry::SliceGeometry(const DicomDataset& dicom)
+  {
+    if (dicom.HasTag(DICOM_TAG_IMAGE_POSITION_PATIENT) &&
+        dicom.HasTag(DICOM_TAG_IMAGE_ORIENTATION_PATIENT))
+    {
+      Setup(dicom.GetStringValue(DICOM_TAG_IMAGE_POSITION_PATIENT),
+            dicom.GetStringValue(DICOM_TAG_IMAGE_ORIENTATION_PATIENT));
+    }
+    else
+    {
+      SetupCanonical();
+    }
+  }   
+
+
+  Vector SliceGeometry::MapSliceToWorldCoordinates(double x,
+                                                   double y) const
+  {
+    return origin_ + x * axisX_ + y * axisY_;
+  }
+
+
+  double SliceGeometry::ProjectAlongNormal(const Vector& point) const
+  {
+    return boost::numeric::ublas::inner_prod(point, normal_);
+  }
+
+
+  void SliceGeometry::ProjectPoint(double& offsetX,
+                                   double& offsetY,
+                                   const Vector& point) const
+  {
+    // Project the point onto the slice
+    Vector projection;
+    GeometryToolbox::ProjectPointOntoPlane(projection, point, normal_, origin_);
+
+    // As the axes are orthonormal vectors thanks to
+    // CheckAndComputeNormal(), the following dot products give the
+    // offset of the origin of the slice wrt. the origin of the
+    // reference plane https://en.wikipedia.org/wiki/Vector_projection
+    offsetX = boost::numeric::ublas::inner_prod(axisX_, projection - origin_);
+    offsetY = boost::numeric::ublas::inner_prod(axisY_, projection - origin_);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/SliceGeometry.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,102 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "DicomDataset.h"
+
+namespace OrthancStone
+{
+  // Geometry of a 3D plane, NOT thread-safe
+  class SliceGeometry
+  {
+  private:
+    Vector    origin_;
+    Vector    normal_;
+    Vector    axisX_;
+    Vector    axisY_;
+
+    void CheckAndComputeNormal();
+
+    void Setup(const std::string& imagePositionPatient,
+               const std::string& imageOrientationPatient);
+
+    void SetupCanonical();
+
+  public:
+    SliceGeometry()
+    {
+      SetupCanonical();
+    }
+
+    SliceGeometry(const Vector& origin,
+                  const Vector& axisX,
+                  const Vector& axisY);
+
+    SliceGeometry(const DicomDataset& dicom);
+
+    SliceGeometry(const std::string& imagePositionPatient,
+                  const std::string& imageOrientationPatient)
+    {
+      Setup(imagePositionPatient, imageOrientationPatient);
+    }
+
+    const Vector& GetNormal() const
+    {
+      return normal_;
+    }
+
+    const Vector& GetOrigin() const
+    {
+      return origin_;
+    }
+
+    const Vector& GetAxisX() const
+    {
+      return axisX_;
+    }
+
+    const Vector& GetAxisY() const
+    {
+      return axisY_;
+    }
+
+    Vector MapSliceToWorldCoordinates(double x,
+                                      double y) const;
+    
+    double ProjectAlongNormal(const Vector& point) const;
+
+    void ProjectPoint(double& offsetX,
+                      double& offsetY,
+                      const Vector& point) const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/ViewportGeometry.cpp	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,217 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "ViewportGeometry.h"
+
+#include "../Orthanc/Core/Logging.h"
+#include "../Orthanc/Core/OrthancException.h"
+
+#include <boost/math/special_functions/round.hpp>
+
+namespace OrthancStone
+{
+  void ViewportGeometry::ComputeTransform()
+  {
+    // The following lines must be read in reverse order!
+    cairo_matrix_t tmp;
+    
+    // Bring the center of the scene to the center of the view
+    cairo_matrix_init_translate(&transform_, 
+                                panX_ + static_cast<double>(width_) / 2.0, 
+                                panY_ + static_cast<double>(height_) / 2.0);
+
+    // Apply the zoom around (0,0)
+    cairo_matrix_init_scale(&tmp, zoom_, zoom_);
+    cairo_matrix_multiply(&transform_, &tmp, &transform_);
+
+    // Bring the center of the scene to (0,0)
+    cairo_matrix_init_translate(&tmp, -(x1_ + x2_) / 2.0, -(y1_ + y2_) / 2.0);
+    cairo_matrix_multiply(&transform_, &tmp, &transform_);
+  }
+
+
+  ViewportGeometry::ViewportGeometry()
+  {
+    x1_ = 0;
+    y1_ = 0;
+    x2_ = 0;
+    y2_ = 0;
+
+    width_ = 0;
+    height_ = 0;
+
+    zoom_ = 1;
+    panX_ = 0;
+    panY_ = 0;
+
+    ComputeTransform();
+  }
+
+
+  void ViewportGeometry::SetDisplaySize(unsigned int width,
+                                        unsigned int height)
+  {
+    if (width_ != width ||
+        height_ != height)
+    {
+      LOG(INFO) << "New display size: " << width << "x" << height;
+
+      width_ = width;
+      height_ = height;
+
+      ComputeTransform();
+    }
+  }
+
+
+  void ViewportGeometry::SetSceneExtent(double x1,
+                                        double y1,
+                                        double x2,
+                                        double y2)
+  {
+    if (x1 == x1_ &&
+        y1 == y1_ &&
+        x2 == x2_ &&
+        y2 == y2_)
+    {
+      return;
+    }
+    else if (x1 > x2 || 
+             y1 > y2)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      LOG(INFO) << "New scene extent: (" << x1 << "," << y1 << ") => (" << x2 << "," << y2 << ")";
+
+      x1_ = x1;
+      y1_ = y1;
+      x2_ = x2;
+      y2_ = y2;
+
+      ComputeTransform();
+    }
+  }
+
+
+  void ViewportGeometry::GetSceneExtent(double& x1,
+                                        double& y1,
+                                        double& x2,
+                                        double& y2) const
+  {
+    x1 = x1_;
+    y1 = y1_;
+    x2 = x2_;
+    y2 = y2_;
+  }
+
+
+  void ViewportGeometry::MapDisplayToScene(double& sceneX /* out */,
+                                           double& sceneY /* out */,
+                                           double x,
+                                           double y) const
+  {
+    cairo_matrix_t transform = transform_;
+
+    if (cairo_matrix_invert(&transform) != CAIRO_STATUS_SUCCESS)
+    {
+      LOG(ERROR) << "Cannot invert singular matrix";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+
+    sceneX = x;
+    sceneY = y;
+    cairo_matrix_transform_point(&transform, &sceneX, &sceneY);
+  }
+
+
+  void ViewportGeometry::MapSceneToDisplay(int& displayX /* out */,
+                                           int& displayY /* out */,
+                                           double x,
+                                           double y) const
+  {
+    cairo_matrix_transform_point(&transform_, &x, &y);
+
+    displayX = static_cast<int>(boost::math::iround(x));
+    displayY = static_cast<int>(boost::math::iround(y));
+  }
+
+
+  void ViewportGeometry::SetDefaultView()
+  {
+    if (width_ > 0 &&
+        height_ > 0 &&
+        x2_ > x1_ + 10 * std::numeric_limits<double>::epsilon() &&
+        y2_ > y1_ + 10 * std::numeric_limits<double>::epsilon())
+    {
+      double zoomX = static_cast<double>(width_) / (x2_ - x1_);
+      double zoomY = static_cast<double>(height_) / (y2_ - y1_);
+      zoom_ = zoomX < zoomY ? zoomX : zoomY;
+
+      panX_ = 0;
+      panY_ = 0;
+
+      ComputeTransform();
+    }
+  }
+
+
+  void ViewportGeometry::ApplyTransform(CairoContext& context) const
+  {
+    cairo_set_matrix(context.GetObject(), &transform_);
+  }
+
+
+  void ViewportGeometry::GetPan(double& x,
+                                double& y) const
+  {
+    x = panX_;
+    y = panY_;
+  }
+
+
+  void ViewportGeometry::SetPan(double x,
+                                double y)
+  {
+    panX_ = x;
+    panY_ = y;
+    ComputeTransform();
+  }
+
+
+  void ViewportGeometry::SetZoom(double zoom)
+  {
+    zoom_ = zoom;
+    ComputeTransform();
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/ViewportGeometry.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,115 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "../Viewport/CairoContext.h"
+
+namespace OrthancStone
+{
+  // Not thread-safe
+  class ViewportGeometry
+  {
+  private:
+    // Extent of the scene (in world units)
+    double   x1_;
+    double   y1_;
+    double   x2_;
+    double   y2_;
+
+    // Size of the display (in pixels)
+    unsigned int  width_;
+    unsigned int  height_;
+
+    // Zoom/pan
+    double   zoom_;
+    double   panX_;  // In pixels (display units)
+    double   panY_;
+
+    cairo_matrix_t  transform_;  // Scene-to-display transformation
+
+    void ComputeTransform();
+
+  public:
+    ViewportGeometry();
+
+    void SetDisplaySize(unsigned int width,
+                        unsigned int height);
+
+    void SetSceneExtent(double x1,
+                        double y1,
+                        double x2,
+                        double y2);
+
+    void GetSceneExtent(double& x1,
+                        double& y1,
+                        double& x2,
+                        double& y2) const;
+
+    void MapDisplayToScene(double& sceneX /* out */,
+                           double& sceneY /* out */,
+                           double x,
+                           double y) const;
+
+    void MapSceneToDisplay(int& displayX /* out */,
+                           int& displayY /* out */,
+                           double x,
+                           double y) const;
+
+    unsigned int GetDisplayWidth() const
+    {
+      return width_;
+    }
+
+    unsigned int GetDisplayHeight() const
+    {
+      return height_;
+    }
+
+    double GetZoom() const
+    {
+      return zoom_;
+    }
+
+    void SetDefaultView();
+
+    void ApplyTransform(CairoContext& context) const;
+
+    void GetPan(double& x,
+                double& y) const;
+
+    void SetPan(double x,
+                double y);
+
+    void SetZoom(double zoom);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Viewport/CairoContext.cpp	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,70 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "CairoContext.h"
+
+#include "../Orthanc/Core/Logging.h"
+#include "../Orthanc/Core/OrthancException.h"
+
+namespace OrthancStone
+{
+  CairoContext::CairoContext(CairoSurface& surface)
+  {
+    context_ = cairo_create(surface.GetObject());
+    if (!context_)
+    {
+      LOG(ERROR) << "Cannot create Cairo drawing context";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+  }
+
+
+  CairoContext::~CairoContext()
+  {
+    if (context_ != NULL)
+    {
+      cairo_destroy(context_);
+      context_ = NULL;
+    }
+  }
+
+
+  void CairoContext::SetSourceColor(uint8_t red,
+                                    uint8_t green,
+                                    uint8_t blue)
+  {
+    cairo_set_source_rgb(context_,
+                         static_cast<float>(red) / 255.0f,
+                         static_cast<float>(green) / 255.0f,
+                         static_cast<float>(blue) / 255.0f);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Viewport/CairoContext.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,64 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "CairoSurface.h"
+
+namespace OrthancStone
+{
+  // This is a RAII wrapper around the Cairo drawing context
+  class CairoContext : public boost::noncopyable
+  {
+  private:
+    cairo_t* context_;
+
+  public:
+    CairoContext(CairoSurface& surface);
+
+    ~CairoContext();
+
+    cairo_t* GetObject()
+    {
+      return context_;
+    }
+
+    void SetSourceColor(uint8_t red,
+                        uint8_t green,
+                        uint8_t blue);
+
+    void SetSourceColor(const uint8_t color[3])
+    {
+      SetSourceColor(color[0], color[1], color[2]);
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Viewport/CairoFont.cpp	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,76 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "CairoFont.h"
+
+#include "../Orthanc/Core/Logging.h"
+#include "../Orthanc/Core/OrthancException.h"
+
+namespace OrthancStone
+{
+  CairoFont::CairoFont(const char* family,
+                       cairo_font_slant_t slant,
+                       cairo_font_weight_t weight)
+  {
+    font_ = cairo_toy_font_face_create(family, slant, weight);
+    if (font_ == NULL)
+    {
+      LOG(ERROR) << "Unknown font: " << family;
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
+    }
+  }
+
+
+  CairoFont::~CairoFont()
+  {
+    if (font_ != NULL)
+    {
+      cairo_font_face_destroy(font_);
+    }
+  }
+
+
+  void CairoFont::Draw(CairoContext& context,
+                       const std::string& text,
+                       double size)
+  {
+    if (size <= 0)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    cairo_t* cr = context.GetObject();
+    cairo_set_font_face(cr, font_);
+    cairo_set_font_size(cr, size);
+    cairo_show_text(cr, text.c_str());    
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Viewport/CairoFont.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,55 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "CairoContext.h"
+
+namespace OrthancStone
+{
+  class CairoFont : public boost::noncopyable
+  {
+  private:
+    cairo_font_face_t*  font_;
+
+  public:
+    CairoFont(const char* family,
+              cairo_font_slant_t slant,
+              cairo_font_weight_t weight);
+
+    ~CairoFont();
+
+    void Draw(CairoContext& context,
+              const std::string& text,
+              double size);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Viewport/CairoSurface.cpp	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,139 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "CairoSurface.h"
+
+#include "../Orthanc/Core/Logging.h"
+#include "../Orthanc/Core/OrthancException.h"
+#include "../Orthanc/Core/Images/ImageProcessing.h"
+
+namespace OrthancStone
+{
+  void CairoSurface::Release()
+  {
+    if (surface_)
+    {
+      cairo_surface_destroy(surface_);
+      surface_ = NULL;
+    }
+  }
+
+
+  void CairoSurface::Allocate(unsigned int width,
+                              unsigned int height)
+  {
+    Release();
+
+    surface_ = cairo_image_surface_create(CAIRO_FORMAT_RGB24, width, height);
+    if (!surface_)
+    {
+      // Should never occur
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+
+    if (cairo_surface_status(surface_) != CAIRO_STATUS_SUCCESS)
+    {
+      LOG(ERROR) << "Cannot create a Cairo surface";
+      cairo_surface_destroy(surface_);
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+
+    width_ = width;
+    height_ = height;
+    pitch_ = cairo_image_surface_get_stride(surface_);
+    buffer_ = cairo_image_surface_get_data(surface_);
+  }
+
+
+  CairoSurface::CairoSurface(Orthanc::ImageAccessor& accessor)
+  {
+    if (accessor.GetFormat() != Orthanc::PixelFormat_BGRA32)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
+    }      
+
+    width_ = accessor.GetWidth();
+    height_ = accessor.GetHeight();
+    pitch_ = accessor.GetPitch();
+    buffer_ = accessor.GetBuffer();
+
+    surface_ = cairo_image_surface_create_for_data
+      (reinterpret_cast<unsigned char*>(buffer_), CAIRO_FORMAT_RGB24, width_, height_, pitch_);
+    if (!surface_)
+    {
+      // Should never occur
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+
+    if (cairo_surface_status(surface_) != CAIRO_STATUS_SUCCESS)
+    {
+      LOG(ERROR) << "Bad pitch for a Cairo surface";
+      cairo_surface_destroy(surface_);
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+  }
+
+
+  void CairoSurface::SetSize(unsigned int width,
+                             unsigned int height)
+  {
+    if (width_ != width ||
+        height_ != height)
+    {
+      Allocate(width, height);
+    }
+  }
+
+
+  void CairoSurface::Copy(const CairoSurface& other)
+  {
+    Orthanc::ImageAccessor source = other.GetConstAccessor();
+    Orthanc::ImageAccessor target = GetAccessor();
+    Orthanc::ImageProcessing::Copy(target, source);
+  }
+
+
+  Orthanc::ImageAccessor CairoSurface::GetConstAccessor() const
+  {
+    Orthanc::ImageAccessor accessor;
+    accessor.AssignReadOnly(Orthanc::PixelFormat_BGRA32, width_, height_, pitch_, buffer_);
+    return accessor;
+  }
+
+
+  Orthanc::ImageAccessor CairoSurface::GetAccessor()
+  {
+    Orthanc::ImageAccessor accessor;
+    accessor.AssignWritable(Orthanc::PixelFormat_BGRA32, width_, height_, pitch_, buffer_);
+    return accessor;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Viewport/CairoSurface.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,116 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "../Orthanc/Core/Images/ImageAccessor.h"
+
+#include <boost/noncopyable.hpp>
+#include <cairo.h>
+
+namespace OrthancStone
+{
+  class CairoSurface : public boost::noncopyable
+  {
+  private:
+    cairo_surface_t* surface_;
+    unsigned int     width_;
+    unsigned int     height_;
+    unsigned int     pitch_;
+    void*            buffer_;
+
+    void Release();
+
+    void Allocate(unsigned int width,
+                  unsigned int height);
+
+  public:
+    CairoSurface() :
+      surface_(NULL)
+    {
+      Allocate(0, 0);
+    }
+
+    CairoSurface(unsigned int width,
+                 unsigned int height) :
+      surface_(NULL)
+    {
+      Allocate(width, height);
+    }
+
+    CairoSurface(Orthanc::ImageAccessor& accessor);
+
+    ~CairoSurface()
+    {
+      Release();
+    }
+
+    void SetSize(unsigned int width,
+                 unsigned int height);
+
+    void Copy(const CairoSurface& other);
+
+    unsigned int GetWidth() const
+    {
+      return width_;
+    }
+
+    unsigned int GetHeight() const
+    {
+      return height_;
+    }
+
+    unsigned int GetPitch() const
+    {
+      return pitch_;
+    }
+
+    const void* GetBuffer() const
+    {
+      return buffer_;
+    }
+
+    void* GetBuffer()
+    {
+      return buffer_;
+    }
+
+    cairo_surface_t* GetObject()
+    {
+      return surface_;
+    }
+
+    Orthanc::ImageAccessor GetConstAccessor() const;
+
+    Orthanc::ImageAccessor GetAccessor();
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Viewport/IMouseTracker.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,52 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "CairoSurface.h"
+#include "../Toolbox/IThreadSafety.h"
+
+namespace OrthancStone
+{
+  // Not thread-safe
+  class IMouseTracker : public IThreadUnsafe
+  {
+  public:
+    virtual void Render(Orthanc::ImageAccessor& surface) = 0;
+
+    virtual void MouseUp() = 0;
+
+    // Returns "true" iff. the background scene must be repainted
+    virtual void MouseMove(int x, 
+                           int y) = 0;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Viewport/IStatusBar.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,48 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "../Toolbox/IThreadSafety.h"
+
+namespace OrthancStone
+{
+  // This class must be thread-safe
+  class IStatusBar : public IThreadSafe
+  {
+  public:
+    virtual void ClearMessage() = 0;
+
+    virtual void SetMessage(const std::string& message) = 0;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Viewport/IViewport.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,97 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "../Toolbox/IThreadSafety.h"
+#include "IStatusBar.h"
+#include "../Enumerations.h"
+
+#include "../Orthanc/Core/Images/ImageAccessor.h"
+
+namespace OrthancStone
+{
+  // This class must be thread-safe
+  class IViewport : public IThreadSafe
+  {
+  public:
+    class IChangeObserver : public boost::noncopyable
+    {
+    public:
+      virtual ~IChangeObserver()
+      {
+      }
+
+      virtual void NotifyChange(const IViewport& scene) = 0;
+    };
+
+    virtual void Register(IChangeObserver& observer) = 0;
+
+    virtual void Unregister(IChangeObserver& observer) = 0;
+
+    virtual void SetStatusBar(IStatusBar& statusBar) = 0;
+
+    virtual void ResetStatusBar() = 0;
+
+    virtual void Start() = 0;
+
+    virtual void Stop() = 0;
+
+    virtual void SetSize(unsigned int width,
+                         unsigned int height) = 0;
+
+    // The function returns "true" iff. a new frame was rendered
+    virtual bool Render(Orthanc::ImageAccessor& surface) = 0;
+
+    virtual void MouseDown(MouseButton button,
+                           int x,
+                           int y,
+                           KeyboardModifiers modifiers) = 0;
+
+    virtual void MouseUp() = 0;
+
+    virtual void MouseMove(int x, 
+                           int y) = 0;
+
+    virtual void MouseEnter() = 0;
+
+    virtual void MouseLeave() = 0;
+
+    virtual void MouseWheel(MouseWheelDirection direction,
+                            int x,
+                            int y,
+                            KeyboardModifiers modifiers) = 0;
+
+    virtual void KeyPressed(char key,
+                            KeyboardModifiers modifiers) = 0;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Viewport/WidgetViewport.cpp	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,321 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "WidgetViewport.h"
+
+#include "../Orthanc/Core/Images/ImageProcessing.h"
+#include "../Orthanc/Core/OrthancException.h"
+
+namespace OrthancStone
+{
+  void WidgetViewport::UnregisterCentralWidget()
+  {
+    mouseTracker_.reset(NULL);
+
+    if (centralWidget_.get() != NULL)
+    {
+      centralWidget_->Unregister(*this);
+    }
+  }
+
+
+  WidgetViewport::WidgetViewport() :
+    statusBar_(NULL),
+    isMouseOver_(false),
+    lastMouseX_(0),
+    lastMouseY_(0),
+    backgroundChanged_(false),
+    started_(false)
+  {
+  }
+
+
+  void WidgetViewport::SetStatusBar(IStatusBar& statusBar)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    statusBar_ = &statusBar;
+
+    if (centralWidget_.get() != NULL)
+    {
+      centralWidget_->SetStatusBar(statusBar);
+    }
+  }
+
+
+  void WidgetViewport::ResetStatusBar()
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    statusBar_ = NULL;
+
+    if (centralWidget_.get() != NULL)
+    {
+      centralWidget_->ResetStatusBar();
+    }
+  }
+
+
+  IWidget& WidgetViewport::SetCentralWidget(IWidget* widget)
+  {
+    if (started_)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+
+    boost::mutex::scoped_lock lock(mutex_);
+
+    if (widget == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    UnregisterCentralWidget();
+      
+    centralWidget_.reset(widget);
+    centralWidget_->Register(*this);
+
+    if (statusBar_ == NULL)
+    {
+      centralWidget_->ResetStatusBar();
+    }
+    else
+    {
+      centralWidget_->SetStatusBar(*statusBar_);
+    }
+
+    backgroundChanged_ = true;
+
+    return *widget;
+  }
+
+
+  void WidgetViewport::NotifyChange(const IWidget& widget)
+  {
+    backgroundChanged_ = true;
+    observers_.NotifyChange(this);
+  }
+
+
+  void WidgetViewport::Start()
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    if (centralWidget_.get() != NULL)
+    {
+      centralWidget_->Start();
+    }
+
+    started_ = true;
+  }
+
+
+  void WidgetViewport::Stop()
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    started_ = false;
+
+    if (centralWidget_.get() != NULL)
+    {
+      centralWidget_->Stop();
+    }
+  }
+
+
+  void WidgetViewport::SetSize(unsigned int width,
+                               unsigned int height)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    background_.SetSize(width, height);
+
+    if (centralWidget_.get() != NULL)
+    {
+      centralWidget_->SetSize(width, height);
+    }
+
+    observers_.NotifyChange(this);
+  }
+
+
+  bool WidgetViewport::Render(Orthanc::ImageAccessor& surface)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    if (!started_ ||
+        centralWidget_.get() == NULL)
+    {
+      return false;
+    }
+
+    if (backgroundChanged_)
+    {
+      Orthanc::ImageAccessor accessor = background_.GetAccessor();
+      if (!centralWidget_->Render(accessor))
+      {
+        return false;
+      }
+    }
+
+    Orthanc::ImageProcessing::Copy(surface, background_.GetAccessor());
+
+    if (mouseTracker_.get() != NULL)
+    {
+      mouseTracker_->Render(surface);
+    }
+    else if (isMouseOver_)
+    {
+      centralWidget_->RenderMouseOver(surface, lastMouseX_, lastMouseY_);
+    }
+
+    return true;
+  }
+
+
+  void WidgetViewport::MouseDown(MouseButton button,
+                                 int x,
+                                 int y,
+                                 KeyboardModifiers modifiers)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    if (!started_)
+    {
+      return;
+    }
+
+    lastMouseX_ = x;
+    lastMouseY_ = y;
+
+    if (centralWidget_.get() != NULL)
+    {
+      mouseTracker_.reset(centralWidget_->CreateMouseTracker(button, x, y, modifiers));
+    }
+    else
+    {
+      mouseTracker_.reset(NULL);
+    }      
+
+    observers_.NotifyChange(this);;
+  }
+
+
+  void WidgetViewport::MouseUp()
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    if (!started_)
+    {
+      return;
+    }
+
+    if (mouseTracker_.get() != NULL)
+    {
+      mouseTracker_->MouseUp();
+      mouseTracker_.reset(NULL);
+      observers_.NotifyChange(this);;
+    }
+  }
+
+
+  void WidgetViewport::MouseMove(int x, 
+                                 int y) 
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    if (!started_)
+    {
+      return;
+    }
+
+    lastMouseX_ = x;
+    lastMouseY_ = y;
+
+    if (mouseTracker_.get() != NULL)
+    {
+      mouseTracker_->MouseMove(x, y);
+    }
+
+    // The scene must be repainted
+    observers_.NotifyChange(this);
+  }
+
+
+  void WidgetViewport::MouseEnter()
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+    isMouseOver_ = true;
+    observers_.NotifyChange(this);
+  }
+
+
+  void WidgetViewport::MouseLeave()
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+    isMouseOver_ = false;
+    observers_.NotifyChange(this);
+  }
+
+
+  void WidgetViewport::MouseWheel(MouseWheelDirection direction,
+                                  int x,
+                                  int y,
+                                  KeyboardModifiers modifiers)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    if (!started_)
+    {
+      return;
+    }
+
+    if (centralWidget_.get() != NULL &&
+        mouseTracker_.get() == NULL)
+    {
+      centralWidget_->MouseWheel(direction, x, y, modifiers);
+    }
+  }
+
+
+  void WidgetViewport::KeyPressed(char key,
+                                  KeyboardModifiers modifiers)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    if (centralWidget_.get() != NULL &&
+        mouseTracker_.get() == NULL)
+    {
+      centralWidget_->KeyPressed(key, modifiers);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Viewport/WidgetViewport.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,117 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "IViewport.h"
+#include "../Toolbox/ObserversRegistry.h"
+#include "../Widgets/IWidget.h"
+
+namespace OrthancStone
+{
+  class WidgetViewport : 
+    public IViewport,
+    public IWidget::IChangeObserver    
+  {
+  private:
+    boost::mutex                  mutex_;
+    std::auto_ptr<IWidget>        centralWidget_;
+    IStatusBar*                   statusBar_;
+    ObserversRegistry<IViewport>  observers_;
+    std::auto_ptr<IMouseTracker>  mouseTracker_;
+    bool                          isMouseOver_;
+    int                           lastMouseX_;
+    int                           lastMouseY_;
+    CairoSurface                  background_;
+    bool                          backgroundChanged_;
+    bool                          started_;
+
+    void UnregisterCentralWidget();
+
+  public:
+    WidgetViewport();
+
+    virtual ~WidgetViewport()
+    {
+      UnregisterCentralWidget();
+    }
+
+    virtual void SetStatusBar(IStatusBar& statusBar);
+
+    virtual void ResetStatusBar();
+
+    IWidget& SetCentralWidget(IWidget* widget);  // Takes ownership
+
+    virtual void NotifyChange(const IWidget& widget);
+
+    virtual void Register(IViewport::IChangeObserver& observer)
+    {
+      observers_.Register(observer);
+    }
+
+    virtual void Unregister(IViewport::IChangeObserver& observer)
+    {
+      observers_.Unregister(observer);
+    }
+
+    virtual void Start();
+
+    virtual void Stop();
+
+    virtual void SetSize(unsigned int width,
+                         unsigned int height);
+
+    virtual bool Render(Orthanc::ImageAccessor& surface);
+
+    virtual void MouseDown(MouseButton button,
+                           int x,
+                           int y,
+                           KeyboardModifiers modifiers);
+
+    virtual void MouseUp();
+
+    virtual void MouseMove(int x, 
+                           int y);
+
+    virtual void MouseEnter();
+
+    virtual void MouseLeave();
+
+    virtual void MouseWheel(MouseWheelDirection direction,
+                            int x,
+                            int y,
+                            KeyboardModifiers modifiers);
+
+    virtual void KeyPressed(char key,
+                            KeyboardModifiers modifiers);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Volumes/ISliceableVolume.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,61 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "../Toolbox/IThreadSafety.h"
+
+namespace OrthancStone
+{
+  class ISliceableVolume : public IThreadSafe
+  {
+  public:
+    // Must be thread-safe
+    class IChangeObserver : public boost::noncopyable
+    {
+    public:
+      virtual ~IChangeObserver()
+      {
+      }
+
+      virtual void NotifyChange(const ISliceableVolume& volume) = 0;
+    };
+
+    virtual void Register(IChangeObserver& observer) = 0;
+
+    virtual void Unregister(IChangeObserver& observer) = 0;
+
+    virtual void Start() = 0;
+
+    virtual void Stop() = 0;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Volumes/ImageBuffer3D.cpp	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,332 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "ImageBuffer3D.h"
+
+#include "../Orthanc/Core/Images/ImageProcessing.h"
+#include "../Orthanc/Core/OrthancException.h"
+
+namespace OrthancStone
+{
+  Orthanc::ImageAccessor ImageBuffer3D::GetAxialSliceAccessor(unsigned int slice,
+                                                              bool readOnly)
+  {
+    if (slice >= depth_)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    Orthanc::ImageAccessor accessor;
+
+    if (readOnly)
+    {
+      accessor.AssignReadOnly(format_, width_, height_, image_.GetPitch(),
+                              image_.GetConstRow(height_ * (depth_ - 1 - slice)));
+    }
+    else
+    {
+      accessor.AssignWritable(format_, width_, height_, image_.GetPitch(),
+                              image_.GetRow(height_ * (depth_ - 1 - slice)));
+    }
+
+    return accessor;
+  }
+
+
+  Orthanc::ImageAccessor ImageBuffer3D::GetCoronalSliceAccessor(unsigned int slice,
+                                                                bool readOnly)
+  {
+    if (slice >= height_)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    Orthanc::ImageAccessor accessor;
+
+    if (readOnly)
+    {
+      accessor.AssignReadOnly(format_, width_, depth_, image_.GetPitch() * height_,
+                              image_.GetConstRow(slice));
+    }
+    else
+    {
+      accessor.AssignWritable(format_, width_, depth_, image_.GetPitch() * height_,
+                              image_.GetRow(slice));
+    }
+
+    return accessor;
+  }
+
+
+  Orthanc::Image*  ImageBuffer3D::ExtractSagittalSlice(unsigned int slice) const
+  {
+    if (slice >= width_)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    std::auto_ptr<Orthanc::Image> result(new Orthanc::Image(format_, height_, depth_));
+
+    unsigned int bytesPerPixel = Orthanc::GetBytesPerPixel(format_);
+
+    for (unsigned int z = 0; z < depth_; z++)
+    {
+      //uint8_t* target = reinterpret_cast<uint8_t*>(result->GetRow(depth_ - 1 - z));
+      uint8_t* target = reinterpret_cast<uint8_t*>(result->GetRow(z));
+
+      for (unsigned int y = 0; y < height_; y++)
+      {
+        const void* source = (reinterpret_cast<const uint8_t*>(image_.GetConstRow(y + z * height_)) + 
+                              bytesPerPixel * slice);
+
+        memcpy(target, source, bytesPerPixel);
+        target += bytesPerPixel;
+      }
+    }
+
+    return result.release();
+  }
+
+
+  ImageBuffer3D::ImageBuffer3D(Orthanc::PixelFormat format,
+                               unsigned int width,
+                               unsigned int height,
+                               unsigned int depth) :
+    image_(format, width, height * depth),
+    format_(format),
+    width_(width),
+    height_(height),
+    depth_(depth)
+  {
+    GeometryToolbox::AssignVector(voxelDimensions_, 1, 1, 1);
+  }
+
+
+  void ImageBuffer3D::Clear()
+  {
+    WriteLock lock(mutex_);
+    Orthanc::ImageProcessing::Set(image_, 0);
+  }
+
+
+  void ImageBuffer3D::SetAxialGeometry(const SliceGeometry& geometry)
+  {
+    WriteLock lock(mutex_);
+    axialGeometry_ = geometry;
+  }
+
+
+  void ImageBuffer3D::SetVoxelDimensions(double x,
+                                         double y,
+                                         double z)
+  {
+    if (x <= 0 ||
+        y <= 0 ||
+        z <= 0)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    {
+      WriteLock lock(mutex_);
+      GeometryToolbox::AssignVector(voxelDimensions_, x, y, z);
+    }
+  }
+
+
+  Vector ImageBuffer3D::GetVoxelDimensions(VolumeProjection projection)
+  {
+    ReadLock lock(mutex_);
+
+    Vector result;
+    switch (projection)
+    {
+      case VolumeProjection_Axial:
+        result = voxelDimensions_;
+        break;
+
+      case VolumeProjection_Coronal:
+        GeometryToolbox::AssignVector(result, voxelDimensions_[0], voxelDimensions_[2], voxelDimensions_[1]);
+        break;
+
+      case VolumeProjection_Sagittal:
+        GeometryToolbox::AssignVector(result, voxelDimensions_[1], voxelDimensions_[2], voxelDimensions_[0]);
+        break;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    return result;
+  }
+
+
+  void ImageBuffer3D::GetSliceSize(unsigned int& width,
+                                   unsigned int& height,
+                                   VolumeProjection projection)
+  {
+    switch (projection)
+    {
+      case VolumeProjection_Axial:
+        width = width_;
+        height = height_;
+        break;
+
+      case VolumeProjection_Coronal:
+        width = width_;
+        height = depth_;
+        break;
+
+      case VolumeProjection_Sagittal:
+        width = height_;
+        height = depth_;
+        break;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  ParallelSlices* ImageBuffer3D::GetGeometry(VolumeProjection projection)
+  {
+    std::auto_ptr<ParallelSlices> result(new ParallelSlices);
+
+    switch (projection)
+    {
+      case VolumeProjection_Axial:
+        for (unsigned int z = 0; z < depth_; z++)
+        {
+          Vector origin = axialGeometry_.GetOrigin();
+          origin += static_cast<double>(z) * voxelDimensions_[2] * axialGeometry_.GetNormal();
+
+          result->AddSlice(origin, 
+                           axialGeometry_.GetAxisX(), 
+                           axialGeometry_.GetAxisY());
+        }
+        break;
+
+      case VolumeProjection_Coronal:
+        for (unsigned int y = 0; y < height_; y++)
+        {
+          Vector origin = axialGeometry_.GetOrigin();
+          origin += static_cast<double>(y) * voxelDimensions_[1] * axialGeometry_.GetAxisY();
+          origin += static_cast<double>(depth_ - 1) * voxelDimensions_[2] * axialGeometry_.GetNormal();
+
+          result->AddSlice(origin, 
+                           axialGeometry_.GetAxisX(), 
+                           -axialGeometry_.GetNormal());
+        }
+        break;
+
+      case VolumeProjection_Sagittal:
+        for (unsigned int x = 0; x < width_; x++)
+        {
+          Vector origin = axialGeometry_.GetOrigin();
+          origin += static_cast<double>(x) * voxelDimensions_[0] * axialGeometry_.GetAxisX();
+          origin += static_cast<double>(depth_ - 1) * voxelDimensions_[2] * axialGeometry_.GetNormal();
+
+          result->AddSlice(origin, 
+                           axialGeometry_.GetAxisY(), 
+                           -axialGeometry_.GetNormal());
+        }
+        break;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);          
+    }
+
+    return result.release();
+  }
+    
+
+  ImageBuffer3D::SliceReader::SliceReader(ImageBuffer3D& that,
+                                          VolumeProjection projection,
+                                          unsigned int slice) :
+  lock_(that.mutex_)
+  {
+    switch (projection)
+    {
+      case VolumeProjection_Axial:
+        accessor_ = that.GetAxialSliceAccessor(slice, true);
+        break;
+
+      case VolumeProjection_Coronal:
+        accessor_ = that.GetCoronalSliceAccessor(slice, true);
+        break;
+
+      case VolumeProjection_Sagittal:
+        sagittal_.reset(that.ExtractSagittalSlice(slice));
+        accessor_ = *sagittal_;
+        break;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);          
+    }
+  }
+
+
+  void ImageBuffer3D::SliceWriter::Flush()
+  {
+    if (sagittal_.get() != NULL)
+    {
+      // TODO
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);          
+    }
+  }
+
+
+  ImageBuffer3D::SliceWriter::SliceWriter(ImageBuffer3D& that,
+                                          VolumeProjection projection,
+                                          unsigned int slice) :
+  lock_(that.mutex_)
+  {
+    switch (projection)
+    {
+      case VolumeProjection_Axial:
+        accessor_ = that.GetAxialSliceAccessor(slice, false);
+        break;
+
+      case VolumeProjection_Coronal:
+        accessor_ = that.GetCoronalSliceAccessor(slice, false);
+        break;
+
+      case VolumeProjection_Sagittal:
+        sagittal_.reset(that.ExtractSagittalSlice(slice));
+        accessor_ = *sagittal_;
+        break;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);          
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Volumes/ImageBuffer3D.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,163 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "../Toolbox/IThreadSafety.h"
+#include "../Toolbox/SliceGeometry.h"
+#include "../Toolbox/ParallelSlices.h"
+
+#include "../Orthanc/Core/Images/Image.h"
+
+#include <boost/thread/shared_mutex.hpp>
+
+#if defined(_WIN32)
+#  include <boost/thread/win32/mutex.hpp>
+#endif
+
+namespace OrthancStone
+{
+  class ImageBuffer3D : public IThreadSafe
+  {
+  private:
+    typedef boost::shared_mutex          Mutex;
+    typedef boost::unique_lock<Mutex>    WriteLock;
+    typedef boost::shared_lock<Mutex>    ReadLock;
+
+    Mutex                  mutex_;
+    SliceGeometry          axialGeometry_;
+    Vector                 voxelDimensions_;
+    Orthanc::Image         image_;
+    Orthanc::PixelFormat   format_;
+    unsigned int           width_;
+    unsigned int           height_;
+    unsigned int           depth_;
+
+    Orthanc::ImageAccessor GetAxialSliceAccessor(unsigned int slice,
+                                                 bool readOnly);
+
+    Orthanc::ImageAccessor GetCoronalSliceAccessor(unsigned int slice,
+                                                   bool readOnly);
+
+    Orthanc::Image*  ExtractSagittalSlice(unsigned int slice) const;
+
+  public:
+    ImageBuffer3D(Orthanc::PixelFormat format,
+                  unsigned int width,
+                  unsigned int height,
+                  unsigned int depth);
+
+    void Clear();
+
+    // Set the geometry of the first axial slice (i.e. the one whose
+    // depth == 0)
+    void SetAxialGeometry(const SliceGeometry& geometry);
+
+    void SetVoxelDimensions(double x,
+                            double y,
+                            double z);
+
+    Vector GetVoxelDimensions(VolumeProjection projection);
+
+    void GetSliceSize(unsigned int& width,
+                      unsigned int& height,
+                      VolumeProjection projection);
+
+    unsigned int GetWidth() const
+    {
+      return width_;
+    }
+
+    unsigned int GetHeight() const
+    {
+      return height_;
+    }
+
+    unsigned int GetDepth() const
+    {
+      return depth_;
+    }
+
+    Orthanc::PixelFormat GetFormat() const
+    {
+      return format_;
+    }
+
+    ParallelSlices* GetGeometry(VolumeProjection projection);
+    
+
+    class SliceReader : public boost::noncopyable
+    {
+    private:
+      ReadLock                       lock_;
+      Orthanc::ImageAccessor         accessor_;
+      std::auto_ptr<Orthanc::Image>  sagittal_;  // Unused for axial and coronal
+
+    public:
+      SliceReader(ImageBuffer3D& that,
+                  VolumeProjection projection,
+                  unsigned int slice);
+
+      const Orthanc::ImageAccessor& GetAccessor() const
+      {
+        return accessor_;
+      }
+    };
+
+
+    class SliceWriter : public boost::noncopyable
+    {
+    private:
+      WriteLock                      lock_;
+      Orthanc::ImageAccessor         accessor_;
+      std::auto_ptr<Orthanc::Image>  sagittal_;  // Unused for axial and coronal
+
+      void Flush();
+
+    public:
+      SliceWriter(ImageBuffer3D& that,
+                  VolumeProjection projection,
+                  unsigned int slice);
+
+      ~SliceWriter()
+      {
+        Flush();
+      }
+
+      Orthanc::ImageAccessor& GetAccessor()
+      {
+        return accessor_;
+      }
+    };
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Volumes/VolumeImage.cpp	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,412 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "VolumeImage.h"
+
+#include "../Orthanc/Core/Logging.h"
+#include "../Layers/FrameRenderer.h"
+
+namespace OrthancStone
+{
+  void VolumeImage::StoreUpdateTime()
+  {
+    lastUpdate_ = MessagingToolbox::Timestamp();
+  }
+
+
+  void VolumeImage::NotifyChange(bool force)
+  {
+    bool go = false;
+
+    if (force)
+    {
+      go = true;
+    }
+    else
+    {
+      // Don't notify the observers more than 5 times per second
+      MessagingToolbox::Timestamp now;
+      go = (now.GetMillisecondsSince(lastUpdate_) > 200);
+    }
+
+    if (go)
+    {
+      StoreUpdateTime();
+      observers_.NotifyChange(this);
+    }
+  }
+
+
+  void VolumeImage::LoadThread(VolumeImage* that)
+  {
+    while (that->continue_)
+    {
+      bool complete = false;
+      bool done = that->policy_->DownloadStep(complete);
+
+      if (complete)
+      {
+        that->loadingComplete_ = true;
+      }
+
+      if (done)
+      {
+        break;
+      }
+      else
+      {
+        that->NotifyChange(false);
+      }
+    }
+
+    that->NotifyChange(true);
+  }
+
+
+  VolumeImage::VolumeImage(ISeriesLoader* loader) :   // Takes ownership
+    loader_(loader),
+    threads_(1),
+    started_(false),
+    continue_(false),
+    loadingComplete_(false)
+  {
+    if (loader == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+
+    const size_t depth = loader_->GetGeometry().GetSliceCount();
+      
+    if (depth < 2)
+    {
+      // Empty or flat series
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+
+    // TODO Check pixel spacing, slice thickness, and windowing are
+    // constant across slices
+    referenceDataset_.reset(loader->DownloadDicom(0));
+
+    double spacingZ;
+
+    {
+      // Project the origin of the first and last slices onto the normal
+      const SliceGeometry& s1 = loader_->GetGeometry().GetSlice(0);
+      const SliceGeometry& s2 = loader_->GetGeometry().GetSlice(depth - 1);
+      const Vector& normal = loader_->GetGeometry().GetNormal();
+
+      double p1 = boost::numeric::ublas::inner_prod(s1.GetOrigin(), normal);
+      double p2 = boost::numeric::ublas::inner_prod(s2.GetOrigin(), normal);
+
+      spacingZ = fabs(p2 - p1) / static_cast<double>(depth);
+
+      // TODO Check that all slices are evenly distributed
+    }      
+
+    buffer_.reset(new ImageBuffer3D(loader_->GetPixelFormat(), 
+                                    loader_->GetWidth(), 
+                                    loader_->GetHeight(),
+                                    depth));
+    buffer_->Clear();
+    buffer_->SetAxialGeometry(loader_->GetGeometry().GetSlice(0));
+
+    double spacingX, spacingY;
+    referenceDataset_->GetPixelSpacing(spacingX, spacingY);
+    buffer_->SetVoxelDimensions(spacingX, spacingY, spacingZ);
+
+    // These 3 values are only used to speed up the LayerFactory
+    axialGeometry_.reset(buffer_->GetGeometry(VolumeProjection_Axial));
+    coronalGeometry_.reset(buffer_->GetGeometry(VolumeProjection_Coronal));
+    sagittalGeometry_.reset(buffer_->GetGeometry(VolumeProjection_Sagittal));
+  }
+
+
+  VolumeImage::~VolumeImage()
+  {
+    Stop();
+      
+    for (size_t i = 0; i < threads_.size(); i++)
+    {
+      if (threads_[i] != NULL)
+      {
+        delete threads_[i];
+      }
+    }
+  }
+
+
+  void VolumeImage::SetDownloadPolicy(IDownloadPolicy* policy)   // Takes ownership
+  {
+    if (started_)
+    {
+      LOG(ERROR) << "Cannot change the number of threads after a call to Start()";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+
+    policy_.reset(policy);
+  }
+
+
+  void VolumeImage::SetThreadCount(size_t count)
+  {
+    if (started_)
+    {
+      LOG(ERROR) << "Cannot change the number of threads after a call to Start()";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+
+    if (count <= 0)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    threads_.resize(count);
+  }
+
+
+  void VolumeImage::Register(IChangeObserver& observer)
+  {
+    observers_.Register(observer);
+  }
+
+
+  void VolumeImage::Unregister(IChangeObserver& observer)
+  {
+    observers_.Unregister(observer);
+  }
+
+
+  void VolumeImage::Start()
+  {
+    if (started_)
+    {
+      LOG(ERROR) << "Cannot call Start() twice";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+
+    started_ = true;
+    StoreUpdateTime();
+
+    if (policy_.get() != NULL &&
+        threads_.size() > 0)
+    {
+      continue_ = true;
+      policy_->Initialize(*buffer_, *loader_);
+
+      for (size_t i = 0; i < threads_.size(); i++)
+      {
+        assert(threads_[i] == NULL);
+        threads_[i] = new boost::thread(LoadThread, this);
+      }
+    }
+  }
+
+
+  void VolumeImage::Stop()
+  {
+    if (!started_)
+    {
+      LOG(ERROR) << "Cannot call Stop() without calling Start() beforehand";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+
+    if (continue_)
+    {
+      continue_ = false;
+
+      for (size_t i = 0; i < threads_.size(); i++)
+      {
+        if (threads_[i]->joinable())
+        {
+          threads_[i]->join();
+        }
+      }
+
+      assert(policy_.get() != NULL);
+      policy_->Finalize();
+    }
+  }
+
+
+  ParallelSlices* VolumeImage::GetGeometry(VolumeProjection projection,
+                                           bool reverse)
+  {
+    std::auto_ptr<ParallelSlices> slices(buffer_->GetGeometry(projection));
+
+    if (reverse)
+    {
+      return slices->Reverse();
+    }
+    else
+    {
+      return slices.release();
+    }
+  }
+
+
+  bool VolumeImage::DetectProjection(VolumeProjection& projection,
+                                     bool& reverse,
+                                     const SliceGeometry& viewportSlice)
+  {
+    if (GeometryToolbox::IsParallelOrOpposite(reverse, viewportSlice.GetNormal(), axialGeometry_->GetNormal()))
+    {
+      projection = VolumeProjection_Axial;
+      return true;
+    }
+    else if (GeometryToolbox::IsParallelOrOpposite(reverse, viewportSlice.GetNormal(), sagittalGeometry_->GetNormal()))
+    {
+      projection = VolumeProjection_Sagittal;
+      return true;
+    }
+    else if (GeometryToolbox::IsParallelOrOpposite(reverse, viewportSlice.GetNormal(), coronalGeometry_->GetNormal()))
+    {
+      projection = VolumeProjection_Coronal;
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  const ParallelSlices& VolumeImage::GetGeometryInternal(VolumeProjection projection)
+  {
+    switch (projection)
+    {
+      case VolumeProjection_Axial:
+        return *axialGeometry_;
+
+      case VolumeProjection_Sagittal:
+        return *sagittalGeometry_;
+
+      case VolumeProjection_Coronal:
+        return *coronalGeometry_;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  bool VolumeImage::LayerFactory::GetExtent(double& x1,
+                                            double& y1,
+                                            double& x2,
+                                            double& y2,
+                                            const SliceGeometry& viewportSlice)
+  {
+    VolumeProjection projection;
+    bool reverse;
+
+    if (that_.buffer_->GetWidth() == 0 ||
+        that_.buffer_->GetHeight() == 0 ||
+        that_.buffer_->GetDepth() == 0 ||
+        !that_.DetectProjection(projection, reverse, viewportSlice))
+    {
+      return false;
+    }
+    else
+    {
+      Vector spacing = that_.GetVoxelDimensions(projection);
+
+      unsigned int width, height;
+      that_.buffer_->GetSliceSize(width, height, projection);
+
+      // As the slices of the volumic image are arranged in a box,
+      // we only consider one single reference slice (the one with index 0).
+      const SliceGeometry& volumeSlice = that_.GetGeometryInternal(projection).GetSlice(0);
+
+      return FrameRenderer::ComputeFrameExtent(x1, y1, x2, y2, 
+                                               viewportSlice, volumeSlice,
+                                               width, height,
+                                               spacing[0], spacing[1]);
+    }
+  }
+
+
+  ILayerRenderer* VolumeImage::LayerFactory::CreateLayerRenderer(const SliceGeometry& viewportSlice)
+  {
+    VolumeProjection projection;
+    bool reverse;
+    
+    if (that_.buffer_->GetWidth() == 0 ||
+        that_.buffer_->GetHeight() == 0 ||
+        that_.buffer_->GetDepth() == 0 ||
+        !that_.DetectProjection(projection, reverse, viewportSlice))
+    {
+      return NULL;
+    }
+
+    const ParallelSlices& geometry = that_.GetGeometryInternal(projection);
+
+    size_t closest;
+    double distance;
+
+    const Vector spacing = that_.GetVoxelDimensions(projection);
+    const double sliceThickness = spacing[2];
+
+    if (geometry.ComputeClosestSlice(closest, distance, viewportSlice.GetOrigin()) &&
+        distance <= sliceThickness / 2.0)
+    {
+      bool isFullQuality;
+
+      if (projection == VolumeProjection_Axial &&
+          that_.policy_.get() != NULL)
+      {
+        isFullQuality = that_.policy_->IsFullQualityAxial(closest);
+      }
+      else
+      {
+        isFullQuality = that_.IsLoadingComplete();
+      }
+
+      std::auto_ptr<Orthanc::Image> frame;
+      SliceGeometry frameSlice = geometry.GetSlice(closest);
+
+      {
+        ImageBuffer3D::SliceReader reader(*that_.buffer_, projection, closest);
+        frame.reset(Orthanc::Image::Clone(reader.GetAccessor()));
+      }
+
+      return FrameRenderer::CreateRenderer(frame.release(), 
+                                           viewportSlice, 
+                                           frameSlice, 
+                                           *that_.referenceDataset_, 
+                                           spacing[0], spacing[1],
+                                           isFullQuality);
+    }
+    else
+    {
+      return NULL;
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Volumes/VolumeImage.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,159 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "ISliceableVolume.h"
+#include "ImageBuffer3D.h"
+#include "../Toolbox/ISeriesLoader.h"
+#include "../Toolbox/ObserversRegistry.h"
+#include "../Messaging/MessagingToolbox.h"
+#include "../Layers/ILayerRendererFactory.h"
+
+#include <boost/thread.hpp>
+
+namespace OrthancStone
+{
+  class VolumeImage : public ISliceableVolume
+  {
+  public:
+    class IDownloadPolicy : public IThreadSafe
+    {
+    public:
+      virtual void Initialize(ImageBuffer3D& buffer,
+                              ISeriesLoader& loader) = 0;
+
+      virtual void Finalize() = 0;
+
+      // Must return "true" if the thread has completed its task. Pay
+      // attention that this method can be invoked concurrently by
+      // several download threads.
+      virtual bool DownloadStep(bool& complete) = 0;
+
+      virtual bool IsFullQualityAxial(size_t slice) = 0;
+    };
+
+
+  private:
+    std::auto_ptr<ISeriesLoader>         loader_;
+    std::auto_ptr<ImageBuffer3D>         buffer_;
+    std::vector<boost::thread*>          threads_;
+    bool                                 started_;
+    bool                                 continue_;
+    ObserversRegistry<ISliceableVolume>  observers_;
+    bool                                 loadingComplete_;
+    MessagingToolbox::Timestamp          lastUpdate_;
+    std::auto_ptr<DicomDataset>          referenceDataset_;
+    std::auto_ptr<IDownloadPolicy>       policy_;
+    
+    std::auto_ptr<ParallelSlices>        axialGeometry_;
+    std::auto_ptr<ParallelSlices>        coronalGeometry_;
+    std::auto_ptr<ParallelSlices>        sagittalGeometry_;
+
+    void StoreUpdateTime();
+
+    void NotifyChange(bool force);
+
+    static void LoadThread(VolumeImage* that);
+
+    bool DetectProjection(VolumeProjection& projection,
+                          bool& reverse,
+                          const SliceGeometry& viewportSlice);
+
+    const ParallelSlices& GetGeometryInternal(VolumeProjection projection);
+
+  public:
+    VolumeImage(ISeriesLoader* loader);   // Takes ownership
+
+    virtual ~VolumeImage();
+
+    void SetDownloadPolicy(IDownloadPolicy* policy);   // Takes ownership
+
+    void SetThreadCount(size_t count);
+
+    size_t GetThreadCount() const
+    {
+      return threads_.size();
+    }
+
+    virtual void Register(IChangeObserver& observer);
+
+    virtual void Unregister(IChangeObserver& observer);
+
+    virtual void Start();
+
+    virtual void Stop();
+
+    ParallelSlices* GetGeometry(VolumeProjection projection,
+                                bool reverse);
+
+    Vector GetVoxelDimensions(VolumeProjection projection)
+    {
+      return buffer_->GetVoxelDimensions(projection);
+    }
+
+    bool IsLoadingComplete() const
+    {
+      return loadingComplete_;
+    }
+
+    class LayerFactory : public ILayerRendererFactory
+    {
+    private:
+      VolumeImage&  that_;
+
+    public:
+      LayerFactory(VolumeImage& that) :
+        that_(that)
+      {
+      }
+
+      virtual bool HasSourceVolume() const
+      {
+        return true;
+      }
+
+      virtual ISliceableVolume& GetSourceVolume() const
+      {
+        return that_;
+      }
+
+      virtual bool GetExtent(double& x1,
+                             double& y1,
+                             double& x2,
+                             double& y2,
+                             const SliceGeometry& viewportSlice);
+
+      virtual ILayerRenderer* CreateLayerRenderer(const SliceGeometry& viewportSlice);    
+    };
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Volumes/VolumeImagePolicyBase.cpp	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,72 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "VolumeImagePolicyBase.h"
+
+namespace OrthancStone
+{
+  VolumeImagePolicyBase::VolumeImagePolicyBase() : 
+    buffer_(NULL),
+    loader_(NULL)
+  {
+  }
+
+
+  void VolumeImagePolicyBase::Initialize(ImageBuffer3D& buffer,
+                                         ISeriesLoader& loader)
+  {
+    if (buffer_ != NULL ||
+        loader_ != NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+
+    buffer_ = &buffer;
+    loader_ = &loader;
+
+    InitializeInternal(buffer, loader);
+  }
+
+
+  bool VolumeImagePolicyBase::DownloadStep(bool& complete)
+  {
+    if (buffer_ == NULL ||
+        loader_ == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      return DownloadStepInternal(complete, *buffer_, *loader_);                                    
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Volumes/VolumeImagePolicyBase.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,65 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "VolumeImage.h"
+
+namespace OrthancStone
+{
+  class VolumeImagePolicyBase : public VolumeImage::IDownloadPolicy
+  {
+  private:
+    ImageBuffer3D*  buffer_;
+    ISeriesLoader*  loader_;
+
+  protected:
+    virtual void InitializeInternal(ImageBuffer3D& buffer,
+                                    ISeriesLoader& loader) = 0;
+
+    virtual bool DownloadStepInternal(bool& complete,
+                                      ImageBuffer3D& buffer,
+                                      ISeriesLoader& loader) = 0;
+
+  public:
+    VolumeImagePolicyBase();
+
+    virtual void Initialize(ImageBuffer3D& buffer,
+                            ISeriesLoader& loader);
+
+    virtual void Finalize()
+    {
+    }
+
+    virtual bool DownloadStep(bool& complete);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Volumes/VolumeImageProgressivePolicy.cpp	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,213 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "VolumeImageProgressivePolicy.h"
+
+#include "../Toolbox/DownloadStack.h"
+#include "../Orthanc/Core/Images/ImageProcessing.h"
+
+namespace OrthancStone
+{
+  class VolumeImageProgressivePolicy::AxialSlicesScheduler
+  {
+  private:
+    size_t         depth_;
+    DownloadStack  stack_;
+
+  public:
+    AxialSlicesScheduler(size_t depth) :
+      depth_(depth),
+      stack_(3 * depth)    // "3" stands for the number of quality levels
+    {
+      assert(depth > 0);
+    }
+
+    void TagFullPriority(int z,
+                         int neighborhood)
+    {
+      DownloadStack::Writer writer(stack_);
+
+      // Also schedule the neighboring slices for download in medium quality
+      for (int offset = neighborhood; offset >= 1; offset--)
+      {
+        writer.SetTopNodePermissive((z + offset) + depth_ * Quality_Medium);
+        writer.SetTopNodePermissive((z - offset) + depth_ * Quality_Medium);
+      }
+
+      writer.SetTopNodePermissive(z + depth_ * Quality_Full);
+    }
+
+    bool LookupSlice(unsigned int& z,
+                     Quality& quality)
+    {
+      unsigned int value;
+      if (stack_.Pop(value))
+      {
+        z = value % depth_;
+
+        switch (value / depth_)
+        {
+          case 0:
+            quality = Quality_Low;
+            break;
+
+          case 1:
+            quality = Quality_Medium;
+            break;
+
+          case 2:
+            quality = Quality_Full;
+            break;
+
+          default:
+            throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+        }
+
+        return true;
+      }
+      else
+      {
+        return false;
+      }
+    }
+  };
+
+
+  bool VolumeImageProgressivePolicy::IsComplete()
+  {
+    boost::mutex::scoped_lock lock(qualityMutex_);
+        
+    for (size_t i = 0; i < axialSlicesQuality_.size(); i++)
+    {
+      if (axialSlicesQuality_[i] != Quality_Full)
+      {
+        return false;
+      }
+    }
+
+    return true;
+  }
+
+
+  void VolumeImageProgressivePolicy::InitializeInternal(ImageBuffer3D& buffer,
+                                                        ISeriesLoader& loader)
+  {
+    const size_t depth = loader.GetGeometry().GetSliceCount();
+
+    isJpegAvailable_ = loader.IsJpegAvailable();
+
+    axialSlicesQuality_.clear();
+    axialSlicesQuality_.resize(depth, Quality_None);
+    scheduler_.reset(new AxialSlicesScheduler(depth));
+  }
+
+
+  bool VolumeImageProgressivePolicy::DownloadStepInternal(bool& complete,
+                                                          ImageBuffer3D& buffer,
+                                                          ISeriesLoader& loader)
+  {
+    unsigned int z;
+    Quality quality;
+
+    if (!scheduler_->LookupSlice(z, quality))
+    {
+      // There is no more frame to be downloaded. Before stopping,
+      // each loader thread checks whether all the frames have been
+      // downloaded at maximum quality.
+      complete = IsComplete();
+      return true;
+    }
+
+    if (quality != Quality_Full &&
+        !isJpegAvailable_)
+    {
+      // Cannot fulfill this command, as progressive JPEG download
+      // is unavailable (i.e. the Web viewer plugin is unavailable)
+      return false;
+    }
+
+    std::auto_ptr<Orthanc::ImageAccessor> frame;
+
+    try
+    {
+      switch (quality)
+      {
+        case Quality_Low:
+          frame.reset(loader.DownloadJpegFrame(z, 10));
+          break;
+
+        case Quality_Medium:
+          frame.reset(loader.DownloadJpegFrame(z, 90));
+          break;
+
+        case Quality_Full:
+          frame.reset(loader.DownloadFrame(z));
+          break;
+
+        default:
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+    }
+    catch (Orthanc::OrthancException&)
+    {
+      // The Orthanc server cannot decode this instance
+      return false;
+    }
+
+    if (frame.get() != NULL)
+    {
+      boost::mutex::scoped_lock lock(qualityMutex_);
+
+      if (axialSlicesQuality_[z] == Quality_None ||
+          axialSlicesQuality_[z] < quality)
+      {
+        axialSlicesQuality_[z] = quality;
+
+        ImageBuffer3D::SliceWriter writer(buffer, VolumeProjection_Axial, z);
+        Orthanc::ImageProcessing::Convert(writer.GetAccessor(), *frame);
+      }
+    }
+
+    return false;
+  }
+
+
+  bool VolumeImageProgressivePolicy::IsFullQualityAxial(size_t slice)
+  {
+    scheduler_->TagFullPriority(slice, 3);
+
+    {
+      boost::mutex::scoped_lock lock(qualityMutex_);
+      return (axialSlicesQuality_[slice] == Quality_Full);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Volumes/VolumeImageProgressivePolicy.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,70 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "VolumeImagePolicyBase.h"
+
+namespace OrthancStone
+{
+  class VolumeImageProgressivePolicy : public VolumeImagePolicyBase
+  {
+  private:
+    enum Quality
+    {
+      Quality_Low = 0,
+      Quality_Medium = 1,
+      Quality_Full = 2,
+      Quality_None = 3
+    };
+
+    class AxialSlicesScheduler;
+
+    std::auto_ptr<AxialSlicesScheduler>   scheduler_;
+    boost::mutex                          qualityMutex_;
+    std::vector<Quality>                  axialSlicesQuality_;
+    bool                                  isJpegAvailable_;
+
+    bool IsComplete();
+
+  protected:
+    virtual void InitializeInternal(ImageBuffer3D& buffer,
+                                    ISeriesLoader& loader);
+
+    virtual bool DownloadStepInternal(bool& complete,
+                                      ImageBuffer3D& buffer,
+                                      ISeriesLoader& loader);
+
+  public:
+    virtual bool IsFullQualityAxial(size_t slice);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Volumes/VolumeImageSimplePolicy.cpp	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,118 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "VolumeImageSimplePolicy.h"
+
+#include "../Orthanc/Core/Images/ImageProcessing.h"
+
+namespace OrthancStone
+{
+  void VolumeImageSimplePolicy::InitializeInternal(ImageBuffer3D& buffer,
+                                                   ISeriesLoader& loader)
+  {
+    boost::mutex::scoped_lock  lock(mutex_);
+
+    const size_t depth = loader.GetGeometry().GetSliceCount();
+    pendingSlices_.clear();
+
+    for (size_t i = 0; i < depth; i++)
+    {
+      pendingSlices_.insert(i);
+    }
+
+    doneSlices_.clear();
+    doneSlices_.resize(depth, false);
+  }
+
+
+  bool VolumeImageSimplePolicy::DownloadStepInternal(bool& complete,
+                                                     ImageBuffer3D& buffer,
+                                                     ISeriesLoader& loader)
+  {
+    size_t slice;
+
+    {
+      boost::mutex::scoped_lock  lock(mutex_);
+      
+      if (pendingSlices_.empty())
+      {
+        return true;
+      }
+      else
+      {
+        slice = *pendingSlices_.begin();
+        pendingSlices_.erase(slice);
+      }
+    }
+
+    std::auto_ptr<Orthanc::ImageAccessor> frame;
+
+    try
+    {
+      frame.reset(loader.DownloadFrame(slice));
+    }
+    catch (Orthanc::OrthancException&)
+    {
+      // The Orthanc server cannot decode this instance
+      return false;
+    }
+
+    if (frame.get() != NULL)
+    {
+      {
+        ImageBuffer3D::SliceWriter writer(buffer, VolumeProjection_Axial, slice);
+        Orthanc::ImageProcessing::Convert(writer.GetAccessor(), *frame);
+      }
+
+      {
+        boost::mutex::scoped_lock  lock(mutex_);
+
+        doneSlices_[slice] = true;
+
+        if (pendingSlices_.empty())
+        {
+          complete = true;
+          return true;
+        }
+      }
+    }
+      
+    return false;
+  }
+
+
+  bool VolumeImageSimplePolicy::IsFullQualityAxial(size_t slice)
+  {
+    boost::mutex::scoped_lock  lock(mutex_);
+    return doneSlices_[slice];
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Volumes/VolumeImageSimplePolicy.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,57 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "VolumeImagePolicyBase.h"
+
+namespace OrthancStone
+{
+  class VolumeImageSimplePolicy : public VolumeImagePolicyBase
+  {
+  private:
+    boost::mutex       mutex_;
+    std::set<size_t>   pendingSlices_;
+    std::vector<bool>  doneSlices_;
+
+  protected:
+    virtual void InitializeInternal(ImageBuffer3D& buffer,
+                                    ISeriesLoader& loader);
+
+    virtual bool DownloadStepInternal(bool& complete,
+                                      ImageBuffer3D& buffer,
+                                      ISeriesLoader& loader);
+
+  public:
+    virtual bool IsFullQualityAxial(size_t slice);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Widgets/CairoWidget.cpp	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,105 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "CairoWidget.h"
+
+#include "../Orthanc/Core/Images/ImageProcessing.h"
+#include "../Orthanc/Core/OrthancException.h"
+
+namespace OrthancStone
+{
+  static bool IsAligned(const Orthanc::ImageAccessor& target)
+  {
+    // TODO
+    return true;
+  }
+
+
+  void CairoWidget::SetSize(unsigned int width,
+                            unsigned int height)
+  {
+    surface_.SetSize(width, height);
+  }
+  
+
+  bool CairoWidget::Render(Orthanc::ImageAccessor& target)
+  {
+    // Don't call the base class here, as
+    // "ClearBackgroundCairo()" is a faster alternative
+
+    if (IsAligned(target))
+    {
+      CairoSurface surface(target);
+      CairoContext context(surface);
+      ClearBackgroundCairo(context);
+      return RenderCairo(context);
+    }
+    else
+    {
+      CairoContext context(surface_);
+      ClearBackgroundCairo(context);
+
+      if (RenderCairo(context))
+      {
+        Orthanc::ImageProcessing::Copy(target, surface_.GetAccessor());
+        return true;
+      }
+      else
+      {
+        return false;
+      }
+    }
+  }
+
+
+  void CairoWidget::RenderMouseOver(Orthanc::ImageAccessor& target,
+                                    int x,
+                                    int y)
+  {
+    if (IsAligned(target))
+    {
+      CairoSurface surface(target);
+      CairoContext context(surface);
+      RenderMouseOverCairo(context, x, y);
+    }
+    else
+    {
+      Orthanc::ImageAccessor accessor = surface_.GetAccessor();
+      Orthanc::ImageProcessing::Copy(accessor, target);
+
+      CairoContext context(surface_);
+      RenderMouseOverCairo(context, x, y);
+
+      Orthanc::ImageProcessing::Copy(target, accessor);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Widgets/CairoWidget.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,61 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "WidgetBase.h"
+
+namespace OrthancStone
+{
+  class CairoWidget : public WidgetBase
+  {
+  private:
+    CairoSurface   surface_;
+
+  protected:
+    virtual bool RenderCairo(CairoContext& context) = 0;
+    
+    virtual void RenderMouseOverCairo(CairoContext& context,
+                                      int x,
+                                      int y) = 0;
+    
+  public:
+    virtual void SetSize(unsigned int width,
+                         unsigned int height);
+
+    virtual bool Render(Orthanc::ImageAccessor& target);
+
+    virtual void RenderMouseOver(Orthanc::ImageAccessor& target,
+                                 int x,
+                                 int y);  
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Widgets/EmptyWidget.cpp	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,45 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "EmptyWidget.h"
+
+#include "../Orthanc/Core/Images/ImageProcessing.h"
+
+namespace OrthancStone
+{
+  bool EmptyWidget::Render(Orthanc::ImageAccessor& surface)
+  {
+    // Note: This call is slow
+    Orthanc::ImageProcessing::Set(surface, red_, green_, blue_, 255);
+    return true;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Widgets/EmptyWidget.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,117 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "IWidget.h"
+
+namespace OrthancStone
+{
+  /**
+   * This is a test widget that simply fills its surface with an
+   * uniform color.
+   **/
+  class EmptyWidget : public IWidget
+  {
+  private:
+    uint8_t  red_;
+    uint8_t  green_;
+    uint8_t  blue_;
+
+  public:
+    EmptyWidget(uint8_t red,
+                uint8_t green,
+                uint8_t blue) :
+      red_(red),
+      green_(green),
+      blue_(blue)
+    {
+    }
+
+    virtual void SetStatusBar(IStatusBar& statusBar)
+    {
+    }
+
+    virtual void ResetStatusBar()
+    {
+    }
+
+    virtual void Register(IChangeObserver& observer)
+    {
+    }
+
+    virtual void Unregister(IChangeObserver& observer)
+    {
+    }
+
+    virtual void Start()
+    {
+    }
+
+    virtual void Stop()
+    {
+    }
+
+    virtual void SetSize(unsigned int width, 
+                         unsigned int height)
+    {
+    }
+ 
+    virtual bool Render(Orthanc::ImageAccessor& surface);
+
+    virtual IMouseTracker* CreateMouseTracker(MouseButton button,
+                                              int x,
+                                              int y,
+                                              KeyboardModifiers modifiers)
+    {
+      return NULL;
+    }
+
+    virtual void RenderMouseOver(Orthanc::ImageAccessor& target,
+                                 int x,
+                                 int y)
+    {
+    }
+
+    virtual void MouseWheel(MouseWheelDirection direction,
+                            int x,
+                            int y,
+                            KeyboardModifiers modifiers)
+    {
+    }
+
+    virtual void KeyPressed(char key,
+                            KeyboardModifiers modifiers)
+    {
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Widgets/IWidget.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,88 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "../Viewport/IMouseTracker.h"
+#include "../Viewport/IStatusBar.h"
+
+namespace OrthancStone
+{
+  class IWidget : public IThreadUnsafe
+  {
+  public:
+    class IChangeObserver : public boost::noncopyable
+    {
+    public:
+      virtual ~IChangeObserver()
+      {
+      }
+      
+      virtual void NotifyChange(const IWidget& widget) = 0;
+    };
+
+    virtual void SetStatusBar(IStatusBar& statusBar) = 0;
+
+    virtual void ResetStatusBar() = 0;
+
+    virtual void Register(IChangeObserver& observer) = 0;
+
+    virtual void Unregister(IChangeObserver& observer) = 0;
+
+    virtual void Start() = 0;
+
+    virtual void Stop() = 0;
+
+    virtual void SetSize(unsigned int width, 
+                         unsigned int height) = 0;
+ 
+    virtual bool Render(Orthanc::ImageAccessor& surface) = 0;
+
+    virtual IMouseTracker* CreateMouseTracker(MouseButton button,
+                                              int x,
+                                              int y,
+                                              KeyboardModifiers modifiers) = 0;
+
+    virtual void RenderMouseOver(Orthanc::ImageAccessor& target,
+                                 int x,
+                                 int y) = 0;
+
+    virtual void MouseWheel(MouseWheelDirection direction,
+                            int x,
+                            int y,
+                            KeyboardModifiers modifiers) = 0;
+
+    virtual void KeyPressed(char key,
+                            KeyboardModifiers modifiers) = 0;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Widgets/IWorldSceneInteractor.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,75 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "IWorldSceneMouseTracker.h"
+
+#include "../Toolbox/SliceGeometry.h"
+#include "../Toolbox/ViewportGeometry.h"
+#include "../Enumerations.h"
+#include "../Viewport/IStatusBar.h"
+
+namespace OrthancStone
+{
+  class WorldSceneWidget;
+
+  class IWorldSceneInteractor : public IThreadSafe
+  {
+  public:
+    virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& widget,
+                                                        const SliceGeometry& slice,
+                                                        const ViewportGeometry& view,
+                                                        MouseButton button,
+                                                        double x,
+                                                        double y,
+                                                        IStatusBar* statusBar) = 0;
+
+    virtual void MouseOver(CairoContext& context,
+                           WorldSceneWidget& widget,
+                           const SliceGeometry& slice,
+                           const ViewportGeometry& view,
+                           double x,
+                           double y,
+                           IStatusBar* statusBar) = 0;
+
+    virtual void MouseWheel(WorldSceneWidget& widget,
+                            MouseWheelDirection direction,
+                            KeyboardModifiers modifiers,
+                            IStatusBar* statusBar) = 0;
+
+    virtual void KeyPressed(WorldSceneWidget& widget,
+                            char key,
+                            KeyboardModifiers modifiers,
+                            IStatusBar* statusBar) = 0;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Widgets/IWorldSceneMouseTracker.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,51 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "../Toolbox/IThreadSafety.h"
+#include "../Viewport/CairoContext.h"
+
+namespace OrthancStone
+{
+  class IWorldSceneMouseTracker : public IThreadUnsafe
+  {
+  public:
+    virtual void Render(CairoContext& context,
+                        double zoom) = 0;
+    
+    virtual void MouseUp() = 0;
+
+    virtual void MouseMove(double x,
+                           double y) = 0;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Widgets/LayeredSceneWidget.cpp	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,637 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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/>.
+ **/
+
+
+#define _USE_MATH_DEFINES  // To access M_PI in Visual Studio
+#include <cmath>
+
+#include "LayeredSceneWidget.h"
+
+#include "../Orthanc/Core/OrthancException.h"
+
+namespace OrthancStone
+{
+  class LayeredSceneWidget::Renderers : public boost::noncopyable
+  {
+  private:
+    boost::mutex                  mutex_;
+    std::vector<ILayerRenderer*>  renderers_;
+    std::vector<bool>             assigned_;
+      
+    void Assign(size_t index,
+                ILayerRenderer* renderer)
+    {
+      if (index >= renderers_.size())
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+      }
+        
+      if (renderers_[index] != NULL)
+      {
+        delete renderers_[index];
+      }
+
+      renderers_[index] = renderer;
+      assigned_[index] = true;
+    }
+
+  public:
+    Renderers(size_t size)
+    {
+      renderers_.resize(size);
+      assigned_.resize(size, false);
+    }
+
+    ~Renderers()
+    {
+      for (size_t i = 0; i < renderers_.size(); i++)
+      {
+        Assign(i, NULL);
+      }
+    }
+
+    static void Merge(Renderers& target,
+                      Renderers& source)
+    {
+      boost::mutex::scoped_lock lockSource(source.mutex_);
+      boost::mutex::scoped_lock lockTarget(target.mutex_);
+
+      size_t count = target.renderers_.size();
+      if (count != source.renderers_.size())
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+
+      for (size_t i = 0; i < count; i++)
+      {
+        if (source.assigned_[i])
+        {
+          target.Assign(i, source.renderers_[i]);  // Transfers ownership
+          source.renderers_[i] = NULL;
+          source.assigned_[i] = false;
+        }
+      }
+    }
+
+    void SetRenderer(size_t index,
+                     ILayerRenderer* renderer)  // Takes ownership
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+      Assign(index, renderer);
+    }
+
+    bool RenderScene(CairoContext& context,
+                     const ViewportGeometry& view)
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+
+      bool fullQuality = true;
+
+      for (size_t i = 0; i < renderers_.size(); i++)
+      {
+        if (renderers_[i] != NULL &&
+            !renderers_[i]->RenderLayer(context, view))
+        {
+          return false;
+        }
+
+        if (renderers_[i] != NULL &&
+            !renderers_[i]->IsFullQuality())
+        {
+          fullQuality = false;
+        }
+      }
+
+      if (!fullQuality)
+      {
+        double x, y;
+        view.MapDisplayToScene(x, y, static_cast<double>(view.GetDisplayWidth()) / 2.0, 10);
+
+        cairo_t *cr = context.GetObject();
+        cairo_translate(cr, x, y);
+        cairo_arc(cr, 0, 0, 5.0 / view.GetZoom(), 0, 2 * M_PI);
+        cairo_set_line_width(cr, 2.0 / view.GetZoom());
+        cairo_set_source_rgb(cr, 1, 1, 1); 
+        cairo_stroke_preserve(cr);
+        cairo_set_source_rgb(cr, 1, 0, 0); 
+        cairo_fill(cr);
+      }
+
+      return true;
+    }
+
+    void SetLayerStyle(size_t index,
+                       const RenderStyle& style)
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+        
+      if (renderers_[index] != NULL)
+      {
+        renderers_[index]->SetLayerStyle(style);
+      }
+    }
+  };
+
+
+
+  class LayeredSceneWidget::PendingLayers : public boost::noncopyable
+  {
+  private:
+    boost::mutex               mutex_;
+    boost::condition_variable  elementAvailable_;
+    size_t                     layerCount_;
+    std::list<size_t>          queue_;
+    std::vector<bool>          layersToUpdate_;
+    bool                       continue_;
+
+    void TagAllLayers()
+    {
+      queue_.clear();
+
+      for (unsigned int i = 0; i < layerCount_; i++)
+      {
+        queue_.push_back(i);
+        layersToUpdate_[i] = true;
+      }
+
+      if (layerCount_ != 0)
+      {
+        elementAvailable_.notify_one();
+      }
+    }
+      
+  public:
+    PendingLayers() : 
+      layerCount_(0), 
+      continue_(true)
+    {
+    }
+
+    void Stop()
+    {
+      continue_ = false;
+      elementAvailable_.notify_one();
+    }
+
+    void SetLayerCount(size_t count)
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+
+      layerCount_ = count;
+      layersToUpdate_.resize(count);
+
+      TagAllLayers();
+    }
+
+    void InvalidateAllLayers()
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+      TagAllLayers();
+    }
+
+    void InvalidateLayer(size_t layer)
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+
+      if (layer < layerCount_)
+      {
+        if (layersToUpdate_[layer])
+        {
+          // The layer is already scheduled for update, ignore this
+          // invalidation
+        }
+        else
+        {
+          queue_.push_back(layer);
+          layersToUpdate_[layer] = true;
+          elementAvailable_.notify_one();
+        }
+      }
+    }
+
+    bool Dequeue(size_t& layer,
+                 bool& isLast)
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+
+      // WARNING: Do NOT use "timed_wait" on condition variables, as
+      // sleeping is not properly supported by Boost for Google NaCl
+      while (queue_.empty() && 
+             continue_)
+      {
+        elementAvailable_.wait(lock);
+      }
+
+      if (!continue_)
+      {
+        return false;
+      }
+
+      layer = queue_.front();
+      layersToUpdate_[layer] = false;
+      queue_.pop_front();
+
+      isLast = queue_.empty();
+
+      return true;
+    }
+  };
+
+
+  class LayeredSceneWidget::Layer : public ISliceableVolume::IChangeObserver
+  {
+  private:
+    boost::mutex                          mutex_;
+    std::auto_ptr<ILayerRendererFactory>  factory_;
+    PendingLayers&                        layers_;
+    size_t                                index_;
+    std::auto_ptr<RenderStyle>            style_;
+
+  public:
+    Layer(ILayerRendererFactory*  factory,
+          PendingLayers& layers,
+          size_t index) :
+      factory_(factory),
+      layers_(layers),
+      index_(index)
+    {
+      if (factory == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+      }
+    }
+
+    virtual void NotifyChange(const OrthancStone::ISliceableVolume&)
+    {
+      layers_.InvalidateLayer(index_);
+    }
+
+    void Start()
+    {
+      if (factory_->HasSourceVolume())
+      {
+        factory_->GetSourceVolume().Register(*this);
+      }
+    }
+
+    void Stop()
+    {
+      if (factory_->HasSourceVolume())
+      {
+        factory_->GetSourceVolume().Unregister(*this);
+      }
+    }
+
+    bool GetExtent(double& x1,
+                   double& y1,
+                   double& x2,
+                   double& y2,
+                   const SliceGeometry& displaySlice) 
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+      assert(factory_.get() != NULL);
+      return factory_->GetExtent(x1, y1, x2, y2, displaySlice);
+    }
+
+    RenderStyle GetStyle() 
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+
+      if (style_.get() == NULL)
+      {
+        return RenderStyle();
+      }
+      else
+      {
+        return *style_;
+      }
+    }
+
+    void SetStyle(const RenderStyle& style)
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+      style_.reset(new RenderStyle(style));
+    }
+
+
+    ILayerRenderer* CreateRenderer(const SliceGeometry& displaySlice)
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+      assert(factory_.get() != NULL);
+
+      std::auto_ptr<ILayerRenderer> renderer(factory_->CreateLayerRenderer(displaySlice));
+        
+      if (renderer.get() != NULL &&
+          style_.get() != NULL)
+      {
+        renderer->SetLayerStyle(*style_);
+      }
+
+      return renderer.release();
+    }
+  };
+
+
+
+  SliceGeometry LayeredSceneWidget::GetSlice()
+  {
+    boost::mutex::scoped_lock lock(sliceMutex_);
+    return slice_;
+  }
+
+
+  void LayeredSceneWidget::UpdateStep()
+  {
+    size_t layer = 0;
+    bool isLast = true;
+    if (!pendingLayers_->Dequeue(layer, isLast))
+    {
+      return;
+    }
+
+    SliceGeometry slice = GetSlice();
+
+    std::auto_ptr<ILayerRenderer> renderer;
+    renderer.reset(layers_[layer]->CreateRenderer(slice));
+
+    if (renderer.get() != NULL)
+    {
+      pendingRenderers_->SetRenderer(layer, renderer.release());
+    }
+    else
+    {
+      pendingRenderers_->SetRenderer(layer, NULL);
+    }
+
+    if (isLast)
+    {
+      Renderers::Merge(*renderers_, *pendingRenderers_);
+      NotifyChange();
+    }
+  }
+    
+
+  bool LayeredSceneWidget::RenderScene(CairoContext& context,
+                                       const ViewportGeometry& view) 
+  {
+    assert(IsStarted());
+    return renderers_->RenderScene(context, view);
+  }
+
+
+  LayeredSceneWidget::LayeredSceneWidget() 
+  {
+    pendingLayers_.reset(new PendingLayers);
+    SetBackgroundCleared(true);
+  }
+
+
+  LayeredSceneWidget::~LayeredSceneWidget()
+  {
+    for (size_t i = 0; i < layers_.size(); i++)
+    {
+      assert(layers_[i] != NULL);
+      delete layers_[i];
+    }
+  }
+
+
+  void LayeredSceneWidget::GetSceneExtent(double& x1,
+                                          double& y1,
+                                          double& x2,
+                                          double& y2)
+  {
+    boost::mutex::scoped_lock lock(sliceMutex_);
+
+    bool first = true;
+
+    for (size_t i = 0; i < layers_.size(); i++)
+    {
+      double ax, ay, bx, by;
+
+      assert(layers_[i] != NULL);
+      if (layers_[i]->GetExtent(ax, ay, bx, by, slice_))
+      {
+        if (ax > bx)
+        {
+          std::swap(ax, bx);
+        }
+
+        if (ay > by)
+        {
+          std::swap(ay, by);
+        }
+
+        if (first)
+        {
+          x1 = ax;
+          y1 = ay;
+          x2 = bx;
+          y2 = by;
+          first = false;
+        }
+        else
+        {
+          x1 = std::min(x1, ax);
+          y1 = std::min(y1, ay);
+          x2 = std::max(x2, bx);
+          y2 = std::max(y2, by);
+        }
+      }
+    }
+
+    if (first)
+    {
+      x1 = -1;
+      y1 = -1;
+      x2 = 1;
+      y2 = 1;
+    }
+
+    // Ensure the extent is non-empty
+    if (x1 >= x2)
+    {
+      double tmp = x1;
+      x1 = tmp - 0.5;
+      x2 = tmp + 0.5;
+    }
+
+    if (y1 >= y2)
+    {
+      double tmp = y1;
+      y1 = tmp - 0.5;
+      y2 = tmp + 0.5;
+    }
+  }
+
+
+
+  ILayerRendererFactory& LayeredSceneWidget::AddLayer(size_t& layerIndex,
+                                                      ILayerRendererFactory* factory)
+  {
+    if (IsStarted())
+    {
+      // Start() has already been invoked
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+
+    layerIndex = layers_.size();
+    layers_.push_back(new Layer(factory, *pendingLayers_, layers_.size()));
+
+    return *factory;
+  }
+
+
+  void LayeredSceneWidget::AddLayer(ILayerRendererFactory* factory)
+  {
+    size_t layerIndex;  // Ignored
+    AddLayer(layerIndex, factory);
+  }
+
+
+  RenderStyle LayeredSceneWidget::GetLayerStyle(size_t layer)
+  {
+    if (layer >= layers_.size())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    return layers_[layer]->GetStyle();
+  }
+
+
+  void LayeredSceneWidget::SetLayerStyle(size_t layer,
+                                         const RenderStyle& style)
+  {
+    if (layer >= layers_.size())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    layers_[layer]->SetStyle(style);
+
+    if (renderers_.get() != NULL)
+    {
+      renderers_->SetLayerStyle(layer, style);
+    }
+
+    InvalidateLayer(layer);
+  }
+
+
+
+  struct LayeredSceneWidget::SliceChangeFunctor
+  {
+    const SliceGeometry& slice_;
+
+    SliceChangeFunctor(const SliceGeometry& slice) :
+      slice_(slice)
+    {
+    }
+
+    void operator() (ISliceObserver& observer,
+                     const LayeredSceneWidget& source)
+    {
+      observer.NotifySliceChange(source, slice_);
+    }
+  };
+
+
+  void LayeredSceneWidget::SetSlice(const SliceGeometry& slice)
+  {
+    { 
+      boost::mutex::scoped_lock lock(sliceMutex_);
+      slice_ = slice;
+    }
+
+    InvalidateAllLayers();
+
+    SliceChangeFunctor functor(slice);
+    observers_.Notify(this, functor);
+  }
+
+
+  void LayeredSceneWidget::InvalidateLayer(unsigned int layer)
+  {
+    pendingLayers_->InvalidateLayer(layer);
+    //NotifyChange();  // TODO Understand why this makes the SDL engine not update the display subsequently
+  }
+
+
+  void LayeredSceneWidget::InvalidateAllLayers()
+  {
+    pendingLayers_->InvalidateAllLayers();
+    //NotifyChange();  // TODO Understand why this makes the SDL engine not update the display subsequently
+  }
+
+
+  void LayeredSceneWidget::Start()
+  {
+    if (IsStarted())
+    {
+      // Start() has already been invoked
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+
+    for (size_t i = 0; i < layers_.size(); i++)
+    {
+      layers_[i]->Start();
+    }
+
+    renderers_.reset(new Renderers(layers_.size()));
+    pendingRenderers_.reset(new Renderers(layers_.size()));
+
+    pendingLayers_->SetLayerCount(layers_.size());
+
+    WorldSceneWidget::Start();
+  }
+
+
+  void LayeredSceneWidget::Stop()
+  {
+    if (!IsStarted())
+    {
+      // Stop() has already been invoked
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+
+    pendingLayers_->Stop();
+    WorldSceneWidget::Stop();
+
+    renderers_.reset(NULL);
+    pendingRenderers_.reset(NULL);
+
+    for (size_t i = 0; i < layers_.size(); i++)
+    {
+      layers_[i]->Stop();
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Widgets/LayeredSceneWidget.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,135 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "WorldSceneWidget.h"
+
+#include "../Layers/ILayerRendererFactory.h"
+
+
+namespace OrthancStone
+{
+  class LayeredSceneWidget : public WorldSceneWidget
+  {
+  public:
+    // Must be thread-safe
+    class ISliceObserver : public boost::noncopyable
+    {
+    public:
+      virtual ~ISliceObserver()
+      {
+      }
+
+      virtual void NotifySliceChange(const LayeredSceneWidget& source,
+                                     const SliceGeometry& slice) = 0;
+    };
+
+  private:
+    struct SliceChangeFunctor;
+    class Renderers;
+    class PendingLayers;
+    class Layer;
+
+    typedef ObserversRegistry<LayeredSceneWidget, ISliceObserver>  Observers;
+
+    std::vector<Layer*>           layers_;
+    std::auto_ptr<Renderers>      renderers_;
+    std::auto_ptr<PendingLayers>  pendingLayers_;
+    std::auto_ptr<Renderers>      pendingRenderers_;
+    boost::mutex                  sliceMutex_;
+    SliceGeometry                 slice_;
+    Observers                     observers_;
+
+  protected:
+    virtual bool HasUpdateThread() const
+    {
+      return true;
+    }
+
+    virtual void UpdateStep();
+
+    virtual bool RenderScene(CairoContext& context,
+                             const ViewportGeometry& view);
+
+  public:
+    LayeredSceneWidget();
+
+    virtual ~LayeredSceneWidget();
+
+    virtual SliceGeometry GetSlice();
+
+    virtual void GetSceneExtent(double& x1,
+                                double& y1,
+                                double& x2,
+                                double& y2);
+
+    ILayerRendererFactory& AddLayer(size_t& layerIndex,
+                                    ILayerRendererFactory* factory);   // Takes ownership
+
+    // Simpler version for basic use cases
+    void AddLayer(ILayerRendererFactory* factory);   // Takes ownership
+
+    size_t GetLayerCount() const
+    {
+      return layers_.size();
+    }
+
+    RenderStyle GetLayerStyle(size_t layer);
+    
+    void SetLayerStyle(size_t layer,
+                       const RenderStyle& style);
+
+    void SetSlice(const SliceGeometry& slice);
+
+    void InvalidateLayer(unsigned int layer);
+
+    void InvalidateAllLayers();
+
+    virtual void Start();
+
+    virtual void Stop();
+
+    using WorldSceneWidget::Register;
+    using WorldSceneWidget::Unregister;
+
+    void Register(ISliceObserver& observer)
+    {
+      observers_.Register(observer);
+    }
+
+    void Unregister(ISliceObserver& observer)
+    {
+      observers_.Unregister(observer);
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Widgets/LayoutWidget.cpp	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,493 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "LayoutWidget.h"
+
+#include "../Orthanc/Core/Logging.h"
+#include "../Orthanc/Core/OrthancException.h"
+
+#include <boost/math/special_functions/round.hpp>
+
+namespace OrthancStone
+{
+  class LayoutWidget::LayoutMouseTracker : public IMouseTracker
+  {
+  private:
+    std::auto_ptr<IMouseTracker>   tracker_;
+    int                            left_;
+    int                            top_;
+    unsigned int                   width_;
+    unsigned int                   height_;
+
+  public:
+    LayoutMouseTracker(IMouseTracker* tracker,
+                       int left,
+                       int top,
+                       unsigned int width,
+                       unsigned int height) :
+      tracker_(tracker),
+      left_(left),
+      top_(top),
+      width_(width),
+      height_(height)
+    {
+      if (tracker == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+    }
+
+    virtual void Render(Orthanc::ImageAccessor& surface)
+    {
+      Orthanc::ImageAccessor accessor = surface.GetRegion(left_, top_, width_, height_);
+      tracker_->Render(accessor);
+    }
+
+    virtual void MouseUp()
+    {
+      tracker_->MouseUp();
+    }
+
+    virtual void MouseMove(int x, 
+                           int y)
+    {
+      tracker_->MouseMove(x - left_, y - top_);
+    }
+  };
+
+
+  class LayoutWidget::ChildWidget : public boost::noncopyable
+  {
+  private:
+    std::auto_ptr<IWidget>  widget_;
+    int                     left_;
+    int                     top_;
+    unsigned int            width_;
+    unsigned int            height_;
+
+  public:
+    ChildWidget(IWidget* widget) :
+      widget_(widget)
+    {
+      assert(widget != NULL);
+      SetEmpty();
+    }
+
+    IWidget& GetWidget() const
+    {
+      return *widget_;
+    }
+
+    void SetRectangle(unsigned int left, 
+                      unsigned int top,
+                      unsigned int width,
+                      unsigned int height)
+    {
+      left_ = left;
+      top_ = top;
+      width_ = width;
+      height_ = height;
+
+      widget_->SetSize(width, height);
+    }
+
+    void SetEmpty()
+    {
+      SetRectangle(0, 0, 0, 0);
+    }
+
+    bool Contains(int x, 
+                  int y) const
+    {
+      return (x >= left_ && 
+              y >= top_ &&
+              x < left_ + static_cast<int>(width_) &&
+              y < top_ + static_cast<int>(height_));
+    }      
+
+    bool Render(Orthanc::ImageAccessor& target)
+    {
+      if (width_ == 0 ||
+          height_ == 0)
+      {
+        return true;
+      }
+      else 
+      {
+        Orthanc::ImageAccessor accessor = target.GetRegion(left_, top_, width_, height_);
+        return widget_->Render(accessor);
+      }
+    }
+
+    IMouseTracker* CreateMouseTracker(MouseButton button,
+                                      int x,
+                                      int y,
+                                      KeyboardModifiers modifiers)
+    {
+      if (Contains(x, y))
+      {
+        IMouseTracker* tracker = widget_->CreateMouseTracker(button, 
+                                                             x - left_, 
+                                                             y - top_, 
+                                                             modifiers);
+        if (tracker)
+        {
+          return new LayoutMouseTracker(tracker, left_, top_, width_, height_);
+        }
+      }
+
+      return NULL;
+    }
+
+    void RenderMouseOver(Orthanc::ImageAccessor& target,
+                         int x,
+                         int y)
+    {
+      if (Contains(x, y))
+      {
+        Orthanc::ImageAccessor accessor = target.GetRegion(left_, top_, width_, height_);
+
+        widget_->RenderMouseOver(accessor, x - left_, y - top_);
+      }
+    }
+
+    void MouseWheel(MouseWheelDirection direction,
+                    int x,
+                    int y,
+                    KeyboardModifiers modifiers)
+    {
+      if (Contains(x, y))
+      {
+        widget_->MouseWheel(direction, x - left_, y - top_, modifiers);
+      }
+    }
+  };
+
+
+  void LayoutWidget::ComputeChildrenExtents()
+  {
+    if (children_.size() == 0)
+    {
+      return;
+    }
+
+    float internal = static_cast<float>(paddingInternal_);
+
+    if (width_ <= paddingLeft_ + paddingRight_ ||
+        height_ <= paddingTop_ + paddingBottom_)
+    {
+      for (size_t i = 0; i < children_.size(); i++)
+      {
+        children_[i]->SetEmpty();          
+      }
+    }
+    else if (isHorizontal_)
+    {
+      unsigned int padding = paddingLeft_ + paddingRight_ + (children_.size() - 1) * paddingInternal_;
+      float childWidth = ((static_cast<float>(width_) - static_cast<float>(padding)) / 
+                          static_cast<float>(children_.size()));
+        
+      for (size_t i = 0; i < children_.size(); i++)
+      {
+        float left = static_cast<float>(paddingLeft_) + static_cast<float>(i) * (childWidth + internal);
+        float right = left + childWidth;
+
+        if (left >= right)
+        {
+          children_[i]->SetEmpty();
+        }
+        else
+        {
+          children_[i]->SetRectangle(static_cast<unsigned int>(left), 
+                                     paddingTop_, 
+                                     boost::math::iround(right - left),
+                                     height_ - paddingTop_ - paddingBottom_);
+        }
+      }
+    }
+    else
+    {
+      unsigned int padding = paddingTop_ + paddingBottom_ + (children_.size() - 1) * paddingInternal_;
+      float childHeight = ((static_cast<float>(height_) - static_cast<float>(padding)) / 
+                           static_cast<float>(children_.size()));
+        
+      for (size_t i = 0; i < children_.size(); i++)
+      {
+        float top = static_cast<float>(paddingTop_) + static_cast<float>(i) * (childHeight + internal);
+        float bottom = top + childHeight;
+
+        if (top >= bottom)
+        {
+          children_[i]->SetEmpty();
+        }
+        else
+        {
+          children_[i]->SetRectangle(paddingTop_, 
+                                     static_cast<unsigned int>(top), 
+                                     width_ - paddingLeft_ - paddingRight_,
+                                     boost::math::iround(bottom - top));
+        }
+      }
+    }
+
+    NotifyChange(*this);
+  }
+
+
+  void LayoutWidget::UpdateStep()
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+  }
+
+
+  LayoutWidget::LayoutWidget() :
+    isHorizontal_(true),
+    started_(false),
+    width_(0),
+    height_(0),
+    paddingLeft_(0),
+    paddingTop_(0),
+    paddingRight_(0),
+    paddingBottom_(0),
+    paddingInternal_(0)
+  {
+  }
+
+
+  LayoutWidget::~LayoutWidget()
+  {
+    for (size_t i = 0; i < children_.size(); i++)
+    {
+      children_[i]->GetWidget().Unregister(*this);
+      delete children_[i];
+    }
+  }
+
+
+  void LayoutWidget::NotifyChange(const IWidget& widget)
+  {
+    // One of the children has changed
+    WidgetBase::NotifyChange();
+  }
+
+
+  void LayoutWidget::SetHorizontal()
+  {
+    isHorizontal_ = true;
+    ComputeChildrenExtents();
+  }
+
+
+  void LayoutWidget::SetVertical()
+  {
+    isHorizontal_ = false;
+    ComputeChildrenExtents();
+  }
+
+
+  void LayoutWidget::SetPadding(unsigned int left,
+                                unsigned int top,
+                                unsigned int right,
+                                unsigned int bottom,
+                                unsigned int spacing)
+  {
+    paddingLeft_ = left;
+    paddingTop_ = top;
+    paddingRight_ = right;
+    paddingBottom_ = bottom;
+    paddingInternal_ = spacing;
+  }
+    
+
+  void LayoutWidget::SetPadding(unsigned int padding)
+  {
+    paddingLeft_ = padding;
+    paddingTop_ = padding;
+    paddingRight_ = padding;
+    paddingBottom_ = padding;
+    paddingInternal_ = padding;
+  }
+
+
+  IWidget& LayoutWidget::AddWidget(IWidget* widget)  // Takes ownership
+  {
+    if (started_)
+    {
+      LOG(ERROR) << "Cannot add child once Start() has been invoked";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+
+    if (widget == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    if (GetStatusBar() != NULL)
+    {
+      widget->SetStatusBar(*GetStatusBar());
+    }
+    else
+    {
+      widget->ResetStatusBar();
+    }
+
+    children_.push_back(new ChildWidget(widget));
+    widget->Register(*this);
+
+    ComputeChildrenExtents();
+
+    return *widget;
+  }
+
+
+  void LayoutWidget::SetStatusBar(IStatusBar& statusBar)
+  {
+    WidgetBase::SetStatusBar(statusBar);
+
+    for (size_t i = 0; i < children_.size(); i++)
+    {
+      children_[i]->GetWidget().SetStatusBar(statusBar);
+    }
+  }
+
+
+  void LayoutWidget::ResetStatusBar()
+  {
+    WidgetBase::ResetStatusBar();
+
+    for (size_t i = 0; i < children_.size(); i++)
+    {
+      children_[i]->GetWidget().ResetStatusBar();
+    }
+  }  
+
+
+  void LayoutWidget::Start()
+  {
+    for (size_t i = 0; i < children_.size(); i++)
+    {
+      children_[i]->GetWidget().Start();
+    }
+
+    WidgetBase::Start();
+  }
+
+
+  void LayoutWidget::Stop()
+  {
+    WidgetBase::Stop();
+
+    for (size_t i = 0; i < children_.size(); i++)
+    {
+      children_[i]->GetWidget().Stop();
+    }
+  }
+
+
+  void LayoutWidget::SetSize(unsigned int width,
+                             unsigned int height)
+  {
+    width_ = width;
+    height_ = height;
+    ComputeChildrenExtents();
+  }
+
+
+  bool LayoutWidget::Render(Orthanc::ImageAccessor& surface)
+  {
+    if (!WidgetBase::Render(surface))
+    {
+      return false;
+    }
+
+    for (size_t i = 0; i < children_.size(); i++)
+    {
+      if (!children_[i]->Render(surface))
+      {
+        return false;
+      }
+    }
+
+    return true;
+  }
+
+    
+  IMouseTracker* LayoutWidget::CreateMouseTracker(MouseButton button,
+                                                  int x,
+                                                  int y,
+                                                  KeyboardModifiers modifiers)
+  {
+    for (size_t i = 0; i < children_.size(); i++)
+    {
+      IMouseTracker* tracker = children_[i]->CreateMouseTracker(button, x, y, modifiers);
+      if (tracker != NULL)
+      {
+        return tracker;
+      }
+    }
+
+    return NULL;
+  }
+
+
+  void LayoutWidget::RenderMouseOver(Orthanc::ImageAccessor& target,
+                                     int x,
+                                     int y)
+  {
+    for (size_t i = 0; i < children_.size(); i++)
+    {
+      children_[i]->RenderMouseOver(target, x, y);
+    }
+  }
+
+
+  void LayoutWidget::MouseWheel(MouseWheelDirection direction,
+                                int x,
+                                int y,
+                                KeyboardModifiers modifiers)
+  {
+    for (size_t i = 0; i < children_.size(); i++)
+    {
+      children_[i]->MouseWheel(direction, x, y, modifiers);
+    }
+  }
+
+
+  void LayoutWidget::KeyPressed(char key,
+                                KeyboardModifiers modifiers)
+  {
+    for (size_t i = 0; i < children_.size(); i++)
+    {
+      children_[i]->GetWidget().KeyPressed(key, modifiers);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Widgets/LayoutWidget.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,147 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "WidgetBase.h"
+
+
+namespace OrthancStone
+{
+  class LayoutWidget : 
+    public WidgetBase,
+    public IWidget::IChangeObserver
+  {
+  private:
+    class LayoutMouseTracker;
+    class ChildWidget;
+
+    std::vector<ChildWidget*>     children_;
+    bool                          isHorizontal_;
+    bool                          started_;
+    unsigned int                  width_;
+    unsigned int                  height_;
+    std::auto_ptr<IMouseTracker>  mouseTracker_;
+    unsigned int                  paddingLeft_;
+    unsigned int                  paddingTop_;
+    unsigned int                  paddingRight_;
+    unsigned int                  paddingBottom_;
+    unsigned int                  paddingInternal_;
+
+    void ComputeChildrenExtents();
+
+  protected:
+    virtual bool HasUpdateThread() const 
+    {
+      return false;
+    }
+
+    virtual void UpdateStep();
+
+
+  public:
+    LayoutWidget();
+
+    virtual ~LayoutWidget();
+
+    virtual void NotifyChange(const IWidget& widget);
+
+    void SetHorizontal();
+
+    void SetVertical();
+
+    void SetPadding(unsigned int left,
+                    unsigned int top,
+                    unsigned int right,
+                    unsigned int bottom,
+                    unsigned int spacing);
+    
+    void SetPadding(unsigned int padding);
+
+    unsigned int GetPaddingLeft() const
+    {
+      return paddingLeft_;
+    }
+
+    unsigned int GetPaddingTop() const
+    {
+      return paddingTop_;
+    }
+
+    unsigned int GetPaddingRight() const
+    {
+      return paddingRight_;
+    }
+
+    unsigned int GetPaddingBottom() const
+    {
+      return paddingBottom_;
+    }
+
+    unsigned int GetPaddingInternal() const
+    {
+      return paddingInternal_;
+    }
+
+    IWidget& AddWidget(IWidget* widget);  // Takes ownership
+
+    virtual void SetStatusBar(IStatusBar& statusBar);
+
+    virtual void ResetStatusBar();
+
+    virtual void Start();
+
+    virtual void Stop();
+
+    virtual void SetSize(unsigned int width,
+                         unsigned int height);
+
+    virtual bool Render(Orthanc::ImageAccessor& surface);
+    
+    virtual IMouseTracker* CreateMouseTracker(MouseButton button,
+                                              int x,
+                                              int y,
+                                              KeyboardModifiers modifiers);
+
+    virtual void RenderMouseOver(Orthanc::ImageAccessor& target,
+                                 int x,
+                                 int y);
+
+    virtual void MouseWheel(MouseWheelDirection direction,
+                            int x,
+                            int y,
+                            KeyboardModifiers modifiers);
+
+    virtual void KeyPressed(char key,
+                            KeyboardModifiers modifiers);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Widgets/TestCairoWidget.cpp	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,138 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "TestCairoWidget.h"
+
+#include "../Orthanc/Core/Toolbox.h"
+
+#include <stdio.h>
+
+
+namespace OrthancStone
+{
+  namespace Samples
+  {
+    void TestCairoWidget::UpdateStep() 
+    {
+      value_ -= 0.01f;
+      if (value_ < 0)
+      {
+        value_ = 1;
+      }
+
+      NotifyChange();
+
+      Orthanc::Toolbox::USleep(25000);
+    }
+
+
+    bool TestCairoWidget::RenderCairo(CairoContext& context)
+    {
+      cairo_t* cr = context.GetObject();
+
+      cairo_set_source_rgb (cr, .3, 0, 0);
+      cairo_paint(cr);
+
+      cairo_set_source_rgb(cr, 0, 1, 0);
+      cairo_rectangle(cr, width_ / 4, height_ / 4, width_ / 2, height_ / 2);
+      cairo_set_line_width(cr, 1.0);
+      cairo_fill(cr);
+
+      cairo_set_source_rgb(cr, 0, 1, value_);
+      cairo_rectangle(cr, width_ / 2 - 50, height_ / 2 - 50, 100, 100);
+      cairo_fill(cr);
+
+      return true;
+    }
+
+
+    void TestCairoWidget::RenderMouseOverCairo(CairoContext& context,
+                                               int x,
+                                               int y)
+    {
+      cairo_t* cr = context.GetObject();
+
+      cairo_set_source_rgb (cr, 1, 0, 0);
+      cairo_rectangle(cr, x - 5, y - 5, 10, 10);
+      cairo_set_line_width(cr, 1.0);
+      cairo_stroke(cr);
+
+      char buf[64];
+      sprintf(buf, "(%d,%d)", x, y);
+      UpdateStatusBar(buf);
+    }
+
+
+    TestCairoWidget::TestCairoWidget(bool animate) :
+      width_(0),
+      height_(0),
+      value_(1),
+      animate_(animate)
+    {
+    }
+
+
+    void TestCairoWidget::SetSize(unsigned int width, 
+                                  unsigned int height)
+    {
+      CairoWidget::SetSize(width, height);
+      width_ = width;
+      height_ = height;
+    }
+ 
+
+    IMouseTracker* TestCairoWidget::CreateMouseTracker(MouseButton button,
+                                                       int x,
+                                                       int y,
+                                                       KeyboardModifiers modifiers)
+    {
+      UpdateStatusBar("Click");
+      return NULL;
+    }
+
+
+    void TestCairoWidget::MouseWheel(MouseWheelDirection direction,
+                                     int x,
+                                     int y,
+                                     KeyboardModifiers modifiers) 
+    {
+      UpdateStatusBar(direction == MouseWheelDirection_Down ? "Wheel down" : "Wheel up");
+    }
+
+    
+    void TestCairoWidget::KeyPressed(char key,
+                                     KeyboardModifiers modifiers)
+    {
+      UpdateStatusBar("Key pressed: \"" + std::string(1, key) + "\"");
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Widgets/TestCairoWidget.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,83 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "CairoWidget.h"
+
+namespace OrthancStone
+{
+  namespace Samples
+  {
+    class TestCairoWidget : public CairoWidget
+    {
+    private:
+      unsigned int  width_;
+      unsigned int  height_;
+      float         value_;
+      bool          animate_;
+
+      virtual bool HasUpdateThread() const
+      {
+        return animate_;
+      }
+
+      virtual void UpdateStep();
+
+    protected:
+      virtual bool RenderCairo(CairoContext& context);
+
+      virtual void RenderMouseOverCairo(CairoContext& context,
+                                        int x,
+                                        int y);
+
+    public:
+      TestCairoWidget(bool animate);
+
+      virtual void SetSize(unsigned int width, 
+                           unsigned int height);
+ 
+      virtual IMouseTracker* CreateMouseTracker(MouseButton button,
+                                                int x,
+                                                int y,
+                                                KeyboardModifiers modifiers);
+
+      virtual void MouseWheel(MouseWheelDirection direction,
+                              int x,
+                              int y,
+                              KeyboardModifiers modifiers);
+    
+      virtual void KeyPressed(char key,
+                              KeyboardModifiers modifiers);
+    };
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Widgets/TestWorldSceneWidget.cpp	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,142 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "TestWorldSceneWidget.h"
+
+#include <stdio.h>
+
+namespace OrthancStone
+{
+  namespace Samples
+  {
+    class TestWorldSceneWidget::Interactor : public IWorldSceneInteractor
+    {
+    public:
+      virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& widget,
+                                                          const SliceGeometry& slice,
+                                                          const ViewportGeometry& view,
+                                                          MouseButton button,
+                                                          double x,
+                                                          double y,
+                                                          IStatusBar* statusBar)
+      {
+        if (statusBar)
+        {
+          char buf[64];
+          sprintf(buf, "X = %0.2f, Y = %0.2f", x, y);
+          statusBar->SetMessage(buf);
+        }
+
+        return NULL;
+      }
+
+      virtual void MouseOver(CairoContext& context,
+                             WorldSceneWidget& widget,
+                             const SliceGeometry& slice,
+                             const ViewportGeometry& view,
+                             double x,
+                             double y,
+                             IStatusBar* statusBar)
+      {
+        double S = 0.5;
+
+        if (fabs(x) <= S &&
+            fabs(y) <= S)
+        {
+          cairo_t* cr = context.GetObject();
+          cairo_set_source_rgb(cr, 1, 0, 0);
+          cairo_rectangle(cr, -S, -S , 2.0 * S, 2.0 * S);
+          cairo_set_line_width(cr, 1.0 / view.GetZoom());
+          cairo_stroke(cr);
+        }
+      }
+
+      virtual void MouseWheel(WorldSceneWidget& widget,
+                              MouseWheelDirection direction,
+                              KeyboardModifiers modifiers,
+                              IStatusBar* statusBar)
+      {
+        if (statusBar)
+        {
+          statusBar->SetMessage(direction == MouseWheelDirection_Down ? "Wheel down" : "Wheel up");
+        }
+      }
+
+      virtual void KeyPressed(WorldSceneWidget& widget,
+                              char key,
+                              KeyboardModifiers modifiers,
+                              IStatusBar* statusBar)
+      {
+        if (statusBar)
+        {
+          statusBar->SetMessage("Key pressed: \"" + std::string(1, key) + "\"");
+        }
+      }
+    };
+
+
+    bool TestWorldSceneWidget::RenderScene(CairoContext& context,
+                                           const ViewportGeometry& view)
+    {
+      cairo_t* cr = context.GetObject();
+
+      // Clear background
+      cairo_set_source_rgb(cr, 0, 0, 0);
+      cairo_paint(cr);
+
+      cairo_set_source_rgb(cr, 0, 1, 0);
+      cairo_rectangle(cr, -10, -.5, 20, 1);
+      cairo_fill(cr);
+
+      return true;
+    }
+
+
+    TestWorldSceneWidget::TestWorldSceneWidget() :
+      interactor_(new Interactor)
+    {
+      SetInteractor(*interactor_);
+    }
+
+
+    void TestWorldSceneWidget::GetSceneExtent(double& x1,
+                                              double& y1,
+                                              double& x2,
+                                              double& y2)
+    {
+      x1 = -10;
+      x2 = 10;
+      y1 = -.5;
+      y2 = .5;
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Widgets/TestWorldSceneWidget.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,66 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "WorldSceneWidget.h"
+
+namespace OrthancStone
+{
+  namespace Samples
+  {
+    class TestWorldSceneWidget : public WorldSceneWidget
+    {
+    private:
+      class Interactor;
+
+      std::auto_ptr<Interactor>   interactor_;
+
+    protected:
+      virtual SliceGeometry GetSlice()
+      {
+        return SliceGeometry();
+      }
+
+      virtual bool RenderScene(CairoContext& context,
+                               const ViewportGeometry& view);
+
+    public:
+      TestWorldSceneWidget();
+
+      virtual void GetSceneExtent(double& x1,
+                                  double& y1,
+                                  double& x2,
+                                  double& y2);
+    };
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Widgets/WidgetBase.cpp	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,190 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "WidgetBase.h"
+
+#include "../Orthanc/Core/OrthancException.h"
+#include "../Orthanc/Core/Images/ImageProcessing.h"
+#include "../Orthanc/Core/Logging.h"
+
+namespace OrthancStone
+{
+  void WidgetBase::ClearBackgroundOrthanc(Orthanc::ImageAccessor& target) const 
+  {
+    // Clear the background using Orthanc
+
+    if (backgroundCleared_)
+    {
+      Orthanc::ImageProcessing::Set(target, 
+                                    backgroundColor_[0],
+                                    backgroundColor_[1],
+                                    backgroundColor_[2],
+                                    255 /* alpha */);
+    }
+  }
+
+
+  void WidgetBase::ClearBackgroundCairo(CairoContext& context) const
+  {
+    // Clear the background using Cairo
+
+    if (IsBackgroundCleared())
+    {
+      uint8_t red, green, blue;
+      GetBackgroundColor(red, green, blue);
+
+      context.SetSourceColor(red, green, blue);
+      cairo_paint(context.GetObject());
+    }
+  }
+
+
+  void WidgetBase::ClearBackgroundCairo(Orthanc::ImageAccessor& target) const
+  {
+    CairoSurface surface(target);
+    CairoContext context(surface);
+    ClearBackgroundCairo(context);
+  }
+
+
+  void WidgetBase::NotifyChange()
+  {
+    observers_.NotifyChange(this);
+  }
+
+
+  void WidgetBase::UpdateStatusBar(const std::string& message)
+  {
+    if (statusBar_ != NULL)
+    {
+      statusBar_->SetMessage(message);
+    }
+  }
+
+
+  void WidgetBase::WorkerThread(WidgetBase* that)
+  {
+    while (that->started_)
+    {
+      that->UpdateStep();
+    }
+  }
+
+
+  WidgetBase::WidgetBase() :
+    statusBar_(NULL),
+    started_(false),
+    backgroundCleared_(false)
+  {
+    backgroundColor_[0] = 0;
+    backgroundColor_[1] = 0;
+    backgroundColor_[2] = 0;
+  }
+
+
+  void WidgetBase::SetBackgroundColor(uint8_t red,
+                                      uint8_t green,
+                                      uint8_t blue)
+  {
+    backgroundColor_[0] = red;
+    backgroundColor_[1] = green;
+    backgroundColor_[2] = blue;
+  }
+
+  void WidgetBase::GetBackgroundColor(uint8_t& red,
+                                      uint8_t& green,
+                                      uint8_t& blue) const
+  {
+    red = backgroundColor_[0];
+    green = backgroundColor_[1];
+    blue = backgroundColor_[2];
+  }
+
+
+  void WidgetBase::Register(IChangeObserver& observer)
+  {
+    observers_.Register(observer);
+  }
+
+
+  void WidgetBase::Unregister(IChangeObserver& observer)
+  {
+    observers_.Unregister(observer);
+  }
+
+
+  void WidgetBase::Start()
+  {
+    if (started_)
+    {
+      LOG(ERROR) << "Cannot Start() twice";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+
+    started_ = true;
+
+    if (HasUpdateThread())
+    {
+      thread_ = boost::thread(WorkerThread, this);
+    }
+  }
+
+
+  void WidgetBase::Stop()
+  {
+    if (!started_)
+    {
+      LOG(ERROR) << "Cannot Stop() if Start() has not been invoked";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+
+    started_ = false;
+
+    if (HasUpdateThread() &&
+        thread_.joinable())
+    {
+      thread_.join();
+    }
+  }
+
+
+  bool WidgetBase::Render(Orthanc::ImageAccessor& surface)
+  {
+#if 0
+    ClearBackgroundOrthanc(surface);
+#else
+    ClearBackgroundCairo(surface);  // Faster than Orthanc
+#endif
+
+    return true;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Widgets/WidgetBase.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,122 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "IWidget.h"
+
+#include "../Viewport/CairoContext.h"
+#include "../Toolbox/ObserversRegistry.h"
+
+#include <boost/thread.hpp>
+
+namespace OrthancStone
+{
+  class WidgetBase : public IWidget
+  {
+  private:
+    IStatusBar*                  statusBar_;
+    ObserversRegistry<IWidget>   observers_;
+    bool                         started_;
+    boost::thread                thread_;
+    bool                         backgroundCleared_;
+    uint8_t                      backgroundColor_[3];
+
+  protected:
+    void ClearBackgroundOrthanc(Orthanc::ImageAccessor& target) const;
+
+    void ClearBackgroundCairo(CairoContext& context) const;
+
+    void ClearBackgroundCairo(Orthanc::ImageAccessor& target) const;
+
+    void NotifyChange();
+
+    void UpdateStatusBar(const std::string& message);
+
+    static void WorkerThread(WidgetBase* that);
+
+    IStatusBar* GetStatusBar() const
+    {
+      return statusBar_;
+    }
+
+    virtual bool HasUpdateThread() const = 0;
+
+    virtual void UpdateStep() = 0;
+
+  public:
+    WidgetBase();
+
+    bool IsStarted() const
+    {
+      return started_;
+    }
+
+    void SetBackgroundCleared(bool clear)
+    {
+      backgroundCleared_ = clear;
+    }
+
+    bool IsBackgroundCleared() const
+    {
+      return backgroundCleared_;
+    }
+
+    void SetBackgroundColor(uint8_t red,
+                            uint8_t green,
+                            uint8_t blue);
+
+    void GetBackgroundColor(uint8_t& red,
+                            uint8_t& green,
+                            uint8_t& blue) const;
+
+    virtual void Register(IChangeObserver& observer);
+
+    virtual void Unregister(IChangeObserver& observer);
+    
+    virtual void SetStatusBar(IStatusBar& statusBar)
+    {
+      statusBar_ = &statusBar;
+    }
+
+    virtual void ResetStatusBar()
+    {
+      statusBar_ = NULL;
+    }    
+
+    virtual void Start();
+
+    virtual void Stop();
+
+    virtual bool Render(Orthanc::ImageAccessor& surface);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Widgets/WorldSceneWidget.cpp	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,461 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "WorldSceneWidget.h"
+
+#include "../Orthanc/Core/OrthancException.h"
+
+namespace OrthancStone
+{
+  static void MapMouseToScene(double& sceneX,
+                              double& sceneY,
+                              const ViewportGeometry& view,
+                              int mouseX,
+                              int mouseY)
+  {
+    // Take the center of the pixel
+    double x, y;
+    x = static_cast<double>(mouseX) + 0.5;
+    y = static_cast<double>(mouseY) + 0.5;
+
+    view.MapDisplayToScene(sceneX, sceneY, x, y);
+  }
+
+
+  struct WorldSceneWidget::ViewChangeFunctor
+  {
+    const ViewportGeometry& view_;
+
+    ViewChangeFunctor(const ViewportGeometry& view) :
+      view_(view)
+    {
+    }
+
+    void operator() (IWorldObserver& observer,
+                     const WorldSceneWidget& source)
+    {
+      observer.NotifyViewChange(source, view_);
+    }
+  };
+
+
+  struct WorldSceneWidget::SizeChangeFunctor
+  {
+    ViewportGeometry& view_;
+
+    SizeChangeFunctor(ViewportGeometry& view) :
+      view_(view)
+    {
+    }
+
+    void operator() (IWorldObserver& observer,
+                     const WorldSceneWidget& source)
+    {
+      observer.NotifySizeChange(source, view_);
+    }
+  };
+
+
+  class WorldSceneWidget::SceneMouseTracker : public IMouseTracker
+  {
+  private:
+    ViewportGeometry                       view_;
+    std::auto_ptr<IWorldSceneMouseTracker>  tracker_;
+      
+  public:
+    SceneMouseTracker(const ViewportGeometry& view,
+                      IWorldSceneMouseTracker* tracker) :
+      view_(view),
+      tracker_(tracker)
+    {
+      assert(tracker != NULL);
+    }
+
+    virtual void Render(Orthanc::ImageAccessor& target)
+    {
+      CairoSurface surface(target);
+      CairoContext context(surface); 
+      view_.ApplyTransform(context);
+      tracker_->Render(context, view_.GetZoom());
+    }
+
+    virtual void MouseUp() 
+    {
+      tracker_->MouseUp();
+    }
+
+    virtual void MouseMove(int x, 
+                           int y)
+    {
+      double sceneX, sceneY;
+      MapMouseToScene(sceneX, sceneY, view_, x, y);
+      tracker_->MouseMove(sceneX, sceneY);
+    }
+  };
+
+
+  class WorldSceneWidget::PanMouseTracker : public IMouseTracker
+  {
+  private:
+    WorldSceneWidget&  that_;  
+    double             previousPanX_;
+    double             previousPanY_;
+    double             downX_;
+    double             downY_;
+
+  public:
+    PanMouseTracker(WorldSceneWidget& that,
+                    int x,
+                    int y) :
+      that_(that),
+      downX_(x),
+      downY_(y)
+    {
+      SharedValue<ViewportGeometry>::Locker locker(that_.view_);
+      locker.GetValue().GetPan(previousPanX_, previousPanY_);
+    }
+
+    virtual void Render(Orthanc::ImageAccessor& surface)
+    {
+    }
+
+    virtual void MouseUp() 
+    {
+    }
+
+    virtual void MouseMove(int x, 
+                           int y)
+    {
+      SharedValue<ViewportGeometry>::Locker locker(that_.view_);
+      locker.GetValue().SetPan(previousPanX_ + x - downX_,
+                               previousPanY_ + y - downY_);
+
+      ViewChangeFunctor functor(locker.GetValue());
+      that_.observers_.Notify(&that_, functor);
+    }
+  };
+
+
+  class WorldSceneWidget::ZoomMouseTracker : public IMouseTracker
+  {
+  private:
+    WorldSceneWidget&  that_;  
+    int                downX_;
+    int                downY_;
+    double             centerX_;
+    double             centerY_;
+    double             oldZoom_;
+
+  public:
+    ZoomMouseTracker(WorldSceneWidget&  that,
+                     int x,
+                     int y) :
+      that_(that),
+      downX_(x),
+      downY_(y)
+    {
+      SharedValue<ViewportGeometry>::Locker locker(that_.view_);
+      oldZoom_ = locker.GetValue().GetZoom();
+      MapMouseToScene(centerX_, centerY_, locker.GetValue(), downX_, downY_);
+    }
+
+    virtual void Render(Orthanc::ImageAccessor& surface)
+    {
+    }
+
+    virtual void MouseUp() 
+    {
+    }
+
+    virtual void MouseMove(int x, 
+                           int y)
+    {
+      static const double MIN_ZOOM = -4;
+      static const double MAX_ZOOM = 4;
+
+      SharedValue<ViewportGeometry>::Locker locker(that_.view_);
+
+      if (locker.GetValue().GetDisplayHeight() <= 3)
+      {
+        return;   // Cannot zoom on such a small image
+      }
+
+      double dy = (static_cast<double>(y - downY_) / 
+                   static_cast<double>(locker.GetValue().GetDisplayHeight() - 1)); // In the range [-1,1]
+      double z;
+
+      // Linear interpolation from [-1, 1] to [MIN_ZOOM, MAX_ZOOM]
+      if (dy < -1.0)
+      {
+        z = MIN_ZOOM;
+      }
+      else if (dy > 1.0)
+      {
+        z = MAX_ZOOM;
+      }
+      else
+      {
+        z = MIN_ZOOM + (MAX_ZOOM - MIN_ZOOM) * (dy + 1.0) / 2.0;
+      }
+
+      z = pow(2.0, z);
+
+      locker.GetValue().SetZoom(oldZoom_ * z);
+
+      // Correct the pan so that the original click point is kept at
+      // the same location on the display
+      double panX, panY;
+      locker.GetValue().GetPan(panX, panY);
+
+      int tx, ty;
+      locker.GetValue().MapSceneToDisplay(tx, ty, centerX_, centerY_);
+      locker.GetValue().SetPan(panX + static_cast<double>(downX_ - tx),
+                               panY + static_cast<double>(downY_ - ty));
+
+      ViewChangeFunctor functor(locker.GetValue());
+      that_.observers_.Notify(&that_, functor);
+    }
+  };
+
+
+  void WorldSceneWidget::UpdateStep()
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+  }
+
+
+  bool WorldSceneWidget::RenderCairo(CairoContext& context)
+  {
+    ViewportGeometry view;
+
+    {
+      SharedValue<ViewportGeometry>::Locker locker(view_);
+      view = locker.GetValue();
+    }
+
+    view.ApplyTransform(context);
+
+    return RenderScene(context, view);
+  }
+
+
+  void WorldSceneWidget::RenderMouseOverCairo(CairoContext& context,
+                                              int x,
+                                              int y)
+  {
+    ViewportGeometry view = GetView();
+    view.ApplyTransform(context);
+
+    double sceneX, sceneY;
+    MapMouseToScene(sceneX, sceneY, view, x, y);
+    RenderSceneMouseOver(context, view, sceneX, sceneY);
+  }
+
+
+  void WorldSceneWidget::SetSceneExtent(SharedValue<ViewportGeometry>::Locker& locker)
+  {
+    double x1, y1, x2, y2;
+    GetSceneExtent(x1, y1, x2, y2);
+    locker.GetValue().SetSceneExtent(x1, y1, x2, y2);
+  }
+
+
+  void WorldSceneWidget::SetSize(unsigned int width,
+                                 unsigned int height)
+  {
+    CairoWidget::SetSize(width, height);
+
+    {
+      SharedValue<ViewportGeometry>::Locker locker(view_);
+      locker.GetValue().SetDisplaySize(width, height);
+
+      if (observers_.IsEmpty())
+      {
+        // Without a size observer, use the default view
+        locker.GetValue().SetDefaultView();
+      }
+      else
+      {
+        // With a size observer, let it decide which view to use
+        SizeChangeFunctor functor(locker.GetValue());
+        observers_.Notify(this, functor);
+      }
+    }
+  }
+
+
+  void WorldSceneWidget::SetInteractor(IWorldSceneInteractor& interactor)
+  {
+    if (IsStarted())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+
+    interactor_ = &interactor;
+  }
+
+
+  void WorldSceneWidget::Start()
+  {
+    ViewportGeometry geometry;
+
+    {
+      SharedValue<ViewportGeometry>::Locker locker(view_);
+      SetSceneExtent(locker);
+      geometry = locker.GetValue();
+    }
+
+    WidgetBase::Start();
+
+    ViewChangeFunctor functor(geometry);
+    observers_.Notify(this, functor);
+  }
+      
+
+  void WorldSceneWidget::SetDefaultView()
+  {
+    ViewportGeometry geometry;
+
+    {
+      SharedValue<ViewportGeometry>::Locker locker(view_);
+      SetSceneExtent(locker);
+      locker.GetValue().SetDefaultView();
+      geometry = locker.GetValue();
+    }
+
+    NotifyChange();
+
+    ViewChangeFunctor functor(geometry);
+    observers_.Notify(this, functor);
+  }
+
+
+  void WorldSceneWidget::SetView(const ViewportGeometry& view)
+  {
+    {
+      SharedValue<ViewportGeometry>::Locker locker(view_);
+      locker.GetValue() = view;
+    }
+
+    NotifyChange();
+
+    ViewChangeFunctor functor(view);
+    observers_.Notify(this, functor);
+  }
+
+
+  ViewportGeometry WorldSceneWidget::GetView()
+  {
+    SharedValue<ViewportGeometry>::Locker locker(view_);
+    return locker.GetValue();
+  }
+
+
+  IMouseTracker* WorldSceneWidget::CreateMouseTracker(MouseButton button,
+                                                      int x,
+                                                      int y,
+                                                      KeyboardModifiers modifiers)
+  {
+    ViewportGeometry view = GetView();
+
+    double sceneX, sceneY;
+    MapMouseToScene(sceneX, sceneY, view, x, y);
+
+    std::auto_ptr<IWorldSceneMouseTracker> tracker(CreateMouseSceneTracker(view, button, sceneX, sceneY, modifiers));
+    if (tracker.get() != NULL)
+    {
+      return new SceneMouseTracker(view, tracker.release());
+    }
+
+    switch (button)
+    {
+      case MouseButton_Middle:
+        return new PanMouseTracker(*this, x, y);
+
+      case MouseButton_Right:
+        return new ZoomMouseTracker(*this, x, y);
+
+      default:
+        return NULL;
+    }
+  }
+
+
+  void WorldSceneWidget::RenderSceneMouseOver(CairoContext& context,
+                                              const ViewportGeometry& view,
+                                              double x,
+                                              double y)
+  {
+    if (interactor_)
+    {
+      interactor_->MouseOver(context, *this, GetSlice(), view, x, y, GetStatusBar());
+    }
+  }
+
+  IWorldSceneMouseTracker* WorldSceneWidget::CreateMouseSceneTracker(const ViewportGeometry& view,
+                                                                     MouseButton button,
+                                                                     double x,
+                                                                     double y,
+                                                                     KeyboardModifiers modifiers)
+  {
+    if (interactor_)
+    {
+      return interactor_->CreateMouseTracker(*this, GetSlice(), view, button, x, y, GetStatusBar());
+    }
+    else
+    {
+      return NULL;
+    }
+  }
+
+
+  void WorldSceneWidget::MouseWheel(MouseWheelDirection direction,
+                                    int x,
+                                    int y,
+                                    KeyboardModifiers modifiers) 
+  {
+    if (interactor_)
+    {
+      interactor_->MouseWheel(*this, direction, modifiers, GetStatusBar());
+    }
+  }
+
+
+  void WorldSceneWidget::KeyPressed(char key,
+                                    KeyboardModifiers modifiers)
+  {
+    if (interactor_)
+    {
+      interactor_->KeyPressed(*this, key, modifiers, GetStatusBar());
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Widgets/WorldSceneWidget.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,158 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "CairoWidget.h"
+#include "IWorldSceneInteractor.h"
+
+#include "../Toolbox/SharedValue.h"
+#include "../Toolbox/ViewportGeometry.h"
+
+namespace OrthancStone
+{
+  class WorldSceneWidget : public CairoWidget
+  {
+  public:
+    // Must be thread-safe
+    class IWorldObserver : public boost::noncopyable
+    {
+    public:
+      virtual ~IWorldObserver()
+      {
+      }
+
+      virtual void NotifySizeChange(const WorldSceneWidget& source,
+                                    ViewportGeometry& view) = 0;  // Can be tuned by the observer
+
+      virtual void NotifyViewChange(const WorldSceneWidget& source,
+                                    const ViewportGeometry& view) = 0;
+    };
+
+  private:
+    struct ViewChangeFunctor;
+    struct SizeChangeFunctor;
+
+    class SceneMouseTracker;
+    class PanMouseTracker;
+    class ZoomMouseTracker;
+
+    typedef ObserversRegistry<WorldSceneWidget, IWorldObserver>  Observers;
+
+    SharedValue<ViewportGeometry>  view_;
+    Observers                      observers_;
+    IWorldSceneInteractor*         interactor_;
+
+
+  protected:
+    virtual bool RenderScene(CairoContext& context,
+                             const ViewportGeometry& view) = 0;
+
+    virtual bool HasUpdateThread() const
+    {
+      return false;
+    }
+
+    virtual void UpdateStep();
+
+    virtual bool RenderCairo(CairoContext& context);
+
+    virtual void RenderMouseOverCairo(CairoContext& context,
+                                      int x,
+                                      int y);
+
+    void SetSceneExtent(SharedValue<ViewportGeometry>::Locker& locker);
+
+  public:
+    WorldSceneWidget() :
+      interactor_(NULL)
+    {
+    }
+
+    using WidgetBase::Register;
+    using WidgetBase::Unregister;
+
+    void Register(IWorldObserver& observer)
+    {
+      observers_.Register(observer);
+    }
+
+    void Unregister(IWorldObserver& observer)
+    {
+      observers_.Unregister(observer);
+    }
+
+    virtual SliceGeometry GetSlice() = 0;
+
+    virtual void GetSceneExtent(double& x1,
+                                double& y1,
+                                double& x2,
+                                double& y2) = 0;
+
+    virtual void SetSize(unsigned int width,
+                         unsigned int height);
+
+    void SetInteractor(IWorldSceneInteractor& interactor);
+
+    virtual void Start();
+      
+    void SetDefaultView();
+
+    void SetView(const ViewportGeometry& view);
+
+    ViewportGeometry GetView();
+
+    virtual IMouseTracker* CreateMouseTracker(MouseButton button,
+                                              int x,
+                                              int y,
+                                              KeyboardModifiers modifiers);
+
+    virtual void RenderSceneMouseOver(CairoContext& context,
+                                      const ViewportGeometry& view,
+                                      double x,
+                                      double y);
+
+    virtual IWorldSceneMouseTracker* CreateMouseSceneTracker(const ViewportGeometry& view,
+                                                             MouseButton button,
+                                                             double x,
+                                                             double y,
+                                                             KeyboardModifiers modifiers);
+
+    virtual void MouseWheel(MouseWheelDirection direction,
+                            int x,
+                            int y,
+                            KeyboardModifiers modifiers);
+
+    virtual void KeyPressed(char key,
+                            KeyboardModifiers modifiers);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/NEWS	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,4 @@
+2016-10-14
+==========
+
+* Initial release
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/README	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,102 @@
+Stone of Orthanc
+================
+
+
+General Information
+-------------------
+
+This repository contains the source code of the Stone of Orthanc.
+
+Stone of Orthanc is a lightweight, cross-platform C++ framework for
+the CPU-based rendering of medical images. It notably features support
+for MPR (multiplanar reconstruction of volume images), PET-CT fusion,
+and accurate physical world coordinates.
+
+Stone of Orthanc comes bundled with its own software-based rendering
+engine (based upon pixman). This engine will use CPU hardware
+acceleration if available (notably SSE2, SSSE3, and NEON instruction
+sets), but not the GPU. This makes Stone a highly versatile framework
+that can run even on low-performance platforms. Stone is able to
+display DICOM series without having to entirely store them in the RAM.
+
+Stone of Orthanc is conceived as a companion toolbox to the Orthanc
+VNA (vendor neutral archive, i.e. DICOM server). As a consequence,
+Stone will smoothly interface with Orthanc out of the
+box. Interestingly, Stone does not contain any DICOM toolkit: It
+entirely relies on the REST API of Orthanc to parse/decode DICOM
+images. However, thanks to the object-oriented architecture of Stone,
+it is possible to avoid this dependency upon Orthanc.
+
+
+Comparison
+----------
+
+Pay attention to the fact that Stone of Orthanc is a toolkit, and not
+a fully-featured application for the visualization of medical
+images. Such applications can be built on the top of Stone of Orthanc.
+
+Stone of Orthanc is quite similar to two other well-known toolkits:
+
+* Cornerstone, a client-side JavaScript toolkit to display medical
+  images in Web browsers, by Chris Hafey <chafey@gmail.com>:
+  https://github.com/chafey/cornerstone
+
+  Contrarily to Cornerstone, Stone of Orthanc can be embedded into
+  native, heavyweight applications.
+
+* VTK, a C++ toolkit for scientific visualization, by Kitware:
+  http://www.vtk.org/
+
+  Contrarily to VTK, Stone of Orthanc is focused on CPU-based, 2D
+  rendering: The GPU will not be used.
+
+
+Dependencies
+------------
+
+Stone of Orthanc is based upon the following projects:
+
+* Orthanc, a lightweight Vendor Neutral Archive (DICOM server):
+  http://www.orthanc-server.com/
+
+* Cairo and pixman, a cross-platform 2D graphics library:
+  https://www.cairographics.org/
+
+* Optionally, SDL, a cross-platform multimedia library:
+  https://www.libsdl.org/
+
+
+Installation and usage
+----------------------
+
+Build instructions are similar to that of Orthanc:
+https://orthanc.chu.ulg.ac.be/book/faq/compiling.html
+
+Stone of Orthanc comes with several sample applications in the
+"Samples" folder. These samples use SDL.
+
+
+Licensing
+---------
+
+Stone of Orthanc is licensed under the GPLv3 license, with the OpenSSL
+exception:
+http://people.gnome.org/~markmc/openssl-and-the-gpl.html
+
+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/CMake/BoostExtendedConfiguration.cmake	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,21 @@
+SET(ORTHANC_BOOST_COMPONENTS program_options)
+
+include(${ORTHANC_ROOT}/Resources/CMake/BoostConfiguration.cmake)
+
+if (BOOST_STATIC)
+  list(APPEND BOOST_SOURCES
+    ${BOOST_SOURCES_DIR}/libs/program_options/src/cmdline.cpp
+    ${BOOST_SOURCES_DIR}/libs/program_options/src/config_file.cpp
+    ${BOOST_SOURCES_DIR}/libs/program_options/src/convert.cpp
+    ${BOOST_SOURCES_DIR}/libs/program_options/src/options_description.cpp
+    ${BOOST_SOURCES_DIR}/libs/program_options/src/parsers.cpp
+    ${BOOST_SOURCES_DIR}/libs/program_options/src/positional_options.cpp
+    ${BOOST_SOURCES_DIR}/libs/program_options/src/split.cpp
+    ${BOOST_SOURCES_DIR}/libs/program_options/src/utf8_codecvt_facet.cpp
+    ${BOOST_SOURCES_DIR}/libs/program_options/src/value_semantic.cpp
+    ${BOOST_SOURCES_DIR}/libs/program_options/src/variables_map.cpp
+    #${BOOST_SOURCES_DIR}/libs/program_options/src/winmain.cpp
+    )
+  add_definitions(-DBOOST_PROGRAM_OPTIONS_NO_LIB)
+endif()
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/CMake/CairoConfiguration.cmake	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,223 @@
+# ./configure --disable-pdf --disable-svg --disable-xlib --disable-xcb --disable-script --disable-ps --disable-ft --disable-fc --disable-png --disable-trace --disable-interpreter
+
+
+if (STATIC_BUILD OR NOT USE_SYSTEM_CAIRO)
+  SET(CAIRO_SOURCES_DIR ${CMAKE_BINARY_DIR}/cairo-1.14.6)
+  SET(CAIRO_URL "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/Stone/cairo-1.14.6.tar.xz")
+  SET(CAIRO_MD5 "23a0b2f0235431d35238df1d3a517fdb")
+
+  DownloadPackage(${CAIRO_MD5} ${CAIRO_URL} "${CAIRO_SOURCES_DIR}")
+
+  file(COPY 
+    ${CMAKE_CURRENT_LIST_DIR}/cairo-features.h
+    DESTINATION ${CAIRO_SOURCES_DIR}/src
+    )
+
+  set(CAIRO_SOURCES
+    ${CAIRO_SOURCES_DIR}/src/cairo-analysis-surface.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-arc.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-array.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-atomic.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-base64-stream.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-base85-stream.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-bentley-ottmann.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-bentley-ottmann-rectangular.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-bentley-ottmann-rectilinear.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-botor-scan-converter.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-boxes.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-boxes-intersect.c
+    ${CAIRO_SOURCES_DIR}/src/cairo.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-cache.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-cff-subset.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-clip-boxes.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-clip.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-clip-polygon.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-clip-region.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-clip-surface.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-clip-tor-scan-converter.c
+    # ${CAIRO_SOURCES_DIR}/src/cairo-cogl-context.c
+    # ${CAIRO_SOURCES_DIR}/src/cairo-cogl-gradient.c
+    # ${CAIRO_SOURCES_DIR}/src/cairo-cogl-surface.c
+    # ${CAIRO_SOURCES_DIR}/src/cairo-cogl-utils.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-color.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-composite-rectangles.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-compositor.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-contour.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-damage.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-debug.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-default-context.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-deflate-stream.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-device.c
+    # ${CAIRO_SOURCES_DIR}/src/cairo-directfb-surface.c
+    # ${CAIRO_SOURCES_DIR}/src/cairo-egl-context.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-error.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-fallback-compositor.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-fixed.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-font-face.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-font-face-twin.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-font-face-twin-data.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-font-options.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-freed-pool.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-freelist.c
+    # ${CAIRO_SOURCES_DIR}/src/cairo-ft-font.c
+    # ${CAIRO_SOURCES_DIR}/src/cairo-gl-composite.c
+    # ${CAIRO_SOURCES_DIR}/src/cairo-gl-device.c
+    # ${CAIRO_SOURCES_DIR}/src/cairo-gl-dispatch.c
+    # ${CAIRO_SOURCES_DIR}/src/cairo-gl-glyphs.c
+    # ${CAIRO_SOURCES_DIR}/src/cairo-gl-gradient.c
+    # ${CAIRO_SOURCES_DIR}/src/cairo-gl-info.c
+    # ${CAIRO_SOURCES_DIR}/src/cairo-gl-msaa-compositor.c
+    # ${CAIRO_SOURCES_DIR}/src/cairo-gl-operand.c
+    # ${CAIRO_SOURCES_DIR}/src/cairo-gl-shaders.c
+    # ${CAIRO_SOURCES_DIR}/src/cairo-gl-source.c
+    # ${CAIRO_SOURCES_DIR}/src/cairo-gl-spans-compositor.c
+    # ${CAIRO_SOURCES_DIR}/src/cairo-gl-surface.c
+    # ${CAIRO_SOURCES_DIR}/src/cairo-gl-traps-compositor.c
+    # ${CAIRO_SOURCES_DIR}/src/cairo-glx-context.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-gstate.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-hash.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-hull.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-image-compositor.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-image-info.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-image-source.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-image-surface.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-line.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-lzw.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-mask-compositor.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-matrix.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-mempool.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-mesh-pattern-rasterizer.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-misc.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-mono-scan-converter.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-mutex.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-no-compositor.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-observer.c
+    # ${CAIRO_SOURCES_DIR}/src/cairo-os2-surface.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-output-stream.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-paginated-surface.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-path-bounds.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-path.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-path-fill.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-path-fixed.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-path-in-fill.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-path-stroke-boxes.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-path-stroke.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-path-stroke-polygon.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-path-stroke-traps.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-path-stroke-tristrip.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-pattern.c
+    # ${CAIRO_SOURCES_DIR}/src/cairo-pdf-operators.c
+    # ${CAIRO_SOURCES_DIR}/src/cairo-pdf-shading.c
+    # ${CAIRO_SOURCES_DIR}/src/cairo-pdf-surface.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-pen.c
+    # ${CAIRO_SOURCES_DIR}/src/cairo-png.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-polygon.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-polygon-intersect.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-polygon-reduce.c
+    # ${CAIRO_SOURCES_DIR}/src/cairo-ps-surface.c
+    # ${CAIRO_SOURCES_DIR}/src/cairo-quartz-font.c
+    # ${CAIRO_SOURCES_DIR}/src/cairo-quartz-image-surface.c
+    # ${CAIRO_SOURCES_DIR}/src/cairo-quartz-surface.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-raster-source-pattern.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-recording-surface.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-rectangle.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-rectangular-scan-converter.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-region.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-rtree.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-scaled-font.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-scaled-font-subsets.c
+    # ${CAIRO_SOURCES_DIR}/src/cairo-script-surface.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-shape-mask-compositor.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-slope.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-spans.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-spans-compositor.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-spline.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-stroke-dash.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-stroke-style.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-surface.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-surface-clipper.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-surface-fallback.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-surface-observer.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-surface-offset.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-surface-snapshot.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-surface-subsurface.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-surface-wrapper.c
+    # ${CAIRO_SOURCES_DIR}/src/cairo-svg-surface.c
+    # ${CAIRO_SOURCES_DIR}/src/cairo-tee-surface.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-time.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-tor22-scan-converter.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-tor-scan-converter.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-toy-font-face.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-traps.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-traps-compositor.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-tristrip.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-truetype-subset.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-type1-fallback.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-type1-glyph-names.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-type1-subset.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-type3-glyph-surface.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-unicode.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-user-font.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-version.c
+    # ${CAIRO_SOURCES_DIR}/src/cairo-vg-surface.c
+    # ${CAIRO_SOURCES_DIR}/src/cairo-wgl-context.c
+    ${CAIRO_SOURCES_DIR}/src/cairo-wideint.c
+    # ${CAIRO_SOURCES_DIR}/src/cairo-xcb-connection.c
+    # ${CAIRO_SOURCES_DIR}/src/cairo-xcb-connection-core.c
+    # ${CAIRO_SOURCES_DIR}/src/cairo-xcb-connection-render.c
+    # ${CAIRO_SOURCES_DIR}/src/cairo-xcb-connection-shm.c
+    # ${CAIRO_SOURCES_DIR}/src/cairo-xcb-resources.c
+    # ${CAIRO_SOURCES_DIR}/src/cairo-xcb-screen.c
+    # ${CAIRO_SOURCES_DIR}/src/cairo-xcb-shm.c
+    # ${CAIRO_SOURCES_DIR}/src/cairo-xcb-surface.c
+    # ${CAIRO_SOURCES_DIR}/src/cairo-xcb-surface-core.c
+    # ${CAIRO_SOURCES_DIR}/src/cairo-xcb-surface-render.c
+    # ${CAIRO_SOURCES_DIR}/src/cairo-xlib-core-compositor.c
+    # ${CAIRO_SOURCES_DIR}/src/cairo-xlib-display.c
+    # ${CAIRO_SOURCES_DIR}/src/cairo-xlib-fallback-compositor.c
+    # ${CAIRO_SOURCES_DIR}/src/cairo-xlib-render-compositor.c
+    # ${CAIRO_SOURCES_DIR}/src/cairo-xlib-screen.c
+    # ${CAIRO_SOURCES_DIR}/src/cairo-xlib-source.c
+    # ${CAIRO_SOURCES_DIR}/src/cairo-xlib-surface.c
+    # ${CAIRO_SOURCES_DIR}/src/cairo-xlib-surface-shm.c
+    # ${CAIRO_SOURCES_DIR}/src/cairo-xlib-visual.c
+    # ${CAIRO_SOURCES_DIR}/src/cairo-xlib-xcb-surface.c
+    # ${CAIRO_SOURCES_DIR}/src/cairo-xml-surface.c
+    )
+
+  include_directories(${CAIRO_SOURCES_DIR}/src)
+
+  set(CAIRO_DEFINITIONS "HAS_PIXMAN_GLYPHS=1")
+
+  if (CMAKE_COMPILER_IS_GNUCXX)
+    set(CAIRO_DEFINITIONS "${CAIRO_DEFINITIONS};HAVE_STDINT_H=1;CAIRO_HAS_PTHREAD=1;HAVE_UINT64_T=1")
+
+    if (${CMAKE_SYSTEM_NAME} STREQUAL "PNaCl")
+      # Disable vectorized instructions when targeting archicture-independent PNaCl
+    else()
+      set(CAIRO_DEFINITIONS "${CAIRO_DEFINITIONS};CAIRO_HAS_REAL_PTHREAD=1;HAVE_GCC_VECTOR_EXTENSIONS;HAVE_FLOAT128")
+    endif()
+
+    set_property(
+      SOURCE ${CAIRO_SOURCES}
+      PROPERTY COMPILE_FLAGS "-Wno-attributes"
+      )
+
+  elseif (MSVC)
+
+  else()
+    message(FATAL_ERROR "Support your platform here")
+
+  endif()
+
+  set_property(
+    SOURCE ${CAIRO_SOURCES}
+    PROPERTY COMPILE_DEFINITIONS "${CAIRO_DEFINITIONS}"
+    )
+
+else()
+
+  pkg_search_module(CAIRO REQUIRED cairo)
+  include_directories(${CAIRO_INCLUDE_DIRS})
+  link_libraries(${CAIRO_LIBRARIES})
+endif()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/CMake/OrthancStone.cmake	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,208 @@
+#####################################################################
+## Parameters of the build
+#####################################################################
+
+# Generic parameters
+SET(STATIC_BUILD OFF CACHE BOOL "Static build of the third-party libraries (necessary for Windows)")
+SET(ALLOW_DOWNLOADS OFF CACHE BOOL "Allow CMake to download packages")
+
+# Optional components
+SET(ENABLE_CURL ON CACHE BOOL "Include support for libcurl")
+SET(ENABLE_SSL OFF CACHE BOOL "Include support for SSL")
+SET(ENABLE_SDL ON CACHE BOOL "Include support for SDL")
+SET(ENABLE_LOGGING ON CACHE BOOL "Enable logging facilities from Orthanc")
+
+# 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_JSONCPP ON CACHE BOOL "Use the system version of JsonCpp")
+SET(USE_SYSTEM_ZLIB ON CACHE BOOL "Use the system version of ZLib")
+SET(USE_SYSTEM_CAIRO ON CACHE BOOL "Use the system version of Cairo")
+SET(USE_SYSTEM_PIXMAN ON CACHE BOOL "Use the system version of Pixman")
+SET(USE_SYSTEM_LIBPNG ON CACHE BOOL "Use the system version of libpng")
+SET(USE_SYSTEM_LIBJPEG ON CACHE BOOL "Use the system version of libjpeg")
+SET(USE_SYSTEM_CURL ON CACHE BOOL "Use the system version of LibCurl")
+SET(USE_SYSTEM_OPENSSL ON CACHE BOOL "Use the system version of OpenSSL")
+SET(USE_SYSTEM_SDL ON CACHE BOOL "Use the system version of SDL2")
+
+
+#####################################################################
+## Configure mandatory third-party components
+#####################################################################
+
+SET(ORTHANC_STONE_DIR ${CMAKE_CURRENT_LIST_DIR}/../..)
+SET(ORTHANC_ROOT ${ORTHANC_STONE_DIR}/Framework/Orthanc)
+
+include(CheckIncludeFiles)
+include(CheckIncludeFileCXX)
+include(CheckLibraryExists)
+include(FindPythonInterp)
+include(FindPkgConfig)
+
+include(${ORTHANC_ROOT}/Resources/CMake/Compiler.cmake)
+include(${ORTHANC_ROOT}/Resources/CMake/AutoGeneratedCode.cmake)
+include(${ORTHANC_ROOT}/Resources/CMake/DownloadPackage.cmake)
+
+include(${ORTHANC_ROOT}/Resources/CMake/JsonCppConfiguration.cmake)
+include(${ORTHANC_ROOT}/Resources/CMake/ZlibConfiguration.cmake)
+include(${ORTHANC_ROOT}/Resources/CMake/LibPngConfiguration.cmake)
+include(${ORTHANC_ROOT}/Resources/CMake/LibJpegConfiguration.cmake)
+
+include(${CMAKE_CURRENT_LIST_DIR}/BoostExtendedConfiguration.cmake)
+include(${CMAKE_CURRENT_LIST_DIR}/CairoConfiguration.cmake)
+include(${CMAKE_CURRENT_LIST_DIR}/PixmanConfiguration.cmake)
+
+
+#####################################################################
+## Configure optional third-party components
+#####################################################################
+
+if (ENABLE_LOGGING)
+  add_definitions(-DORTHANC_ENABLE_LOGGING=1)
+else()
+  add_definitions(-DORTHANC_ENABLE_LOGGING=0)
+endif()
+
+if (ENABLE_SDL)
+  include(${CMAKE_CURRENT_LIST_DIR}/SdlConfiguration.cmake)  
+  add_definitions(-DORTHANC_ENABLE_SDL=1)
+else()
+  add_definitions(-DORTHANC_ENABLE_SDL=0)
+endif()
+
+if (ENABLE_CURL)
+  add_definitions(-DORTHANC_ENABLE_CURL=1)
+  include(${ORTHANC_ROOT}/Resources/CMake/LibCurlConfiguration.cmake)
+
+  if (ENABLE_SSL)
+    set(ENABLE_PKCS11 OFF)
+    add_definitions(-DORTHANC_SSL_ENABLED=1)
+    include(${ORTHANC_ROOT}/Resources/CMake/OpenSslConfiguration.cmake)
+  else()
+    add_definitions(-DORTHANC_SSL_ENABLED=0)
+  endif()
+else()
+  add_definitions(
+    -DORTHANC_SSL_ENABLED=0
+    -DORTHANC_ENABLE_CURL=0
+    )
+endif()
+
+add_definitions(
+  -DORTHANC_ENABLE_MD5=0
+  -DORTHANC_ENABLE_BASE64=1
+  -DORTHANC_PUGIXML_ENABLED=0
+  -DORTHANC_PKCS11_ENABLED=0
+  )
+
+
+#####################################################################
+## Link the colormaps into the binaries
+#####################################################################
+
+EmbedResources(
+  COLORMAP_HOT    ${ORTHANC_STONE_DIR}/Resources/Colormaps/hot.lut
+  COLORMAP_JET    ${ORTHANC_STONE_DIR}/Resources/Colormaps/jet.lut
+  COLORMAP_RED    ${ORTHANC_STONE_DIR}/Resources/Colormaps/red.lut
+  COLORMAP_GREEN  ${ORTHANC_STONE_DIR}/Resources/Colormaps/green.lut
+  COLORMAP_BLUE   ${ORTHANC_STONE_DIR}/Resources/Colormaps/blue.lut
+  )
+
+
+#####################################################################
+## System-specific patches
+#####################################################################
+
+if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows" AND
+    NOT MSVC AND
+    ENABLE_SDL)
+  # This is necessary when compiling EXE for Windows using MinGW
+  link_libraries(mingw32)
+endif()
+
+
+
+#####################################################################
+## All the source files required to build Stone of Orthanc
+#####################################################################
+
+list(APPEND ORTHANC_STONE_SOURCES
+  ${ORTHANC_STONE_DIR}/Framework/Applications/BasicApplicationContext.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Applications/IBasicApplication.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Applications/Sdl/SdlBuffering.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Applications/Sdl/SdlEngine.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Applications/Sdl/SdlWindow.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Layers/CircleMeasureTracker.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Layers/ColorFrameRenderer.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Layers/DicomStructureSetRendererFactory.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Layers/FrameRenderer.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Layers/GrayscaleFrameRenderer.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Layers/LineLayerRenderer.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Layers/LineMeasureTracker.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Layers/RenderStyle.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Layers/SeriesFrameRendererFactory.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Layers/SiblingSliceLocationFactory.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Layers/SingleFrameRendererFactory.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Messaging/CurlOrthancConnection.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Messaging/MessagingToolbox.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Toolbox/BinarySemaphore.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Toolbox/DicomDataset.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Toolbox/DicomFrameConverter.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Toolbox/DicomStructureSet.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Toolbox/DownloadStack.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Toolbox/GeometryToolbox.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Toolbox/OrthancSeriesLoader.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Toolbox/ParallelSlices.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Toolbox/ParallelSlicesCursor.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Toolbox/SliceGeometry.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Toolbox/ViewportGeometry.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Viewport/CairoContext.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Viewport/CairoFont.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Viewport/CairoSurface.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Viewport/WidgetViewport.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Volumes/ImageBuffer3D.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Volumes/VolumeImage.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Volumes/VolumeImagePolicyBase.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Volumes/VolumeImageProgressivePolicy.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Volumes/VolumeImageSimplePolicy.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Widgets/CairoWidget.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Widgets/EmptyWidget.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Widgets/LayeredSceneWidget.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Widgets/LayoutWidget.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Widgets/TestCairoWidget.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Widgets/TestWorldSceneWidget.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Widgets/WidgetBase.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Widgets/WorldSceneWidget.cpp
+
+  ${ORTHANC_ROOT}/Core/ChunkedBuffer.cpp
+  ${ORTHANC_ROOT}/Core/Compression/DeflateBaseCompressor.cpp
+  ${ORTHANC_ROOT}/Core/Compression/GzipCompressor.cpp
+  ${ORTHANC_ROOT}/Core/Enumerations.cpp
+  ${ORTHANC_ROOT}/Core/HttpClient.cpp
+  ${ORTHANC_ROOT}/Core/Images/Image.cpp
+  ${ORTHANC_ROOT}/Core/Images/ImageAccessor.cpp
+  ${ORTHANC_ROOT}/Core/Images/ImageBuffer.cpp
+  ${ORTHANC_ROOT}/Core/Images/ImageProcessing.cpp
+  ${ORTHANC_ROOT}/Core/Images/JpegErrorManager.cpp
+  ${ORTHANC_ROOT}/Core/Images/JpegReader.cpp
+  ${ORTHANC_ROOT}/Core/Images/PngReader.cpp
+  ${ORTHANC_ROOT}/Core/Logging.cpp
+  ${ORTHANC_ROOT}/Core/Toolbox.cpp
+  ${ORTHANC_ROOT}/Core/WebServiceParameters.cpp
+  ${ORTHANC_ROOT}/Resources/ThirdParty/base64/base64.cpp
+
+  ${AUTOGENERATED_SOURCES}
+
+  # Mandatory components
+  ${BOOST_SOURCES}
+  ${CAIRO_SOURCES}
+  ${JSONCPP_SOURCES}
+  ${PIXMAN_SOURCES}
+  ${ZLIB_SOURCES}
+  ${LIBPNG_SOURCES}
+  ${LIBJPEG_SOURCES}
+
+  # Optional components
+  ${OPENSSL_SOURCES}
+  ${CURL_SOURCES}
+  ${SDL_SOURCES}
+  )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/CMake/PixmanConfiguration.cmake	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,206 @@
+if (STATIC_BUILD OR NOT USE_SYSTEM_PIXMAN)
+  SET(PIXMAN_SOURCES_DIR ${CMAKE_BINARY_DIR}/pixman-0.34.0)
+  SET(PIXMAN_URL "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/Stone/pixman-0.34.0.tar.gz")
+  SET(PIXMAN_MD5 "e80ebae4da01e77f68744319f01d52a3")
+
+  if (IS_DIRECTORY "${PIXMAN_SOURCES_DIR}")
+    set(FirstRun OFF)
+  else()
+    set(FirstRun ON)
+  endif()
+
+  DownloadPackage(${PIXMAN_MD5} ${PIXMAN_URL} "${PIXMAN_SOURCES_DIR}")
+
+  # Apply a patch for NaCl32: This bypasses the custom implementation of
+  # "cpuid" that makes use of assembly code leading to "unrecognized
+  # instruction" when validating ".nexe" files using "ncval"
+  execute_process(
+    COMMAND ${PATCH_EXECUTABLE} -p0 -N -i ${CMAKE_CURRENT_LIST_DIR}/PixmanConfiguration.patch
+    WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+    RESULT_VARIABLE Failure
+    )
+
+  if (Failure AND FirstRun)
+    message(FATAL_ERROR "Error while patching a file")
+  endif()
+
+  set(PIXMAN_VERSION_MAJOR 0)
+  set(PIXMAN_VERSION_MINOR 34)
+  set(PIXMAN_VERSION_MICRO 0)
+  configure_file(
+    ${PIXMAN_SOURCES_DIR}/pixman/pixman-version.h.in
+    ${PIXMAN_SOURCES_DIR}/pixman/pixman-version.h)
+
+  list(APPEND PIXMAN_SOURCES 
+    ${PIXMAN_SOURCES_DIR}/pixman/pixman-access-accessors.c
+    ${PIXMAN_SOURCES_DIR}/pixman/pixman-access.c
+    ${PIXMAN_SOURCES_DIR}/pixman/pixman-arm.c
+    #${PIXMAN_SOURCES_DIR}/pixman/pixman-arm-neon.c
+    #${PIXMAN_SOURCES_DIR}/pixman/pixman-arm-simd.c
+    ${PIXMAN_SOURCES_DIR}/pixman/pixman-bits-image.c
+    ${PIXMAN_SOURCES_DIR}/pixman/pixman.c
+    ${PIXMAN_SOURCES_DIR}/pixman/pixman-combine32.c
+    ${PIXMAN_SOURCES_DIR}/pixman/pixman-combine-float.c
+    ${PIXMAN_SOURCES_DIR}/pixman/pixman-conical-gradient.c
+    ${PIXMAN_SOURCES_DIR}/pixman/pixman-edge-accessors.c
+    ${PIXMAN_SOURCES_DIR}/pixman/pixman-edge.c
+    ${PIXMAN_SOURCES_DIR}/pixman/pixman-fast-path.c
+    ${PIXMAN_SOURCES_DIR}/pixman/pixman-filter.c
+    ${PIXMAN_SOURCES_DIR}/pixman/pixman-general.c
+    ${PIXMAN_SOURCES_DIR}/pixman/pixman-glyph.c
+    ${PIXMAN_SOURCES_DIR}/pixman/pixman-gradient-walker.c
+    ${PIXMAN_SOURCES_DIR}/pixman/pixman-image.c
+    ${PIXMAN_SOURCES_DIR}/pixman/pixman-implementation.c
+    ${PIXMAN_SOURCES_DIR}/pixman/pixman-linear-gradient.c
+    ${PIXMAN_SOURCES_DIR}/pixman/pixman-matrix.c
+    ${PIXMAN_SOURCES_DIR}/pixman/pixman-mips.c
+    #${PIXMAN_SOURCES_DIR}/pixman/pixman-mips-dspr2.c
+    ${PIXMAN_SOURCES_DIR}/pixman/pixman-mmx.c
+    ${PIXMAN_SOURCES_DIR}/pixman/pixman-noop.c
+    ${PIXMAN_SOURCES_DIR}/pixman/pixman-ppc.c
+    ${PIXMAN_SOURCES_DIR}/pixman/pixman-radial-gradient.c
+    ${PIXMAN_SOURCES_DIR}/pixman/pixman-region16.c
+    ${PIXMAN_SOURCES_DIR}/pixman/pixman-region32.c
+    #${PIXMAN_SOURCES_DIR}/pixman/pixman-region.c
+    ${PIXMAN_SOURCES_DIR}/pixman/pixman-solid-fill.c
+    #${PIXMAN_SOURCES_DIR}/pixman/pixman-sse2.c
+    #${PIXMAN_SOURCES_DIR}/pixman/pixman-ssse3.c
+    ${PIXMAN_SOURCES_DIR}/pixman/pixman-timer.c
+    ${PIXMAN_SOURCES_DIR}/pixman/pixman-trap.c
+    ${PIXMAN_SOURCES_DIR}/pixman/pixman-utils.c
+    #${PIXMAN_SOURCES_DIR}/pixman/pixman-vmx.c
+    ${PIXMAN_SOURCES_DIR}/pixman/pixman-x86.c
+    )
+  
+  set(PIXMAN_DEFINITIONS "PACKAGE=\"pixman\"")
+
+  if (CMAKE_SYSTEM_PROCESSOR)
+    message("Processor: ${CMAKE_SYSTEM_PROCESSOR}")
+  else()
+    message("Processor: Not applicable")
+  endif()
+
+
+  if (CMAKE_SYSTEM_NAME STREQUAL "Windows")
+
+    ##########################
+    ## Windows 32 or 64
+    ##########################
+
+    if (CMAKE_COMPILER_IS_GNUCXX)
+      set(PIXMAN_DEFINITIONS "${PIXMAN_DEFINITIONS};TLS=__thread;HAVE_GCC_VECTOR_EXTENSIONS;HAVE_BUILTIN_CLZ;HAVE_FEDIVBYZERO=1;HAVE_FENV_H=1;HAVE_MPROTECT=1;HAVE_FLOAT128;HAVE_POSIX_MEMALIGN;USE_GCC_INLINE_ASM=1;HAVE_GETPAGESIZE=1")
+
+      # The option "-mstackrealign" is necessary to avoid a crash on
+      # Windows if enabling SSE2. As an alternative, it is possible to
+      # fully disable hardware acceleration.
+      # https://bugs.freedesktop.org/show_bug.cgi?id=68300#c4
+      SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -msse2 -mssse3 -mstackrealign")
+      SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -msse2 -mssse3 -mstackrealign")
+    endif()
+
+    list(APPEND PIXMAN_SOURCES 
+      ${PIXMAN_SOURCES_DIR}/pixman/pixman-sse2.c
+      ${PIXMAN_SOURCES_DIR}/pixman/pixman-ssse3.c
+      )
+    add_definitions(
+      -DUSE_X86_MMX=1
+      -DUSE_SSE2=1
+      -DUSE_SSSE3=1
+      )
+
+
+    ##########################
+    ## Generic x86 processor
+    ##########################
+
+  elseif (CMAKE_SYSTEM_PROCESSOR STREQUAL "x86" OR
+      CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64" OR
+      CMAKE_SYSTEM_NAME STREQUAL "NaCl32" OR
+      CMAKE_SYSTEM_NAME STREQUAL "NaCl64")
+
+    set(PIXMAN_DEFINITIONS "${PIXMAN_DEFINITIONS};TLS=__thread;HAVE_GCC_VECTOR_EXTENSIONS;HAVE_BUILTIN_CLZ;HAVE_MPROTECT=1;HAVE_FLOAT128;HAVE_POSIX_MEMALIGN;USE_GCC_INLINE_ASM;HAVE_GETPAGESIZE=1")
+
+    if (${CMAKE_SYSTEM_NAME} STREQUAL "NaCl32" OR
+        ${CMAKE_SYSTEM_NAME} STREQUAL "NaCl64")
+      # The MMX instructions lead to "unrecognized instruction" when
+      # validating ".nexe" files using "ncval", disable them
+    else()
+      #add_definitions(-DUSE_X86_MMX=1)
+    endif()
+
+    list(APPEND PIXMAN_SOURCES 
+      ${PIXMAN_SOURCES_DIR}/pixman/pixman-sse2.c
+      ${PIXMAN_SOURCES_DIR}/pixman/pixman-ssse3.c
+      )
+    add_definitions(
+      -DUSE_SSE2=1
+      -DUSE_SSSE3=1
+      )
+    SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -msse2 -mssse3")
+    SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -msse2 -mssse3")
+
+
+    ##########################
+    ## ARM processor
+    ##########################
+
+  elseif (CMAKE_SYSTEM_PROCESSOR STREQUAL "armv5te" OR
+      CMAKE_SYSTEM_PROCESSOR STREQUAL "armv6" OR
+      CMAKE_SYSTEM_PROCESSOR STREQUAL "armv7-a" OR
+      CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64")
+
+    set(PIXMAN_DEFINITIONS "${PIXMAN_DEFINITIONS};TLS=__thread")
+
+    if (NEON)
+      message("Processor with NEON instructions")
+      list(APPEND PIXMAN_SOURCES 
+        ${PIXMAN_SOURCES_DIR}/pixman/pixman-arm-neon.c
+        ${PIXMAN_SOURCES_DIR}/pixman/pixman-arm-neon-asm.S
+        ${PIXMAN_SOURCES_DIR}/pixman/pixman-arm-neon-asm-bilinear.S
+        )
+      add_definitions(
+        -DUSE_ARM_NEON=1
+        )
+    elseif()
+      message("Processor without NEON instructions")
+    endif()
+
+    add_definitions(
+      -DUSE_ARM_SIMD=1
+      )
+    list(APPEND PIXMAN_SOURCES 
+      ${PIXMAN_SOURCES_DIR}/pixman/pixman-arm.c
+      ${PIXMAN_SOURCES_DIR}/pixman/pixman-arm-simd-asm.S
+      ${PIXMAN_SOURCES_DIR}/pixman/pixman-arm-simd-asm-scaled.S
+      ${PIXMAN_SOURCES_DIR}/pixman/pixman-arm-simd.c
+      )
+
+    ##########################
+    ## Portable Google NaCl
+    ##########################
+
+  elseif (CMAKE_SYSTEM_NAME STREQUAL "PNaCl")
+    # No hardware acceleration
+    set(PIXMAN_DEFINITIONS "${PIXMAN_DEFINITIONS};TLS=__thread")
+
+  else()
+    message(FATAL_ERROR "Support your platform here")
+  endif()
+
+
+  include_directories(
+    ${PIXMAN_SOURCES_DIR}/pixman
+    )
+
+  set_property(
+    SOURCE ${PIXMAN_SOURCES}
+    PROPERTY COMPILE_DEFINITIONS ${PIXMAN_DEFINITIONS}
+    )
+
+else()
+
+  pkg_search_module(PIXMAN REQUIRED pixman-1)
+  include_directories(${PIXMAN_INCLUDE_DIRS})
+  link_libraries(${PIXMAN_LIBRARIES})
+
+endif()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/CMake/PixmanConfiguration.patch	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,13 @@
+diff -urEb pixman-0.34.0.orig/pixman/pixman-x86.c pixman-0.34.0/pixman/pixman-x86.c
+--- pixman-0.34.0.orig/pixman/pixman-x86.c	2016-07-05 12:46:52.889101224 +0200
++++ pixman-0.34.0/pixman/pixman-x86.c	2016-07-05 12:47:07.253101808 +0200
+@@ -80,7 +80,7 @@
+ static pixman_bool_t
+ have_cpuid (void)
+ {
+-#if _PIXMAN_X86_64 || defined (_MSC_VER)
++#if _PIXMAN_X86_64 || defined (_MSC_VER) || defined(__native_client__)
+ 
+     return TRUE;
+ 
+Only in pixman-0.34.0/pixman: pixman-x86.c~
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/CMake/SdlConfiguration.cmake	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,160 @@
+if (STATIC_BUILD OR NOT USE_SYSTEM_SDL)
+  SET(SDL_SOURCES_DIR ${CMAKE_BINARY_DIR}/SDL2-2.0.4)
+  SET(SDL_URL "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/Stone/SDL2-2.0.4.tar.gz")
+  SET(SDL_MD5 "44fc4a023349933e7f5d7a582f7b886e")
+
+  DownloadPackage(${SDL_MD5} ${SDL_URL} "${SDL_SOURCES_DIR}")
+
+  include_directories(${SDL_SOURCES_DIR}/include)
+
+  set(TMP "${SDL_SOURCES_DIR}/include/SDL_config_premake.h")
+  if (NOT EXISTS "${TMP}")
+    file(WRITE "${TMP}" "
+#include \"SDL_platform.h\"
+#define HAVE_STDARG_H 1
+#define HAVE_STDDEF_H 1
+#define HAVE_STDINT_H 1
+")
+  endif()
+
+  # General source files
+  file(GLOB SDL_SOURCES
+    ${SDL_SOURCES_DIR}/src/*.c
+    ${SDL_SOURCES_DIR}/src/atomic/*.c
+    ${SDL_SOURCES_DIR}/src/audio/*.c
+    ${SDL_SOURCES_DIR}/src/cpuinfo/*.c
+    ${SDL_SOURCES_DIR}/src/dynapi/*.c
+    ${SDL_SOURCES_DIR}/src/events/*.c
+    ${SDL_SOURCES_DIR}/src/file/*.c
+    ${SDL_SOURCES_DIR}/src/haptic/*.c
+    ${SDL_SOURCES_DIR}/src/joystick/*.c
+    ${SDL_SOURCES_DIR}/src/libm/*.c
+    ${SDL_SOURCES_DIR}/src/power/*.c
+    ${SDL_SOURCES_DIR}/src/render/*.c
+    ${SDL_SOURCES_DIR}/src/stdlib/*.c
+    ${SDL_SOURCES_DIR}/src/thread/*.c
+    ${SDL_SOURCES_DIR}/src/timer/*.c
+    ${SDL_SOURCES_DIR}/src/video/*.c
+
+    ${SDL_SOURCES_DIR}/src/loadso/dummy/*.c
+    #${SDL_SOURCES_DIR}/src/timer/dummy/*.c
+    ${SDL_SOURCES_DIR}/src/audio/dummy/*.c
+    ${SDL_SOURCES_DIR}/src/filesystem/dummy/*.c
+    ${SDL_SOURCES_DIR}/src/haptic/dummy/*.c
+    ${SDL_SOURCES_DIR}/src/joystick/dummy/*.c
+    #${SDL_SOURCES_DIR}/src/main/dummy/*.c
+    ${SDL_SOURCES_DIR}/src/video/dummy/*.c
+    )
+
+  add_definitions(
+    -DUSING_PREMAKE_CONFIG_H=1
+
+    -DSDL_AUDIO_DISABLED=1
+    -DSDL_AUDIO_DRIVER_DUMMY=1
+    -DSDL_FILESYSTEM_DISABLED=1
+    -DSDL_FILESYSTEM_DUMMY=1
+    -DSDL_FILE_DISABLED=1
+    -DSDL_HAPTIC_DISABLED=1
+    -DSDL_JOYSTICK_DISABLED=1
+
+    #-DSDL_THREADS_DISABLED=1
+    )
+
+  if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
+    file(GLOB TMP
+      ${SDL_SOURCES_DIR}/src/core/linux/*.c
+      ${SDL_SOURCES_DIR}/src/loadso/dlopen/*.c
+      ${SDL_SOURCES_DIR}/src/render/opengl/*.c
+      ${SDL_SOURCES_DIR}/src/render/opengles2/*.c
+      ${SDL_SOURCES_DIR}/src/render/software/*.c
+      ${SDL_SOURCES_DIR}/src/thread/pthread/*.c
+      ${SDL_SOURCES_DIR}/src/timer/unix/*.c
+      ${SDL_SOURCES_DIR}/src/video/x11/*.c
+      )
+
+    list(APPEND SDL_SOURCES ${TMP})
+
+    add_definitions(
+      -DSDL_LOADSO_DLOPEN=1
+      -DSDL_THREAD_PTHREAD=1
+      -DSDL_TIMER_UNIX=1
+      -DSDL_POWER_DISABLED=1
+
+      -DSDL_VIDEO_DRIVER_X11=1
+      -DSDL_VIDEO_OPENGL=1
+      -DSDL_VIDEO_OPENGL_ES2=1
+      -DSDL_VIDEO_RENDER_OGL=1
+      -DSDL_VIDEO_RENDER_OGL_ES2=1
+      -DSDL_VIDEO_OPENGL_GLX=1
+      -DSDL_VIDEO_OPENGL_EGL=1
+      
+      -DSDL_ASSEMBLY_ROUTINES=1
+      -DSDL_THREAD_PTHREAD_RECURSIVE_MUTEX=1
+      -DSDL_VIDEO_DRIVER_X11_SUPPORTS_GENERIC_EVENTS=1
+      -DHAVE_GCC_SYNC_LOCK_TEST_AND_SET=1
+      )
+
+    link_libraries(X11 Xext)
+
+  elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
+    file(GLOB TMP
+      ${SDL_SOURCES_DIR}/src/audio/directsound/*.c
+      ${SDL_SOURCES_DIR}/src/audio/disk/*.c
+      ${SDL_SOURCES_DIR}/src/audio/winmm/*.c
+      ${SDL_SOURCES_DIR}/src/joystick/windows/*.c
+      ${SDL_SOURCES_DIR}/src/haptic/windows/*.c
+      ${SDL_SOURCES_DIR}/src/power/windows/*.c
+
+      ${SDL_SOURCES_DIR}/src/main/windows/*.c
+      ${SDL_SOURCES_DIR}/src/core/windows/*.c
+      ${SDL_SOURCES_DIR}/src/loadso/windows/*.c
+      ${SDL_SOURCES_DIR}/src/render/direct3d/*.c
+      ${SDL_SOURCES_DIR}/src/render/direct3d11/*.c
+      ${SDL_SOURCES_DIR}/src/render/opengl/*.c
+      ${SDL_SOURCES_DIR}/src/render/psp/*.c
+      ${SDL_SOURCES_DIR}/src/render/opengles/*.c
+      ${SDL_SOURCES_DIR}/src/render/opengles2/*.c
+      ${SDL_SOURCES_DIR}/src/render/software/*.c
+      ${SDL_SOURCES_DIR}/src/thread/generic/SDL_syscond.c   # Don't include more files from "thread/generic/*.c"!
+      ${SDL_SOURCES_DIR}/src/thread/windows/*.c
+      ${SDL_SOURCES_DIR}/src/timer/windows/*.c
+      ${SDL_SOURCES_DIR}/src/video/windows/*.c
+      ${SDL_SOURCES_DIR}/src/windows/dlopen/*.c
+      )
+
+    list(APPEND SDL_SOURCES ${TMP})
+
+    # NB: OpenGL ES headers are not available in MinGW-W64
+    add_definitions(
+      -DSDL_LOADSO_WINDOWS=1
+      -DSDL_THREAD_WINDOWS=1
+      -DSDL_TIMER_WINDOWS=1
+      -DSDL_POWER_WINDOWS=1
+
+      -DSDL_VIDEO_OPENGL=1
+      -DSDL_VIDEO_OPENGL_WGL=1
+      -DSDL_VIDEO_RENDER_D3D=1
+      -DSDL_VIDEO_RENDER_OGL=1
+      -DSDL_VIDEO_DRIVER_WINDOWS=1
+      )
+
+    if (MSVC)
+      add_definitions(
+        -D__FLTUSED__
+        -DHAVE_LIBC=1
+      )
+    else()
+      add_definitions(
+        -DHAVE_GCC_ATOMICS=1
+        -DSDL_ASSEMBLY_ROUTINES=1
+        )
+    endif()
+    
+    link_libraries(imm32 winmm version)
+  endif()
+
+else()
+  pkg_search_module(SDL2 REQUIRED sdl2)
+  include_directories(${SDL2_INCLUDE_DIRS})
+  link_libraries(${SDL2_LIBRARIES})
+endif()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/CMake/cairo-features.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,45 @@
+#ifndef CAIRO_FEATURES_H
+#define CAIRO_FEATURES_H
+
+#define CAIRO_HAS_GOBJECT_FUNCTIONS 1
+#define CAIRO_HAS_IMAGE_SURFACE 1
+#define CAIRO_HAS_MIME_SURFACE 1
+#define CAIRO_HAS_OBSERVER_SURFACE 1
+#define CAIRO_HAS_RECORDING_SURFACE 1
+#define CAIRO_HAS_USER_FONT 1
+
+/*#undef CAIRO_HAS_BEOS_SURFACE */
+/*#undef CAIRO_HAS_COGL_SURFACE */
+/*#undef CAIRO_HAS_DIRECTFB_SURFACE */
+/*#undef CAIRO_HAS_DRM_SURFACE */
+/*#undef CAIRO_HAS_EGL_FUNCTIONS */
+/*#undef CAIRO_HAS_FC_FONT */
+/*#undef CAIRO_HAS_FT_FONT */
+/*#undef CAIRO_HAS_GALLIUM_SURFACE */
+/*#undef CAIRO_HAS_GLESV2_SURFACE */
+/*#undef CAIRO_HAS_GLX_FUNCTIONS */
+/*#undef CAIRO_HAS_GL_SURFACE */
+/*#undef CAIRO_HAS_OS2_SURFACE */
+/*#undef CAIRO_HAS_PDF_SURFACE */
+/*#undef CAIRO_HAS_PNG_FUNCTIONS */
+/*#undef CAIRO_HAS_PS_SURFACE */
+/*#undef CAIRO_HAS_QT_SURFACE */
+/*#undef CAIRO_HAS_QUARTZ_FONT */
+/*#undef CAIRO_HAS_QUARTZ_IMAGE_SURFACE */
+/*#undef CAIRO_HAS_QUARTZ_SURFACE */
+/*#undef CAIRO_HAS_SCRIPT_SURFACE */
+/*#undef CAIRO_HAS_SKIA_SURFACE */
+/*#undef CAIRO_HAS_SVG_SURFACE */
+/*#undef CAIRO_HAS_TEE_SURFACE */
+/*#undef CAIRO_HAS_VG_SURFACE */
+/*#undef CAIRO_HAS_WGL_FUNCTIONS */
+/*#undef CAIRO_HAS_WIN32_FONT */
+/*#undef CAIRO_HAS_WIN32_SURFACE */
+/*#undef CAIRO_HAS_XCB_SHM_FUNCTIONS */
+/*#undef CAIRO_HAS_XCB_SURFACE */
+/*#undef CAIRO_HAS_XLIB_SURFACE */
+/*#undef CAIRO_HAS_XLIB_XCB_FUNCTIONS */
+/*#undef CAIRO_HAS_XLIB_XRENDER_SURFACE */
+/*#undef CAIRO_HAS_XML_SURFACE */
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Colormaps/GenerateColormaps.py	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,36 @@
+#!/usr/bin/python
+
+import array
+import matplotlib.pyplot as plt
+
+def GenerateColormap(name):
+    colormap = []
+
+    for gray in range(256):
+        if name == 'red':
+            color = (gray / 255.0, 0, 0)
+        elif name == 'green':
+            color = (0, gray / 255.0, 0)
+        elif name == 'blue':
+            color = (0, 0, gray / 255.0)
+        else:
+            color = plt.get_cmap(name) (gray)
+
+        colormap += map(lambda k: int(round(color[k] * 255)), range(3))
+
+    colormap[0] = 0
+    colormap[1] = 0
+    colormap[2] = 0
+
+    return array.array('B', colormap).tostring()
+
+
+for name in [ 
+        'hot', 
+        'jet', 
+        'blue',
+        'green',
+        'red',
+]:
+    with open('%s.lut' % name, 'w') as f:
+        f.write(GenerateColormap(name))
Binary file Resources/Colormaps/blue.lut has changed
Binary file Resources/Colormaps/green.lut has changed
Binary file Resources/Colormaps/hot.lut has changed
Binary file Resources/Colormaps/jet.lut has changed
Binary file Resources/Colormaps/red.lut has changed
Binary file Resources/OrthancLogoDocumentation.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/OrthancStone.doxygen	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,1795 @@
+# Doxyfile 1.8.1.2
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project.
+#
+# All text after a hash (#) is considered a comment and will be ignored.
+# The format is:
+#       TAG = value [value, ...]
+# For lists items can also be appended using:
+#       TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (" ").
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the config file
+# that follow. The default is UTF-8 which is also the encoding used for all
+# text before the first occurrence of this tag. Doxygen uses libiconv (or the
+# iconv built into libc) for the transcoding. See
+# http://www.gnu.org/software/libiconv for the list of possible encodings.
+
+DOXYFILE_ENCODING      = UTF-8
+
+# The PROJECT_NAME tag is a single word (or sequence of words) that should
+# identify the project. Note that if you do not use Doxywizard you need
+# to put quotes around the project name if it contains spaces.
+
+PROJECT_NAME           = OrthancStone
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number.
+# This could be handy for archiving the generated documentation or
+# if some version control system is used.
+
+PROJECT_NUMBER         =
+
+# Using the PROJECT_BRIEF tag one can provide an optional one line description
+# for a project that appears at the top of each page and should give viewer
+# a quick idea about the purpose of the project. Keep the description short.
+
+PROJECT_BRIEF          = "Stone of Orthanc API"
+
+# With the PROJECT_LOGO tag one can specify an logo or icon that is
+# included in the documentation. The maximum height of the logo should not
+# exceed 55 pixels and the maximum width should not exceed 200 pixels.
+# Doxygen will copy the logo to the output directory.
+
+PROJECT_LOGO           = @ORTHANC_STONE_DIR@/Resources/OrthancLogoDocumentation.png
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
+# base path where the generated documentation will be put.
+# If a relative path is entered, it will be relative to the location
+# where doxygen was started. If left blank the current directory will be used.
+
+OUTPUT_DIRECTORY       = OrthancStoneDocumentation
+
+# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create
+# 4096 sub-directories (in 2 levels) under the output directory of each output
+# format and will distribute the generated files over these directories.
+# Enabling this option can be useful when feeding doxygen a huge amount of
+# source files, where putting all generated files in the same directory would
+# otherwise cause performance problems for the file system.
+
+CREATE_SUBDIRS         = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# The default language is English, other supported languages are:
+# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional,
+# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German,
+# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English
+# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian,
+# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak,
+# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese.
+
+OUTPUT_LANGUAGE        = English
+
+# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will
+# include brief member descriptions after the members that are listed in
+# the file and class documentation (similar to JavaDoc).
+# Set to NO to disable this.
+
+BRIEF_MEMBER_DESC      = YES
+
+# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend
+# the brief description of a member or function before the detailed description.
+# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+
+REPEAT_BRIEF           = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator
+# that is used to form the text in various listings. Each string
+# in this list, if found as the leading text of the brief description, will be
+# stripped from the text and the result after processing the whole list, is
+# used as the annotated text. Otherwise, the brief description is used as-is.
+# If left blank, the following values are used ("$name" is automatically
+# replaced with the name of the entity): "The $name class" "The $name widget"
+# "The $name file" "is" "provides" "specifies" "contains"
+# "represents" "a" "an" "the"
+
+ABBREVIATE_BRIEF       =
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# Doxygen will generate a detailed section even if there is only a brief
+# description.
+
+ALWAYS_DETAILED_SEC    = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
+# inherited members of a class in the documentation of that class as if those
+# members were ordinary class members. Constructors, destructors and assignment
+# operators of the base classes will not be shown.
+
+INLINE_INHERITED_MEMB  = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full
+# path before files name in the file list and in the header files. If set
+# to NO the shortest path that makes the file name unique will be used.
+
+FULL_PATH_NAMES        = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag
+# can be used to strip a user-defined part of the path. Stripping is
+# only done if one of the specified strings matches the left-hand part of
+# the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the
+# path to strip.
+
+STRIP_FROM_PATH        =
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of
+# the path mentioned in the documentation of a class, which tells
+# the reader which header file to include in order to use a class.
+# If left blank only the name of the header file containing the class
+# definition is used. Otherwise one should specify the include paths that
+# are normally passed to the compiler using the -I flag.
+
+STRIP_FROM_INC_PATH    =
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter
+# (but less readable) file names. This can be useful if your file system
+# doesn't support long names like on DOS, Mac, or CD-ROM.
+
+SHORT_NAMES            = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen
+# will interpret the first line (until the first dot) of a JavaDoc-style
+# comment as the brief description. If set to NO, the JavaDoc
+# comments will behave just like regular Qt-style comments
+# (thus requiring an explicit @brief command for a brief description.)
+
+JAVADOC_AUTOBRIEF      = NO
+
+# If the QT_AUTOBRIEF tag is set to YES then Doxygen will
+# interpret the first line (until the first dot) of a Qt-style
+# comment as the brief description. If set to NO, the comments
+# will behave just like regular Qt-style comments (thus requiring
+# an explicit \brief command for a brief description.)
+
+QT_AUTOBRIEF           = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen
+# treat a multi-line C++ special comment block (i.e. a block of //! or ///
+# comments) as a brief description. This used to be the default behaviour.
+# The new default is to treat a multi-line C++ comment block as a detailed
+# description. Set this tag to YES if you prefer the old behaviour instead.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented
+# member inherits the documentation from any documented member that it
+# re-implements.
+
+INHERIT_DOCS           = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce
+# a new page for each member. If set to NO, the documentation of a member will
+# be part of the file/class/namespace that contains it.
+
+SEPARATE_MEMBER_PAGES  = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab.
+# Doxygen uses this value to replace tabs by spaces in code fragments.
+
+TAB_SIZE               = 8
+
+# This tag can be used to specify a number of aliases that acts
+# as commands in the documentation. An alias has the form "name=value".
+# For example adding "sideeffect=\par Side Effects:\n" will allow you to
+# put the command \sideeffect (or @sideeffect) in the documentation, which
+# will result in a user-defined paragraph with heading "Side Effects:".
+# You can put \n's in the value part of an alias to insert newlines.
+
+ALIASES                =
+
+# This tag can be used to specify a number of word-keyword mappings (TCL only).
+# A mapping has the form "name=value". For example adding
+# "class=itcl::class" will allow you to use the command class in the
+# itcl::class meaning.
+
+TCL_SUBST              =
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C
+# sources only. Doxygen will then generate output that is more tailored for C.
+# For instance, some of the names that are used will be different. The list
+# of all members will be omitted, etc.
+
+OPTIMIZE_OUTPUT_FOR_C  = NO
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java
+# sources only. Doxygen will then generate output that is more tailored for
+# Java. For instance, namespaces will be presented as packages, qualified
+# scopes will look different, etc.
+
+OPTIMIZE_OUTPUT_JAVA   = NO
+
+# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
+# sources only. Doxygen will then generate output that is more tailored for
+# Fortran.
+
+OPTIMIZE_FOR_FORTRAN   = NO
+
+# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
+# sources. Doxygen will then generate output that is tailored for
+# VHDL.
+
+OPTIMIZE_OUTPUT_VHDL   = NO
+
+# Doxygen selects the parser to use depending on the extension of the files it
+# parses. With this tag you can assign which parser to use for a given extension.
+# Doxygen has a built-in mapping, but you can override or extend it using this
+# tag. The format is ext=language, where ext is a file extension, and language
+# is one of the parsers supported by doxygen: IDL, Java, Javascript, CSharp, C,
+# C++, D, PHP, Objective-C, Python, Fortran, VHDL, C, C++. For instance to make
+# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C
+# (default is Fortran), use: inc=Fortran f=C. Note that for custom extensions
+# you also need to set FILE_PATTERNS otherwise the files are not read by doxygen.
+
+EXTENSION_MAPPING      =
+
+# If MARKDOWN_SUPPORT is enabled (the default) then doxygen pre-processes all
+# comments according to the Markdown format, which allows for more readable
+# documentation. See http://daringfireball.net/projects/markdown/ for details.
+# The output of markdown processing is further processed by doxygen, so you
+# can mix doxygen, HTML, and XML commands with Markdown formatting.
+# Disable only in case of backward compatibilities issues.
+
+MARKDOWN_SUPPORT       = YES
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
+# to include (a tag file for) the STL sources as input, then you should
+# set this tag to YES in order to let doxygen match functions declarations and
+# definitions whose arguments contain STL classes (e.g. func(std::string); v.s.
+# func(std::string) {}). This also makes the inheritance and collaboration
+# diagrams that involve STL classes more complete and accurate.
+
+BUILTIN_STL_SUPPORT    = YES
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# enable parsing support.
+
+CPP_CLI_SUPPORT        = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only.
+# Doxygen will parse them like normal C++ but will assume all classes use public
+# instead of private inheritance when no explicit protection keyword is present.
+
+SIP_SUPPORT            = NO
+
+# For Microsoft's IDL there are propget and propput attributes to indicate getter
+# and setter methods for a property. Setting this option to YES (the default)
+# will make doxygen replace the get and set methods by a property in the
+# documentation. This will only work if the methods are indeed getting or
+# setting a simple type. If this is not the case, or you want to show the
+# methods anyway, you should set this option to NO.
+
+IDL_PROPERTY_SUPPORT   = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES, then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+
+DISTRIBUTE_GROUP_DOC   = NO
+
+# Set the SUBGROUPING tag to YES (the default) to allow class member groups of
+# the same type (for instance a group of public functions) to be put as a
+# subgroup of that type (e.g. under the Public Functions section). Set it to
+# NO to prevent subgrouping. Alternatively, this can be done per class using
+# the \nosubgrouping command.
+
+SUBGROUPING            = YES
+
+# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and
+# unions are shown inside the group in which they are included (e.g. using
+# @ingroup) instead of on a separate page (for HTML and Man pages) or
+# section (for LaTeX and RTF).
+
+INLINE_GROUPED_CLASSES = NO
+
+# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and
+# unions with only public data fields will be shown inline in the documentation
+# of the scope in which they are defined (i.e. file, namespace, or group
+# documentation), provided this scope is documented. If set to NO (the default),
+# structs, classes, and unions are shown on a separate page (for HTML and Man
+# pages) or section (for LaTeX and RTF).
+
+INLINE_SIMPLE_STRUCTS  = NO
+
+# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum
+# is documented as struct, union, or enum with the name of the typedef. So
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
+# with name TypeT. When disabled the typedef will appear as a member of a file,
+# namespace, or class. And the struct will be named TypeS. This can typically
+# be useful for C code in case the coding convention dictates that all compound
+# types are typedef'ed and only the typedef is referenced, never the tag name.
+
+TYPEDEF_HIDES_STRUCT   = NO
+
+# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to
+# determine which symbols to keep in memory and which to flush to disk.
+# When the cache is full, less often used symbols will be written to disk.
+# For small to medium size projects (<1000 input files) the default value is
+# probably good enough. For larger projects a too small cache size can cause
+# doxygen to be busy swapping symbols to and from disk most of the time
+# causing a significant performance penalty.
+# If the system has enough physical memory increasing the cache will improve the
+# performance by keeping more symbols in memory. Note that the value works on
+# a logarithmic scale so increasing the size by one will roughly double the
+# memory usage. The cache size is given by this formula:
+# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0,
+# corresponding to a cache size of 2^16 = 65536 symbols.
+
+SYMBOL_CACHE_SIZE      = 0
+
+# Similar to the SYMBOL_CACHE_SIZE the size of the symbol lookup cache can be
+# set using LOOKUP_CACHE_SIZE. This cache is used to resolve symbols given
+# their name and scope. Since this can be an expensive process and often the
+# same symbol appear multiple times in the code, doxygen keeps a cache of
+# pre-resolved symbols. If the cache is too small doxygen will become slower.
+# If the cache is too large, memory is wasted. The cache size is given by this
+# formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range is 0..9, the default is 0,
+# corresponding to a cache size of 2^16 = 65536 symbols.
+
+LOOKUP_CACHE_SIZE      = 0
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in
+# documentation are documented, even if no documentation was available.
+# Private class members and static file members will be hidden unless
+# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES
+
+EXTRACT_ALL            = YES
+
+# If the EXTRACT_PRIVATE tag is set to YES all private members of a class
+# will be included in the documentation.
+
+EXTRACT_PRIVATE        = NO
+
+# If the EXTRACT_PACKAGE tag is set to YES all members with package or internal scope will be included in the documentation.
+
+EXTRACT_PACKAGE        = NO
+
+# If the EXTRACT_STATIC tag is set to YES all static members of a file
+# will be included in the documentation.
+
+EXTRACT_STATIC         = NO
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs)
+# defined locally in source files will be included in the documentation.
+# If set to NO only classes defined in header files are included.
+
+EXTRACT_LOCAL_CLASSES  = YES
+
+# This flag is only useful for Objective-C code. When set to YES local
+# methods, which are defined in the implementation section but not in
+# the interface are included in the documentation.
+# If set to NO (the default) only methods in the interface are included.
+
+EXTRACT_LOCAL_METHODS  = NO
+
+# If this flag is set to YES, the members of anonymous namespaces will be
+# extracted and appear in the documentation as a namespace called
+# 'anonymous_namespace{file}', where file will be replaced with the base
+# name of the file that contains the anonymous namespace. By default
+# anonymous namespaces are hidden.
+
+EXTRACT_ANON_NSPACES   = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all
+# undocumented members of documented classes, files or namespaces.
+# If set to NO (the default) these members will be included in the
+# various overviews, but no documentation section is generated.
+# This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_MEMBERS     = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy.
+# If set to NO (the default) these classes will be included in the various
+# overviews. This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_CLASSES     = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all
+# friend (class|struct|union) declarations.
+# If set to NO (the default) these declarations will be included in the
+# documentation.
+
+HIDE_FRIEND_COMPOUNDS  = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any
+# documentation blocks found inside the body of a function.
+# If set to NO (the default) these blocks will be appended to the
+# function's detailed documentation block.
+
+HIDE_IN_BODY_DOCS      = NO
+
+# The INTERNAL_DOCS tag determines if documentation
+# that is typed after a \internal command is included. If the tag is set
+# to NO (the default) then the documentation will be excluded.
+# Set it to YES to include the internal documentation.
+
+INTERNAL_DOCS          = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate
+# file names in lower-case letters. If set to YES upper-case letters are also
+# allowed. This is useful if you have classes or files whose names only differ
+# in case and if your file system supports case sensitive file names. Windows
+# and Mac users are advised to set this option to NO.
+
+CASE_SENSE_NAMES       = YES
+
+# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen
+# will show members with their full class and namespace scopes in the
+# documentation. If set to YES the scope will be hidden.
+
+HIDE_SCOPE_NAMES       = NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen
+# will put a list of the files that are included by a file in the documentation
+# of that file.
+
+SHOW_INCLUDE_FILES     = YES
+
+# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen
+# will list include files with double quotes in the documentation
+# rather than with sharp brackets.
+
+FORCE_LOCAL_INCLUDES   = NO
+
+# If the INLINE_INFO tag is set to YES (the default) then a tag [inline]
+# is inserted in the documentation for inline members.
+
+INLINE_INFO            = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen
+# will sort the (detailed) documentation of file and class members
+# alphabetically by member name. If set to NO the members will appear in
+# declaration order.
+
+SORT_MEMBER_DOCS       = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the
+# brief documentation of file, namespace and class members alphabetically
+# by member name. If set to NO (the default) the members will appear in
+# declaration order.
+
+SORT_BRIEF_DOCS        = NO
+
+# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen
+# will sort the (brief and detailed) documentation of class members so that
+# constructors and destructors are listed first. If set to NO (the default)
+# the constructors will appear in the respective orders defined by
+# SORT_MEMBER_DOCS and SORT_BRIEF_DOCS.
+# This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO
+# and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO.
+
+SORT_MEMBERS_CTORS_1ST = NO
+
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the
+# hierarchy of group names into alphabetical order. If set to NO (the default)
+# the group names will appear in their defined order.
+
+SORT_GROUP_NAMES       = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be
+# sorted by fully-qualified names, including namespaces. If set to
+# NO (the default), the class list will be sorted only by class name,
+# not including the namespace part.
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the
+# alphabetical list.
+
+SORT_BY_SCOPE_NAME     = NO
+
+# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to
+# do proper type resolution of all parameters of a function it will reject a
+# match between the prototype and the implementation of a member function even
+# if there is only one candidate or it is obvious which candidate to choose
+# by doing a simple string match. By disabling STRICT_PROTO_MATCHING doxygen
+# will still accept a match between prototype and implementation in such cases.
+
+STRICT_PROTO_MATCHING  = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or
+# disable (NO) the todo list. This list is created by putting \todo
+# commands in the documentation.
+
+GENERATE_TODOLIST      = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or
+# disable (NO) the test list. This list is created by putting \test
+# commands in the documentation.
+
+GENERATE_TESTLIST      = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or
+# disable (NO) the bug list. This list is created by putting \bug
+# commands in the documentation.
+
+GENERATE_BUGLIST       = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or
+# disable (NO) the deprecated list. This list is created by putting
+# \deprecated commands in the documentation.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional
+# documentation sections, marked by \if sectionname ... \endif.
+
+ENABLED_SECTIONS       =
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines
+# the initial value of a variable or macro consists of for it to appear in
+# the documentation. If the initializer consists of more lines than specified
+# here it will be hidden. Use a value of 0 to hide initializers completely.
+# The appearance of the initializer of individual variables and macros in the
+# documentation can be controlled using \showinitializer or \hideinitializer
+# command in the documentation regardless of this setting.
+
+MAX_INITIALIZER_LINES  = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated
+# at the bottom of the documentation of classes and structs. If set to YES the
+# list will mention the files that were used to generate the documentation.
+
+SHOW_USED_FILES        = YES
+
+# Set the SHOW_FILES tag to NO to disable the generation of the Files page.
+# This will remove the Files entry from the Quick Index and from the
+# Folder Tree View (if specified). The default is YES.
+
+SHOW_FILES             = YES
+
+# Set the SHOW_NAMESPACES tag to NO to disable the generation of the
+# Namespaces page.
+# This will remove the Namespaces entry from the Quick Index
+# and from the Folder Tree View (if specified). The default is YES.
+
+SHOW_NAMESPACES        = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that
+# doxygen should invoke to get the current version for each file (typically from
+# the version control system). Doxygen will invoke the program by executing (via
+# popen()) the command <command> <input-file>, where <command> is the value of
+# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file
+# provided by doxygen. Whatever the program writes to standard output
+# is used as the file version. See the manual for examples.
+
+FILE_VERSION_FILTER    =
+
+# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed
+# by doxygen. The layout file controls the global structure of the generated
+# output files in an output format independent way. To create the layout file
+# that represents doxygen's defaults, run doxygen with the -l option.
+# You can optionally specify a file name after the option, if omitted
+# DoxygenLayout.xml will be used as the name of the layout file.
+
+LAYOUT_FILE            =
+
+# The CITE_BIB_FILES tag can be used to specify one or more bib files
+# containing the references data. This must be a list of .bib files. The
+# .bib extension is automatically appended if omitted. Using this command
+# requires the bibtex tool to be installed. See also
+# http://en.wikipedia.org/wiki/BibTeX for more info. For LaTeX the style
+# of the bibliography can be controlled using LATEX_BIB_STYLE. To use this
+# feature you need bibtex and perl available in the search path.
+
+CITE_BIB_FILES         =
+
+#---------------------------------------------------------------------------
+# configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated
+# by doxygen. Possible values are YES and NO. If left blank NO is used.
+
+QUIET                  = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated by doxygen. Possible values are YES and NO. If left blank
+# NO is used.
+
+WARNINGS               = YES
+
+# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings
+# for undocumented members. If EXTRACT_ALL is set to YES then this flag will
+# automatically be disabled.
+
+WARN_IF_UNDOCUMENTED   = YES
+
+# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as not documenting some
+# parameters in a documented function, or documenting parameters that
+# don't exist or using markup commands wrongly.
+
+WARN_IF_DOC_ERROR      = YES
+
+# The WARN_NO_PARAMDOC option can be enabled to get warnings for
+# functions that are documented, but have no documentation for their parameters
+# or return value. If set to NO (the default) doxygen will only warn about
+# wrong or incomplete parameter documentation, but not about the absence of
+# documentation.
+
+WARN_NO_PARAMDOC       = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that
+# doxygen can produce. The string should contain the $file, $line, and $text
+# tags, which will be replaced by the file and line number from which the
+# warning originated and the warning text. Optionally the format may contain
+# $version, which will be replaced by the version of the file (if it could
+# be obtained via FILE_VERSION_FILTER)
+
+WARN_FORMAT            = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning
+# and error messages should be written. If left blank the output is written
+# to stderr.
+
+WARN_LOGFILE           =
+
+#---------------------------------------------------------------------------
+# configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag can be used to specify the files and/or directories that contain
+# documented source files. You may enter file names like "myfile.cpp" or
+# directories like "/usr/src/myproject". Separate the files or directories
+# with spaces.
+
+INPUT                  = @ORTHANC_STONE_DIR@/Framework \
+                         @ORTHANC_STONE_DIR@/Tests/Sdl/dev.h
+
+# This tag can be used to specify the character encoding of the source files
+# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is
+# also the default input encoding. Doxygen uses libiconv (or the iconv built
+# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for
+# the list of possible encodings.
+
+INPUT_ENCODING         = UTF-8
+
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank the following patterns are tested:
+# *.c *.cc *.cxx *.cpp *.c++ *.d *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh
+# *.hxx *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.dox *.py
+# *.f90 *.f *.for *.vhd *.vhdl
+
+FILE_PATTERNS          = *.h
+
+# The RECURSIVE tag can be used to turn specify whether or not subdirectories
+# should be searched for input files as well. Possible values are YES and NO.
+# If left blank NO is used.
+
+RECURSIVE              = YES
+
+# The EXCLUDE tag can be used to specify files and/or directories that should be
+# excluded from the INPUT source files. This way you can easily exclude a
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+# Note that relative paths are relative to the directory from which doxygen is
+# run.
+
+EXCLUDE                = @ORTHANC_STONE_DIR@/Framework/Orthanc/Plugins/ \
+                         @ORTHANC_STONE_DIR@/Framework/Orthanc/Resources/ \
+                         @ORTHANC_STONE_DIR@/Framework/Orthanc/Sdk-1.1.0/
+
+# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
+# directories that are symbolic links (a Unix file system feature) are excluded
+# from the input.
+
+EXCLUDE_SYMLINKS       = NO
+
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories. Note that the wildcards are matched
+# against the file with absolute path, so to exclude all test directories
+# for example use the pattern */test/*
+
+EXCLUDE_PATTERNS       =
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
+# (namespaces, classes, functions, etc.) that should be excluded from the
+# output. The symbol name can be a fully qualified name, a word, or if the
+# wildcard * is used, a substring. Examples: ANamespace, AClass,
+# AClass::ANamespace, ANamespace::*Test
+
+EXCLUDE_SYMBOLS        = Orthanc::Internals
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or
+# directories that contain example code fragments that are included (see
+# the \include command).
+
+EXAMPLE_PATH           =
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank all files are included.
+
+EXAMPLE_PATTERNS       =
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude
+# commands irrespective of the value of the RECURSIVE tag.
+# Possible values are YES and NO. If left blank NO is used.
+
+EXAMPLE_RECURSIVE      = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or
+# directories that contain image that are included in the documentation (see
+# the \image command).
+
+IMAGE_PATH             =
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command <filter> <input-file>, where <filter>
+# is the value of the INPUT_FILTER tag, and <input-file> is the name of an
+# input file. Doxygen will then use the output that the filter program writes
+# to standard output.
+# If FILTER_PATTERNS is specified, this tag will be
+# ignored.
+
+INPUT_FILTER           =
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
+# basis.
+# Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match.
+# The filters are a list of the form:
+# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further
+# info on how filters are used. If FILTER_PATTERNS is empty or if
+# non of the patterns match the file name, INPUT_FILTER is applied.
+
+FILTER_PATTERNS        =
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER) will be used to filter the input files when producing source
+# files to browse (i.e. when SOURCE_BROWSER is set to YES).
+
+FILTER_SOURCE_FILES    = NO
+
+# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file
+# pattern. A pattern will override the setting for FILTER_PATTERN (if any)
+# and it is also possible to disable source filtering for a specific pattern
+# using *.ext= (so without naming a filter). This option only has effect when
+# FILTER_SOURCE_FILES is enabled.
+
+FILTER_SOURCE_PATTERNS =
+
+#---------------------------------------------------------------------------
+# configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will
+# be generated. Documented entities will be cross-referenced with these sources.
+# Note: To get rid of all source code in the generated output, make sure also
+# VERBATIM_HEADERS is set to NO.
+
+SOURCE_BROWSER         = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body
+# of functions and classes directly in the documentation.
+
+INLINE_SOURCES         = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct
+# doxygen to hide any special comment blocks from generated source code
+# fragments. Normal C, C++ and Fortran comments will always remain visible.
+
+STRIP_CODE_COMMENTS    = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES
+# then for each documented function all documented
+# functions referencing it will be listed.
+
+REFERENCED_BY_RELATION = NO
+
+# If the REFERENCES_RELATION tag is set to YES
+# then for each documented function all documented entities
+# called/used by that function will be listed.
+
+REFERENCES_RELATION    = NO
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES (the default)
+# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from
+# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will
+# link to the source code.
+# Otherwise they will link to the documentation.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code
+# will point to the HTML generated by the htags(1) tool instead of doxygen
+# built-in source browser. The htags tool is part of GNU's global source
+# tagging system (see http://www.gnu.org/software/global/global.html). You
+# will need version 4.8.6 or higher.
+
+USE_HTAGS              = NO
+
+# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen
+# will generate a verbatim copy of the header file for each class for
+# which an include is specified. Set to NO to disable this.
+
+VERBATIM_HEADERS       = YES
+
+#---------------------------------------------------------------------------
+# configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index
+# of all compounds will be generated. Enable this if the project
+# contains a lot of classes, structs, unions or interfaces.
+
+ALPHABETICAL_INDEX     = YES
+
+# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then
+# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns
+# in which this list will be split (can be a number in the range [1..20])
+
+COLS_IN_ALPHA_INDEX    = 5
+
+# In case all classes in a project start with a common prefix, all
+# classes will be put under the same header in the alphabetical index.
+# The IGNORE_PREFIX tag can be used to specify one or more prefixes that
+# should be ignored while generating the index headers.
+
+IGNORE_PREFIX          =
+
+#---------------------------------------------------------------------------
+# configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES (the default) Doxygen will
+# generate HTML output.
+
+GENERATE_HTML          = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `html' will be used as the default path.
+
+HTML_OUTPUT            = doc
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for
+# each generated HTML page (for example: .htm,.php,.asp). If it is left blank
+# doxygen will generate files with .html extension.
+
+HTML_FILE_EXTENSION    = .html
+
+# The HTML_HEADER tag can be used to specify a personal HTML header for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard header. Note that when using a custom header you are responsible
+#  for the proper inclusion of any scripts and style sheets that doxygen
+# needs, which is dependent on the configuration options used.
+# It is advised to generate a default header using "doxygen -w html
+# header.html footer.html stylesheet.css YourConfigFile" and then modify
+# that header. Note that the header is subject to change so you typically
+# have to redo this when upgrading to a newer version of doxygen or when
+# changing the value of configuration settings such as GENERATE_TREEVIEW!
+
+HTML_HEADER            =
+
+# The HTML_FOOTER tag can be used to specify a personal HTML footer for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard footer.
+
+HTML_FOOTER            =
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading
+# style sheet that is used by each HTML page. It can be used to
+# fine-tune the look of the HTML output. If the tag is left blank doxygen
+# will generate a default style sheet. Note that doxygen will try to copy
+# the style sheet file to the HTML output directory, so don't put your own
+# style sheet in the HTML output directory as well, or it will be erased!
+
+HTML_STYLESHEET        =
+
+# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the HTML output directory. Note
+# that these files will be copied to the base HTML output directory. Use the
+# $relpath$ marker in the HTML_HEADER and/or HTML_FOOTER files to load these
+# files. In the HTML_STYLESHEET file, use the file name only. Also note that
+# the files will be copied as-is; there are no commands or markers available.
+
+HTML_EXTRA_FILES       =
+
+# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output.
+# Doxygen will adjust the colors in the style sheet and background images
+# according to this color. Hue is specified as an angle on a colorwheel,
+# see http://en.wikipedia.org/wiki/Hue for more information.
+# For instance the value 0 represents red, 60 is yellow, 120 is green,
+# 180 is cyan, 240 is blue, 300 purple, and 360 is red again.
+# The allowed range is 0 to 359.
+
+HTML_COLORSTYLE_HUE    = 220
+
+# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of
+# the colors in the HTML output. For a value of 0 the output will use
+# grayscales only. A value of 255 will produce the most vivid colors.
+
+HTML_COLORSTYLE_SAT    = 100
+
+# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to
+# the luminance component of the colors in the HTML output. Values below
+# 100 gradually make the output lighter, whereas values above 100 make
+# the output darker. The value divided by 100 is the actual gamma applied,
+# so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2,
+# and 100 does not change the gamma.
+
+HTML_COLORSTYLE_GAMMA  = 80
+
+# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
+# page will contain the date and time when the page was generated. Setting
+# this to NO can help when comparing the output of multiple runs.
+
+HTML_TIMESTAMP         = NO
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
+# documentation will contain sections that can be hidden and shown after the
+# page has loaded.
+
+HTML_DYNAMIC_SECTIONS  = NO
+
+# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of
+# entries shown in the various tree structured indices initially; the user
+# can expand and collapse entries dynamically later on. Doxygen will expand
+# the tree to such a level that at most the specified number of entries are
+# visible (unless a fully collapsed tree already exceeds this amount).
+# So setting the number of entries 1 will produce a full collapsed tree by
+# default. 0 is a special value representing an infinite number of entries
+# and will result in a full expanded tree by default.
+
+HTML_INDEX_NUM_ENTRIES = 100
+
+# If the GENERATE_DOCSET tag is set to YES, additional index files
+# will be generated that can be used as input for Apple's Xcode 3
+# integrated development environment, introduced with OSX 10.5 (Leopard).
+# To create a documentation set, doxygen will generate a Makefile in the
+# HTML output directory. Running make will produce the docset in that
+# directory and running "make install" will install the docset in
+# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find
+# it at startup.
+# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html
+# for more information.
+
+GENERATE_DOCSET        = NO
+
+# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the
+# feed. A documentation feed provides an umbrella under which multiple
+# documentation sets from a single provider (such as a company or product suite)
+# can be grouped.
+
+DOCSET_FEEDNAME        = "Doxygen generated docs"
+
+# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that
+# should uniquely identify the documentation set bundle. This should be a
+# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen
+# will append .docset to the name.
+
+DOCSET_BUNDLE_ID       = org.doxygen.Project
+
+# When GENERATE_PUBLISHER_ID tag specifies a string that should uniquely identify
+# the documentation publisher. This should be a reverse domain-name style
+# string, e.g. com.mycompany.MyDocSet.documentation.
+
+DOCSET_PUBLISHER_ID    = org.doxygen.Publisher
+
+# The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher.
+
+DOCSET_PUBLISHER_NAME  = Publisher
+
+# If the GENERATE_HTMLHELP tag is set to YES, additional index files
+# will be generated that can be used as input for tools like the
+# Microsoft HTML help workshop to generate a compiled HTML help file (.chm)
+# of the generated HTML documentation.
+
+GENERATE_HTMLHELP      = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can
+# be used to specify the file name of the resulting .chm file. You
+# can add a path in front of the file if the result should not be
+# written to the html output directory.
+
+CHM_FILE               =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can
+# be used to specify the location (absolute path including file name) of
+# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run
+# the HTML help compiler on the generated index.hhp.
+
+HHC_LOCATION           =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag
+# controls if a separate .chi index file is generated (YES) or that
+# it should be included in the master .chm file (NO).
+
+GENERATE_CHI           = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING
+# is used to encode HtmlHelp index (hhk), content (hhc) and project file
+# content.
+
+CHM_INDEX_ENCODING     =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag
+# controls whether a binary table of contents is generated (YES) or a
+# normal table of contents (NO) in the .chm file.
+
+BINARY_TOC             = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members
+# to the contents of the HTML help documentation and to the tree view.
+
+TOC_EXPAND             = NO
+
+# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
+# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated
+# that can be used as input for Qt's qhelpgenerator to generate a
+# Qt Compressed Help (.qch) of the generated HTML documentation.
+
+GENERATE_QHP           = NO
+
+# If the QHG_LOCATION tag is specified, the QCH_FILE tag can
+# be used to specify the file name of the resulting .qch file.
+# The path specified is relative to the HTML output folder.
+
+QCH_FILE               =
+
+# The QHP_NAMESPACE tag specifies the namespace to use when generating
+# Qt Help Project output. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#namespace
+
+QHP_NAMESPACE          = org.doxygen.Project
+
+# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating
+# Qt Help Project output. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#virtual-folders
+
+QHP_VIRTUAL_FOLDER     = doc
+
+# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to
+# add. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#custom-filters
+
+QHP_CUST_FILTER_NAME   =
+
+# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the
+# custom filter to add. For more information please see
+# <a href="http://doc.trolltech.com/qthelpproject.html#custom-filters">
+# Qt Help Project / Custom Filters</a>.
+
+QHP_CUST_FILTER_ATTRS  =
+
+# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
+# project's
+# filter section matches.
+# <a href="http://doc.trolltech.com/qthelpproject.html#filter-attributes">
+# Qt Help Project / Filter Attributes</a>.
+
+QHP_SECT_FILTER_ATTRS  =
+
+# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can
+# be used to specify the location of Qt's qhelpgenerator.
+# If non-empty doxygen will try to run qhelpgenerator on the generated
+# .qhp file.
+
+QHG_LOCATION           =
+
+# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files
+#  will be generated, which together with the HTML files, form an Eclipse help
+# plugin. To install this plugin and make it available under the help contents
+# menu in Eclipse, the contents of the directory containing the HTML and XML
+# files needs to be copied into the plugins directory of eclipse. The name of
+# the directory within the plugins directory should be the same as
+# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before
+# the help appears.
+
+GENERATE_ECLIPSEHELP   = NO
+
+# A unique identifier for the eclipse help plugin. When installing the plugin
+# the directory name containing the HTML and XML files should also have
+# this name.
+
+ECLIPSE_DOC_ID         = org.doxygen.Project
+
+# The DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs)
+# at top of each HTML page. The value NO (the default) enables the index and
+# the value YES disables it. Since the tabs have the same information as the
+# navigation tree you can set this option to NO if you already set
+# GENERATE_TREEVIEW to YES.
+
+DISABLE_INDEX          = NO
+
+# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
+# structure should be generated to display hierarchical information.
+# If the tag value is set to YES, a side panel will be generated
+# containing a tree-like index structure (just like the one that
+# is generated for HTML Help). For this to work a browser that supports
+# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser).
+# Windows users are probably better off using the HTML help feature.
+# Since the tree basically has the same information as the tab index you
+# could consider to set DISABLE_INDEX to NO when enabling this option.
+
+GENERATE_TREEVIEW      = NO
+
+# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values
+# (range [0,1..20]) that doxygen will group on one line in the generated HTML
+# documentation. Note that a value of 0 will completely suppress the enum
+# values from appearing in the overview section.
+
+ENUM_VALUES_PER_LINE   = 1
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be
+# used to set the initial width (in pixels) of the frame in which the tree
+# is shown.
+
+TREEVIEW_WIDTH         = 250
+
+# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open
+# links to external symbols imported via tag files in a separate window.
+
+EXT_LINKS_IN_WINDOW    = NO
+
+# Use this tag to change the font size of Latex formulas included
+# as images in the HTML documentation. The default is 10. Note that
+# when you change the font size after a successful doxygen run you need
+# to manually remove any form_*.png images from the HTML output directory
+# to force them to be regenerated.
+
+FORMULA_FONTSIZE       = 10
+
+# Use the FORMULA_TRANPARENT tag to determine whether or not the images
+# generated for formulas are transparent PNGs. Transparent PNGs are
+# not supported properly for IE 6.0, but are supported on all modern browsers.
+# Note that when changing this option you need to delete any form_*.png files
+# in the HTML output before the changes have effect.
+
+FORMULA_TRANSPARENT    = YES
+
+# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax
+# (see http://www.mathjax.org) which uses client side Javascript for the
+# rendering instead of using prerendered bitmaps. Use this if you do not
+# have LaTeX installed or if you want to formulas look prettier in the HTML
+# output. When enabled you may also need to install MathJax separately and
+# configure the path to it using the MATHJAX_RELPATH option.
+
+USE_MATHJAX            = NO
+
+# When MathJax is enabled you need to specify the location relative to the
+# HTML output directory using the MATHJAX_RELPATH option. The destination
+# directory should contain the MathJax.js script. For instance, if the mathjax
+# directory is located at the same level as the HTML output directory, then
+# MATHJAX_RELPATH should be ../mathjax. The default value points to
+# the MathJax Content Delivery Network so you can quickly see the result without
+# installing MathJax.
+# However, it is strongly recommended to install a local
+# copy of MathJax from http://www.mathjax.org before deployment.
+
+MATHJAX_RELPATH        = http://www.mathjax.org/mathjax
+
+# The MATHJAX_EXTENSIONS tag can be used to specify one or MathJax extension
+# names that should be enabled during MathJax rendering.
+
+MATHJAX_EXTENSIONS     =
+
+# When the SEARCHENGINE tag is enabled doxygen will generate a search box
+# for the HTML output. The underlying search engine uses javascript
+# and DHTML and should work on any modern browser. Note that when using
+# HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets
+# (GENERATE_DOCSET) there is already a search function so this one should
+# typically be disabled. For large projects the javascript based search engine
+# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution.
+
+SEARCHENGINE           = YES
+
+# When the SERVER_BASED_SEARCH tag is enabled the search engine will be
+# implemented using a PHP enabled web server instead of at the web client
+# using Javascript. Doxygen will generate the search PHP script and index
+# file to put on the web server. The advantage of the server
+# based approach is that it scales better to large projects and allows
+# full text search. The disadvantages are that it is more difficult to setup
+# and does not have live searching capabilities.
+
+SERVER_BASED_SEARCH    = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will
+# generate Latex output.
+
+GENERATE_LATEX         = NO
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `latex' will be used as the default path.
+
+LATEX_OUTPUT           = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
+# invoked. If left blank `latex' will be used as the default command name.
+# Note that when enabling USE_PDFLATEX this option is only used for
+# generating bitmaps for formulas in the HTML output, but not in the
+# Makefile that is written to the output directory.
+
+LATEX_CMD_NAME         = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to
+# generate index for LaTeX. If left blank `makeindex' will be used as the
+# default command name.
+
+MAKEINDEX_CMD_NAME     = makeindex
+
+# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact
+# LaTeX documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_LATEX          = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used
+# by the printer. Possible values are: a4, letter, legal and
+# executive. If left blank a4wide will be used.
+
+PAPER_TYPE             = a4
+
+# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX
+# packages that should be included in the LaTeX output.
+
+EXTRA_PACKAGES         =
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for
+# the generated latex document. The header should contain everything until
+# the first chapter. If it is left blank doxygen will generate a
+# standard header. Notice: only use this tag if you know what you are doing!
+
+LATEX_HEADER           =
+
+# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for
+# the generated latex document. The footer should contain everything after
+# the last chapter. If it is left blank doxygen will generate a
+# standard footer. Notice: only use this tag if you know what you are doing!
+
+LATEX_FOOTER           =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated
+# is prepared for conversion to pdf (using ps2pdf). The pdf file will
+# contain links (just like the HTML output) instead of page references
+# This makes the output suitable for online browsing using a pdf viewer.
+
+PDF_HYPERLINKS         = YES
+
+# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of
+# plain latex in the generated Makefile. Set this option to YES to get a
+# higher quality PDF documentation.
+
+USE_PDFLATEX           = YES
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode.
+# command to the generated LaTeX files. This will instruct LaTeX to keep
+# running if errors occur, instead of asking the user for help.
+# This option is also used when generating formulas in HTML.
+
+LATEX_BATCHMODE        = NO
+
+# If LATEX_HIDE_INDICES is set to YES then doxygen will not
+# include the index chapters (such as File Index, Compound Index, etc.)
+# in the output.
+
+LATEX_HIDE_INDICES     = NO
+
+# If LATEX_SOURCE_CODE is set to YES then doxygen will include
+# source code with syntax highlighting in the LaTeX output.
+# Note that which sources are shown also depends on other settings
+# such as SOURCE_BROWSER.
+
+LATEX_SOURCE_CODE      = NO
+
+# The LATEX_BIB_STYLE tag can be used to specify the style to use for the
+# bibliography, e.g. plainnat, or ieeetr. The default style is "plain". See
+# http://en.wikipedia.org/wiki/BibTeX for more info.
+
+LATEX_BIB_STYLE        = plain
+
+#---------------------------------------------------------------------------
+# configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output
+# The RTF output is optimized for Word 97 and may not look very pretty with
+# other RTF readers or editors.
+
+GENERATE_RTF           = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `rtf' will be used as the default path.
+
+RTF_OUTPUT             = rtf
+
+# If the COMPACT_RTF tag is set to YES Doxygen generates more compact
+# RTF documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_RTF            = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated
+# will contain hyperlink fields. The RTF file will
+# contain links (just like the HTML output) instead of page references.
+# This makes the output suitable for online browsing using WORD or other
+# programs which support those fields.
+# Note: wordpad (write) and others do not support links.
+
+RTF_HYPERLINKS         = NO
+
+# Load style sheet definitions from file. Syntax is similar to doxygen's
+# config file, i.e. a series of assignments. You only have to provide
+# replacements, missing definitions are set to their default value.
+
+RTF_STYLESHEET_FILE    =
+
+# Set optional variables used in the generation of an rtf document.
+# Syntax is similar to doxygen's config file.
+
+RTF_EXTENSIONS_FILE    =
+
+#---------------------------------------------------------------------------
+# configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES (the default) Doxygen will
+# generate man pages
+
+GENERATE_MAN           = NO
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `man' will be used as the default path.
+
+MAN_OUTPUT             = man
+
+# The MAN_EXTENSION tag determines the extension that is added to
+# the generated man pages (default is the subroutine's section .3)
+
+MAN_EXTENSION          = .3
+
+# If the MAN_LINKS tag is set to YES and Doxygen generates man output,
+# then it will generate one additional man file for each entity
+# documented in the real man page(s). These additional files
+# only source the real man page, but without them the man command
+# would be unable to find the correct page. The default is NO.
+
+MAN_LINKS              = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES Doxygen will
+# generate an XML file that captures the structure of
+# the code including all documentation.
+
+GENERATE_XML           = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `xml' will be used as the default path.
+
+XML_OUTPUT             = xml
+
+# The XML_SCHEMA tag can be used to specify an XML schema,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_SCHEMA             =
+
+# The XML_DTD tag can be used to specify an XML DTD,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_DTD                =
+
+# If the XML_PROGRAMLISTING tag is set to YES Doxygen will
+# dump the program listings (including syntax highlighting
+# and cross-referencing information) to the XML output. Note that
+# enabling this will significantly increase the size of the XML output.
+
+XML_PROGRAMLISTING     = YES
+
+#---------------------------------------------------------------------------
+# configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will
+# generate an AutoGen Definitions (see autogen.sf.net) file
+# that captures the structure of the code including all
+# documentation. Note that this feature is still experimental
+# and incomplete at the moment.
+
+GENERATE_AUTOGEN_DEF   = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES Doxygen will
+# generate a Perl module file that captures the structure of
+# the code including all documentation. Note that this
+# feature is still experimental and incomplete at the
+# moment.
+
+GENERATE_PERLMOD       = NO
+
+# If the PERLMOD_LATEX tag is set to YES Doxygen will generate
+# the necessary Makefile rules, Perl scripts and LaTeX code to be able
+# to generate PDF and DVI output from the Perl module output.
+
+PERLMOD_LATEX          = NO
+
+# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be
+# nicely formatted so it can be parsed by a human reader.
+# This is useful
+# if you want to understand what is going on.
+# On the other hand, if this
+# tag is set to NO the size of the Perl module output will be much smaller
+# and Perl will parse it just the same.
+
+PERLMOD_PRETTY         = YES
+
+# The names of the make variables in the generated doxyrules.make file
+# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX.
+# This is useful so different doxyrules.make files included by the same
+# Makefile don't overwrite each other's variables.
+
+PERLMOD_MAKEVAR_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will
+# evaluate all C-preprocessor directives found in the sources and include
+# files.
+
+ENABLE_PREPROCESSING   = YES
+
+# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro
+# names in the source code. If set to NO (the default) only conditional
+# compilation will be performed. Macro expansion can be done in a controlled
+# way by setting EXPAND_ONLY_PREDEF to YES.
+
+MACRO_EXPANSION        = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES
+# then the macro expansion is limited to the macros specified with the
+# PREDEFINED and EXPAND_AS_DEFINED tags.
+
+EXPAND_ONLY_PREDEF     = NO
+
+# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files
+# pointed to by INCLUDE_PATH will be searched when a #include is found.
+
+SEARCH_INCLUDES        = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by
+# the preprocessor.
+
+INCLUDE_PATH           =
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will
+# be used.
+
+INCLUDE_FILE_PATTERNS  =
+
+# The PREDEFINED tag can be used to specify one or more macro names that
+# are defined before the preprocessor is started (similar to the -D option of
+# gcc). The argument of the tag is a list of macros of the form: name
+# or name=definition (no spaces). If the definition and the = are
+# omitted =1 is assumed. To prevent a macro definition from being
+# undefined via #undef or recursively expanded use the := operator
+# instead of the = operator.
+
+PREDEFINED             =
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then
+# this tag can be used to specify a list of macro names that should be expanded.
+# The macro definition that is found in the sources will be used.
+# Use the PREDEFINED tag if you want to use a different macro definition that
+# overrules the definition found in the source code.
+
+EXPAND_AS_DEFINED      =
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then
+# doxygen's preprocessor will remove all references to function-like macros
+# that are alone on a line, have an all uppercase name, and do not end with a
+# semicolon, because these will confuse the parser if not removed.
+
+SKIP_FUNCTION_MACROS   = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to external references
+#---------------------------------------------------------------------------
+
+# The TAGFILES option can be used to specify one or more tagfiles. For each
+# tag file the location of the external documentation should be added. The
+# format of a tag file without this location is as follows:
+#
+# TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+#
+# TAGFILES = file1=loc1 "file2 = loc2" ...
+# where "loc1" and "loc2" can be relative or absolute paths
+# or URLs. Note that each tag file must have a unique name (where the name does
+# NOT include the path). If a tag file is not located in the directory in which
+# doxygen is run, you must also specify the path to the tagfile here.
+
+TAGFILES               =
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create
+# a tag file that is based on the input files it reads.
+
+GENERATE_TAGFILE       =
+
+# If the ALLEXTERNALS tag is set to YES all external classes will be listed
+# in the class index. If set to NO only the inherited external classes
+# will be listed.
+
+ALLEXTERNALS           = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed
+# in the modules index. If set to NO, only the current project's groups will
+# be listed.
+
+EXTERNAL_GROUPS        = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script
+# interpreter (i.e. the result of `which perl').
+
+PERL_PATH              = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will
+# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base
+# or super classes. Setting the tag to NO turns the diagrams off. Note that
+# this option also works with HAVE_DOT disabled, but it is recommended to
+# install and use dot, since it yields more powerful graphs.
+
+CLASS_DIAGRAMS         = YES
+
+# You can define message sequence charts within doxygen comments using the \msc
+# command. Doxygen will then run the mscgen tool (see
+# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the
+# documentation. The MSCGEN_PATH tag allows you to specify the directory where
+# the mscgen tool resides. If left empty the tool is assumed to be found in the
+# default search path.
+
+MSCGEN_PATH            =
+
+# If set to YES, the inheritance and collaboration graphs will hide
+# inheritance and usage relations if the target is undocumented
+# or is not a class.
+
+HIDE_UNDOC_RELATIONS   = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz, a graph visualization
+# toolkit from AT&T and Lucent Bell Labs. The other options in this section
+# have no effect if this option is set to NO (the default)
+
+HAVE_DOT               = YES
+
+# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is
+# allowed to run in parallel. When set to 0 (the default) doxygen will
+# base this on the number of processors available in the system. You can set it
+# explicitly to a value larger than 0 to get control over the balance
+# between CPU load and processing speed.
+
+DOT_NUM_THREADS        = 0
+
+# By default doxygen will use the Helvetica font for all dot files that
+# doxygen generates. When you want a differently looking font you can specify
+# the font name using DOT_FONTNAME. You need to make sure dot is able to find
+# the font, which can be done by putting it in a standard location or by setting
+# the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the
+# directory containing the font.
+
+DOT_FONTNAME           = Helvetica
+
+# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs.
+# The default size is 10pt.
+
+DOT_FONTSIZE           = 10
+
+# By default doxygen will tell dot to use the Helvetica font.
+# If you specify a different font using DOT_FONTNAME you can use DOT_FONTPATH to
+# set the path where dot can find it.
+
+DOT_FONTPATH           =
+
+# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect inheritance relations. Setting this tag to YES will force the
+# CLASS_DIAGRAMS tag to NO.
+
+CLASS_GRAPH            = YES
+
+# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect implementation dependencies (inheritance, containment, and
+# class references variables) of the class with other documented classes.
+
+COLLABORATION_GRAPH    = NO
+
+# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for groups, showing the direct groups dependencies
+
+GROUP_GRAPHS           = YES
+
+# If the UML_LOOK tag is set to YES doxygen will generate inheritance and
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
+# Language.
+
+UML_LOOK               = NO
+
+# If the UML_LOOK tag is enabled, the fields and methods are shown inside
+# the class node. If there are many fields or methods and many nodes the
+# graph may become too big to be useful. The UML_LIMIT_NUM_FIELDS
+# threshold limits the number of items for each type to make the size more
+# managable. Set this to 0 for no limit. Note that the threshold may be
+# exceeded by 50% before the limit is enforced.
+
+UML_LIMIT_NUM_FIELDS   = 10
+
+# If set to YES, the inheritance and collaboration graphs will show the
+# relations between templates and their instances.
+
+TEMPLATE_RELATIONS     = NO
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT
+# tags are set to YES then doxygen will generate a graph for each documented
+# file showing the direct and indirect include dependencies of the file with
+# other documented files.
+
+INCLUDE_GRAPH          = YES
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and
+# HAVE_DOT tags are set to YES then doxygen will generate a graph for each
+# documented header file showing the documented files that directly or
+# indirectly include this file.
+
+INCLUDED_BY_GRAPH      = YES
+
+# If the CALL_GRAPH and HAVE_DOT options are set to YES then
+# doxygen will generate a call dependency graph for every global function
+# or class method. Note that enabling this option will significantly increase
+# the time of a run. So in most cases it will be better to enable call graphs
+# for selected functions only using the \callgraph command.
+
+CALL_GRAPH             = NO
+
+# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then
+# doxygen will generate a caller dependency graph for every global function
+# or class method. Note that enabling this option will significantly increase
+# the time of a run. So in most cases it will be better to enable caller
+# graphs for selected functions only using the \callergraph command.
+
+CALLER_GRAPH           = NO
+
+# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen
+# will generate a graphical hierarchy of all classes instead of a textual one.
+
+GRAPHICAL_HIERARCHY    = YES
+
+# If the DIRECTORY_GRAPH and HAVE_DOT tags are set to YES
+# then doxygen will show the dependencies a directory has on other directories
+# in a graphical way. The dependency relations are determined by the #include
+# relations between the files in the directories.
+
+DIRECTORY_GRAPH        = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot. Possible values are svg, png, jpg, or gif.
+# If left blank png will be used. If you choose svg you need to set
+# HTML_FILE_EXTENSION to xhtml in order to make the SVG files
+# visible in IE 9+ (other browsers do not have this requirement).
+
+DOT_IMAGE_FORMAT       = png
+
+# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to
+# enable generation of interactive SVG images that allow zooming and panning.
+# Note that this requires a modern browser other than Internet Explorer.
+# Tested and working are Firefox, Chrome, Safari, and Opera. For IE 9+ you
+# need to set HTML_FILE_EXTENSION to xhtml in order to make the SVG files
+# visible. Older versions of IE do not have SVG support.
+
+INTERACTIVE_SVG        = NO
+
+# The tag DOT_PATH can be used to specify the path where the dot tool can be
+# found. If left blank, it is assumed the dot tool can be found in the path.
+
+DOT_PATH               =
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the
+# \dotfile command).
+
+DOTFILE_DIRS           =
+
+# The MSCFILE_DIRS tag can be used to specify one or more directories that
+# contain msc files that are included in the documentation (see the
+# \mscfile command).
+
+MSCFILE_DIRS           =
+
+# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of
+# nodes that will be shown in the graph. If the number of nodes in a graph
+# becomes larger than this value, doxygen will truncate the graph, which is
+# visualized by representing a node as a red box. Note that doxygen if the
+# number of direct children of the root node in a graph is already larger than
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note
+# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+
+DOT_GRAPH_MAX_NODES    = 50
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the
+# graphs generated by dot. A depth value of 3 means that only nodes reachable
+# from the root by following a path via at most 3 edges will be shown. Nodes
+# that lay further from the root node will be omitted. Note that setting this
+# option to 1 or 2 may greatly reduce the computation time needed for large
+# code bases. Also note that the size of a graph can be further restricted by
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+
+MAX_DOT_GRAPH_DEPTH    = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
+# background. This is disabled by default, because dot on Windows does not
+# seem to support this out of the box. Warning: Depending on the platform used,
+# enabling this option may lead to badly anti-aliased labels on the edges of
+# a graph (i.e. they become hard to read).
+
+DOT_TRANSPARENT        = NO
+
+# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output
+# files in one run (i.e. multiple -o and -T options on the command line). This
+# makes dot run faster, but since only newer versions of dot (>1.8.10)
+# support this, this feature is disabled by default.
+
+DOT_MULTI_TARGETS      = YES
+
+# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will
+# generate a legend page explaining the meaning of the various boxes and
+# arrows in the dot generated graphs.
+
+GENERATE_LEGEND        = YES
+
+# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will
+# remove the intermediate dot files that are used to generate
+# the various graphs.
+
+DOT_CLEANUP            = YES
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/SyncOrthancFolder.py	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,118 @@
+#!/usr/bin/python
+
+#
+# This maintenance script updates the content of the "Orthanc" folder
+# to match the latest version of the Orthanc source code.
+#
+
+import sys
+import multiprocessing
+import os
+import stat
+import urllib2
+
+TARGET = os.path.join(os.path.dirname(__file__), '../Framework/Orthanc')
+REPOSITORY = 'https://bitbucket.org/sjodogne/orthanc/raw'
+
+FILES = [
+    'Core/ChunkedBuffer.cpp',
+    'Core/ChunkedBuffer.h',
+    'Core/Compression/DeflateBaseCompressor.cpp',
+    'Core/Compression/DeflateBaseCompressor.h',
+    'Core/Compression/GzipCompressor.cpp',
+    'Core/Compression/GzipCompressor.h',
+    'Core/Compression/IBufferCompressor.h',
+    'Core/Enumerations.cpp',
+    'Core/Enumerations.h',
+    'Core/HttpClient.cpp',
+    'Core/HttpClient.h',
+    'Core/Images/Image.cpp',
+    'Core/Images/Image.h',
+    'Core/Images/ImageAccessor.cpp',
+    'Core/Images/ImageAccessor.h',
+    'Core/Images/ImageBuffer.cpp',
+    'Core/Images/ImageBuffer.h',
+    'Core/Images/ImageProcessing.cpp',
+    'Core/Images/ImageProcessing.h',
+    'Core/Images/JpegErrorManager.cpp',
+    'Core/Images/JpegErrorManager.h',
+    'Core/Images/JpegReader.cpp',
+    'Core/Images/JpegReader.h',
+    'Core/Images/PngReader.cpp',
+    'Core/Images/PngReader.h',
+    'Core/Logging.cpp',
+    'Core/Logging.h',
+    'Core/OrthancException.h',
+    'Core/PrecompiledHeaders.h',
+    'Core/Toolbox.cpp',
+    'Core/Toolbox.h',
+    'Core/WebServiceParameters.cpp',
+    'Core/WebServiceParameters.h',
+    'Resources/CMake/AutoGeneratedCode.cmake',
+    'Resources/CMake/BoostConfiguration.cmake',
+    'Resources/CMake/Compiler.cmake',
+    'Resources/CMake/DownloadPackage.cmake',
+    'Resources/CMake/JsonCppConfiguration.cmake',
+    'Resources/CMake/LibCurlConfiguration.cmake',
+    'Resources/CMake/LibIconvConfiguration.cmake',
+    'Resources/CMake/LibJpegConfiguration.cmake',
+    'Resources/CMake/LibPngConfiguration.cmake',
+    'Resources/CMake/OpenSslConfiguration.cmake',
+    'Resources/CMake/ZlibConfiguration.cmake',
+    'Resources/EmbedResources.py',
+    'Resources/MinGW-W64-Toolchain32.cmake',
+    'Resources/MinGW-W64-Toolchain64.cmake',
+    'Resources/MinGWToolchain.cmake',
+    'Resources/ThirdParty/VisualStudio/stdint.h',
+    'Resources/ThirdParty/base64/base64.cpp',
+    'Resources/ThirdParty/base64/base64.h',
+    'Resources/ThirdParty/patch/msys-1.0.dll',
+    'Resources/ThirdParty/patch/patch.exe',
+    'Resources/ThirdParty/patch/patch.exe.manifest',
+    'Resources/WindowsResources.py',
+    'Resources/WindowsResources.rc',
+]
+
+EXE = [
+    'Resources/WindowsResources.py',
+]
+
+
+def Download(x):
+    branch = x[0]
+    source = x[1]
+    target = os.path.join(TARGET, x[2])
+    print target
+
+    try:
+        os.makedirs(os.path.dirname(target))
+    except:
+        pass
+
+    url = '%s/%s/%s' % (REPOSITORY, branch, source)
+
+    with open(target, 'w') as f:
+        f.write(urllib2.urlopen(url).read())
+
+
+commands = []
+
+for f in FILES:
+    commands.append([ 'default', f, f ])
+
+
+if sys.platform == 'win32':
+    # Sequential execution
+    for c in commands:
+        Download(c)
+else:
+    # Concurrent downloads
+    pool = multiprocessing.Pool(10)
+    pool.map(Download, commands)
+
+
+for exe in EXE:
+    path = os.path.join(TARGET, exe)
+    st = os.stat(path)
+    os.chmod(path, st.st_mode | stat.S_IEXEC)
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Samples/BasicPetCtFusionApplication.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,213 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "SampleInteractor.h"
+
+#include "../Framework/Orthanc/Core/Logging.h"
+
+namespace OrthancStone
+{
+  namespace Samples
+  {
+    class BasicPetCtFusionApplication : public SampleApplicationBase
+    {
+    private:
+      class Interactor : public SampleInteractor
+      {
+      public:
+        static void SetStyle(LayeredSceneWidget& widget,
+                             bool ct,
+                             bool pet)
+        {
+          if (ct)
+          {
+            RenderStyle style;
+            style.windowing_ = ImageWindowing_Bone;
+            widget.SetLayerStyle(0, style);
+          }
+          else
+          {
+            RenderStyle style;
+            style.visible_ = false;
+            widget.SetLayerStyle(0, style);
+          }
+
+          if (ct && pet)
+          {
+            RenderStyle style;
+            style.applyLut_ = true;
+            style.alpha_ = 0.5;
+            widget.SetLayerStyle(1, style);
+          }
+          else if (pet)
+          {
+            RenderStyle style;
+            style.applyLut_ = true;
+            widget.SetLayerStyle(1, style);
+          }
+          else
+          {
+            RenderStyle style;
+            style.visible_ = false;
+            widget.SetLayerStyle(1, style);
+          }
+        }
+
+
+        static bool IsVisible(LayeredSceneWidget& widget,
+                              size_t layer)
+        {
+          RenderStyle style = widget.GetLayerStyle(layer);
+          return style.visible_;
+        }
+
+
+        static void ToggleInterpolation(LayeredSceneWidget& widget,
+                                        size_t layer)
+        {
+          RenderStyle style = widget.GetLayerStyle(layer);
+         
+          if (style.interpolation_ == ImageInterpolation_Linear)
+          {
+            style.interpolation_ = ImageInterpolation_Nearest;
+          }
+          else
+          {
+            style.interpolation_ = ImageInterpolation_Linear;
+          }
+
+          widget.SetLayerStyle(layer, style);
+        }
+
+
+        Interactor(VolumeImage& volume,
+                   VolumeProjection projection, 
+                   bool reverse) :
+          SampleInteractor(volume, projection, reverse)
+        {
+        }
+
+
+        virtual void KeyPressed(WorldSceneWidget& widget,
+                                char key,
+                                KeyboardModifiers modifiers,
+                                IStatusBar* statusBar)
+        {
+          LayeredSceneWidget& layered = dynamic_cast<LayeredSceneWidget&>(widget);
+
+          switch (key)
+          {
+            case 'c':
+              // Toggle the visibility of the CT layer
+              SetStyle(layered, !IsVisible(layered, 0), IsVisible(layered, 1));
+              break;
+
+            case 'p':
+              // Toggle the visibility of the PET layer
+              SetStyle(layered, IsVisible(layered, 0), !IsVisible(layered, 1));
+              break;
+
+            case 'i':
+            {
+              // Toggle on/off the interpolation
+              ToggleInterpolation(layered, 0);
+              ToggleInterpolation(layered, 1);
+              break;
+            }
+
+            default:
+              break;
+          }
+        }
+      };
+
+
+    public:
+      virtual void DeclareCommandLineOptions(boost::program_options::options_description& options)
+      {
+        boost::program_options::options_description generic("Sample options");
+        generic.add_options()
+          ("ct", boost::program_options::value<std::string>(), 
+           "Orthanc ID of the CT series")
+          ("pet", boost::program_options::value<std::string>(), 
+           "Orthanc ID of the PET series")
+          ("threads", boost::program_options::value<unsigned int>()->default_value(3), 
+           "Number of download threads for the CT series")
+          ;
+
+        options.add(generic);    
+      }
+
+      virtual void Initialize(BasicApplicationContext& context,
+                              IStatusBar& statusBar,
+                              const boost::program_options::variables_map& parameters)
+      {
+        using namespace OrthancStone;
+
+        if (parameters.count("ct") != 1 ||
+            parameters.count("pet") != 1)
+        {
+          LOG(ERROR) << "The series ID is missing";
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+        }
+
+        std::string ct = parameters["ct"].as<std::string>();
+        std::string pet = parameters["pet"].as<std::string>();
+        unsigned int threads = parameters["threads"].as<unsigned int>();
+
+        VolumeImage& ctVolume = context.AddSeriesVolume(ct, true /* progressive download */, threads);
+        VolumeImage& petVolume = context.AddSeriesVolume(pet, true /* progressive download */, 1);
+
+        // Take the PET volume as the reference for the slices
+        std::auto_ptr<Interactor> interactor(new Interactor(petVolume, VolumeProjection_Axial, false /* don't reverse normal */));
+
+        std::auto_ptr<LayeredSceneWidget> widget(new LayeredSceneWidget);
+        widget->AddLayer(new VolumeImage::LayerFactory(ctVolume));
+        widget->AddLayer(new VolumeImage::LayerFactory(petVolume));
+        widget->SetSlice(interactor->GetCursor().GetCurrentSlice());
+        widget->SetInteractor(*interactor);
+
+        Interactor::SetStyle(*widget, true, true);   // Initially, show both CT and PET layers
+
+        context.AddInteractor(interactor.release());
+        context.SetCentralWidget(widget.release());
+
+        statusBar.SetMessage("Use the key \"t\" to toggle the fullscreen mode");
+        statusBar.SetMessage("Use the key \"c\" to show/hide the CT layer");
+        statusBar.SetMessage("Use the key \"p\" to show/hide the PET layer");
+        statusBar.SetMessage("Use the key \"i\" to toggle the smoothing of the images");
+      }
+    };
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Samples/EmptyApplication.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,70 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "SampleApplicationBase.h"
+
+#include "../Framework/Widgets/EmptyWidget.h"
+
+namespace OrthancStone
+{
+  namespace Samples
+  {
+    class EmptyApplication : public SampleApplicationBase
+    {
+    public:
+      virtual void DeclareCommandLineOptions(boost::program_options::options_description& options)
+      {
+        boost::program_options::options_description generic("Sample options");
+        generic.add_options()
+          ("red", boost::program_options::value<int>()->default_value(255), "Background color: red channel")
+          ("green", boost::program_options::value<int>()->default_value(0), "Background color: green channel")
+          ("blue", boost::program_options::value<int>()->default_value(0), "Background color: blue channel")
+          ;
+
+        options.add(generic);    
+      }
+
+      virtual void Initialize(BasicApplicationContext& context,
+                              IStatusBar& statusBar,
+                              const boost::program_options::variables_map& parameters)
+      {
+        int red = parameters["red"].as<int>();
+        int green = parameters["green"].as<int>();
+        int blue = parameters["blue"].as<int>();
+
+        context.SetCentralWidget(new EmptyWidget(red, green, blue));
+      }
+    };
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Samples/LayoutPetCtFusionApplication.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,409 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "SampleInteractor.h"
+
+#include "../Framework/Layers/SiblingSliceLocationFactory.h"
+#include "../Framework/Layers/DicomStructureSetRendererFactory.h"
+#include "../Framework/Widgets/LayoutWidget.h"
+
+#include "../Framework/Orthanc/Core/Logging.h"
+
+namespace OrthancStone
+{
+  namespace Samples
+  {
+    class LayoutPetCtFusionApplication : 
+      public SampleApplicationBase,
+      public LayeredSceneWidget::ISliceObserver,
+      public WorldSceneWidget::IWorldObserver
+    {
+    private:
+      class Interactor : public SampleInteractor
+      {
+      private:
+        LayoutPetCtFusionApplication& that_;
+
+      public:
+        Interactor(LayoutPetCtFusionApplication& that,
+                   VolumeImage& volume,
+                   VolumeProjection projection, 
+                   bool reverse) :
+          SampleInteractor(volume, projection, reverse),
+          that_(that)
+        {
+        }
+
+        virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& widget,
+                                                            const SliceGeometry& slice,
+                                                            const ViewportGeometry& view,
+                                                            MouseButton button,
+                                                            double x,
+                                                            double y,
+                                                            IStatusBar* statusBar)
+        {
+          if (button == MouseButton_Left)
+          {
+            // Center the sibling views over the clicked point
+            Vector p = slice.MapSliceToWorldCoordinates(x, y);
+
+            if (statusBar != NULL)
+            {
+              char buf[64];
+              sprintf(buf, "Click on coordinates (%.02f,%.02f,%.02f) in cm", p[0] / 10.0, p[1] / 10.0, p[2] / 10.0);
+              statusBar->SetMessage(buf);
+            }
+
+            that_.interactorAxial_->LookupSliceContainingPoint(*that_.ctAxial_, p);
+            that_.interactorCoronal_->LookupSliceContainingPoint(*that_.ctCoronal_, p);
+            that_.interactorSagittal_->LookupSliceContainingPoint(*that_.ctSagittal_, p);
+          }
+
+          return NULL;
+        }
+
+        virtual void KeyPressed(WorldSceneWidget& widget,
+                                char key,
+                                KeyboardModifiers modifiers,
+                                IStatusBar* statusBar)
+        {
+          if (key == 's')
+          {
+            that_.SetDefaultView();
+          }
+        }
+      };
+
+      bool                 processingEvent_;
+      Interactor*          interactorAxial_;
+      Interactor*          interactorCoronal_;
+      Interactor*          interactorSagittal_;
+      LayeredSceneWidget*  ctAxial_;
+      LayeredSceneWidget*  ctCoronal_;
+      LayeredSceneWidget*  ctSagittal_;
+      LayeredSceneWidget*  petAxial_;
+      LayeredSceneWidget*  petCoronal_;
+      LayeredSceneWidget*  petSagittal_;
+      LayeredSceneWidget*  fusionAxial_;
+      LayeredSceneWidget*  fusionCoronal_;
+      LayeredSceneWidget*  fusionSagittal_;
+
+
+      void SetDefaultView()
+      {
+        petAxial_->SetDefaultView();
+        petCoronal_->SetDefaultView();
+        petSagittal_->SetDefaultView();
+      }
+
+
+      void AddLayer(LayeredSceneWidget& widget,
+                    VolumeImage& volume,
+                    bool isCt)
+      {
+        size_t layer;
+        widget.AddLayer(layer, new VolumeImage::LayerFactory(volume));
+
+        if (isCt)
+        {
+          RenderStyle style; 
+          style.windowing_ = ImageWindowing_Bone;
+          widget.SetLayerStyle(layer, style);
+        }
+        else
+        {
+          RenderStyle style; 
+          style.applyLut_ = true;
+          style.alpha_ = (layer == 0 ? 1.0 : 0.5);
+          widget.SetLayerStyle(layer, style);
+        }
+      }
+
+
+      void ConnectSiblingLocations(LayeredSceneWidget& axial,
+                                   LayeredSceneWidget& coronal,
+                                   LayeredSceneWidget& sagittal)
+      {
+        SiblingSliceLocationFactory::Configure(axial, coronal);
+        SiblingSliceLocationFactory::Configure(axial, sagittal);
+        SiblingSliceLocationFactory::Configure(coronal, sagittal);
+      }
+
+
+      void SynchronizeView(const WorldSceneWidget& source,
+                           const ViewportGeometry& view,
+                           LayeredSceneWidget& widget1,
+                           LayeredSceneWidget& widget2,
+                           LayeredSceneWidget& widget3)
+      {
+        if (&source == &widget1 ||
+            &source == &widget2 ||
+            &source == &widget3)
+        {
+          if (&source != &widget1)
+          {
+            widget1.SetView(view);
+          }
+
+          if (&source != &widget2)
+          {
+            widget2.SetView(view);
+          }
+
+          if (&source != &widget3)
+          {
+            widget3.SetView(view);
+          }
+        }
+      }
+
+
+      void SynchronizeSlice(const LayeredSceneWidget& source,
+                            const SliceGeometry& slice,
+                            LayeredSceneWidget& widget1,
+                            LayeredSceneWidget& widget2,
+                            LayeredSceneWidget& widget3)
+      {
+        if (&source == &widget1 ||
+            &source == &widget2 ||
+            &source == &widget3)
+        {
+          if (&source != &widget1)
+          {
+            widget1.SetSlice(slice);
+          }
+
+          if (&source != &widget2)
+          {
+            widget2.SetSlice(slice);
+          }
+
+          if (&source != &widget3)
+          {
+            widget3.SetSlice(slice);
+          }
+        }
+      }
+
+
+      LayeredSceneWidget* CreateWidget()
+      {
+        std::auto_ptr<LayeredSceneWidget> widget(new LayeredSceneWidget);
+        widget->Register(dynamic_cast<WorldSceneWidget::IWorldObserver&>(*this));
+        widget->Register(dynamic_cast<LayeredSceneWidget::ISliceObserver&>(*this));
+        return widget.release();
+      }
+
+
+      void CreateLayout(BasicApplicationContext& context)
+      {
+        std::auto_ptr<OrthancStone::LayoutWidget> layout(new OrthancStone::LayoutWidget);
+        layout->SetBackgroundCleared(true);
+        //layout->SetBackgroundColor(255,0,0);
+        layout->SetPadding(5);
+
+        OrthancStone::LayoutWidget& layoutA = dynamic_cast<OrthancStone::LayoutWidget&>
+          (layout->AddWidget(new OrthancStone::LayoutWidget));
+        layoutA.SetPadding(0, 0, 0, 0, 5);
+        layoutA.SetVertical();
+        petAxial_ = &dynamic_cast<LayeredSceneWidget&>(layoutA.AddWidget(CreateWidget()));
+        OrthancStone::LayoutWidget& layoutA2 = dynamic_cast<OrthancStone::LayoutWidget&>
+          (layoutA.AddWidget(new OrthancStone::LayoutWidget));
+        layoutA2.SetPadding(0, 0, 0, 0, 5);
+        petSagittal_ = &dynamic_cast<LayeredSceneWidget&>(layoutA2.AddWidget(CreateWidget()));
+        petCoronal_ = &dynamic_cast<LayeredSceneWidget&>(layoutA2.AddWidget(CreateWidget()));
+
+        OrthancStone::LayoutWidget& layoutB = dynamic_cast<OrthancStone::LayoutWidget&>
+          (layout->AddWidget(new OrthancStone::LayoutWidget));
+        layoutB.SetPadding(0, 0, 0, 0, 5);
+        layoutB.SetVertical();
+        ctAxial_ = &dynamic_cast<LayeredSceneWidget&>(layoutB.AddWidget(CreateWidget()));
+        OrthancStone::LayoutWidget& layoutB2 = dynamic_cast<OrthancStone::LayoutWidget&>
+          (layoutB.AddWidget(new OrthancStone::LayoutWidget));
+        layoutB2.SetPadding(0, 0, 0, 0, 5);
+        ctSagittal_ = &dynamic_cast<LayeredSceneWidget&>(layoutB2.AddWidget(CreateWidget()));
+        ctCoronal_ = &dynamic_cast<LayeredSceneWidget&>(layoutB2.AddWidget(CreateWidget()));
+
+        OrthancStone::LayoutWidget& layoutC = dynamic_cast<OrthancStone::LayoutWidget&>
+          (layout->AddWidget(new OrthancStone::LayoutWidget));
+        layoutC.SetPadding(0, 0, 0, 0, 5);
+        layoutC.SetVertical();
+        fusionAxial_ = &dynamic_cast<LayeredSceneWidget&>(layoutC.AddWidget(CreateWidget()));
+        OrthancStone::LayoutWidget& layoutC2 = dynamic_cast<OrthancStone::LayoutWidget&>
+          (layoutC.AddWidget(new OrthancStone::LayoutWidget));
+        layoutC2.SetPadding(0, 0, 0, 0, 5);
+        fusionSagittal_ = &dynamic_cast<LayeredSceneWidget&>(layoutC2.AddWidget(CreateWidget()));
+        fusionCoronal_ = &dynamic_cast<LayeredSceneWidget&>(layoutC2.AddWidget(CreateWidget()));
+  
+        context.SetCentralWidget(layout.release());
+      }
+
+
+    public:
+      virtual void DeclareCommandLineOptions(boost::program_options::options_description& options)
+      {
+        boost::program_options::options_description generic("Sample options");
+        generic.add_options()
+          ("ct", boost::program_options::value<std::string>(), 
+           "Orthanc ID of the CT series")
+          ("pet", boost::program_options::value<std::string>(), 
+           "Orthanc ID of the PET series")
+          ("rt", boost::program_options::value<std::string>(), 
+           "Orthanc ID of the DICOM RT-STRUCT series (optional)")
+          ("threads", boost::program_options::value<unsigned int>()->default_value(3), 
+           "Number of download threads for the CT series")
+          ;
+
+        options.add(generic);    
+      }
+
+      virtual void Initialize(BasicApplicationContext& context,
+                              IStatusBar& statusBar,
+                              const boost::program_options::variables_map& parameters)
+      {
+        using namespace OrthancStone;
+
+        processingEvent_ = true;
+
+        if (parameters.count("ct") != 1 ||
+            parameters.count("pet") != 1)
+        {
+          LOG(ERROR) << "The series ID is missing";
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+        }
+
+        std::string ct = parameters["ct"].as<std::string>();
+        std::string pet = parameters["pet"].as<std::string>();
+        unsigned int threads = parameters["threads"].as<unsigned int>();
+
+        VolumeImage& ctVolume = context.AddSeriesVolume(ct, true /* progressive download */, threads);
+        VolumeImage& petVolume = context.AddSeriesVolume(pet, true /* progressive download */, 1);
+
+        // Take the PET volume as the reference for the slices
+        interactorAxial_ = &dynamic_cast<Interactor&>
+          (context.AddInteractor(new Interactor(*this, petVolume, VolumeProjection_Axial, false)));
+        interactorCoronal_ = &dynamic_cast<Interactor&>
+          (context.AddInteractor(new Interactor(*this, petVolume, VolumeProjection_Coronal, false)));
+        interactorSagittal_ = &dynamic_cast<Interactor&>
+          (context.AddInteractor(new Interactor(*this, petVolume, VolumeProjection_Sagittal, true)));
+
+        CreateLayout(context);
+
+        AddLayer(*ctAxial_, ctVolume, true);
+        AddLayer(*ctCoronal_, ctVolume, true);
+        AddLayer(*ctSagittal_, ctVolume, true);
+
+        AddLayer(*petAxial_, petVolume, false);
+        AddLayer(*petCoronal_, petVolume, false);
+        AddLayer(*petSagittal_, petVolume, false);
+
+        AddLayer(*fusionAxial_, ctVolume, true);
+        AddLayer(*fusionAxial_, petVolume, false);
+        AddLayer(*fusionCoronal_, ctVolume, true);
+        AddLayer(*fusionCoronal_, petVolume, false);
+        AddLayer(*fusionSagittal_, ctVolume, true);
+        AddLayer(*fusionSagittal_, petVolume, false);
+
+        if (parameters.count("rt") == 1)
+        {
+          DicomStructureSet& rtStruct = context.AddStructureSet(parameters["rt"].as<std::string>());
+
+          Vector p = rtStruct.GetStructureCenter(0);
+          interactorAxial_->GetCursor().LookupSliceContainingPoint(p);
+
+          ctAxial_->AddLayer(new DicomStructureSetRendererFactory(rtStruct));
+          petAxial_->AddLayer(new DicomStructureSetRendererFactory(rtStruct));
+          fusionAxial_->AddLayer(new DicomStructureSetRendererFactory(rtStruct));
+        }        
+
+        ConnectSiblingLocations(*ctAxial_, *ctCoronal_, *ctSagittal_); 
+        ConnectSiblingLocations(*petAxial_, *petCoronal_, *petSagittal_); 
+        ConnectSiblingLocations(*fusionAxial_, *fusionCoronal_, *fusionSagittal_); 
+
+        interactorAxial_->AddWidget(*ctAxial_);
+        interactorAxial_->AddWidget(*petAxial_);
+        interactorAxial_->AddWidget(*fusionAxial_);
+        
+        interactorCoronal_->AddWidget(*ctCoronal_);
+        interactorCoronal_->AddWidget(*petCoronal_);
+        interactorCoronal_->AddWidget(*fusionCoronal_);
+        
+        interactorSagittal_->AddWidget(*ctSagittal_);
+        interactorSagittal_->AddWidget(*petSagittal_);
+        interactorSagittal_->AddWidget(*fusionSagittal_);
+
+        processingEvent_ = false;
+
+        statusBar.SetMessage("Use the key \"t\" to toggle the fullscreen mode");
+        statusBar.SetMessage("Use the key \"s\" to reinitialize the layout");
+      }
+
+      virtual void NotifySizeChange(const WorldSceneWidget& source,
+                                    ViewportGeometry& view)
+      {
+        view.SetDefaultView();
+      }
+
+      virtual void NotifyViewChange(const WorldSceneWidget& source,
+                                    const ViewportGeometry& view)
+      {
+        if (!processingEvent_)  // Avoid reentrant calls
+        {
+          processingEvent_ = true;
+
+          SynchronizeView(source, view, *ctAxial_, *petAxial_, *fusionAxial_);
+          SynchronizeView(source, view, *ctCoronal_, *petCoronal_, *fusionCoronal_);
+          SynchronizeView(source, view, *ctSagittal_, *petSagittal_, *fusionSagittal_);
+          
+          processingEvent_ = false;
+        }
+      }
+
+      virtual void NotifySliceChange(const LayeredSceneWidget& source,
+                                     const SliceGeometry& slice)
+      {
+        if (!processingEvent_)  // Avoid reentrant calls
+        {
+          processingEvent_ = true;
+
+          SynchronizeSlice(source, slice, *ctAxial_, *petAxial_, *fusionAxial_);
+          SynchronizeSlice(source, slice, *ctCoronal_, *petCoronal_, *fusionCoronal_);
+          SynchronizeSlice(source, slice, *ctSagittal_, *petSagittal_, *fusionSagittal_);
+          
+          processingEvent_ = false;
+        }
+      }
+    };
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Samples/SampleApplicationBase.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,58 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "../Framework/Applications/IBasicApplication.h"
+
+namespace OrthancStone
+{
+  namespace Samples
+  {
+    class SampleApplicationBase : public IBasicApplication
+    {
+    public:
+      virtual std::string GetTitle() const
+      {
+        return "Stone of Orthanc - Sample";
+      }
+
+      virtual void DeclareCommandLineOptions(boost::program_options::options_description& options)
+      {
+      }
+
+      virtual void Finalize()
+      {
+      }
+    };
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Samples/SampleInteractor.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,144 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "SampleApplicationBase.h"
+
+#include "../Framework/Widgets/LayeredSceneWidget.h"
+#include "../Framework/Widgets/IWorldSceneInteractor.h"
+#include "../Framework/Toolbox/ParallelSlicesCursor.h"
+
+namespace OrthancStone
+{
+  namespace Samples
+  {
+    /**
+     * This is a basic mouse interactor for sample applications. It
+     * contains a set of parallel slices in the 3D space. The mouse
+     * wheel events make the widget change the slice that is
+     * displayed.
+     **/
+    class SampleInteractor : public IWorldSceneInteractor
+    {
+    private:
+      ParallelSlicesCursor   cursor_;
+
+    public:
+      SampleInteractor(VolumeImage& volume,
+                       VolumeProjection projection, 
+                       bool reverse)
+      {
+        std::auto_ptr<ParallelSlices> slices(volume.GetGeometry(projection, reverse));
+        cursor_.SetGeometry(*slices);
+      }
+
+      SampleInteractor(ISeriesLoader& series, 
+                       bool reverse)
+      {
+        if (reverse)
+        {
+          std::auto_ptr<ParallelSlices> slices(series.GetGeometry().Reverse());
+          cursor_.SetGeometry(*slices);
+        }
+        else
+        {
+          cursor_.SetGeometry(series.GetGeometry());
+        }
+      }
+
+      SampleInteractor(const ParallelSlices& slices)
+      {
+        cursor_.SetGeometry(slices);
+      }
+
+      ParallelSlicesCursor& GetCursor()
+      {
+        return cursor_;
+      }
+
+      void AddWidget(LayeredSceneWidget& widget)
+      {
+        widget.SetInteractor(*this);
+        widget.SetSlice(cursor_.GetCurrentSlice());
+      }
+
+      virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& widget,
+                                                          const SliceGeometry& slice,
+                                                          const ViewportGeometry& view,
+                                                          MouseButton button,
+                                                          double x,
+                                                          double y,
+                                                          IStatusBar* statusBar)
+      {
+        return NULL;
+      }
+
+      virtual void MouseOver(CairoContext& context,
+                             WorldSceneWidget& widget,
+                             const SliceGeometry& slice,
+                             const ViewportGeometry& view,
+                             double x,
+                             double y,
+                             IStatusBar* statusBar)
+      {
+      }
+
+      virtual void MouseWheel(WorldSceneWidget& widget,
+                              MouseWheelDirection direction,
+                              KeyboardModifiers modifiers,
+                              IStatusBar* statusBar)
+      {
+        if (cursor_.ApplyWheelEvent(direction, modifiers))
+        {
+          dynamic_cast<LayeredSceneWidget&>(widget).SetSlice(cursor_.GetCurrentSlice());
+        }
+      }
+
+      virtual void KeyPressed(WorldSceneWidget& widget,
+                              char key,
+                              KeyboardModifiers modifiers,
+                              IStatusBar* statusBar)
+      {
+      }
+
+      void LookupSliceContainingPoint(LayeredSceneWidget& widget,
+                                      const Vector& p)
+      {
+        if (cursor_.LookupSliceContainingPoint(p))
+        {
+          widget.SetSlice(cursor_.GetCurrentSlice());
+        }
+      }
+    };
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Samples/SampleMainSdl.cpp	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,73 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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/>.
+ **/
+
+
+// The macro "ORTHANC_STONE_SAMPLE" must be set by the CMake script
+
+#if ORTHANC_STONE_SAMPLE == 1
+#include "../../Samples/EmptyApplication.h"
+typedef OrthancStone::Samples::EmptyApplication Application;
+
+#elif ORTHANC_STONE_SAMPLE == 2
+#include "../../Samples/TestPatternApplication.h"
+typedef OrthancStone::Samples::TestPatternApplication Application;
+
+#elif ORTHANC_STONE_SAMPLE == 3
+#include "../../Samples/SingleFrameApplication.h"
+typedef OrthancStone::Samples::SingleFrameApplication Application;
+
+#elif ORTHANC_STONE_SAMPLE == 4
+#include "../../Samples/SingleVolumeApplication.h"
+typedef OrthancStone::Samples::SingleVolumeApplication Application;
+
+#elif ORTHANC_STONE_SAMPLE == 5
+#include "../../Samples/BasicPetCtFusionApplication.h"
+typedef OrthancStone::Samples::BasicPetCtFusionApplication Application;
+
+#elif ORTHANC_STONE_SAMPLE == 6
+#include "../../Samples/SynchronizedSeriesApplication.h"
+typedef OrthancStone::Samples::SynchronizedSeriesApplication Application;
+
+#elif ORTHANC_STONE_SAMPLE == 7
+#include "../../Samples/LayoutPetCtFusionApplication.h"
+typedef OrthancStone::Samples::LayoutPetCtFusionApplication Application;
+
+#else
+#error Please set the ORTHANC_STONE_SAMPLE macro
+#endif
+
+
+int main(int argc, char* argv[]) 
+{
+  Application application;
+
+  return OrthancStone::IBasicApplication::ExecuteWithSdl(application, argc, argv);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Samples/SingleFrameApplication.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,96 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "SampleApplicationBase.h"
+
+#include "../Framework/Layers/SingleFrameRendererFactory.h"
+#include "../Framework/Widgets/LayeredSceneWidget.h"
+#include "../Framework/Orthanc/Core/Logging.h"
+
+namespace OrthancStone
+{
+  namespace Samples
+  {
+    class SingleFrameApplication : public SampleApplicationBase
+    {
+    public:
+      virtual void DeclareCommandLineOptions(boost::program_options::options_description& options)
+      {
+        boost::program_options::options_description generic("Sample options");
+        generic.add_options()
+          ("instance", boost::program_options::value<std::string>(), 
+           "Orthanc ID of the instance")
+          ("frame", boost::program_options::value<unsigned int>()->default_value(0),
+           "Number of the frame, for multi-frame DICOM instances")
+          ("smooth", boost::program_options::value<bool>()->default_value(true), 
+           "Enable linear interpolation to smooth the image")
+          ;
+
+        options.add(generic);    
+      }
+
+      virtual void Initialize(BasicApplicationContext& context,
+                              IStatusBar& statusBar,
+                              const boost::program_options::variables_map& parameters)
+      {
+        using namespace OrthancStone;
+
+        if (parameters.count("instance") != 1)
+        {
+          LOG(ERROR) << "The instance ID is missing";
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+        }
+
+        std::string instance = parameters["instance"].as<std::string>();
+        int frame = parameters["frame"].as<unsigned int>();
+
+        std::auto_ptr<SingleFrameRendererFactory>  renderer;
+        renderer.reset(new SingleFrameRendererFactory(context.GetOrthancConnection(), instance, frame));
+
+        std::auto_ptr<LayeredSceneWidget> widget(new LayeredSceneWidget);
+        widget->SetSlice(renderer->GetSliceGeometry());
+        widget->AddLayer(renderer.release());
+
+        if (parameters["smooth"].as<bool>())
+        {
+          RenderStyle s; 
+          s.interpolation_ = ImageInterpolation_Linear;
+          widget->SetLayerStyle(0, s);
+        }
+
+        context.SetCentralWidget(widget.release());
+      }
+    };
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Samples/SingleVolumeApplication.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,295 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "SampleInteractor.h"
+
+#include "../Framework/Orthanc/Core/Toolbox.h"
+#include "../Framework/Layers/LineMeasureTracker.h"
+#include "../Framework/Layers/CircleMeasureTracker.h"
+#include "../Framework/Orthanc/Core/Logging.h"
+
+namespace OrthancStone
+{
+  namespace Samples
+  {
+    class SingleVolumeApplication : public SampleApplicationBase
+    {
+    private:
+      class Interactor : public SampleInteractor
+      {
+      private:
+        enum MouseMode
+        {
+          MouseMode_None,
+          MouseMode_TrackCoordinates,
+          MouseMode_LineMeasure,
+          MouseMode_CircleMeasure
+        };
+
+        MouseMode mouseMode_;
+
+        void SetMouseMode(MouseMode mode,
+                          IStatusBar* statusBar)
+        {
+          if (mouseMode_ == mode)
+          {
+            mouseMode_ = MouseMode_None;
+          }
+          else
+          {
+            mouseMode_ = mode;
+          }
+
+          if (statusBar)
+          {
+            switch (mouseMode_)
+            {
+              case MouseMode_None:
+                statusBar->SetMessage("Disabling the mouse tools");
+                break;
+
+              case MouseMode_TrackCoordinates:
+                statusBar->SetMessage("Tracking the mouse coordinates");
+                break;
+
+              case MouseMode_LineMeasure:
+                statusBar->SetMessage("Mouse clicks will now measure the distances");
+                break;
+
+              case MouseMode_CircleMeasure:
+                statusBar->SetMessage("Mouse clicks will now draw circles");
+                break;
+
+              default:
+                break;
+            }
+          }
+        }
+
+      public:
+        Interactor(VolumeImage& volume,
+                   VolumeProjection projection, 
+                   bool reverse) :
+          SampleInteractor(volume, projection, reverse),
+          mouseMode_(MouseMode_None)
+        {
+        }
+        
+        virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& widget,
+                                                            const SliceGeometry& slice,
+                                                            const ViewportGeometry& view,
+                                                            MouseButton button,
+                                                            double x,
+                                                            double y,
+                                                            IStatusBar* statusBar)
+        {
+          if (button == MouseButton_Left)
+          {
+            switch (mouseMode_)
+            {
+              case MouseMode_LineMeasure:
+                return new LineMeasureTracker(NULL, slice, x, y, 255, 0, 0, 14 /* font size */);
+              
+              case MouseMode_CircleMeasure:
+                return new CircleMeasureTracker(NULL, slice, x, y, 255, 0, 0, 14 /* font size */);
+
+              default:
+                break;
+            }
+          }
+
+          return NULL;
+        }
+
+        virtual void MouseOver(CairoContext& context,
+                               WorldSceneWidget& widget,
+                               const SliceGeometry& slice,
+                               const ViewportGeometry& view,
+                               double x,
+                               double y,
+                               IStatusBar* statusBar)
+        {
+          if (mouseMode_ == MouseMode_TrackCoordinates &&
+              statusBar != NULL)
+          {
+            Vector p = slice.MapSliceToWorldCoordinates(x, y);
+            
+            char buf[64];
+            sprintf(buf, "X = %.02f Y = %.02f Z = %.02f (in cm)", p[0] / 10.0, p[1] / 10.0, p[2] / 10.0);
+            statusBar->SetMessage(buf);
+          }
+        }
+
+
+        virtual void KeyPressed(WorldSceneWidget& widget,
+                                char key,
+                                KeyboardModifiers modifiers,
+                                IStatusBar* statusBar)
+        {
+          switch (key)
+          {
+            case 't':
+              SetMouseMode(MouseMode_TrackCoordinates, statusBar);
+              break;
+
+            case 'm':
+              SetMouseMode(MouseMode_LineMeasure, statusBar);
+              break;
+
+            case 'c':
+              SetMouseMode(MouseMode_CircleMeasure, statusBar);
+              break;
+
+            case 'b':
+            {
+              if (statusBar)
+              {
+                statusBar->SetMessage("Setting Hounsfield window to bones");
+              }
+
+              RenderStyle style;
+              style.windowing_ = ImageWindowing_Bone;
+              dynamic_cast<LayeredSceneWidget&>(widget).SetLayerStyle(0, style);
+              break;
+            }
+
+            case 'l':
+            {
+              if (statusBar)
+              {
+                statusBar->SetMessage("Setting Hounsfield window to lung");
+              }
+
+              RenderStyle style;
+              style.windowing_ = ImageWindowing_Lung;
+              dynamic_cast<LayeredSceneWidget&>(widget).SetLayerStyle(0, style);
+              break;
+            }
+
+            case 'd':
+            {
+              if (statusBar)
+              {
+                statusBar->SetMessage("Setting Hounsfield window to what is written in the DICOM file");
+              }
+
+              RenderStyle style;
+              style.windowing_ = ImageWindowing_Default;
+              dynamic_cast<LayeredSceneWidget&>(widget).SetLayerStyle(0, style);
+              break;
+            }
+
+            default:
+              break;
+          }
+        }
+      };
+
+
+    public:
+      virtual void DeclareCommandLineOptions(boost::program_options::options_description& options)
+      {
+        boost::program_options::options_description generic("Sample options");
+        generic.add_options()
+          ("series", boost::program_options::value<std::string>(), 
+           "Orthanc ID of the series")
+          ("threads", boost::program_options::value<unsigned int>()->default_value(3), 
+           "Number of download threads")
+          ("projection", boost::program_options::value<std::string>()->default_value("axial"), 
+           "Projection of interest (can be axial, sagittal or coronal)")
+          ("reverse", boost::program_options::value<bool>()->default_value(false), 
+           "Reverse the normal direction of the volume")
+          ;
+
+        options.add(generic);    
+      }
+
+      virtual void Initialize(BasicApplicationContext& context,
+                              IStatusBar& statusBar,
+                              const boost::program_options::variables_map& parameters)
+      {
+        using namespace OrthancStone;
+
+        if (parameters.count("series") != 1)
+        {
+          LOG(ERROR) << "The series ID is missing";
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+        }
+
+        std::string series = parameters["series"].as<std::string>();
+        unsigned int threads = parameters["threads"].as<unsigned int>();
+        bool reverse = parameters["reverse"].as<bool>();
+
+        std::string tmp = parameters["projection"].as<std::string>();
+        Orthanc::Toolbox::ToLowerCase(tmp);
+        
+        VolumeProjection projection;
+        if (tmp == "axial")
+        {
+          projection = VolumeProjection_Axial;
+        }
+        else if (tmp == "sagittal")
+        {
+          projection = VolumeProjection_Sagittal;
+        }
+        else if (tmp == "coronal")
+        {
+          projection = VolumeProjection_Coronal;
+        }
+        else
+        {
+          LOG(ERROR) << "Unknown projection: " << tmp;
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+        }
+
+        VolumeImage& volume = context.AddSeriesVolume(series, true /* progressive download */, threads);
+
+        std::auto_ptr<Interactor> interactor(new Interactor(volume, projection, reverse));
+
+        std::auto_ptr<LayeredSceneWidget> widget(new LayeredSceneWidget);
+        widget->AddLayer(new VolumeImage::LayerFactory(volume));
+        widget->SetSlice(interactor->GetCursor().GetCurrentSlice());
+        widget->SetInteractor(*interactor);
+
+        context.AddInteractor(interactor.release());
+        context.SetCentralWidget(widget.release());
+
+        statusBar.SetMessage("Use the keys \"b\", \"l\" and \"d\" to change Hounsfield windowing");
+        statusBar.SetMessage("Use the keys \"t\" to track the (X,Y,Z) mouse coordinates");
+        statusBar.SetMessage("Use the keys \"m\" to measure distances");
+        statusBar.SetMessage("Use the keys \"c\" to draw circles");
+      }
+    };
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Samples/SynchronizedSeriesApplication.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,118 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "SampleInteractor.h"
+
+#include "../Framework/Toolbox/OrthancSeriesLoader.h"
+#include "../Framework/Layers/SeriesFrameRendererFactory.h"
+#include "../Framework/Layers/SiblingSliceLocationFactory.h"
+#include "../Framework/Widgets/LayoutWidget.h"
+#include "../Framework/Orthanc/Core/Logging.h"
+
+namespace OrthancStone
+{
+  namespace Samples
+  {
+    class SynchronizedSeriesApplication : public SampleApplicationBase
+    {
+    private:   
+      LayeredSceneWidget* CreateSeriesWidget(BasicApplicationContext& context,
+                                             const std::string& series)
+      {
+        std::auto_ptr<ISeriesLoader> loader(new OrthancSeriesLoader(context.GetOrthancConnection(), series));
+
+        std::auto_ptr<SampleInteractor> interactor(new SampleInteractor(*loader, false));
+
+        std::auto_ptr<LayeredSceneWidget> widget(new LayeredSceneWidget);
+        widget->AddLayer(new SeriesFrameRendererFactory(loader.release(), false));
+        widget->SetSlice(interactor->GetCursor().GetCurrentSlice());
+        widget->SetInteractor(*interactor);
+
+        context.AddInteractor(interactor.release());
+
+        return widget.release();
+      }
+
+    public:
+      virtual void DeclareCommandLineOptions(boost::program_options::options_description& options)
+      {
+        boost::program_options::options_description generic("Sample options");
+        generic.add_options()
+          ("a", boost::program_options::value<std::string>(), 
+           "Orthanc ID of the 1st series")
+          ("b", boost::program_options::value<std::string>(), 
+           "Orthanc ID of the 2nd series")
+          ("c", boost::program_options::value<std::string>(), 
+           "Orthanc ID of the 3rd series")
+          ;
+
+        options.add(generic);    
+      }
+
+      virtual void Initialize(BasicApplicationContext& context,
+                              IStatusBar& statusBar,
+                              const boost::program_options::variables_map& parameters)
+      {
+        if (parameters.count("a") != 1 ||
+            parameters.count("b") != 1 ||
+            parameters.count("c") != 1)
+        {
+          LOG(ERROR) << "At least one of the three series IDs is missing";
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+        }
+
+        std::auto_ptr<LayeredSceneWidget> a(CreateSeriesWidget(context, parameters["a"].as<std::string>()));
+        std::auto_ptr<LayeredSceneWidget> b(CreateSeriesWidget(context, parameters["b"].as<std::string>()));
+        std::auto_ptr<LayeredSceneWidget> c(CreateSeriesWidget(context, parameters["c"].as<std::string>()));
+
+        SiblingSliceLocationFactory::Configure(*a, *b);
+        SiblingSliceLocationFactory::Configure(*a, *c);
+        SiblingSliceLocationFactory::Configure(*b, *c);
+
+        std::auto_ptr<LayoutWidget> layout(new LayoutWidget);
+        layout->SetPadding(5);
+        layout->AddWidget(a.release());
+
+        std::auto_ptr<LayoutWidget> layoutB(new LayoutWidget);
+        layoutB->SetVertical();
+        layoutB->SetPadding(5);
+        layoutB->AddWidget(b.release());
+        layoutB->AddWidget(c.release());
+        layout->AddWidget(layoutB.release());
+
+        context.SetCentralWidget(layout.release());        
+      }
+    };
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Samples/TestPatternApplication.h	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,74 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 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 "SampleApplicationBase.h"
+
+#include "../Framework/Widgets/TestCairoWidget.h"
+#include "../Framework/Widgets/TestWorldSceneWidget.h"
+#include "../Framework/Widgets/LayoutWidget.h"
+
+namespace OrthancStone
+{
+  namespace Samples
+  {
+    class TestPatternApplication : public SampleApplicationBase
+    {
+    public:
+      virtual void DeclareCommandLineOptions(boost::program_options::options_description& options)
+      {
+        boost::program_options::options_description generic("Sample options");
+        generic.add_options()
+          ("animate", boost::program_options::value<bool>()->default_value(true), "Animate the test pattern")
+          ;
+
+        options.add(generic);    
+      }
+
+      virtual void Initialize(BasicApplicationContext& context,
+                              IStatusBar& statusBar,
+                              const boost::program_options::variables_map& parameters)
+      {
+        using namespace OrthancStone;
+
+        std::auto_ptr<LayoutWidget> layout(new LayoutWidget);
+        layout->SetPadding(10);
+        layout->SetBackgroundCleared(true);
+        layout->AddWidget(new TestCairoWidget(parameters["animate"].as<bool>()));
+        layout->AddWidget(new TestWorldSceneWidget);
+
+        context.SetCentralWidget(layout.release());
+      }
+    };
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/TODO	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,57 @@
+Stone of Orthanc
+================
+
+
+-------
+General
+-------
+
+* Documentation
+* LayoutPetCtFusionApplication: fix initial view
+
+
+---------------------------------
+Radiotherapy and nuclear medicine
+---------------------------------
+
+* Project RT-STRUCT in sagittal + coronal views
+* Speedup RT-STRUCT loading
+* Automatic segmentation + manual contouring
+* Display segments in mask
+
+
+-------------
+Optimizations
+-------------
+
+* Tune number of loading threads in LayeredSceneWidget
+* Add cache over IOrthancServices (for SDL/Qt/...)
+* LayoutWidget: Do not update full background if only 1 widget has changed
+* LayoutWidget: Threads to refresh each child
+* Implement binary search to speed up search for closest slice
+* Avoid the creation of new threads when updating the frame factory
+  (as seen with gdb)
+
+
+-----------------
+Platform-specific
+-----------------
+
+* Qt widget example
+* Add precompiled headers for Microsoft Visual Studio
+* Investigate crash in CurlOrthancConnection if using MinGW32 in Release mode
+
+
+---------------------
+Source code cosmetics
+---------------------
+
+* Remove #include "OrthancException.h" in "ObserversRegistry.h"
+* Use "SampleInteractor::AddWidget()" in all samples
+
+
+------
+Future
+------
+
+* Create a wrapper for Python