Mercurial > hg > orthanc-webviewer
changeset 0:02f7a0400a91
initial commit
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/AUTHORS Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,13 @@ +Web Viewer plugin for Orthanc +============================= + + +Authors +------- + +* Sebastien Jodogne <s.jodogne@gmail.com> + Department of Medical Physics + University Hospital of Liege + Belgium + + Overall design and lead developer.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/CMakeLists.txt Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,194 @@ +# Orthanc - A Lightweight, RESTful DICOM Store +# Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics +# Department, University Hospital of Liege, Belgium +# +# This program is free software: you can redistribute it and/or +# modify it under the terms of the GNU Affero General Public License +# as published by the Free Software Foundation, either version 3 of +# the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + +cmake_minimum_required(VERSION 2.8) + +project(OrthancPostgreSQL) + +# Parameters of the build +set(STATIC_BUILD OFF CACHE BOOL "Static build of the third-party libraries (necessary for Windows)") +SET(STANDALONE_BUILD ON CACHE BOOL "Standalone build (all the resources are embedded, necessary for releases)") +set(ALLOW_DOWNLOADS OFF CACHE BOOL "Allow CMake to download packages") + +# Advanced parameters to fine-tune linking against system libraries +set(USE_SYSTEM_BOOST ON CACHE BOOL "Use the system version of Boost") +set(USE_SYSTEM_GDCM ON CACHE BOOL "Use the system version of Grassroot DICOM (GDCM)") +set(USE_SYSTEM_GOOGLE_TEST ON CACHE BOOL "Use the system version of Google Test") +set(USE_SYSTEM_JSONCPP ON CACHE BOOL "Use the system version of JsonCpp") +set(USE_SYSTEM_LIBJPEG ON CACHE BOOL "Use the system version of libjpeg") +set(USE_SYSTEM_LIBPNG ON CACHE BOOL "Use the system version of libpng") +set(USE_SYSTEM_ZLIB ON CACHE BOOL "Use the system version of zlib") +set(USE_SYSTEM_SQLITE ON CACHE BOOL "Use the system version of SQLite") + +# Distribution-specific settings +set(USE_GTEST_DEBIAN_SOURCE_PACKAGE OFF CACHE BOOL "Use the sources of Google Test shipped with libgtest-dev (Debian only)") +mark_as_advanced(USE_GTEST_DEBIAN_SOURCE_PACKAGE) + +# Force static build when cross-compiling +if (CMAKE_CROSSCOMPILING) + set(STATIC_BUILD ON) + set(STANDALONE_BUILD ON) +endif() + +if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + SET(OS_LIBRARIES uuid rt dl) + SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pthread") + SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -pthread") +elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") + SET(OS_LIBRARIES rpcrt4 ws2_32 secur32) + if (CMAKE_COMPILER_IS_GNUCXX) + SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static-libgcc -static-libstdc++") + SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -static-libgcc -static-libstdc++") + endif() +endif () + +if (CMAKE_COMPILER_IS_GNUCXX) + SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--version-script=${CMAKE_SOURCE_DIR}/Resources/VersionScript.map -Wl,--no-undefined") +endif() + + +include(CheckIncludeFiles) +include(CheckIncludeFileCXX) +include(CheckLibraryExists) +include(${CMAKE_SOURCE_DIR}/Resources/CMake/AutoGeneratedCode.cmake) +include(${CMAKE_SOURCE_DIR}/Resources/CMake/DownloadPackage.cmake) + +include(${CMAKE_SOURCE_DIR}/Resources/CMake/BoostConfiguration.cmake) +include(${CMAKE_SOURCE_DIR}/Resources/CMake/GdcmConfiguration.cmake) +include(${CMAKE_SOURCE_DIR}/Resources/CMake/GoogleTestConfiguration.cmake) +include(${CMAKE_SOURCE_DIR}/Resources/CMake/JsonCppConfiguration.cmake) +include(${CMAKE_SOURCE_DIR}/Resources/CMake/LibJpegConfiguration.cmake) +include(${CMAKE_SOURCE_DIR}/Resources/CMake/LibPngConfiguration.cmake) +include(${CMAKE_SOURCE_DIR}/Resources/CMake/ZlibConfiguration.cmake) +include(${CMAKE_SOURCE_DIR}/Resources/CMake/SQLiteConfiguration.cmake) + +include(${CMAKE_SOURCE_DIR}/Resources/CMake/JavaScriptLibraries.cmake) + + +# Check that the Orthanc SDK headers are available or download them +if (STATIC_BUILD) + set(ORTHANC_SDK_URL "http://orthanc.googlecode.com/hg") # TODO use 0.8.6 release + file(MAKE_DIRECTORY ${AUTOGENERATED_DIR}/orthanc) + file(DOWNLOAD "${ORTHANC_SDK_URL}/Plugins/Include/OrthancCPlugin.h" + "${AUTOGENERATED_DIR}/orthanc/OrthancCPlugin.h" SHOW_PROGRESS) + if (${MSVC}) + add_definitions(-D_CRT_SECURE_NO_WARNINGS=1) + file(DOWNLOAD "${ORTHANC_SDK_URL}/Resources/ThirdParty/VisualStudio/stdint.h" + "${AUTOGENERATED_DIR}/stdint.h" SHOW_PROGRESS) + endif() +else () + CHECK_INCLUDE_FILE_CXX(orthanc/OrthancCPlugin.h HAVE_ORTHANC_H) + if (NOT HAVE_ORTHANC_H) + message(FATAL_ERROR "Please install the headers of the Orthanc plugins SDK") + endif() +endif() + + +if (STANDALONE_BUILD) + add_definitions( + -DORTHANC_STANDALONE=1 + ) + set(EMBEDDED_RESOURCES + WEB_VIEWER ${CMAKE_SOURCE_DIR}/WebApplication + ) +else() + add_definitions( + -DORTHANC_STANDALONE=0 + -DWEB_VIEWER_PATH="${CMAKE_SOURCE_DIR}/WebApplication/" + ) +endif() + +EmbedResources( + ORTHANC_EXPLORER ${CMAKE_SOURCE_DIR}/Resources/OrthancExplorer.js + JAVASCRIPT_LIBS ${JAVASCRIPT_LIBS_DIR} + ${EMBEDDED_RESOURCES} + ) + +add_definitions( + -DORTHANC_SQLITE_STANDALONE=1 + ) + +set(CORE_SOURCES + ${BOOST_SOURCES} + ${JSONCPP_SOURCES} + ${SQLITE_SOURCES} + ${LIBJPEG_SOURCES} + ${ZLIB_SOURCES} + ${LIBPNG_SOURCES} + + # Sources inherited from Orthanc core + ${CMAKE_SOURCE_DIR}/Orthanc/ChunkedBuffer.cpp + ${CMAKE_SOURCE_DIR}/Orthanc/Enumerations.cpp + ${CMAKE_SOURCE_DIR}/Orthanc/FileStorage/FilesystemStorage.cpp + ${CMAKE_SOURCE_DIR}/Orthanc/ImageFormats/ImageAccessor.cpp + ${CMAKE_SOURCE_DIR}/Orthanc/ImageFormats/ImageBuffer.cpp + ${CMAKE_SOURCE_DIR}/Orthanc/ImageFormats/ImageProcessing.cpp + ${CMAKE_SOURCE_DIR}/Orthanc/ImageFormats/PngWriter.cpp + ${CMAKE_SOURCE_DIR}/Orthanc/MultiThreading/SharedMessageQueue.cpp + ${CMAKE_SOURCE_DIR}/Orthanc/OrthancException.cpp + ${CMAKE_SOURCE_DIR}/Orthanc/SQLite/Connection.cpp + ${CMAKE_SOURCE_DIR}/Orthanc/SQLite/FunctionContext.cpp + ${CMAKE_SOURCE_DIR}/Orthanc/SQLite/Statement.cpp + ${CMAKE_SOURCE_DIR}/Orthanc/SQLite/StatementId.cpp + ${CMAKE_SOURCE_DIR}/Orthanc/SQLite/StatementReference.cpp + ${CMAKE_SOURCE_DIR}/Orthanc/SQLite/Transaction.cpp + ${CMAKE_SOURCE_DIR}/Orthanc/Toolbox.cpp + ${CMAKE_SOURCE_DIR}/Orthanc/Uuid.cpp + ${CMAKE_SOURCE_DIR}/Resources/ThirdParty/base64/base64.cpp + + ${CMAKE_SOURCE_DIR}/Plugin/Cache/CacheManager.cpp + ${CMAKE_SOURCE_DIR}/Plugin/Cache/CacheScheduler.cpp + ${CMAKE_SOURCE_DIR}/Plugin/JpegWriter.cpp + ${CMAKE_SOURCE_DIR}/Plugin/ViewerToolbox.cpp + ${CMAKE_SOURCE_DIR}/Plugin/SeriesVolumeSorter.cpp + ${CMAKE_SOURCE_DIR}/Plugin/ViewerPrefetchPolicy.cpp + ${CMAKE_SOURCE_DIR}/Plugin/InstanceInformation.cpp + ${CMAKE_SOURCE_DIR}/Plugin/InstanceInformationAdapter.cpp + ${CMAKE_SOURCE_DIR}/Plugin/SeriesInformationAdapter.cpp + ) + +add_library(OrthancWebViewer + SHARED + ${CORE_SOURCES} + ${AUTOGENERATED_SOURCES} + ${CMAKE_SOURCE_DIR}/Plugin/Plugin.cpp + + # The following files depend on GDCM + ${CMAKE_SOURCE_DIR}/Plugin/ParsedDicomImage.cpp + ${CMAKE_SOURCE_DIR}/Plugin/DecodedImageAdapter.cpp + ) + +if (STATIC_BUILD OR NOT USE_SYSTEM_GDCM) + add_dependencies(OrthancWebViewer GDCM) +endif() + +target_link_libraries(OrthancWebViewer ${GDCM_LIBRARIES} ${OS_LIBRARIES}) + +install( + TARGETS OrthancWebViewer + RUNTIME DESTINATION lib # Destination for Windows + LIBRARY DESTINATION share/orthanc/plugins # Destination for Linux + ) + +add_executable(UnitTests + ${CORE_SOURCES} + ${GTEST_SOURCES} + UnitTestsSources/UnitTestsMain.cpp + ) + +target_link_libraries(UnitTests ${OS_LIBRARIES})
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/COPYING Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +<http://www.gnu.org/licenses/>.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/NEWS Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,9 @@ +Pending changes in the mainline +=============================== + + + +2015-02-25 +========== + +* Initial release
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Orthanc/ChunkedBuffer.cpp Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,99 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "PrecompiledHeaders.h" +#include "ChunkedBuffer.h" + +#include <cassert> +#include <string.h> + + +namespace Orthanc +{ + void ChunkedBuffer::Clear() + { + numBytes_ = 0; + + for (Chunks::iterator it = chunks_.begin(); + it != chunks_.end(); ++it) + { + delete *it; + } + } + + + void ChunkedBuffer::AddChunk(const char* chunkData, + size_t chunkSize) + { + if (chunkSize == 0) + { + return; + } + + assert(chunkData != NULL); + chunks_.push_back(new std::string(chunkData, chunkSize)); + numBytes_ += chunkSize; + } + + + void ChunkedBuffer::AddChunk(const std::string& chunk) + { + if (chunk.size() > 0) + { + AddChunk(&chunk[0], chunk.size()); + } + } + + + void ChunkedBuffer::Flatten(std::string& result) + { + result.resize(numBytes_); + + size_t pos = 0; + for (Chunks::iterator it = chunks_.begin(); + it != chunks_.end(); ++it) + { + assert(*it != NULL); + + size_t s = (*it)->size(); + if (s != 0) + { + memcpy(&result[pos], (*it)->c_str(), s); + pos += s; + } + + delete *it; + } + + chunks_.clear(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Orthanc/ChunkedBuffer.h Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,71 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include <list> +#include <string> + +namespace Orthanc +{ + class ChunkedBuffer + { + private: + typedef std::list<std::string*> Chunks; + size_t numBytes_; + Chunks chunks_; + + void Clear(); + + public: + ChunkedBuffer() : numBytes_(0) + { + } + + ~ChunkedBuffer() + { + Clear(); + } + + size_t GetNumBytes() const + { + return numBytes_; + } + + void AddChunk(const char* chunkData, + size_t chunkSize); + + void AddChunk(const std::string& chunk); + + void Flatten(std::string& result); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Orthanc/Enumerations.cpp Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,61 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "PrecompiledHeaders.h" +#include "Enumerations.h" + +#include "OrthancException.h" + +namespace Orthanc +{ + unsigned int GetBytesPerPixel(PixelFormat format) + { + switch (format) + { + case PixelFormat_Grayscale8: + return 1; + + case PixelFormat_Grayscale16: + case PixelFormat_SignedGrayscale16: + return 2; + + case PixelFormat_RGB24: + return 3; + + case PixelFormat_RGBA32: + return 4; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Orthanc/Enumerations.h Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,119 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +namespace Orthanc +{ + enum Endianness + { + Endianness_Unknown, + Endianness_Big, + Endianness_Little + }; + + enum ErrorCode + { + // Generic error codes + ErrorCode_Success, + ErrorCode_Custom, + ErrorCode_InternalError, + ErrorCode_NotImplemented, + ErrorCode_ParameterOutOfRange, + ErrorCode_NotEnoughMemory, + ErrorCode_BadParameterType, + ErrorCode_BadSequenceOfCalls, + ErrorCode_InexistentItem, + ErrorCode_BadRequest, + ErrorCode_NetworkProtocol, + ErrorCode_SystemCommand, + ErrorCode_Database, + + // Specific error codes + ErrorCode_UriSyntax, + ErrorCode_InexistentFile, + ErrorCode_CannotWriteFile, + ErrorCode_BadFileFormat, + ErrorCode_Timeout, + ErrorCode_UnknownResource, + ErrorCode_IncompatibleDatabaseVersion, + ErrorCode_FullStorage, + ErrorCode_CorruptedFile, + ErrorCode_InexistentTag, + ErrorCode_ReadOnly, + ErrorCode_IncompatibleImageFormat, + ErrorCode_IncompatibleImageSize, + ErrorCode_SharedLibrary, + ErrorCode_Plugin + }; + + /** + * {summary}{The memory layout of the pixels (resp. voxels) of a 2D (resp. 3D) image.} + **/ + enum PixelFormat + { + /** + * {summary}{Color image in RGB24 format.} + * {description}{This format describes a color image. The pixels are stored in 3 + * consecutive bytes. The memory layout is RGB.} + **/ + PixelFormat_RGB24 = 1, + + /** + * {summary}{Color image in RGBA32 format.} + * {description}{This format describes a color image. The pixels are stored in 4 + * consecutive bytes. The memory layout is RGBA.} + **/ + PixelFormat_RGBA32 = 2, + + /** + * {summary}{Graylevel 8bpp image.} + * {description}{The image is graylevel. Each pixel is unsigned and stored in one byte.} + **/ + PixelFormat_Grayscale8 = 3, + + /** + * {summary}{Graylevel, unsigned 16bpp image.} + * {description}{The image is graylevel. Each pixel is unsigned and stored in two bytes.} + **/ + PixelFormat_Grayscale16 = 4, + + /** + * {summary}{Graylevel, signed 16bpp image.} + * {description}{The image is graylevel. Each pixel is signed and stored in two bytes.} + **/ + PixelFormat_SignedGrayscale16 = 5 + }; + + + unsigned int GetBytesPerPixel(PixelFormat format); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Orthanc/FileStorage/FilesystemStorage.cpp Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,254 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "../PrecompiledHeaders.h" +#include "FilesystemStorage.h" + +// http://stackoverflow.com/questions/1576272/storing-large-number-of-files-in-file-system +// http://stackoverflow.com/questions/446358/storing-a-large-number-of-images + +#include "../OrthancException.h" +#include "../Toolbox.h" +#include "../Uuid.h" + +#include <boost/filesystem/fstream.hpp> + +static std::string ToString(const boost::filesystem::path& p) +{ +#if BOOST_HAS_FILESYSTEM_V3 == 1 + return p.filename().string(); +#else + return p.filename(); +#endif +} + + +namespace Orthanc +{ + boost::filesystem::path FilesystemStorage::GetPath(const std::string& uuid) const + { + namespace fs = boost::filesystem; + + if (!Toolbox::IsUuid(uuid)) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + fs::path path = root_; + + path /= std::string(&uuid[0], &uuid[2]); + path /= std::string(&uuid[2], &uuid[4]); + path /= uuid; + +#if BOOST_HAS_FILESYSTEM_V3 == 1 + path.make_preferred(); +#endif + + return path; + } + + FilesystemStorage::FilesystemStorage(std::string root) + { + //root_ = boost::filesystem::absolute(root).string(); + root_ = root; + + Toolbox::CreateNewDirectory(root); + } + + void FilesystemStorage::Create(const std::string& uuid, + const void* content, + size_t size) + { + boost::filesystem::path path; + + path = GetPath(uuid); + + if (boost::filesystem::exists(path)) + { + // Extremely unlikely case: This Uuid has already been created + // in the past. + throw OrthancException(ErrorCode_InternalError); + } + + if (boost::filesystem::exists(path.parent_path())) + { + if (!boost::filesystem::is_directory(path.parent_path())) + { + throw OrthancException("The subdirectory to be created is already occupied by a regular file"); + } + } + else + { + if (!boost::filesystem::create_directories(path.parent_path())) + { + throw OrthancException("Unable to create a subdirectory in the file storage"); + } + } + + boost::filesystem::ofstream f; + f.open(path, std::ofstream::out | std::ios::binary); + if (!f.good()) + { + throw OrthancException("Unable to create a new file in the file storage"); + } + + if (size != 0) + { + f.write(static_cast<const char*>(content), size); + if (!f.good()) + { + f.close(); + throw OrthancException("Unable to write to the new file in the file storage"); + } + } + + f.close(); + } + + + void FilesystemStorage::Read(std::string& content, + const std::string& uuid) + { + content.clear(); + Toolbox::ReadFile(content, GetPath(uuid).string()); + } + + + uintmax_t FilesystemStorage::GetSize(const std::string& uuid) const + { + boost::filesystem::path path = GetPath(uuid); + return boost::filesystem::file_size(path); + } + + + + void FilesystemStorage::ListAllFiles(std::set<std::string>& result) const + { + namespace fs = boost::filesystem; + + result.clear(); + + if (fs::exists(root_) && fs::is_directory(root_)) + { + for (fs::recursive_directory_iterator current(root_), end; current != end ; ++current) + { + if (fs::is_regular_file(current->status())) + { + try + { + fs::path d = current->path(); + std::string uuid = ToString(d); + if (Toolbox::IsUuid(uuid)) + { + fs::path p0 = d.parent_path().parent_path().parent_path(); + std::string p1 = ToString(d.parent_path().parent_path()); + std::string p2 = ToString(d.parent_path()); + if (p1.length() == 2 && + p2.length() == 2 && + p1 == uuid.substr(0, 2) && + p2 == uuid.substr(2, 2) && + p0 == root_) + { + result.insert(uuid); + } + } + } + catch (fs::filesystem_error) + { + } + } + } + } + } + + + void FilesystemStorage::Clear() + { + namespace fs = boost::filesystem; + typedef std::set<std::string> List; + + List result; + ListAllFiles(result); + + for (List::const_iterator it = result.begin(); it != result.end(); ++it) + { + Remove(*it); + } + } + + + void FilesystemStorage::Remove(const std::string& uuid) + { + namespace fs = boost::filesystem; + + fs::path p = GetPath(uuid); + + try + { + fs::remove(p); + } + catch (...) + { + // Ignore the error + } + + // Remove the two parent directories, ignoring the error code if + // these directories are not empty + + try + { +#if BOOST_HAS_FILESYSTEM_V3 == 1 + boost::system::error_code err; + fs::remove(p.parent_path(), err); + fs::remove(p.parent_path().parent_path(), err); +#else + fs::remove(p.parent_path()); + fs::remove(p.parent_path().parent_path()); +#endif + } + catch (...) + { + // Ignore the error + } + } + + + uintmax_t FilesystemStorage::GetCapacity() const + { + return boost::filesystem::space(root_).capacity; + } + + uintmax_t FilesystemStorage::GetAvailableSpace() const + { + return boost::filesystem::space(root_).available; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Orthanc/FileStorage/FilesystemStorage.h Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,69 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include <boost/filesystem.hpp> +#include <set> + +namespace Orthanc +{ + class FilesystemStorage : public boost::noncopyable + { + private: + boost::filesystem::path root_; + + boost::filesystem::path GetPath(const std::string& uuid) const; + + public: + FilesystemStorage(std::string root); + + virtual void Create(const std::string& uuid, + const void* content, + size_t size); + + virtual void Read(std::string& content, + const std::string& uuid); + + virtual void Remove(const std::string& uuid); + + void ListAllFiles(std::set<std::string>& result) const; + + uintmax_t GetSize(const std::string& uuid) const; + + void Clear(); + + uintmax_t GetCapacity() const; + + uintmax_t GetAvailableSpace() const; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Orthanc/IDynamicObject.h Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,52 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include <boost/noncopyable.hpp> + +namespace Orthanc +{ + /** + * This class should be the ancestor to any class whose type is + * determined at the runtime, and that can be dynamically allocated. + * Being a child of IDynamicObject only implies the existence of a + * virtual destructor. + **/ + class IDynamicObject : public boost::noncopyable + { + public: + virtual ~IDynamicObject() + { + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Orthanc/ImageFormats/ImageAccessor.cpp Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,129 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "../PrecompiledHeaders.h" +#include "ImageAccessor.h" + +#include "../OrthancException.h" + +#include <stdint.h> +#include <cassert> +#include <boost/lexical_cast.hpp> + +namespace Orthanc +{ + void* ImageAccessor::GetBuffer() const + { + if (readOnly_) + { + throw OrthancException(ErrorCode_ReadOnly); + } + + return buffer_; + } + + + const void* ImageAccessor::GetConstRow(unsigned int y) const + { + if (buffer_ != NULL) + { + return reinterpret_cast<const uint8_t*>(buffer_) + y * pitch_; + } + else + { + return NULL; + } + } + + + void* ImageAccessor::GetRow(unsigned int y) const + { + if (readOnly_) + { + throw OrthancException(ErrorCode_ReadOnly); + } + + if (buffer_ != NULL) + { + return reinterpret_cast<uint8_t*>(buffer_) + y * pitch_; + } + else + { + return NULL; + } + } + + + void ImageAccessor::AssignEmpty(PixelFormat format) + { + readOnly_ = false; + format_ = format; + width_ = 0; + height_ = 0; + pitch_ = 0; + buffer_ = NULL; + } + + + void ImageAccessor::AssignReadOnly(PixelFormat format, + unsigned int width, + unsigned int height, + unsigned int pitch, + const void *buffer) + { + readOnly_ = true; + format_ = format; + width_ = width; + height_ = height; + pitch_ = pitch; + buffer_ = const_cast<void*>(buffer); + + assert(GetBytesPerPixel() * width_ <= pitch_); + } + + + void ImageAccessor::AssignWritable(PixelFormat format, + unsigned int width, + unsigned int height, + unsigned int pitch, + void *buffer) + { + readOnly_ = false; + format_ = format; + width_ = width; + height_ = height; + pitch_ = pitch; + buffer_ = buffer; + + assert(GetBytesPerPixel() * width_ <= pitch_); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Orthanc/ImageFormats/ImageAccessor.h Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,115 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../Enumerations.h" + +namespace Orthanc +{ + class ImageAccessor + { + private: + bool readOnly_; + PixelFormat format_; + unsigned int width_; + unsigned int height_; + unsigned int pitch_; + void *buffer_; + + public: + ImageAccessor() + { + AssignEmpty(PixelFormat_Grayscale8); + } + + bool IsReadOnly() const + { + return readOnly_; + } + + PixelFormat GetFormat() const + { + return format_; + } + + unsigned int GetBytesPerPixel() const + { + return ::Orthanc::GetBytesPerPixel(format_); + } + + unsigned int GetWidth() const + { + return width_; + } + + unsigned int GetHeight() const + { + return height_; + } + + unsigned int GetPitch() const + { + return pitch_; + } + + unsigned int GetSize() const + { + return GetHeight() * GetPitch(); + } + + const void* GetConstBuffer() const + { + return buffer_; + } + + void* GetBuffer() const; + + const void* GetConstRow(unsigned int y) const; + + void* GetRow(unsigned int y) const; + + void AssignEmpty(PixelFormat format); + + void AssignReadOnly(PixelFormat format, + unsigned int width, + unsigned int height, + unsigned int pitch, + const void *buffer); + + void AssignWritable(PixelFormat format, + unsigned int width, + unsigned int height, + unsigned int pitch, + void *buffer); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Orthanc/ImageFormats/ImageBuffer.cpp Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,192 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "../PrecompiledHeaders.h" +#include "ImageBuffer.h" + +#include "../OrthancException.h" + +#include <stdio.h> +#include <stdlib.h> + +namespace Orthanc +{ + void ImageBuffer::Allocate() + { + if (changed_) + { + Deallocate(); + + /* + if (forceMinimalPitch_) + { + TODO: Align pitch and memory buffer to optimal size for SIMD. + } + */ + + pitch_ = GetBytesPerPixel() * width_; + size_t size = pitch_ * height_; + + if (size == 0) + { + buffer_ = NULL; + } + else + { + buffer_ = malloc(size); + if (buffer_ == NULL) + { + throw OrthancException(ErrorCode_NotEnoughMemory); + } + } + + changed_ = false; + } + } + + + void ImageBuffer::Deallocate() + { + if (buffer_ != NULL) + { + free(buffer_); + buffer_ = NULL; + changed_ = true; + } + } + + + ImageBuffer::ImageBuffer(unsigned int width, + unsigned int height, + PixelFormat format) + { + Initialize(); + SetWidth(width); + SetHeight(height); + SetFormat(format); + } + + + void ImageBuffer::Initialize() + { + changed_ = false; + forceMinimalPitch_ = true; + format_ = PixelFormat_Grayscale8; + width_ = 0; + height_ = 0; + pitch_ = 0; + buffer_ = NULL; + } + + + void ImageBuffer::SetFormat(PixelFormat format) + { + if (format != format_) + { + changed_ = true; + format_ = format; + } + } + + + void ImageBuffer::SetWidth(unsigned int width) + { + if (width != width_) + { + changed_ = true; + width_ = width; + } + } + + + void ImageBuffer::SetHeight(unsigned int height) + { + if (height != height_) + { + changed_ = true; + height_ = height; + } + } + + + ImageAccessor ImageBuffer::GetAccessor() + { + Allocate(); + + ImageAccessor accessor; + accessor.AssignWritable(format_, width_, height_, pitch_, buffer_); + return accessor; + } + + + ImageAccessor ImageBuffer::GetConstAccessor() + { + Allocate(); + + ImageAccessor accessor; + accessor.AssignReadOnly(format_, width_, height_, pitch_, buffer_); + return accessor; + } + + + void ImageBuffer::SetMinimalPitchForced(bool force) + { + if (force != forceMinimalPitch_) + { + changed_ = true; + forceMinimalPitch_ = force; + } + } + + + void ImageBuffer::AcquireOwnership(ImageBuffer& other) + { + // Remove the content of the current image + Deallocate(); + + // Force the allocation of the other image (if not already + // allocated) + other.Allocate(); + + // Transfer the content of the other image + changed_ = false; + forceMinimalPitch_ = other.forceMinimalPitch_; + format_ = other.format_; + width_ = other.width_; + height_ = other.height_; + pitch_ = other.pitch_; + buffer_ = other.buffer_; + + // Force the reinitialization of the other image + other.Initialize(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Orthanc/ImageFormats/ImageBuffer.h Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,115 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "ImageAccessor.h" + +#include <vector> +#include <stdint.h> +#include <boost/noncopyable.hpp> + +namespace Orthanc +{ + class ImageBuffer : public boost::noncopyable + { + private: + bool changed_; + + bool forceMinimalPitch_; // Currently unused + PixelFormat format_; + unsigned int width_; + unsigned int height_; + unsigned int pitch_; + void *buffer_; + + void Initialize(); + + void Allocate(); + + void Deallocate(); + + public: + ImageBuffer(unsigned int width, + unsigned int height, + PixelFormat format); + + ImageBuffer() + { + Initialize(); + } + + ~ImageBuffer() + { + Deallocate(); + } + + PixelFormat GetFormat() const + { + return format_; + } + + void SetFormat(PixelFormat format); + + unsigned int GetWidth() const + { + return width_; + } + + void SetWidth(unsigned int width); + + unsigned int GetHeight() const + { + return height_; + } + + void SetHeight(unsigned int height); + + unsigned int GetBytesPerPixel() const + { + return ::Orthanc::GetBytesPerPixel(format_); + } + + ImageAccessor GetAccessor(); + + ImageAccessor GetConstAccessor(); + + bool IsMinimalPitchForced() const + { + return forceMinimalPitch_; + } + + void SetMinimalPitchForced(bool force); + + void AcquireOwnership(ImageBuffer& other); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Orthanc/ImageFormats/ImageProcessing.cpp Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,532 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "../PrecompiledHeaders.h" +#include "ImageProcessing.h" + +#include "../OrthancException.h" + +#include <boost/math/special_functions/round.hpp> + +#include <cassert> +#include <string.h> +#include <limits> +#include <stdint.h> + +namespace Orthanc +{ + template <typename TargetType, typename SourceType> + static void ConvertInternal(ImageAccessor& target, + const ImageAccessor& source) + { + const TargetType minValue = std::numeric_limits<TargetType>::min(); + const TargetType maxValue = std::numeric_limits<TargetType>::max(); + + for (unsigned int y = 0; y < source.GetHeight(); y++) + { + TargetType* t = reinterpret_cast<TargetType*>(target.GetRow(y)); + const SourceType* s = reinterpret_cast<const SourceType*>(source.GetConstRow(y)); + + for (unsigned int x = 0; x < source.GetWidth(); x++, t++, s++) + { + if (static_cast<int32_t>(*s) < static_cast<int32_t>(minValue)) + { + *t = minValue; + } + else if (static_cast<int32_t>(*s) > static_cast<int32_t>(maxValue)) + { + *t = maxValue; + } + else + { + *t = static_cast<TargetType>(*s); + } + } + } + } + + + template <typename TargetType> + static void ConvertColorToGrayscale(ImageAccessor& target, + const ImageAccessor& source) + { + assert(source.GetFormat() == PixelFormat_RGB24); + + const TargetType minValue = std::numeric_limits<TargetType>::min(); + const TargetType maxValue = std::numeric_limits<TargetType>::max(); + + for (unsigned int y = 0; y < source.GetHeight(); y++) + { + TargetType* t = reinterpret_cast<TargetType*>(target.GetRow(y)); + const uint8_t* s = reinterpret_cast<const uint8_t*>(source.GetConstRow(y)); + + for (unsigned int x = 0; x < source.GetWidth(); x++, t++, s += 3) + { + // Y = 0.2126 R + 0.7152 G + 0.0722 B + int32_t v = (2126 * static_cast<int32_t>(s[0]) + + 7152 * static_cast<int32_t>(s[1]) + + 0722 * static_cast<int32_t>(s[2])) / 1000; + + if (static_cast<int32_t>(v) < static_cast<int32_t>(minValue)) + { + *t = minValue; + } + else if (static_cast<int32_t>(v) > static_cast<int32_t>(maxValue)) + { + *t = maxValue; + } + else + { + *t = static_cast<TargetType>(v); + } + } + } + } + + + template <typename PixelType> + static void SetInternal(ImageAccessor& image, + int64_t constant) + { + for (unsigned int y = 0; y < image.GetHeight(); y++) + { + PixelType* p = reinterpret_cast<PixelType*>(image.GetRow(y)); + + for (unsigned int x = 0; x < image.GetWidth(); x++, p++) + { + *p = static_cast<PixelType>(constant); + } + } + } + + + template <typename PixelType> + static void GetMinMaxValueInternal(PixelType& minValue, + PixelType& maxValue, + const ImageAccessor& source) + { + // Deal with the special case of empty image + if (source.GetWidth() == 0 || + source.GetHeight() == 0) + { + minValue = 0; + maxValue = 0; + return; + } + + minValue = std::numeric_limits<PixelType>::max(); + maxValue = std::numeric_limits<PixelType>::min(); + + for (unsigned int y = 0; y < source.GetHeight(); y++) + { + const PixelType* p = reinterpret_cast<const PixelType*>(source.GetConstRow(y)); + + for (unsigned int x = 0; x < source.GetWidth(); x++, p++) + { + if (*p < minValue) + { + minValue = *p; + } + + if (*p > maxValue) + { + maxValue = *p; + } + } + } + } + + + + template <typename PixelType> + static void AddConstantInternal(ImageAccessor& image, + int64_t constant) + { + if (constant == 0) + { + return; + } + + const int64_t minValue = std::numeric_limits<PixelType>::min(); + const int64_t maxValue = std::numeric_limits<PixelType>::max(); + + for (unsigned int y = 0; y < image.GetHeight(); y++) + { + PixelType* p = reinterpret_cast<PixelType*>(image.GetRow(y)); + + for (unsigned int x = 0; x < image.GetWidth(); x++, p++) + { + int64_t v = static_cast<int64_t>(*p) + constant; + + if (v > maxValue) + { + *p = std::numeric_limits<PixelType>::max(); + } + else if (v < minValue) + { + *p = std::numeric_limits<PixelType>::min(); + } + else + { + *p = static_cast<PixelType>(v); + } + } + } + } + + + + template <typename PixelType> + void MultiplyConstantInternal(ImageAccessor& image, + float factor) + { + if (abs(factor - 1.0f) <= std::numeric_limits<float>::epsilon()) + { + return; + } + + const int64_t minValue = std::numeric_limits<PixelType>::min(); + const int64_t maxValue = std::numeric_limits<PixelType>::max(); + + for (unsigned int y = 0; y < image.GetHeight(); y++) + { + PixelType* p = reinterpret_cast<PixelType*>(image.GetRow(y)); + + for (unsigned int x = 0; x < image.GetWidth(); x++, p++) + { + int64_t v = boost::math::llround(static_cast<float>(*p) * factor); + + if (v > maxValue) + { + *p = std::numeric_limits<PixelType>::max(); + } + else if (v < minValue) + { + *p = std::numeric_limits<PixelType>::min(); + } + else + { + *p = static_cast<PixelType>(v); + } + } + } + } + + + template <typename PixelType> + void ShiftScaleInternal(ImageAccessor& image, + float offset, + float scaling) + { + const float minValue = static_cast<float>(std::numeric_limits<PixelType>::min()); + const float maxValue = static_cast<float>(std::numeric_limits<PixelType>::max()); + + for (unsigned int y = 0; y < image.GetHeight(); y++) + { + PixelType* p = reinterpret_cast<PixelType*>(image.GetRow(y)); + + for (unsigned int x = 0; x < image.GetWidth(); x++, p++) + { + float v = (static_cast<float>(*p) + offset) * scaling; + + if (v > maxValue) + { + *p = std::numeric_limits<PixelType>::max(); + } + else if (v < minValue) + { + *p = std::numeric_limits<PixelType>::min(); + } + else + { + *p = static_cast<PixelType>(boost::math::iround(v)); + } + } + } + } + + + void ImageProcessing::Copy(ImageAccessor& target, + const ImageAccessor& source) + { + if (target.GetWidth() != source.GetWidth() || + target.GetHeight() != source.GetHeight()) + { + throw OrthancException(ErrorCode_IncompatibleImageSize); + } + + if (target.GetFormat() != source.GetFormat()) + { + throw OrthancException(ErrorCode_IncompatibleImageFormat); + } + + unsigned int lineSize = GetBytesPerPixel(source.GetFormat()) * source.GetWidth(); + + assert(source.GetPitch() >= lineSize && target.GetPitch() >= lineSize); + + for (unsigned int y = 0; y < source.GetHeight(); y++) + { + memcpy(target.GetRow(y), source.GetConstRow(y), lineSize); + } + } + + + void ImageProcessing::Convert(ImageAccessor& target, + const ImageAccessor& source) + { + if (target.GetWidth() != source.GetWidth() || + target.GetHeight() != source.GetHeight()) + { + throw OrthancException(ErrorCode_IncompatibleImageSize); + } + + if (source.GetFormat() == target.GetFormat()) + { + Copy(target, source); + return; + } + + if (target.GetFormat() == PixelFormat_Grayscale16 && + source.GetFormat() == PixelFormat_Grayscale8) + { + ConvertInternal<uint16_t, uint8_t>(target, source); + return; + } + + if (target.GetFormat() == PixelFormat_SignedGrayscale16 && + source.GetFormat() == PixelFormat_Grayscale8) + { + ConvertInternal<int16_t, uint8_t>(target, source); + return; + } + + if (target.GetFormat() == PixelFormat_Grayscale8 && + source.GetFormat() == PixelFormat_Grayscale16) + { + ConvertInternal<uint8_t, uint16_t>(target, source); + return; + } + + if (target.GetFormat() == PixelFormat_SignedGrayscale16 && + source.GetFormat() == PixelFormat_Grayscale16) + { + ConvertInternal<int16_t, uint16_t>(target, source); + return; + } + + if (target.GetFormat() == PixelFormat_Grayscale8 && + source.GetFormat() == PixelFormat_SignedGrayscale16) + { + ConvertInternal<uint8_t, int16_t>(target, source); + return; + } + + if (target.GetFormat() == PixelFormat_Grayscale16 && + source.GetFormat() == PixelFormat_SignedGrayscale16) + { + ConvertInternal<uint16_t, int16_t>(target, source); + return; + } + + if (target.GetFormat() == PixelFormat_Grayscale8 && + source.GetFormat() == PixelFormat_RGB24) + { + ConvertColorToGrayscale<uint8_t>(target, source); + return; + } + + if (target.GetFormat() == PixelFormat_Grayscale16 && + source.GetFormat() == PixelFormat_RGB24) + { + ConvertColorToGrayscale<uint16_t>(target, source); + return; + } + + if (target.GetFormat() == PixelFormat_SignedGrayscale16 && + source.GetFormat() == PixelFormat_RGB24) + { + ConvertColorToGrayscale<int16_t>(target, source); + return; + } + + throw OrthancException(ErrorCode_NotImplemented); + } + + + + void ImageProcessing::Set(ImageAccessor& image, + int64_t value) + { + switch (image.GetFormat()) + { + case PixelFormat_Grayscale8: + SetInternal<uint8_t>(image, value); + return; + + case PixelFormat_Grayscale16: + SetInternal<uint16_t>(image, value); + return; + + case PixelFormat_SignedGrayscale16: + SetInternal<int16_t>(image, value); + return; + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + } + + + void ImageProcessing::ShiftRight(ImageAccessor& image, + unsigned int shift) + { + if (image.GetWidth() == 0 || + image.GetHeight() == 0 || + shift == 0) + { + // Nothing to do + return; + } + + throw OrthancException(ErrorCode_NotImplemented); + } + + + void ImageProcessing::GetMinMaxValue(int64_t& minValue, + int64_t& maxValue, + const ImageAccessor& image) + { + switch (image.GetFormat()) + { + case PixelFormat_Grayscale8: + { + uint8_t a, b; + GetMinMaxValueInternal<uint8_t>(a, b, image); + minValue = a; + maxValue = b; + break; + } + + case PixelFormat_Grayscale16: + { + uint16_t a, b; + GetMinMaxValueInternal<uint16_t>(a, b, image); + minValue = a; + maxValue = b; + break; + } + + case PixelFormat_SignedGrayscale16: + { + int16_t a, b; + GetMinMaxValueInternal<int16_t>(a, b, image); + minValue = a; + maxValue = b; + break; + } + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + } + + + + void ImageProcessing::AddConstant(ImageAccessor& image, + int64_t value) + { + switch (image.GetFormat()) + { + case PixelFormat_Grayscale8: + AddConstantInternal<uint8_t>(image, value); + return; + + case PixelFormat_Grayscale16: + AddConstantInternal<uint16_t>(image, value); + return; + + case PixelFormat_SignedGrayscale16: + AddConstantInternal<int16_t>(image, value); + return; + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + } + + + void ImageProcessing::MultiplyConstant(ImageAccessor& image, + float factor) + { + switch (image.GetFormat()) + { + case PixelFormat_Grayscale8: + MultiplyConstantInternal<uint8_t>(image, factor); + return; + + case PixelFormat_Grayscale16: + MultiplyConstantInternal<uint16_t>(image, factor); + return; + + case PixelFormat_SignedGrayscale16: + MultiplyConstantInternal<int16_t>(image, factor); + return; + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + } + + + void ImageProcessing::ShiftScale(ImageAccessor& image, + float offset, + float scaling) + { + switch (image.GetFormat()) + { + case PixelFormat_Grayscale8: + ShiftScaleInternal<uint8_t>(image, offset, scaling); + return; + + case PixelFormat_Grayscale16: + ShiftScaleInternal<uint16_t>(image, offset, scaling); + return; + + case PixelFormat_SignedGrayscale16: + ShiftScaleInternal<int16_t>(image, offset, scaling); + return; + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Orthanc/ImageFormats/ImageProcessing.h Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,70 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "ImageAccessor.h" + +#include <stdint.h> + +namespace Orthanc +{ + class ImageProcessing + { + public: + static void Copy(ImageAccessor& target, + const ImageAccessor& source); + + static void Convert(ImageAccessor& target, + const ImageAccessor& source); + + static void Set(ImageAccessor& image, + int64_t value); + + static void ShiftRight(ImageAccessor& target, + unsigned int shift); + + static void GetMinMaxValue(int64_t& minValue, + int64_t& maxValue, + const ImageAccessor& image); + + static void AddConstant(ImageAccessor& image, + int64_t value); + + static void MultiplyConstant(ImageAccessor& image, + float factor); + + static void ShiftScale(ImageAccessor& image, + float offset, + float scaling); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Orthanc/ImageFormats/PngWriter.cpp Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,269 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "../PrecompiledHeaders.h" +#include "PngWriter.h" + +#include <vector> +#include <stdint.h> +#include <png.h> +#include "../OrthancException.h" +#include "../ChunkedBuffer.h" +#include "../Toolbox.h" + + +// http://www.libpng.org/pub/png/libpng-1.2.5-manual.html#section-4 +// http://zarb.org/~gc/html/libpng.html +/* + void write_row_callback(png_ptr, png_uint_32 row, int pass) + { + }*/ + + + + +/* bool isError_; + +// http://www.libpng.org/pub/png/book/chapter14.html#png.ch14.div.2 + +static void ErrorHandler(png_structp png, png_const_charp message) +{ +printf("** [%s]\n", message); + +PngWriter* that = (PngWriter*) png_get_error_ptr(png); +that->isError_ = true; +printf("** %d\n", (int)that); + +//((PngWriter*) payload)->isError_ = true; +} + +static void WarningHandler(png_structp png, png_const_charp message) +{ + printf("++ %d\n", (int)message); +}*/ + + +namespace Orthanc +{ + struct PngWriter::PImpl + { + png_structp png_; + png_infop info_; + + // Filled by Prepare() + std::vector<uint8_t*> rows_; + int bitDepth_; + int colorType_; + }; + + + + PngWriter::PngWriter() : pimpl_(new PImpl) + { + pimpl_->png_ = NULL; + pimpl_->info_ = NULL; + + pimpl_->png_ = png_create_write_struct + (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); //this, ErrorHandler, WarningHandler); + if (!pimpl_->png_) + { + throw OrthancException(ErrorCode_NotEnoughMemory); + } + + pimpl_->info_ = png_create_info_struct(pimpl_->png_); + if (!pimpl_->info_) + { + png_destroy_write_struct(&pimpl_->png_, NULL); + throw OrthancException(ErrorCode_NotEnoughMemory); + } + } + + PngWriter::~PngWriter() + { + if (pimpl_->info_) + { + png_destroy_info_struct(pimpl_->png_, &pimpl_->info_); + } + + if (pimpl_->png_) + { + png_destroy_write_struct(&pimpl_->png_, NULL); + } + } + + + + void PngWriter::Prepare(unsigned int width, + unsigned int height, + unsigned int pitch, + PixelFormat format, + const void* buffer) + { + pimpl_->rows_.resize(height); + for (unsigned int y = 0; y < height; y++) + { + pimpl_->rows_[y] = const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(buffer)) + y * pitch; + } + + switch (format) + { + case PixelFormat_RGB24: + pimpl_->bitDepth_ = 8; + pimpl_->colorType_ = PNG_COLOR_TYPE_RGB; + break; + + case PixelFormat_RGBA32: + pimpl_->bitDepth_ = 8; + pimpl_->colorType_ = PNG_COLOR_TYPE_RGBA; + break; + + case PixelFormat_Grayscale8: + pimpl_->bitDepth_ = 8; + pimpl_->colorType_ = PNG_COLOR_TYPE_GRAY; + break; + + case PixelFormat_Grayscale16: + case PixelFormat_SignedGrayscale16: + pimpl_->bitDepth_ = 16; + pimpl_->colorType_ = PNG_COLOR_TYPE_GRAY; + break; + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + } + + + void PngWriter::Compress(unsigned int width, + unsigned int height, + unsigned int pitch, + PixelFormat format) + { + png_set_IHDR(pimpl_->png_, pimpl_->info_, width, height, + pimpl_->bitDepth_, pimpl_->colorType_, PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); + + png_write_info(pimpl_->png_, pimpl_->info_); + + if (height > 0) + { + switch (format) + { + case PixelFormat_Grayscale16: + case PixelFormat_SignedGrayscale16: + { + int transforms = 0; + if (Toolbox::DetectEndianness() == Endianness_Little) + { + transforms = PNG_TRANSFORM_SWAP_ENDIAN; + } + + png_set_rows(pimpl_->png_, pimpl_->info_, &pimpl_->rows_[0]); + png_write_png(pimpl_->png_, pimpl_->info_, transforms, NULL); + + break; + } + + default: + png_write_image(pimpl_->png_, &pimpl_->rows_[0]); + } + } + + png_write_end(pimpl_->png_, NULL); + } + + + void PngWriter::WriteToFile(const char* filename, + unsigned int width, + unsigned int height, + unsigned int pitch, + PixelFormat format, + const void* buffer) + { + Prepare(width, height, pitch, format, buffer); + + FILE* fp = fopen(filename, "wb"); + if (!fp) + { + throw OrthancException(ErrorCode_CannotWriteFile); + } + + png_init_io(pimpl_->png_, fp); + + if (setjmp(png_jmpbuf(pimpl_->png_))) + { + // Error during writing PNG + throw OrthancException(ErrorCode_CannotWriteFile); + } + + Compress(width, height, pitch, format); + + fclose(fp); + } + + + + + static void MemoryCallback(png_structp png_ptr, + png_bytep data, + png_size_t size) + { + ChunkedBuffer* buffer = reinterpret_cast<ChunkedBuffer*>(png_get_io_ptr(png_ptr)); + buffer->AddChunk(reinterpret_cast<const char*>(data), size); + } + + + + void PngWriter::WriteToMemory(std::string& png, + unsigned int width, + unsigned int height, + unsigned int pitch, + PixelFormat format, + const void* buffer) + { + ChunkedBuffer chunks; + + Prepare(width, height, pitch, format, buffer); + + if (setjmp(png_jmpbuf(pimpl_->png_))) + { + // Error during writing PNG + throw OrthancException(ErrorCode_InternalError); + } + + png_set_write_fn(pimpl_->png_, &chunks, MemoryCallback, NULL); + + Compress(width, height, pitch, format); + + chunks.Flatten(png); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Orthanc/ImageFormats/PngWriter.h Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,92 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "ImageAccessor.h" + +#include <boost/shared_ptr.hpp> +#include <string> + +namespace Orthanc +{ + class PngWriter + { + private: + struct PImpl; + boost::shared_ptr<PImpl> pimpl_; + + void Compress(unsigned int width, + unsigned int height, + unsigned int pitch, + PixelFormat format); + + void Prepare(unsigned int width, + unsigned int height, + unsigned int pitch, + PixelFormat format, + const void* buffer); + + public: + PngWriter(); + + ~PngWriter(); + + void WriteToFile(const char* filename, + unsigned int width, + unsigned int height, + unsigned int pitch, + PixelFormat format, + const void* buffer); + + void WriteToMemory(std::string& png, + unsigned int width, + unsigned int height, + unsigned int pitch, + PixelFormat format, + const void* buffer); + + void WriteToFile(const char* filename, + const ImageAccessor& accessor) + { + WriteToFile(filename, accessor.GetWidth(), accessor.GetHeight(), + accessor.GetPitch(), accessor.GetFormat(), accessor.GetConstBuffer()); + } + + void WriteToMemory(std::string& png, + const ImageAccessor& accessor) + { + WriteToMemory(png, accessor.GetWidth(), accessor.GetHeight(), + accessor.GetPitch(), accessor.GetFormat(), accessor.GetConstBuffer()); + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Orthanc/MultiThreading/SharedMessageQueue.cpp Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,189 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "../PrecompiledHeaders.h" +#include "SharedMessageQueue.h" + + + +/** + * FIFO (queue): + * + * back front + * +--+--+--+--+--+--+--+--+--+--+--+ + * Enqueue -> | | | | | | | | | | | | + * | | | | | | | | | | | | -> Dequeue + * +--+--+--+--+--+--+--+--+--+--+--+ + * ^ + * | + * Make room here + * + * + * LIFO (stack): + * + * back front + * +--+--+--+--+--+--+--+--+--+--+--+ + * | | | | | | | | | | | | <- Enqueue + * | | | | | | | | | | | | -> Dequeue + * +--+--+--+--+--+--+--+--+--+--+--+ + * ^ + * | + * Make room here + **/ + + + +namespace Orthanc +{ + SharedMessageQueue::SharedMessageQueue(unsigned int maxSize) : + isFifo_(true), + maxSize_(maxSize) + { + } + + + SharedMessageQueue::~SharedMessageQueue() + { + for (Queue::iterator it = queue_.begin(); it != queue_.end(); ++it) + { + delete *it; + } + } + + + void SharedMessageQueue::Enqueue(IDynamicObject* message) + { + boost::mutex::scoped_lock lock(mutex_); + + if (maxSize_ != 0 && queue_.size() > maxSize_) + { + if (isFifo_) + { + // Too many elements in the queue: Make room + delete queue_.front(); + queue_.pop_front(); + } + else + { + // Too many elements in the stack: Make room + delete queue_.back(); + queue_.pop_back(); + } + } + + if (isFifo_) + { + // Queue policy (FIFO) + queue_.push_back(message); + } + else + { + // Stack policy (LIFO) + queue_.push_front(message); + } + + elementAvailable_.notify_one(); + } + + + IDynamicObject* SharedMessageQueue::Dequeue(int32_t millisecondsTimeout) + { + boost::mutex::scoped_lock lock(mutex_); + + // Wait for a message to arrive in the FIFO queue + while (queue_.empty()) + { + if (millisecondsTimeout == 0) + { + elementAvailable_.wait(lock); + } + else + { + bool success = elementAvailable_.timed_wait + (lock, boost::posix_time::milliseconds(millisecondsTimeout)); + if (!success) + { + return NULL; + } + } + } + + std::auto_ptr<IDynamicObject> message(queue_.front()); + queue_.pop_front(); + + if (queue_.empty()) + { + emptied_.notify_all(); + } + + return message.release(); + } + + + + bool SharedMessageQueue::WaitEmpty(int32_t millisecondsTimeout) + { + boost::mutex::scoped_lock lock(mutex_); + + // Wait for the queue to become empty + while (!queue_.empty()) + { + if (millisecondsTimeout == 0) + { + emptied_.wait(lock); + } + else + { + if (!emptied_.timed_wait + (lock, boost::posix_time::milliseconds(millisecondsTimeout))) + { + return false; + } + } + } + + return true; + } + + + void SharedMessageQueue::SetFifoPolicy() + { + boost::mutex::scoped_lock lock(mutex_); + isFifo_ = true; + } + + void SharedMessageQueue::SetLifoPolicy() + { + boost::mutex::scoped_lock lock(mutex_); + isFifo_ = false; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Orthanc/MultiThreading/SharedMessageQueue.h Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,82 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../IDynamicObject.h" + +#include <stdint.h> +#include <list> +#include <boost/thread.hpp> + +namespace Orthanc +{ + class SharedMessageQueue : public boost::noncopyable + { + private: + typedef std::list<IDynamicObject*> Queue; + + bool isFifo_; + unsigned int maxSize_; + Queue queue_; + boost::mutex mutex_; + boost::condition_variable elementAvailable_; + boost::condition_variable emptied_; + + public: + explicit SharedMessageQueue(unsigned int maxSize = 0); + + ~SharedMessageQueue(); + + // This transfers the ownership of the message + void Enqueue(IDynamicObject* message); + + // The caller is responsible to delete the dequeud message! + IDynamicObject* Dequeue(int32_t millisecondsTimeout); + + bool WaitEmpty(int32_t millisecondsTimeout); + + bool IsFifoPolicy() const + { + return isFifo_; + } + + bool IsLifoPolicy() const + { + return !isFifo_; + } + + void SetFifoPolicy(); + + void SetLifoPolicy(); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Orthanc/OrthancException.cpp Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,141 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "PrecompiledHeaders.h" +#include "OrthancException.h" + +namespace Orthanc +{ + const char* OrthancException::What() const + { + if (error_ == ErrorCode_Custom) + { + return custom_.c_str(); + } + else + { + return GetDescription(error_); + } + } + + + const char* OrthancException::GetDescription(ErrorCode error) + { + switch (error) + { + case ErrorCode_Success: + return "Success"; + + case ErrorCode_ParameterOutOfRange: + return "Parameter out of range"; + + case ErrorCode_NotImplemented: + return "Not implemented yet"; + + case ErrorCode_InternalError: + return "Internal error"; + + case ErrorCode_NotEnoughMemory: + return "Not enough memory"; + + case ErrorCode_UriSyntax: + return "Badly formatted URI"; + + case ErrorCode_BadParameterType: + return "Bad type for a parameter"; + + case ErrorCode_InexistentFile: + return "Inexistent file"; + + case ErrorCode_BadFileFormat: + return "Bad file format"; + + case ErrorCode_CannotWriteFile: + return "Cannot write to file"; + + case ErrorCode_Timeout: + return "Timeout"; + + case ErrorCode_UnknownResource: + return "Unknown resource"; + + case ErrorCode_BadSequenceOfCalls: + return "Bad sequence of calls"; + + case ErrorCode_IncompatibleDatabaseVersion: + return "Incompatible version of the database"; + + case ErrorCode_FullStorage: + return "The file storage is full"; + + case ErrorCode_InexistentItem: + return "Accessing an inexistent item"; + + case ErrorCode_BadRequest: + return "Bad request"; + + case ErrorCode_NetworkProtocol: + return "Error in the network protocol"; + + case ErrorCode_CorruptedFile: + return "Corrupted file (inconsistent MD5 hash)"; + + case ErrorCode_InexistentTag: + return "Inexistent tag"; + + case ErrorCode_ReadOnly: + return "Cannot modify a read-only data structure"; + + case ErrorCode_IncompatibleImageSize: + return "Incompatible size of the images"; + + case ErrorCode_IncompatibleImageFormat: + return "Incompatible format of the images"; + + case ErrorCode_SharedLibrary: + return "Error while using a shared library (plugin)"; + + case ErrorCode_SystemCommand: + return "Error while calling a system command"; + + case ErrorCode_Plugin: + return "Error encountered inside a plugin"; + + case ErrorCode_Database: + return "Error with the database engine"; + + case ErrorCode_Custom: + default: + return "???"; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Orthanc/OrthancException.h Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,72 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include <string> +#include "Enumerations.h" + +namespace Orthanc +{ + class OrthancException + { + protected: + ErrorCode error_; + std::string custom_; + + public: + static const char* GetDescription(ErrorCode error); + + OrthancException(const char* custom) : + error_(ErrorCode_Custom), + custom_(custom) + { + } + + OrthancException(const std::string& custom) : + error_(ErrorCode_Custom), + custom_(custom) + { + } + + OrthancException(ErrorCode error) : error_(error) + { + } + + ErrorCode GetErrorCode() const + { + return error_; + } + + const char* What() const; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Orthanc/PrecompiledHeaders.cpp Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,33 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "PrecompiledHeaders.h"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Orthanc/PrecompiledHeaders.h Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,52 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#if defined(_WIN32) && !defined(NOMINMAX) +#define NOMINMAX +#endif + +#if ORTHANC_USE_PRECOMPILED_HEADERS == 1 + +#include <boost/filesystem.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/thread.hpp> +#include <boost/thread/shared_mutex.hpp> +#include <json/value.h> + +#include "Enumerations.h" +#include "OrthancException.h" +#include "Toolbox.h" +#include "Uuid.h" + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Orthanc/README.txt Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,1 @@ +This folder contains an excerpt of the source code of Orthanc 0.8.6.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Orthanc/SQLite/Connection.cpp Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,393 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * + * Copyright (C) 2012-2015 Sebastien Jodogne <s.jodogne@gmail.com>, + * Medical Physics Department, CHU of Liege, Belgium + * + * Copyright (c) 2012 The Chromium Authors. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc., the name of the CHU of Liege, + * nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **/ + + +#if ORTHANC_SQLITE_STANDALONE != 1 +#include "../PrecompiledHeaders.h" +#endif + +#include "Connection.h" +#include "OrthancSQLiteException.h" + +#include <memory> +#include <cassert> +#include <sqlite3.h> +#include <string.h> + +#if ORTHANC_SQLITE_STANDALONE != 1 +#include <glog/logging.h> +#endif + + +namespace Orthanc +{ + namespace SQLite + { + Connection::Connection() : + db_(NULL), + transactionNesting_(0), + needsRollback_(false) + { + } + + + Connection::~Connection() + { + Close(); + } + + + void Connection::CheckIsOpen() const + { + if (!db_) + { + throw OrthancSQLiteException("SQLite: The database is not opened"); + } + } + + void Connection::Open(const std::string& path) + { + if (db_) + { + throw OrthancSQLiteException("SQLite: Connection is already open"); + } + + int err = sqlite3_open(path.c_str(), &db_); + if (err != SQLITE_OK) + { + Close(); + db_ = NULL; + throw OrthancSQLiteException("SQLite: Unable to open the database"); + } + + // Execute PRAGMAs at this point + // http://www.sqlite.org/pragma.html + Execute("PRAGMA FOREIGN_KEYS=ON;"); + Execute("PRAGMA RECURSIVE_TRIGGERS=ON;"); + } + + void Connection::OpenInMemory() + { + Open(":memory:"); + } + + void Connection::Close() + { + ClearCache(); + + if (db_) + { + sqlite3_close(db_); + db_ = NULL; + } + } + + void Connection::ClearCache() + { + for (CachedStatements::iterator + it = cachedStatements_.begin(); + it != cachedStatements_.end(); ++it) + { + delete it->second; + } + + cachedStatements_.clear(); + } + + + StatementReference& Connection::GetCachedStatement(const StatementId& id, + const char* sql) + { + CachedStatements::iterator i = cachedStatements_.find(id); + if (i != cachedStatements_.end()) + { + if (i->second->GetReferenceCount() >= 1) + { + throw OrthancSQLiteException("SQLite: This cached statement is already being referred to"); + } + + return *i->second; + } + else + { + StatementReference* statement = new StatementReference(db_, sql); + cachedStatements_[id] = statement; + return *statement; + } + } + + + bool Connection::Execute(const char* sql) + { +#if ORTHANC_SQLITE_STANDALONE != 1 + VLOG(1) << "SQLite::Connection::Execute " << sql; +#endif + + CheckIsOpen(); + + int error = sqlite3_exec(db_, sql, NULL, NULL, NULL); + if (error == SQLITE_ERROR) + { + throw OrthancSQLiteException("SQLite Execute error: " + std::string(sqlite3_errmsg(db_))); + } + else + { + return error == SQLITE_OK; + } + } + + int Connection::ExecuteAndReturnErrorCode(const char* sql) + { + CheckIsOpen(); + return sqlite3_exec(db_, sql, NULL, NULL, NULL); + } + + // Info querying ------------------------------------------------------------- + + bool Connection::IsSQLValid(const char* sql) + { + sqlite3_stmt* stmt = NULL; + if (sqlite3_prepare_v2(db_, sql, -1, &stmt, NULL) != SQLITE_OK) + return false; + + sqlite3_finalize(stmt); + return true; + } + + bool Connection::DoesTableOrIndexExist(const char* name, + const char* type) const + { + // Our SQL is non-mutating, so this cast is OK. + Statement statement(const_cast<Connection&>(*this), + "SELECT name FROM sqlite_master WHERE type=? AND name=?"); + statement.BindString(0, type); + statement.BindString(1, name); + return statement.Step(); // Table exists if any row was returned. + } + + bool Connection::DoesTableExist(const char* table_name) const + { + return DoesTableOrIndexExist(table_name, "table"); + } + + bool Connection::DoesIndexExist(const char* index_name) const + { + return DoesTableOrIndexExist(index_name, "index"); + } + + bool Connection::DoesColumnExist(const char* table_name, const char* column_name) const + { + std::string sql("PRAGMA TABLE_INFO("); + sql.append(table_name); + sql.append(")"); + + // Our SQL is non-mutating, so this cast is OK. + Statement statement(const_cast<Connection&>(*this), sql.c_str()); + + while (statement.Step()) { + if (!statement.ColumnString(1).compare(column_name)) + return true; + } + return false; + } + + int64_t Connection::GetLastInsertRowId() const + { + return sqlite3_last_insert_rowid(db_); + } + + int Connection::GetLastChangeCount() const + { + return sqlite3_changes(db_); + } + + int Connection::GetErrorCode() const + { + return sqlite3_errcode(db_); + } + + int Connection::GetLastErrno() const + { + int err = 0; + if (SQLITE_OK != sqlite3_file_control(db_, NULL, SQLITE_LAST_ERRNO, &err)) + return -2; + + return err; + } + + const char* Connection::GetErrorMessage() const + { + return sqlite3_errmsg(db_); + } + + + bool Connection::BeginTransaction() + { + if (needsRollback_) + { + assert(transactionNesting_ > 0); + + // When we're going to rollback, fail on this begin and don't actually + // mark us as entering the nested transaction. + return false; + } + + bool success = true; + if (!transactionNesting_) + { + needsRollback_ = false; + + Statement begin(*this, SQLITE_FROM_HERE, "BEGIN TRANSACTION"); + if (!begin.Run()) + return false; + } + transactionNesting_++; + return success; + } + + void Connection::RollbackTransaction() + { + if (!transactionNesting_) + { + throw OrthancSQLiteException("Rolling back a nonexistent transaction"); + } + + transactionNesting_--; + + if (transactionNesting_ > 0) + { + // Mark the outermost transaction as needing rollback. + needsRollback_ = true; + return; + } + + DoRollback(); + } + + bool Connection::CommitTransaction() + { + if (!transactionNesting_) + { + throw OrthancSQLiteException("Committing a nonexistent transaction"); + } + transactionNesting_--; + + if (transactionNesting_ > 0) + { + // Mark any nested transactions as failing after we've already got one. + return !needsRollback_; + } + + if (needsRollback_) + { + DoRollback(); + return false; + } + + Statement commit(*this, SQLITE_FROM_HERE, "COMMIT"); + return commit.Run(); + } + + void Connection::DoRollback() + { + Statement rollback(*this, SQLITE_FROM_HERE, "ROLLBACK"); + rollback.Run(); + needsRollback_ = false; + } + + + + + + + static void ScalarFunctionCaller(sqlite3_context* rawContext, + int argc, + sqlite3_value** argv) + { + FunctionContext context(rawContext, argc, argv); + + void* payload = sqlite3_user_data(rawContext); + assert(payload != NULL); + + IScalarFunction& func = *reinterpret_cast<IScalarFunction*>(payload); + func.Compute(context); + } + + + static void ScalarFunctionDestroyer(void* payload) + { + assert(payload != NULL); + delete reinterpret_cast<IScalarFunction*>(payload); + } + + + IScalarFunction* Connection::Register(IScalarFunction* func) + { + int err = sqlite3_create_function_v2(db_, + func->GetName(), + func->GetCardinality(), + SQLITE_UTF8, + func, + ScalarFunctionCaller, + NULL, + NULL, + ScalarFunctionDestroyer); + + if (err != SQLITE_OK) + { + delete func; + throw OrthancSQLiteException("SQLite: Unable to register a function"); + } + + return func; + } + + + void Connection::FlushToDisk() + { +#if ORTHANC_SQLITE_STANDALONE != 1 + VLOG(1) << "SQLite::Connection::FlushToDisk"; +#endif + + int err = sqlite3_wal_checkpoint(db_, NULL); + + if (err != SQLITE_OK) + { + throw OrthancSQLiteException("SQLite: Unable to flush the database"); + } + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Orthanc/SQLite/Connection.h Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,177 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * + * Copyright (C) 2012-2015 Sebastien Jodogne <s.jodogne@gmail.com>, + * Medical Physics Department, CHU of Liege, Belgium + * + * Copyright (c) 2012 The Chromium Authors. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc., the name of the CHU of Liege, + * nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **/ + + +#pragma once + +#include "Statement.h" +#include "IScalarFunction.h" + +#include <string> +#include <map> + +struct sqlite3; +struct sqlite3_stmt; + +#define SQLITE_FROM_HERE SQLite::StatementId(__FILE__, __LINE__) + +namespace Orthanc +{ + namespace SQLite + { + class Connection : NonCopyable + { + friend class Statement; + friend class Transaction; + + private: + // All cached statements. Keeping a reference to these statements means that + // they'll remain active. + typedef std::map<StatementId, StatementReference*> CachedStatements; + CachedStatements cachedStatements_; + + // The actual sqlite database. Will be NULL before Init has been called or if + // Init resulted in an error. + sqlite3* db_; + + // Number of currently-nested transactions. + int transactionNesting_; + + // True if any of the currently nested transactions have been rolled back. + // When we get to the outermost transaction, this will determine if we do + // a rollback instead of a commit. + bool needsRollback_; + + void ClearCache(); + + void CheckIsOpen() const; + + sqlite3* GetWrappedObject() + { + return db_; + } + + StatementReference& GetCachedStatement(const StatementId& id, + const char* sql); + + bool DoesTableOrIndexExist(const char* name, + const char* type) const; + + void DoRollback(); + + public: + // The database is opened by calling Open[InMemory](). Any uncommitted + // transactions will be rolled back when this object is deleted. + Connection(); + ~Connection(); + + void Open(const std::string& path); + + void OpenInMemory(); + + void Close(); + + bool Execute(const char* sql); + + bool Execute(const std::string& sql) + { + return Execute(sql.c_str()); + } + + void FlushToDisk(); + + IScalarFunction* Register(IScalarFunction* func); // Takes the ownership of the function + + // Info querying ------------------------------------------------------------- + + // Used to check a |sql| statement for syntactic validity. If the + // statement is valid SQL, returns true. + bool IsSQLValid(const char* sql); + + // Returns true if the given table exists. + bool DoesTableExist(const char* table_name) const; + + // Returns true if the given index exists. + bool DoesIndexExist(const char* index_name) const; + + // Returns true if a column with the given name exists in the given table. + bool DoesColumnExist(const char* table_name, const char* column_name) const; + + // Returns sqlite's internal ID for the last inserted row. Valid only + // immediately after an insert. + int64_t GetLastInsertRowId() const; + + // Returns sqlite's count of the number of rows modified by the last + // statement executed. Will be 0 if no statement has executed or the database + // is closed. + int GetLastChangeCount() const; + + // Errors -------------------------------------------------------------------- + + // Returns the error code associated with the last sqlite operation. + int GetErrorCode() const; + + // Returns the errno associated with GetErrorCode(). See + // SQLITE_LAST_ERRNO in SQLite documentation. + int GetLastErrno() const; + + // Returns a pointer to a statically allocated string associated with the + // last sqlite operation. + const char* GetErrorMessage() const; + + + // Diagnostics (for unit tests) ---------------------------------------------- + + int ExecuteAndReturnErrorCode(const char* sql); + + bool HasCachedStatement(const StatementId& id) const + { + return cachedStatements_.find(id) != cachedStatements_.end(); + } + + int GetTransactionNesting() const + { + return transactionNesting_; + } + + // Transactions -------------------------------------------------------------- + + bool BeginTransaction(); + void RollbackTransaction(); + bool CommitTransaction(); + }; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Orthanc/SQLite/FunctionContext.cpp Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,125 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * + * Copyright (C) 2012-2015 Sebastien Jodogne <s.jodogne@gmail.com>, + * Medical Physics Department, CHU of Liege, Belgium + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of the CHU of Liege, nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **/ + + +#if ORTHANC_SQLITE_STANDALONE != 1 +#include "../PrecompiledHeaders.h" +#endif + +#include "FunctionContext.h" +#include "OrthancSQLiteException.h" + +#include <sqlite3.h> + +namespace Orthanc +{ + namespace SQLite + { + FunctionContext::FunctionContext(struct sqlite3_context* context, + int argc, + struct ::Mem** argv) + { + assert(context != NULL); + assert(argc >= 0); + assert(argv != NULL); + + context_ = context; + argc_ = static_cast<unsigned int>(argc); + argv_ = argv; + } + + void FunctionContext::CheckIndex(unsigned int index) const + { + if (index >= argc_) + { + throw OrthancSQLiteException("Parameter out of range"); + } + } + + ColumnType FunctionContext::GetColumnType(unsigned int index) const + { + CheckIndex(index); + return static_cast<SQLite::ColumnType>(sqlite3_value_type(argv_[index])); + } + + int FunctionContext::GetIntValue(unsigned int index) const + { + CheckIndex(index); + return sqlite3_value_int(argv_[index]); + } + + int64_t FunctionContext::GetInt64Value(unsigned int index) const + { + CheckIndex(index); + return sqlite3_value_int64(argv_[index]); + } + + double FunctionContext::GetDoubleValue(unsigned int index) const + { + CheckIndex(index); + return sqlite3_value_double(argv_[index]); + } + + std::string FunctionContext::GetStringValue(unsigned int index) const + { + CheckIndex(index); + return std::string(reinterpret_cast<const char*>(sqlite3_value_text(argv_[index]))); + } + + bool FunctionContext::IsNullValue(unsigned int index) const + { + CheckIndex(index); + return sqlite3_value_type(argv_[index]) == SQLITE_NULL; + } + + void FunctionContext::SetNullResult() + { + sqlite3_result_null(context_); + } + + void FunctionContext::SetIntResult(int value) + { + sqlite3_result_int(context_, value); + } + + void FunctionContext::SetDoubleResult(double value) + { + sqlite3_result_double(context_, value); + } + + void FunctionContext::SetStringResult(const std::string& str) + { + sqlite3_result_text(context_, str.data(), str.size(), SQLITE_TRANSIENT); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Orthanc/SQLite/FunctionContext.h Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,88 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * + * Copyright (C) 2012-2015 Sebastien Jodogne <s.jodogne@gmail.com>, + * Medical Physics Department, CHU of Liege, Belgium + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of the CHU of Liege, nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **/ + + +#pragma once + +#include "Statement.h" + +struct sqlite3_context; +struct Mem; // This corresponds to the opaque type "sqlite3_value" + +namespace Orthanc +{ + namespace SQLite + { + class FunctionContext : public NonCopyable + { + friend class Connection; + + private: + struct sqlite3_context* context_; + unsigned int argc_; + struct ::Mem** argv_; + + void CheckIndex(unsigned int index) const; + + public: + FunctionContext(struct sqlite3_context* context, + int argc, + struct ::Mem** argv); + + ColumnType GetColumnType(unsigned int index) const; + + unsigned int GetParameterCount() const + { + return argc_; + } + + int GetIntValue(unsigned int index) const; + + int64_t GetInt64Value(unsigned int index) const; + + double GetDoubleValue(unsigned int index) const; + + std::string GetStringValue(unsigned int index) const; + + bool IsNullValue(unsigned int index) const; + + void SetNullResult(); + + void SetIntResult(int value); + + void SetDoubleResult(double value); + + void SetStringResult(const std::string& str); + }; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Orthanc/SQLite/IScalarFunction.h Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,58 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * + * Copyright (C) 2012-2015 Sebastien Jodogne <s.jodogne@gmail.com>, + * Medical Physics Department, CHU of Liege, Belgium + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of the CHU of Liege, nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **/ + + +#pragma once + +#include "NonCopyable.h" +#include "FunctionContext.h" + +namespace Orthanc +{ + namespace SQLite + { + class IScalarFunction : public NonCopyable + { + public: + virtual ~IScalarFunction() + { + } + + virtual const char* GetName() const = 0; + + virtual unsigned int GetCardinality() const = 0; + + virtual void Compute(FunctionContext& context) = 0; + }; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Orthanc/SQLite/ITransaction.h Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,66 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * + * Copyright (C) 2012-2015 Sebastien Jodogne <s.jodogne@gmail.com>, + * Medical Physics Department, CHU of Liege, Belgium + * + * Copyright (c) 2012 The Chromium Authors. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc., the name of the CHU of Liege, + * nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **/ + + +#pragma once + +#include "NonCopyable.h" + +namespace Orthanc +{ + namespace SQLite + { + class ITransaction : public NonCopyable + { + public: + virtual ~ITransaction() + { + } + + // Begins the transaction. This uses the default sqlite "deferred" transaction + // type, which means that the DB lock is lazily acquired the next time the + // database is accessed, not in the begin transaction command. + virtual void Begin() = 0; + + // Rolls back the transaction. This will happen automatically if you do + // nothing when the transaction goes out of scope. + virtual void Rollback() = 0; + + // Commits the transaction, returning true on success. + virtual void Commit() = 0; + }; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Orthanc/SQLite/NonCopyable.h Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,60 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * + * Copyright (C) 2012-2015 Sebastien Jodogne <s.jodogne@gmail.com>, + * Medical Physics Department, CHU of Liege, Belgium + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc., the name of the CHU of Liege, + * nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **/ + + +#pragma once + +namespace Orthanc +{ + namespace SQLite + { + // This class mimics "boost::noncopyable" + class NonCopyable + { + private: + NonCopyable(const NonCopyable&); + + NonCopyable& operator= (const NonCopyable&); + + protected: + NonCopyable() + { + } + + ~NonCopyable() + { + } + }; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Orthanc/SQLite/OrthancSQLiteException.h Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,67 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * + * Copyright (C) 2012-2015 Sebastien Jodogne <s.jodogne@gmail.com>, + * Medical Physics Department, CHU of Liege, Belgium + * + * Copyright (c) 2012 The Chromium Authors. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc., the name of the CHU of Liege, + * nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **/ + + +#pragma once + + +#if ORTHANC_SQLITE_STANDALONE == 1 +#include <stdexcept> + +namespace Orthanc +{ + namespace SQLite + { + class OrthancSQLiteException : public ::std::runtime_error + { + public: + OrthancSQLiteException(const std::string& what) : + ::std::runtime_error(what) + { + } + + OrthancSQLiteException(const char* what) : + ::std::runtime_error(what) + { + } + }; + } +} + +#else +# include "../OrthancException.h" +# define OrthancSQLiteException ::Orthanc::OrthancException +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Orthanc/SQLite/README.txt Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,40 @@ +Introduction +============ + +The code in this folder is a standalone object-oriented wrapper around +SQLite3. It is derived from the code of Chromium: + +http://src.chromium.org/viewvc/chrome/trunk/src/sql/ +http://maxradi.us/documents/sqlite/ + + +Main differences with Chromium +============================== + +* The reference counting mechanism has been reimplemented to make it + simpler. +* The OrthancException class is used for the exception mechanisms. +* A statement is always valid (is_valid() always return true). +* The classes and the methods have been renamed to meet Orthanc's + coding conventions. + + +Reuse in another software +========================= + +To use the Orthanc SQLite wrapper in another project than Orthanc, you +just have to define the "ORTHANC_SQLITE_STANDALONE" macro. + +All the C++ exceptions generated by the wrapper will be objects of the +class "::Orthanc::SQLite::OrthancSQLiteException", that derives from +the standard exception class "::std::runtime_error". + + +Licensing +========= + +The code in this folder is licensed under the 3-clause BSD license, in +order to respect the original license of the code. + +It is pretty straightforward to extract the code from this folder and +to include it in another project.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Orthanc/SQLite/Statement.cpp Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,340 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * + * Copyright (C) 2012-2015 Sebastien Jodogne <s.jodogne@gmail.com>, + * Medical Physics Department, CHU of Liege, Belgium + * + * Copyright (c) 2012 The Chromium Authors. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc., the name of the CHU of Liege, + * nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **/ + + +#if ORTHANC_SQLITE_STANDALONE != 1 +#include "../PrecompiledHeaders.h" +#endif + +#include "Statement.h" +#include "Connection.h" + +#include <sqlite3.h> +#include <string.h> +#include <stdio.h> +#include <algorithm> + +#if ORTHANC_SQLITE_STANDALONE != 1 +#include <glog/logging.h> +#endif + +#if defined(_MSC_VER) +#define snprintf _snprintf +#endif + +namespace Orthanc +{ + namespace SQLite + { + int Statement::CheckError(int err) const + { + bool succeeded = (err == SQLITE_OK || err == SQLITE_ROW || err == SQLITE_DONE); + if (!succeeded) + { + char buffer[128]; + snprintf(buffer, sizeof(buffer) - 1, "SQLite error code %d", err); + throw OrthancSQLiteException(buffer); + } + + return err; + } + + void Statement::CheckOk(int err) const + { + if (err == SQLITE_RANGE) + { + // Binding to a non-existent variable is evidence of a serious error. + throw OrthancSQLiteException("Bind value out of range"); + } + else if (err != SQLITE_OK) + { + char buffer[128]; + snprintf(buffer, sizeof(buffer) - 1, "SQLite error code %d", err); + throw OrthancSQLiteException(buffer); + } + } + + + Statement::Statement(Connection& database, + const StatementId& id, + const std::string& sql) : + reference_(database.GetCachedStatement(id, sql.c_str())) + { + Reset(true); + } + + + Statement::Statement(Connection& database, + const StatementId& id, + const char* sql) : + reference_(database.GetCachedStatement(id, sql)) + { + Reset(true); + } + + + Statement::Statement(Connection& database, + const std::string& sql) : + reference_(database.GetWrappedObject(), sql.c_str()) + { + } + + + Statement::Statement(Connection& database, + const char* sql) : + reference_(database.GetWrappedObject(), sql) + { + } + + + bool Statement::Run() + { +#if ORTHANC_SQLITE_STANDALONE != 1 + VLOG(1) << "SQLite::Statement::Run " << sqlite3_sql(GetStatement()); +#endif + + return CheckError(sqlite3_step(GetStatement())) == SQLITE_DONE; + } + + bool Statement::Step() + { +#if ORTHANC_SQLITE_STANDALONE != 1 + VLOG(1) << "SQLite::Statement::Step " << sqlite3_sql(GetStatement()); +#endif + + return CheckError(sqlite3_step(GetStatement())) == SQLITE_ROW; + } + + void Statement::Reset(bool clear_bound_vars) + { + // We don't call CheckError() here because sqlite3_reset() returns + // the last error that Step() caused thereby generating a second + // spurious error callback. + if (clear_bound_vars) + sqlite3_clear_bindings(GetStatement()); + //VLOG(1) << "SQLite::Statement::Reset"; + sqlite3_reset(GetStatement()); + } + + std::string Statement::GetOriginalSQLStatement() + { + return std::string(sqlite3_sql(GetStatement())); + } + + + void Statement::BindNull(int col) + { + CheckOk(sqlite3_bind_null(GetStatement(), col + 1)); + } + + void Statement::BindBool(int col, bool val) + { + BindInt(col, val ? 1 : 0); + } + + void Statement::BindInt(int col, int val) + { + CheckOk(sqlite3_bind_int(GetStatement(), col + 1, val)); + } + + void Statement::BindInt64(int col, int64_t val) + { + CheckOk(sqlite3_bind_int64(GetStatement(), col + 1, val)); + } + + void Statement::BindDouble(int col, double val) + { + CheckOk(sqlite3_bind_double(GetStatement(), col + 1, val)); + } + + void Statement::BindCString(int col, const char* val) + { + CheckOk(sqlite3_bind_text(GetStatement(), col + 1, val, -1, SQLITE_TRANSIENT)); + } + + void Statement::BindString(int col, const std::string& val) + { + CheckOk(sqlite3_bind_text(GetStatement(), + col + 1, + val.data(), + val.size(), + SQLITE_TRANSIENT)); + } + + /*void Statement::BindString16(int col, const string16& value) + { + BindString(col, UTF16ToUTF8(value)); + }*/ + + void Statement::BindBlob(int col, const void* val, int val_len) + { + CheckOk(sqlite3_bind_blob(GetStatement(), col + 1, val, val_len, SQLITE_TRANSIENT)); + } + + + int Statement::ColumnCount() const + { + return sqlite3_column_count(GetStatement()); + } + + + ColumnType Statement::GetColumnType(int col) const + { + // Verify that our enum matches sqlite's values. + assert(COLUMN_TYPE_INTEGER == SQLITE_INTEGER); + assert(COLUMN_TYPE_FLOAT == SQLITE_FLOAT); + assert(COLUMN_TYPE_TEXT == SQLITE_TEXT); + assert(COLUMN_TYPE_BLOB == SQLITE_BLOB); + assert(COLUMN_TYPE_NULL == SQLITE_NULL); + + return static_cast<ColumnType>(sqlite3_column_type(GetStatement(), col)); + } + + ColumnType Statement::GetDeclaredColumnType(int col) const + { + std::string column_type(sqlite3_column_decltype(GetStatement(), col)); + std::transform(column_type.begin(), column_type.end(), column_type.begin(), tolower); + + if (column_type == "integer") + return COLUMN_TYPE_INTEGER; + else if (column_type == "float") + return COLUMN_TYPE_FLOAT; + else if (column_type == "text") + return COLUMN_TYPE_TEXT; + else if (column_type == "blob") + return COLUMN_TYPE_BLOB; + + return COLUMN_TYPE_NULL; + } + + bool Statement::ColumnIsNull(int col) const + { + return sqlite3_column_type(GetStatement(), col) == SQLITE_NULL; + } + + bool Statement::ColumnBool(int col) const + { + return !!ColumnInt(col); + } + + int Statement::ColumnInt(int col) const + { + return sqlite3_column_int(GetStatement(), col); + } + + int64_t Statement::ColumnInt64(int col) const + { + return sqlite3_column_int64(GetStatement(), col); + } + + double Statement::ColumnDouble(int col) const + { + return sqlite3_column_double(GetStatement(), col); + } + + std::string Statement::ColumnString(int col) const + { + const char* str = reinterpret_cast<const char*>( + sqlite3_column_text(GetStatement(), col)); + int len = sqlite3_column_bytes(GetStatement(), col); + + std::string result; + if (str && len > 0) + result.assign(str, len); + return result; + } + + /*string16 Statement::ColumnString16(int col) const + { + std::string s = ColumnString(col); + return !s.empty() ? UTF8ToUTF16(s) : string16(); + }*/ + + int Statement::ColumnByteLength(int col) const + { + return sqlite3_column_bytes(GetStatement(), col); + } + + const void* Statement::ColumnBlob(int col) const + { + return sqlite3_column_blob(GetStatement(), col); + } + + bool Statement::ColumnBlobAsString(int col, std::string* blob) + { + const void* p = ColumnBlob(col); + size_t len = ColumnByteLength(col); + blob->resize(len); + if (blob->size() != len) { + return false; + } + blob->assign(reinterpret_cast<const char*>(p), len); + return true; + } + + /*bool Statement::ColumnBlobAsString16(int col, string16* val) const + { + const void* data = ColumnBlob(col); + size_t len = ColumnByteLength(col) / sizeof(char16); + val->resize(len); + if (val->size() != len) + return false; + val->assign(reinterpret_cast<const char16*>(data), len); + return true; + }*/ + + /*bool Statement::ColumnBlobAsVector(int col, std::vector<char>* val) const + { + val->clear(); + + const void* data = sqlite3_column_blob(GetStatement(), col); + int len = sqlite3_column_bytes(GetStatement(), col); + if (data && len > 0) { + val->resize(len); + memcpy(&(*val)[0], data, len); + } + return true; + }*/ + + /*bool Statement::ColumnBlobAsVector( + int col, + std::vector<unsigned char>* val) const + { + return ColumnBlobAsVector(col, reinterpret_cast< std::vector<char>* >(val)); + }*/ + + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Orthanc/SQLite/Statement.h Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,174 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * + * Copyright (C) 2012-2015 Sebastien Jodogne <s.jodogne@gmail.com>, + * Medical Physics Department, CHU of Liege, Belgium + * + * Copyright (c) 2012 The Chromium Authors. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc., the name of the CHU of Liege, + * nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **/ + + +#pragma once + +#include "NonCopyable.h" +#include "OrthancSQLiteException.h" +#include "StatementId.h" +#include "StatementReference.h" + +#include <vector> +#include <stdint.h> + +#if ORTHANC_BUILD_UNIT_TESTS == 1 +#include <gtest/gtest_prod.h> +#endif + +struct sqlite3_stmt; + + +namespace Orthanc +{ + namespace SQLite + { + class Connection; + + // Possible return values from ColumnType in a statement. These + // should match the values in sqlite3.h. + enum ColumnType + { + COLUMN_TYPE_INTEGER = 1, + COLUMN_TYPE_FLOAT = 2, + COLUMN_TYPE_TEXT = 3, + COLUMN_TYPE_BLOB = 4, + COLUMN_TYPE_NULL = 5 + }; + + class Statement : public NonCopyable + { + friend class Connection; + +#if ORTHANC_BUILD_UNIT_TESTS == 1 + FRIEND_TEST(SQLStatementTest, Run); + FRIEND_TEST(SQLStatementTest, Reset); +#endif + + private: + StatementReference reference_; + + int CheckError(int err) const; + + void CheckOk(int err) const; + + struct sqlite3_stmt* GetStatement() const + { + return reference_.GetWrappedObject(); + } + + public: + Statement(Connection& database, + const std::string& sql); + + Statement(Connection& database, + const StatementId& id, + const std::string& sql); + + Statement(Connection& database, + const char* sql); + + Statement(Connection& database, + const StatementId& id, + const char* sql); + + ~Statement() + { + Reset(); + } + + bool Run(); + + bool Step(); + + // Diagnostics -------------------------------------------------------------- + + std::string GetOriginalSQLStatement(); + + + // Binding ------------------------------------------------------------------- + + // These all take a 0-based argument index + void BindNull(int col); + void BindBool(int col, bool val); + void BindInt(int col, int val); + void BindInt64(int col, int64_t val); + void BindDouble(int col, double val); + void BindCString(int col, const char* val); + void BindString(int col, const std::string& val); + //void BindString16(int col, const string16& value); + void BindBlob(int col, const void* value, int value_len); + + + // Retrieving ---------------------------------------------------------------- + + // Returns the number of output columns in the result. + int ColumnCount() const; + + // Returns the type associated with the given column. + // + // Watch out: the type may be undefined if you've done something to cause a + // "type conversion." This means requesting the value of a column of a type + // where that type is not the native type. For safety, call ColumnType only + // on a column before getting the value out in any way. + ColumnType GetColumnType(int col) const; + ColumnType GetDeclaredColumnType(int col) const; + + // These all take a 0-based argument index. + bool ColumnIsNull(int col) const ; + bool ColumnBool(int col) const; + int ColumnInt(int col) const; + int64_t ColumnInt64(int col) const; + double ColumnDouble(int col) const; + std::string ColumnString(int col) const; + //string16 ColumnString16(int col) const; + + // When reading a blob, you can get a raw pointer to the underlying data, + // along with the length, or you can just ask us to copy the blob into a + // vector. Danger! ColumnBlob may return NULL if there is no data! + int ColumnByteLength(int col) const; + const void* ColumnBlob(int col) const; + bool ColumnBlobAsString(int col, std::string* blob); + //bool ColumnBlobAsString16(int col, string16* val) const; + //bool ColumnBlobAsVector(int col, std::vector<char>* val) const; + //bool ColumnBlobAsVector(int col, std::vector<unsigned char>* val) const; + + // Resets the statement to its initial condition. This includes any current + // result row, and also the bound variables if the |clear_bound_vars| is true. + void Reset(bool clear_bound_vars = true); + }; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Orthanc/SQLite/StatementId.cpp Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,58 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * + * Copyright (C) 2012-2015 Sebastien Jodogne <s.jodogne@gmail.com>, + * Medical Physics Department, CHU of Liege, Belgium + * + * Copyright (c) 2012 The Chromium Authors. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc., the name of the CHU of Liege, + * nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **/ + + +#if ORTHANC_SQLITE_STANDALONE != 1 +#include "../PrecompiledHeaders.h" +#endif + +#include "StatementId.h" + +#include <string.h> + +namespace Orthanc +{ + namespace SQLite + { + bool StatementId::operator< (const StatementId& other) const + { + if (line_ != other.line_) + return line_ < other.line_; + + return strcmp(file_, other.file_) < 0; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Orthanc/SQLite/StatementId.h Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,60 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * + * Copyright (C) 2012-2015 Sebastien Jodogne <s.jodogne@gmail.com>, + * Medical Physics Department, CHU of Liege, Belgium + * + * Copyright (c) 2012 The Chromium Authors. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc., the name of the CHU of Liege, + * nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **/ + + +#pragma once + +namespace Orthanc +{ + namespace SQLite + { + class StatementId + { + private: + const char* file_; + int line_; + + StatementId(); // Forbidden + + public: + StatementId(const char* file, int line) : file_(file), line_(line) + { + } + + bool operator< (const StatementId& other) const; + }; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Orthanc/SQLite/StatementReference.cpp Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,151 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * + * Copyright (C) 2012-2015 Sebastien Jodogne <s.jodogne@gmail.com>, + * Medical Physics Department, CHU of Liege, Belgium + * + * Copyright (c) 2012 The Chromium Authors. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc., the name of the CHU of Liege, + * nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **/ + + +#if ORTHANC_SQLITE_STANDALONE != 1 +#include "../PrecompiledHeaders.h" +#endif + +#include "StatementReference.h" +#include "OrthancSQLiteException.h" + +#if ORTHANC_SQLITE_STANDALONE != 1 +#include <glog/logging.h> +#endif + +#include <cassert> +#include "sqlite3.h" + +namespace Orthanc +{ + namespace SQLite + { + bool StatementReference::IsRoot() const + { + return root_ == NULL; + } + + StatementReference::StatementReference() + { + root_ = NULL; + refCount_ = 0; + statement_ = NULL; + assert(IsRoot()); + } + + StatementReference::StatementReference(sqlite3* database, + const char* sql) + { + if (database == NULL || sql == NULL) + { + throw OrthancSQLiteException("Parameter out of range"); + } + + root_ = NULL; + refCount_ = 0; + + int error = sqlite3_prepare_v2(database, sql, -1, &statement_, NULL); + if (error != SQLITE_OK) + { + throw OrthancSQLiteException("SQLite: " + std::string(sqlite3_errmsg(database))); + } + + assert(IsRoot()); + } + + StatementReference::StatementReference(StatementReference& other) + { + refCount_ = 0; + + if (other.IsRoot()) + { + root_ = &other; + } + else + { + root_ = other.root_; + } + + root_->refCount_++; + statement_ = root_->statement_; + + assert(!IsRoot()); + } + + StatementReference::~StatementReference() + { + if (IsRoot()) + { + if (refCount_ != 0) + { + // There remain references to this object. We cannot throw + // an exception because: + // http://www.parashift.com/c++-faq/dtors-shouldnt-throw.html + +#if ORTHANC_SQLITE_STANDALONE != 1 + LOG(ERROR) << "Bad value of the reference counter"; +#endif + } + else if (statement_ != NULL) + { + sqlite3_finalize(statement_); + } + } + else + { + if (root_->refCount_ == 0) + { + // There remain references to this object. We cannot throw + // an exception because: + // http://www.parashift.com/c++-faq/dtors-shouldnt-throw.html + +#if ORTHANC_SQLITE_STANDALONE != 1 + LOG(ERROR) << "Bad value of the reference counter"; +#endif + } + else + { + root_->refCount_--; + } + } + } + + uint32_t StatementReference::GetReferenceCount() const + { + return refCount_; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Orthanc/SQLite/StatementReference.h Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,81 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * + * Copyright (C) 2012-2015 Sebastien Jodogne <s.jodogne@gmail.com>, + * Medical Physics Department, CHU of Liege, Belgium + * + * Copyright (c) 2012 The Chromium Authors. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc., the name of the CHU of Liege, + * nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **/ + + +#pragma once + +#include "NonCopyable.h" + +#include <stdint.h> +#include <cassert> +#include <stdlib.h> + +struct sqlite3; +struct sqlite3_stmt; + +namespace Orthanc +{ + namespace SQLite + { + class StatementReference : NonCopyable + { + private: + StatementReference* root_; // Only used for non-root nodes + uint32_t refCount_; // Only used for root node + struct sqlite3_stmt* statement_; + + bool IsRoot() const; + + public: + StatementReference(); + + StatementReference(sqlite3* database, + const char* sql); + + StatementReference(StatementReference& other); + + ~StatementReference(); + + uint32_t GetReferenceCount() const; + + struct sqlite3_stmt* GetWrappedObject() const + { + assert(statement_ != NULL); + return statement_; + } + }; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Orthanc/SQLite/Transaction.cpp Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,106 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * + * Copyright (C) 2012-2015 Sebastien Jodogne <s.jodogne@gmail.com>, + * Medical Physics Department, CHU of Liege, Belgium + * + * Copyright (c) 2012 The Chromium Authors. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc., the name of the CHU of Liege, + * nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **/ + + +#if ORTHANC_SQLITE_STANDALONE != 1 +#include "../PrecompiledHeaders.h" +#endif + +#include "Transaction.h" +#include "OrthancSQLiteException.h" + +namespace Orthanc +{ + namespace SQLite + { + Transaction::Transaction(Connection& connection) : + connection_(connection), + isOpen_(false) + { + } + + Transaction::~Transaction() + { + if (isOpen_) + { + connection_.RollbackTransaction(); + } + } + + void Transaction::Begin() + { + if (isOpen_) + { + throw OrthancSQLiteException("SQLite: Beginning a transaction twice!"); + } + + isOpen_ = connection_.BeginTransaction(); + if (!isOpen_) + { + throw OrthancSQLiteException("SQLite: Unable to create a transaction"); + } + } + + void Transaction::Rollback() + { + if (!isOpen_) + { + throw OrthancSQLiteException("SQLite: Attempting to roll back a nonexistent transaction. " + "Did you remember to call Begin()?"); + } + + isOpen_ = false; + + connection_.RollbackTransaction(); + } + + void Transaction::Commit() + { + if (!isOpen_) + { + throw OrthancSQLiteException("SQLite: Attempting to roll back a nonexistent transaction. " + "Did you remember to call Begin()?"); + } + + isOpen_ = false; + + if (!connection_.CommitTransaction()) + { + throw OrthancSQLiteException("SQLite: Failure when committing the transaction"); + } + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Orthanc/SQLite/Transaction.h Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,71 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * + * Copyright (C) 2012-2015 Sebastien Jodogne <s.jodogne@gmail.com>, + * Medical Physics Department, CHU of Liege, Belgium + * + * Copyright (c) 2012 The Chromium Authors. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc., the name of the CHU of Liege, + * nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **/ + + +#pragma once + +#include "Connection.h" +#include "ITransaction.h" + +namespace Orthanc +{ + namespace SQLite + { + class Transaction : public ITransaction + { + private: + Connection& connection_; + + // True when the transaction is open, false when it's already been committed + // or rolled back. + bool isOpen_; + + public: + explicit Transaction(Connection& connection); + + virtual ~Transaction(); + + // Returns true when there is a transaction that has been successfully begun. + bool IsOpen() const { return isOpen_; } + + virtual void Begin(); + + virtual void Rollback(); + + virtual void Commit(); + }; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Orthanc/Toolbox.cpp Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,246 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "PrecompiledHeaders.h" +#include "Toolbox.h" + +#include "OrthancException.h" + +#include <stdint.h> +#include <string.h> +#include <boost/filesystem.hpp> +#include <boost/filesystem/fstream.hpp> +#include <algorithm> +#include <ctype.h> + +#if defined(_WIN32) +#include <windows.h> +#include <process.h> // For "_spawnvp()" +#else +#include <unistd.h> // For "execvp()" +#include <sys/wait.h> // For "waitpid()" +#endif + +#if defined(__APPLE__) && defined(__MACH__) +#include <mach-o/dyld.h> /* _NSGetExecutablePath */ +#include <limits.h> /* PATH_MAX */ +#endif + +#if defined(__linux) || defined(__FreeBSD_kernel__) +#include <limits.h> /* PATH_MAX */ +#include <signal.h> +#include <unistd.h> +#endif + +#if BOOST_HAS_LOCALE != 1 +#error Since version 0.7.6, Orthanc entirely relies on boost::locale +#endif + +#include <boost/locale.hpp> + + + +namespace Orthanc +{ + void Toolbox::TokenizeString(std::vector<std::string>& result, + const std::string& value, + char separator) + { + result.clear(); + + std::string currentItem; + + for (size_t i = 0; i < value.size(); i++) + { + if (value[i] == separator) + { + result.push_back(currentItem); + currentItem.clear(); + } + else + { + currentItem.push_back(value[i]); + } + } + + result.push_back(currentItem); + } + + + void Toolbox::CreateNewDirectory(const std::string& path) + { + if (boost::filesystem::exists(path)) + { + if (!boost::filesystem::is_directory(path)) + { + throw OrthancException("Cannot create the directory over an existing file: " + path); + } + } + else + { + if (!boost::filesystem::create_directories(path)) + { + throw OrthancException("Unable to create the directory: " + path); + } + } + } + + + bool Toolbox::IsExistingFile(const std::string& path) + { + return boost::filesystem::exists(path); + } + + + void Toolbox::ReadFile(std::string& content, + const std::string& path) + { + boost::filesystem::ifstream f; + f.open(path, std::ifstream::in | std::ifstream::binary); + if (!f.good()) + { + throw OrthancException(ErrorCode_InexistentFile); + } + + // http://www.cplusplus.com/reference/iostream/istream/tellg/ + f.seekg(0, std::ios::end); + std::streamsize size = f.tellg(); + f.seekg(0, std::ios::beg); + + content.resize(size); + if (size != 0) + { + f.read(reinterpret_cast<char*>(&content[0]), size); + } + + f.close(); + } + + + void Toolbox::WriteFile(const std::string& content, + const std::string& path) + { + boost::filesystem::ofstream f; + f.open(path, std::ofstream::binary); + if (!f.good()) + { + throw OrthancException(ErrorCode_CannotWriteFile); + } + + if (content.size() != 0) + { + f.write(content.c_str(), content.size()); + } + + f.close(); + } + + + + void Toolbox::RemoveFile(const std::string& path) + { + if (boost::filesystem::exists(path)) + { + if (boost::filesystem::is_regular_file(path)) + boost::filesystem::remove(path); + else + throw OrthancException("The path is not a regular file: " + path); + } + } + + + + void Toolbox::USleep(uint64_t microSeconds) + { +#if defined(_WIN32) + ::Sleep(static_cast<DWORD>(microSeconds / static_cast<uint64_t>(1000))); +#elif defined(__linux) || defined(__APPLE__) || defined(__FreeBSD_kernel__) + usleep(microSeconds); +#else +#error Support your platform here +#endif + } + + + Endianness Toolbox::DetectEndianness() + { + // http://sourceforge.net/p/predef/wiki/Endianness/ + + uint8_t buffer[4]; + + buffer[0] = 0x00; + buffer[1] = 0x01; + buffer[2] = 0x02; + buffer[3] = 0x03; + + switch (*((uint32_t *)buffer)) + { + case 0x00010203: + return Endianness_Big; + + case 0x03020100: + return Endianness_Little; + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + } + + + std::string Toolbox::StripSpaces(const std::string& source) + { + size_t first = 0; + + while (first < source.length() && + isspace(source[first])) + { + first++; + } + + if (first == source.length()) + { + // String containing only spaces + return ""; + } + + size_t last = source.length(); + while (last > first && + isspace(source[last - 1])) + { + last--; + } + + assert(first <= last); + return source.substr(first, last - first); + } +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Orthanc/Toolbox.h Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,70 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "Enumerations.h" + +#include <stdint.h> +#include <vector> +#include <string> +#include <json/json.h> + +namespace Orthanc +{ + typedef std::vector<std::string> UriComponents; + + namespace Toolbox + { + Endianness DetectEndianness(); + + void TokenizeString(std::vector<std::string>& result, + const std::string& source, + char separator); + + void CreateNewDirectory(const std::string& path); + + bool IsExistingFile(const std::string& path); + + void ReadFile(std::string& content, + const std::string& path); + + void WriteFile(const std::string& content, + const std::string& path); + + void USleep(uint64_t microSeconds); + + void RemoveFile(const std::string& path); + + std::string StripSpaces(const std::string& source); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Orthanc/Uuid.cpp Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,162 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "PrecompiledHeaders.h" +#include "Uuid.h" + +// http://stackoverflow.com/a/1626302 + +extern "C" +{ +#ifdef WIN32 +#include <rpc.h> +#else +#include <uuid/uuid.h> +#endif +} + +#include <boost/filesystem.hpp> + +namespace Orthanc +{ + namespace Toolbox + { + std::string GenerateUuid() + { +#ifdef WIN32 + UUID uuid; + UuidCreate ( &uuid ); + + unsigned char * str; + UuidToStringA ( &uuid, &str ); + + std::string s( ( char* ) str ); + + RpcStringFreeA ( &str ); +#else + uuid_t uuid; + uuid_generate_random ( uuid ); + char s[37]; + uuid_unparse ( uuid, s ); +#endif + return s; + } + + + bool IsUuid(const std::string& str) + { + if (str.size() != 36) + { + return false; + } + + for (size_t i = 0; i < str.length(); i++) + { + if (i == 8 || i == 13 || i == 18 || i == 23) + { + if (str[i] != '-') + return false; + } + else + { + if (!isalnum(str[i])) + return false; + } + } + + return true; + } + + + bool StartsWithUuid(const std::string& str) + { + if (str.size() < 36) + { + return false; + } + + if (str.size() == 36) + { + return IsUuid(str); + } + + assert(str.size() > 36); + if (!isspace(str[36])) + { + return false; + } + + return IsUuid(str.substr(0, 36)); + } + + + static std::string CreateTemporaryPath(const char* extension) + { +#if BOOST_HAS_FILESYSTEM_V3 == 1 + boost::filesystem::path tmpDir = boost::filesystem::temp_directory_path(); +#elif defined(__linux__) + boost::filesystem::path tmpDir("/tmp"); +#else +#error Support your platform here +#endif + + // We use UUID to create unique path to temporary files + std::string filename = "Orthanc-" + Orthanc::Toolbox::GenerateUuid(); + + if (extension != NULL) + { + filename.append(extension); + } + + tmpDir /= filename; + return tmpDir.string(); + } + + + TemporaryFile::TemporaryFile() : + path_(CreateTemporaryPath(NULL)) + { + } + + + TemporaryFile::TemporaryFile(const char* extension) : + path_(CreateTemporaryPath(extension)) + { + } + + + TemporaryFile::~TemporaryFile() + { + boost::filesystem::remove(path_); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Orthanc/Uuid.h Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,86 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include <string> + +/** + * GUID vs. UUID + * The simple answer is: no difference, they are the same thing. Treat + * them as a 16 byte (128 bits) value that is used as a unique + * value. In Microsoft-speak they are called GUIDs, but call them + * UUIDs when not using Microsoft-speak. + * http://stackoverflow.com/questions/246930/is-there-any-difference-between-a-guid-and-a-uuid + **/ + +#include "Toolbox.h" + +namespace Orthanc +{ + namespace Toolbox + { + std::string GenerateUuid(); + + bool IsUuid(const std::string& str); + + bool StartsWithUuid(const std::string& str); + + class TemporaryFile + { + private: + std::string path_; + + public: + TemporaryFile(); + + TemporaryFile(const char* extension); + + ~TemporaryFile(); + + const std::string& GetPath() const + { + return path_; + } + + void Write(const std::string& content) + { + Toolbox::WriteFile(content, path_); + } + + void Read(std::string& content) const + { + Toolbox::ReadFile(content, path_); + } + }; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugin/Cache/CacheIndex.h Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,65 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../../Orthanc/IDynamicObject.h" + +#include <string> + +namespace OrthancPlugins +{ + class CacheIndex : public Orthanc::IDynamicObject + { + private: + int bundle_; + std::string item_; + + public: + CacheIndex(const CacheIndex& other) : + bundle_(other.bundle_), + item_(other.item_) + { + } + + CacheIndex(int bundle, + const std::string& item) : + bundle_(bundle), + item_(item) + { + } + + int GetBundle() const + { + return bundle_; + } + + const std::string& GetItem() const + { + return item_; + } + + bool operator== (const CacheIndex& other) const + { + return (bundle_ == other.bundle_ && + item_ == other.item_); + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugin/Cache/CacheManager.cpp Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,592 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "CacheManager.h" + +#include "../../Orthanc/Uuid.h" +#include "../../Orthanc/SQLite/Transaction.h" + +#include <boost/lexical_cast.hpp> + + +namespace OrthancPlugins +{ + class CacheManager::Bundle + { + private: + uint32_t count_; + uint64_t space_; + + public: + Bundle() : count_(0), space_(0) + { + } + + Bundle(uint32_t count, + uint64_t space) : + count_(count), space_(space) + { + } + + uint32_t GetCount() const + { + return count_; + } + + uint64_t GetSpace() const + { + return space_; + } + + void Remove(uint64_t fileSize) + { + if (count_ == 0 || + space_ < fileSize) + { + throw std::runtime_error("Internal error"); + } + + count_ -= 1; + space_ -= fileSize; + } + + void Add(uint64_t fileSize) + { + count_ += 1; + space_ += fileSize; + } + }; + + + class CacheManager::BundleQuota + { + private: + uint32_t maxCount_; + uint64_t maxSpace_; + + public: + BundleQuota(uint32_t maxCount, + uint64_t maxSpace) : + maxCount_(maxCount), maxSpace_(maxSpace) + { + } + + BundleQuota() + { + // Default quota + maxCount_ = 0; // No limit on the number of files + maxSpace_ = 100 * 1024 * 1024; // Max 100MB per bundle + } + + uint32_t GetMaxCount() const + { + return maxCount_; + } + + uint64_t GetMaxSpace() const + { + return maxSpace_; + } + + bool IsSatisfied(const Bundle& bundle) const + { + if (maxCount_ != 0 && + bundle.GetCount() > maxCount_) + { + return false; + } + + if (maxSpace_ != 0 && + bundle.GetSpace() > maxSpace_) + { + return false; + } + + return true; + } + }; + + + struct CacheManager::PImpl + { + Orthanc::SQLite::Connection& db_; + Orthanc::FilesystemStorage& storage_; + + bool sanityCheck_; + Bundles bundles_; + BundleQuota defaultQuota_; + BundleQuotas quotas_; + + PImpl(Orthanc::SQLite::Connection& db, + Orthanc::FilesystemStorage& storage) : + db_(db), + storage_(storage), + sanityCheck_(false) + { + } + }; + + + const CacheManager::BundleQuota& CacheManager::GetBundleQuota(int bundleIndex) const + { + BundleQuotas::const_iterator found = pimpl_->quotas_.find(bundleIndex); + + if (found == pimpl_->quotas_.end()) + { + return pimpl_->defaultQuota_; + } + else + { + return found->second; + } + } + + + CacheManager::Bundle CacheManager::GetBundle(int bundleIndex) const + { + Bundles::const_iterator it = pimpl_->bundles_.find(bundleIndex); + + if (it == pimpl_->bundles_.end()) + { + return Bundle(); + } + else + { + return it->second; + } + } + + + void CacheManager::MakeRoom(Bundle& bundle, + std::list<std::string>& toRemove, + int bundleIndex, + const BundleQuota& quota) + { + using namespace Orthanc; + + toRemove.clear(); + + // Make room in the bundle + while (!quota.IsSatisfied(bundle)) + { + SQLite::Statement s(pimpl_->db_, SQLITE_FROM_HERE, "SELECT seq, fileUuid, fileSize FROM Cache WHERE bundle=? ORDER BY seq"); + s.BindInt(0, bundleIndex); + + if (s.Step()) + { + SQLite::Statement t(pimpl_->db_, SQLITE_FROM_HERE, "DELETE FROM Cache WHERE seq=?"); + t.BindInt64(0, s.ColumnInt64(0)); + t.Run(); + + toRemove.push_back(s.ColumnString(1)); + bundle.Remove(s.ColumnInt64(2)); + } + else + { + // Should never happen + throw std::runtime_error("Internal error"); + } + } + } + + + + void CacheManager::EnsureQuota(int bundleIndex, + const BundleQuota& quota) + { + using namespace Orthanc; + + // Remove the cached files that exceed the quota + std::auto_ptr<SQLite::Transaction> transaction(new SQLite::Transaction(pimpl_->db_)); + transaction->Begin(); + + Bundle bundle = GetBundle(bundleIndex); + + std::list<std::string> toRemove; + MakeRoom(bundle, toRemove, bundleIndex, quota); + + transaction->Commit(); + for (std::list<std::string>::const_iterator + it = toRemove.begin(); it != toRemove.end(); it++) + { + pimpl_->storage_.Remove(*it); + } + + pimpl_->bundles_[bundleIndex] = bundle; + } + + + + void CacheManager::ReadBundleStatistics() + { + using namespace Orthanc; + + pimpl_->bundles_.clear(); + + SQLite::Statement s(pimpl_->db_, SQLITE_FROM_HERE, "SELECT bundle,COUNT(*),SUM(fileSize) FROM Cache GROUP BY bundle"); + while (s.Step()) + { + int index = s.ColumnInt(0); + Bundle bundle(static_cast<uint32_t>(s.ColumnInt(1)), + static_cast<uint64_t>(s.ColumnInt64(2))); + pimpl_->bundles_[index] = bundle; + } + } + + + + void CacheManager::SanityCheck() + { + if (!pimpl_->sanityCheck_) + { + return; + } + + using namespace Orthanc; + + SQLite::Statement s(pimpl_->db_, SQLITE_FROM_HERE, "SELECT bundle,COUNT(*),SUM(fileSize) FROM Cache GROUP BY bundle"); + while (s.Step()) + { + const Bundle& bundle = GetBundle(s.ColumnInt(0)); + if (bundle.GetCount() != static_cast<uint32_t>(s.ColumnInt(1)) || + bundle.GetSpace() != static_cast<uint64_t>(s.ColumnInt64(2))) + { + throw std::runtime_error("SANITY ERROR in cache: " + boost::lexical_cast<std::string>(bundle.GetCount()) + + "/" + boost::lexical_cast<std::string>(bundle.GetSpace()) + + " vs " + boost::lexical_cast<std::string>(s.ColumnInt(1)) + "/" + + boost::lexical_cast<std::string>(s.ColumnInt64(2))); + } + } + } + + + + CacheManager::CacheManager(Orthanc::SQLite::Connection& db, + Orthanc::FilesystemStorage& storage) : + pimpl_(new PImpl(db, storage)) + { + Open(); + ReadBundleStatistics(); + } + + + void CacheManager::SetSanityCheckEnabled(bool enabled) + { + pimpl_->sanityCheck_ = enabled; + } + + + void CacheManager::Open() + { + if (!pimpl_->db_.DoesTableExist("Cache")) + { + pimpl_->db_.Execute("CREATE TABLE Cache(seq INTEGER PRIMARY KEY, bundle INTEGER, item TEXT, fileUuid TEXT, fileSize INT);"); + pimpl_->db_.Execute("CREATE INDEX CacheBundles ON Cache(bundle);"); + pimpl_->db_.Execute("CREATE INDEX CacheIndex ON Cache(bundle, item);"); + } + + // Performance tuning of SQLite with PRAGMAs + // http://www.sqlite.org/pragma.html + pimpl_->db_.Execute("PRAGMA SYNCHRONOUS=OFF;"); + pimpl_->db_.Execute("PRAGMA JOURNAL_MODE=WAL;"); + pimpl_->db_.Execute("PRAGMA LOCKING_MODE=EXCLUSIVE;"); + } + + + void CacheManager::Store(int bundleIndex, + const std::string& item, + const std::string& content) + { + SanityCheck(); + + const BundleQuota quota = GetBundleQuota(bundleIndex); + + if (quota.GetMaxSpace() > 0 && + content.size() > quota.GetMaxSpace()) + { + // Cannot store such a large instance into the cache, forget about it + return; + } + + using namespace Orthanc; + + std::auto_ptr<SQLite::Transaction> transaction(new SQLite::Transaction(pimpl_->db_)); + transaction->Begin(); + + Bundle bundle = GetBundle(bundleIndex); + + std::list<std::string> toRemove; + bundle.Add(content.size()); + MakeRoom(bundle, toRemove, bundleIndex, quota); + + // Store the cached content on the disk + const char* data = content.size() ? &content[0] : NULL; + std::string uuid = Toolbox::GenerateUuid(); + pimpl_->storage_.Create(uuid, data, content.size()); + + bool ok = true; + + // Remove the previous cached value. This might happen if the same + // item is accessed very quickly twice: Another factory could have + // been cached a value before the check for existence in Access(). + { + SQLite::Statement s(pimpl_->db_, SQLITE_FROM_HERE, "SELECT seq, fileUuid, fileSize FROM Cache WHERE bundle=? AND item=?"); + s.BindInt(0, bundleIndex); + s.BindString(1, item); + if (s.Step()) + { + SQLite::Statement t(pimpl_->db_, SQLITE_FROM_HERE, "DELETE FROM Cache WHERE seq=?"); + t.BindInt64(0, s.ColumnInt64(0)); + t.Run(); + + toRemove.push_back(s.ColumnString(1)); + bundle.Remove(s.ColumnInt64(2)); + } + } + + if (ok) + { + SQLite::Statement s(pimpl_->db_, SQLITE_FROM_HERE, "INSERT INTO Cache VALUES(NULL, ?, ?, ?, ?)"); + s.BindInt(0, bundleIndex); + s.BindString(1, item); + s.BindString(2, uuid); + s.BindInt64(3, content.size()); + + if (!s.Run()) + { + ok = false; + } + } + + if (!ok) + { + // Error: Remove the stored file + pimpl_->storage_.Remove(uuid); + } + else + { + transaction->Commit(); + + pimpl_->bundles_[bundleIndex] = bundle; + + for (std::list<std::string>::const_iterator + it = toRemove.begin(); it != toRemove.end(); it++) + { + pimpl_->storage_.Remove(*it); + } + } + + SanityCheck(); + } + + + + bool CacheManager::LocateInCache(std::string& uuid, + uint64_t& size, + int bundle, + const std::string& item) + { + using namespace Orthanc; + SanityCheck(); + + std::auto_ptr<SQLite::Transaction> transaction(new SQLite::Transaction(pimpl_->db_)); + transaction->Begin(); + + SQLite::Statement s(pimpl_->db_, SQLITE_FROM_HERE, "SELECT seq, fileUuid, fileSize FROM Cache WHERE bundle=? AND item=?"); + s.BindInt(0, bundle); + s.BindString(1, item); + if (!s.Step()) + { + return false; + } + + int64_t seq = s.ColumnInt64(0); + uuid = s.ColumnString(1); + size = s.ColumnInt64(2); + + // Touch the cache to fulfill the LRU scheme. + SQLite::Statement t(pimpl_->db_, SQLITE_FROM_HERE, "DELETE FROM Cache WHERE seq=?"); + t.BindInt64(0, seq); + if (t.Run()) + { + SQLite::Statement u(pimpl_->db_, SQLITE_FROM_HERE, "INSERT INTO Cache VALUES(NULL, ?, ?, ?, ?)"); + u.BindInt(0, bundle); + u.BindString(1, item); + u.BindString(2, uuid); + u.BindInt64(3, size); + if (u.Run()) + { + // Everything was OK. Commit the changes to the cache. + transaction->Commit(); + return true; + } + } + + return false; + } + + + bool CacheManager::IsCached(int bundle, + const std::string& item) + { + std::string uuid; + uint64_t size; + return LocateInCache(uuid, size, bundle, item); + } + + + bool CacheManager::Access(std::string& content, + int bundle, + const std::string& item) + { + std::string uuid; + uint64_t size; + if (!LocateInCache(uuid, size, bundle, item)) + { + return false; + } + + bool ok; + try + { + pimpl_->storage_.Read(content, uuid); + ok = (content.size() == size); + } + catch (std::runtime_error&) + { + ok = false; + } + + if (ok) + { + return true; + } + else + { + throw std::runtime_error("Error in the filesystem"); + } + } + + + void CacheManager::Invalidate(int bundleIndex, + const std::string& item) + { + using namespace Orthanc; + SanityCheck(); + + std::auto_ptr<SQLite::Transaction> transaction(new SQLite::Transaction(pimpl_->db_)); + transaction->Begin(); + + Bundle bundle = GetBundle(bundleIndex); + + SQLite::Statement s(pimpl_->db_, SQLITE_FROM_HERE, "SELECT seq, fileUuid, fileSize FROM Cache WHERE bundle=? AND item=?"); + s.BindInt(0, bundleIndex); + s.BindString(1, item); + if (s.Step()) + { + int64_t seq = s.ColumnInt64(0); + const std::string uuid = s.ColumnString(1); + uint64_t expectedSize = s.ColumnInt64(2); + bundle.Remove(expectedSize); + + SQLite::Statement t(pimpl_->db_, SQLITE_FROM_HERE, "DELETE FROM Cache WHERE seq=?"); + t.BindInt64(0, seq); + if (t.Run()) + { + transaction->Commit(); + pimpl_->bundles_[bundleIndex] = bundle; + pimpl_->storage_.Remove(uuid); + } + } + } + + + + void CacheManager::SetBundleQuota(int bundle, + uint32_t maxCount, + uint64_t maxSpace) + { + SanityCheck(); + + const BundleQuota quota(maxCount, maxSpace); + EnsureQuota(bundle, quota); + pimpl_->quotas_[bundle] = quota; + + SanityCheck(); + } + + void CacheManager::SetDefaultQuota(uint32_t maxCount, + uint64_t maxSpace) + { + using namespace Orthanc; + SanityCheck(); + + pimpl_->defaultQuota_ = BundleQuota(maxCount, maxSpace); + + SQLite::Statement s(pimpl_->db_, SQLITE_FROM_HERE, "SELECT DISTINCT bundle FROM Cache"); + while (s.Step()) + { + EnsureQuota(s.ColumnInt(0), pimpl_->defaultQuota_); + } + + SanityCheck(); + } + + + void CacheManager::Clear() + { + using namespace Orthanc; + SanityCheck(); + + SQLite::Statement s(pimpl_->db_, SQLITE_FROM_HERE, "SELECT fileUuid FROM Cache"); + while (s.Step()) + { + pimpl_->storage_.Remove(s.ColumnString(0)); + } + + SQLite::Statement t(pimpl_->db_, SQLITE_FROM_HERE, "DELETE FROM Cache"); + t.Run(); + + ReadBundleStatistics(); + SanityCheck(); + } + + + + void CacheManager::Clear(int bundle) + { + using namespace Orthanc; + SanityCheck(); + + SQLite::Statement s(pimpl_->db_, SQLITE_FROM_HERE, "SELECT fileUuid FROM Cache WHERE bundle=?"); + s.BindInt(0, bundle); + while (s.Step()) + { + pimpl_->storage_.Remove(s.ColumnString(0)); + } + + SQLite::Statement t(pimpl_->db_, SQLITE_FROM_HERE, "DELETE FROM Cache WHERE bundle=?"); + t.BindInt(0, bundle); + t.Run(); + + ReadBundleStatistics(); + SanityCheck(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugin/Cache/CacheManager.h Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,96 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../../Orthanc/SQLite/Connection.h" +#include "../../Orthanc/FileStorage/FilesystemStorage.h" + +namespace OrthancPlugins +{ + class CacheManager : public boost::noncopyable + { + private: + struct PImpl; + boost::shared_ptr<PImpl> pimpl_; + + class Bundle; + class BundleQuota; + + typedef std::map<int, Bundle> Bundles; + typedef std::map<int, BundleQuota> BundleQuotas; + + const BundleQuota& GetBundleQuota(int bundleIndex) const; + + Bundle GetBundle(int bundleIndex) const; + + void MakeRoom(Bundle& bundle, + std::list<std::string>& toRemove, + int bundleIndex, + const BundleQuota& quota); + + void EnsureQuota(int bundleIndex, + const BundleQuota& quota); + + void ReadBundleStatistics(); + + void Open(); + + bool LocateInCache(std::string& uuid, + uint64_t& size, + int bundle, + const std::string& item); + + void SanityCheck(); // Only for debug + + + public: + CacheManager(Orthanc::SQLite::Connection& db, + Orthanc::FilesystemStorage& storage); + + void SetSanityCheckEnabled(bool enabled); + + void Clear(); + + void Clear(int bundle); + + void SetBundleQuota(int bundle, + uint32_t maxCount, + uint64_t maxSpace); + + void SetDefaultQuota(uint32_t maxCount, + uint64_t maxSpace); + + bool IsCached(int bundle, + const std::string& item); + + bool Access(std::string& content, + int bundle, + const std::string& item); + + void Invalidate(int bundle, + const std::string& item); + + void Store(int bundle, + const std::string& item, + const std::string& content); + + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugin/Cache/CacheScheduler.cpp Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,385 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "CacheScheduler.h" + +#include "CacheIndex.h" + +#include "../../Orthanc/OrthancException.h" +#include <stdio.h> + +namespace OrthancPlugins +{ + class DynamicString : public Orthanc::IDynamicObject + { + private: + std::string value_; + + public: + DynamicString(const std::string& value) : value_(value) + { + } + + const std::string& GetValue() const + { + return value_; + } + }; + + + class CacheScheduler::PrefetchQueue : public boost::noncopyable + { + private: + boost::mutex mutex_; + Orthanc::SharedMessageQueue queue_; + std::set<std::string> content_; + + public: + PrefetchQueue(size_t maxSize) : queue_(maxSize) + { + queue_.SetLifoPolicy(); + } + + void Enqueue(const std::string& item) + { + boost::mutex::scoped_lock lock(mutex_); + + if (content_.find(item) != content_.end()) + { + // This cache index is already pending in the queue + return; + } + + content_.insert(item); + queue_.Enqueue(new DynamicString(item)); + } + + DynamicString* Dequeue(int32_t msTimeout) + { + std::auto_ptr<Orthanc::IDynamicObject> message(queue_.Dequeue(msTimeout)); + if (message.get() == NULL) + { + return NULL; + } + + const DynamicString& index = dynamic_cast<const DynamicString&>(*message); + + { + boost::mutex::scoped_lock lock(mutex_); + content_.erase(index.GetValue()); + } + + return dynamic_cast<DynamicString*>(message.release()); + } + }; + + + class CacheScheduler::Prefetcher : public boost::noncopyable + { + private: + int bundleIndex_; + ICacheFactory& factory_; + CacheManager& cache_; + boost::mutex& cacheMutex_; + PrefetchQueue& queue_; + + bool done_; + boost::thread thread_; + boost::mutex invalidatedMutex_; + bool invalidated_; + std::string prefetching_; + + static void Worker(Prefetcher* that) + { + while (!(that->done_)) + { + std::auto_ptr<DynamicString> prefetch(that->queue_.Dequeue(500)); + + if (prefetch.get() != NULL) + { + { + boost::mutex::scoped_lock lock(that->invalidatedMutex_); + that->invalidated_ = false; + that->prefetching_ = prefetch->GetValue(); + } + + { + boost::mutex::scoped_lock lock(that->cacheMutex_); + if (that->cache_.IsCached(that->bundleIndex_, prefetch->GetValue())) + { + // This item is already cached + continue; + } + } + + std::string content; + if (!that->factory_.Create(content, prefetch->GetValue())) + { + // The factory cannot generate this item + continue; + } + + { + boost::mutex::scoped_lock lock(that->invalidatedMutex_); + if (that->invalidated_) + { + // This item has been invalidated + continue; + } + + { + boost::mutex::scoped_lock lock2(that->cacheMutex_); + that->cache_.Store(that->bundleIndex_, prefetch->GetValue(), content); + } + } + } + } + } + + + public: + Prefetcher(int bundleIndex, + ICacheFactory& factory, + CacheManager& cache, + boost::mutex& cacheMutex, + PrefetchQueue& queue) : + bundleIndex_(bundleIndex), + factory_(factory), + cache_(cache), + cacheMutex_(cacheMutex), + queue_(queue) + { + done_ = false; + thread_ = boost::thread(Worker, this); + } + + ~Prefetcher() + { + done_ = true; + if (thread_.joinable()) + { + thread_.join(); + } + } + + void SignalInvalidated(const std::string& item) + { + boost::mutex::scoped_lock lock(invalidatedMutex_); + + if (prefetching_ == item) + { + invalidated_ = true; + } + } + }; + + + + class CacheScheduler::BundleScheduler + { + private: + std::auto_ptr<ICacheFactory> factory_; + PrefetchQueue queue_; + std::vector<Prefetcher*> prefetchers_; + + public: + BundleScheduler(int bundleIndex, + ICacheFactory* factory, + CacheManager& cache, + boost::mutex& cacheMutex, + size_t numThreads, + size_t queueSize) : + factory_(factory), + queue_(queueSize) + { + prefetchers_.resize(numThreads, NULL); + + for (size_t i = 0; i < numThreads; i++) + { + prefetchers_[i] = new Prefetcher(bundleIndex, *factory_, cache, cacheMutex, queue_); + } + } + + ~BundleScheduler() + { + for (size_t i = 0; i < prefetchers_.size(); i++) + { + if (prefetchers_[i] != NULL) + delete prefetchers_[i]; + } + } + + void Invalidate(const std::string& item) + { + for (size_t i = 0; i < prefetchers_.size(); i++) + { + prefetchers_[i]->SignalInvalidated(item); + } + } + + void Prefetch(const std::string& item) + { + queue_.Enqueue(item); + } + + bool CallFactory(std::string& content, + const std::string& item) + { + content.clear(); + return factory_->Create(content, item); + } + }; + + + + CacheScheduler::BundleScheduler& CacheScheduler::GetBundleScheduler(unsigned int bundleIndex) + { + boost::mutex::scoped_lock lock(factoryMutex_); + + BundleSchedulers::iterator it = bundles_.find(bundleIndex); + if (it == bundles_.end()) + { + throw Orthanc::OrthancException("No factory associated with this bundle"); + } + + return *(it->second); + } + + + + CacheScheduler::CacheScheduler(CacheManager& cache, + unsigned int maxPrefetchSize) : + maxPrefetchSize_(maxPrefetchSize), + cache_(cache), + policy_(NULL) + { + } + + + CacheScheduler::~CacheScheduler() + { + for (BundleSchedulers::iterator it = bundles_.begin(); + it != bundles_.end(); it++) + { + delete it->second; + } + } + + + void CacheScheduler::Register(int bundle, + ICacheFactory* factory /* takes ownership */, + size_t numThreads) + { + boost::mutex::scoped_lock lock(factoryMutex_); + + BundleSchedulers::iterator it = bundles_.find(bundle); + if (it != bundles_.end()) + { + // This bundle is already registered + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + + bundles_[bundle] = new BundleScheduler(bundle, factory, cache_, cacheMutex_, numThreads, maxPrefetchSize_); + } + + + void CacheScheduler::Invalidate(int bundle, + const std::string& item) + { + { + boost::mutex::scoped_lock lock(cacheMutex_); + cache_.Invalidate(bundle, item); + } + + GetBundleScheduler(bundle).Invalidate(item); + } + + + void CacheScheduler::ApplyPrefetchPolicy(int bundle, + const std::string& item, + const std::string& content) + { + boost::recursive_mutex::scoped_lock lock(policyMutex_); + + if (policy_.get() != NULL) + { + std::list<CacheIndex> toPrefetch; + + { + policy_->Apply(toPrefetch, *this, CacheIndex(bundle, item), content); + } + + for (std::list<CacheIndex>::const_reverse_iterator + it = toPrefetch.rbegin(); it != toPrefetch.rend(); ++it) + { + Prefetch(it->GetBundle(), it->GetItem()); + } + } + } + + + bool CacheScheduler::Access(std::string& content, + int bundle, + const std::string& item) + { + bool existing; + + { + boost::mutex::scoped_lock lock(cacheMutex_); + existing = cache_.Access(content, bundle, item); + } + + if (existing) + { + ApplyPrefetchPolicy(bundle, item, content); + return true; + } + + if (!GetBundleScheduler(bundle).CallFactory(content, item)) + { + // This item cannot be generated by the factory + return false; + } + + { + boost::mutex::scoped_lock lock(cacheMutex_); + cache_.Store(bundle, item, content); + } + + ApplyPrefetchPolicy(bundle, item, content); + + return true; + } + + + void CacheScheduler::Prefetch(int bundle, + const std::string& item) + { + GetBundleScheduler(bundle).Prefetch(item); + } + + + void CacheScheduler::RegisterPolicy(IPrefetchPolicy* policy) + { + boost::recursive_mutex::scoped_lock lock(policyMutex_); + policy_.reset(policy); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugin/Cache/CacheScheduler.h Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,79 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "CacheManager.h" +#include "ICacheFactory.h" +#include "IPrefetchPolicy.h" +#include "../../Orthanc/MultiThreading/SharedMessageQueue.h" + +#include <boost/thread.hpp> +#include <stdio.h> + +namespace OrthancPlugins +{ + class CacheScheduler : public boost::noncopyable + { + private: + class Prefetcher; + class PrefetchQueue; + class BundleScheduler; + + typedef std::map<int, BundleScheduler*> BundleSchedulers; + + size_t maxPrefetchSize_; + + boost::mutex cacheMutex_; + boost::mutex factoryMutex_; + boost::recursive_mutex policyMutex_; + CacheManager& cache_; + std::auto_ptr<IPrefetchPolicy> policy_; + BundleSchedulers bundles_; + + void ApplyPrefetchPolicy(int bundle, + const std::string& item, + const std::string& content); + + BundleScheduler& GetBundleScheduler(unsigned int bundleIndex); + + public: + CacheScheduler(CacheManager& cache, + unsigned int maxPrefetchSize); + + ~CacheScheduler(); + + void Register(int bundle, + ICacheFactory* factory /* takes ownership */, + size_t numThreads); + + void RegisterPolicy(IPrefetchPolicy* policy /* takes ownership */); + + void Invalidate(int bundle, + const std::string& item); + + bool Access(std::string& content, + int bundle, + const std::string& item); + + void Prefetch(int bundle, + const std::string& item); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugin/Cache/ICacheFactory.h Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,41 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include <string> +#include <boost/noncopyable.hpp> + + +namespace OrthancPlugins +{ + class ICacheFactory : public boost::noncopyable + { + public: + virtual ~ICacheFactory() + { + } + + // WARNING: No mutual exclusion is enforced! Several threads could + // call this method at the same time. + virtual bool Create(std::string& content, + const std::string& key) = 0; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugin/Cache/IPrefetchPolicy.h Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,46 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "CacheIndex.h" + +#include <boost/noncopyable.hpp> +#include <list> + +namespace OrthancPlugins +{ + class CacheScheduler; + + class IPrefetchPolicy : public boost::noncopyable + { + public: + virtual ~IPrefetchPolicy() + { + } + + // Mutual exclusion is enforced when calling this method. + // "toPrefetch" must be listed from top-priority to low-priority. + virtual void Apply(std::list<CacheIndex>& toPrefetch, + CacheScheduler& cache, + const CacheIndex& index, + const std::string& content) = 0; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugin/DecodedImageAdapter.cpp Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,122 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "DecodedImageAdapter.h" + +#include "ViewerToolbox.h" +#include "ParsedDicomImage.h" + +#include <boost/lexical_cast.hpp> +#include <boost/algorithm/string/predicate.hpp> +#include <json/writer.h> + +namespace OrthancPlugins +{ + bool DecodedImageAdapter::ParseUri(CompressionType& type, + uint8_t& compressionLevel, + std::string& instanceId, + const std::string& uri) + { + size_t separator = uri.find('-'); + if (separator == std::string::npos && + separator >= 1) + { + return false; + } + + std::string compression = uri.substr(0, separator); + instanceId = uri.substr(separator + 1); + + if (compression == "deflate") + { + type = CompressionType_Deflate; + } + else if (boost::starts_with(compression, "jpeg")) + { + type = CompressionType_Jpeg; + int level = boost::lexical_cast<int>(compression.substr(4)); + if (level <= 0 || level > 100) + { + return false; + } + + compressionLevel = static_cast<uint8_t>(level); + } + else + { + return false; + } + + return true; + } + + + + bool DecodedImageAdapter::Create(std::string& content, + const std::string& uri) + { + std::string message = "Decoding DICOM instance: " + uri; + OrthancPluginLogInfo(context_, message.c_str()); + + CompressionType type; + uint8_t level; + std::string instanceId; + + if (!ParseUri(type, level, instanceId, uri)) + { + return false; + } + + std::string file = "/instances/" + instanceId + "/file"; + + std::string dicom; + if (!GetStringFromOrthanc(dicom, context_, file)) + { + return false; + } + + ParsedDicomImage image(dicom); + + Json::Value json; + + if (type == CompressionType_Deflate) + { + if (!image.EncodeUsingDeflate(json, 9)) + { + return false; + } + } + else if (type == CompressionType_Jpeg) + { + if (!image.EncodeUsingJpeg(json, level)) + { + return false; + } + } + else + { + return false; + } + + Json::FastWriter writer; + content = writer.write(json); + return true; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugin/DecodedImageAdapter.h Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,54 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "Cache/ICacheFactory.h" + +#include <orthanc/OrthancCPlugin.h> +#include <stdint.h> + +namespace OrthancPlugins +{ + class DecodedImageAdapter : public ICacheFactory + { + private: + enum CompressionType + { + CompressionType_Jpeg, + CompressionType_Deflate + }; + + static bool ParseUri(CompressionType& type, + uint8_t& compressionLevel, + std::string& instanceId, + const std::string& uri); + + OrthancPluginContext* context_; + + public: + DecodedImageAdapter(OrthancPluginContext* context) : context_(context) + { + } + + virtual bool Create(std::string& content, + const std::string& uri); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugin/InstanceInformation.cpp Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,130 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "InstanceInformation.h" + +#include "../Orthanc/OrthancException.h" + +#include <cassert> +#include <string.h> +#include <json/value.h> +#include <json/reader.h> +#include <json/writer.h> + + +namespace OrthancPlugins +{ + void InstanceInformation::SetPosition(const std::vector<float>& normal, + const std::vector<float>& position) + { + if (normal.size() != 3 || + position.size() != 3) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadParameterType); + } + + hasPosition_ = true; + memcpy(normal_, &normal[0], sizeof(float) * 3); + memcpy(position_, &position[0], sizeof(float) * 3); + } + + void InstanceInformation::SetIndexInSeries(int index) + { + hasIndex_ = true; + index_ = index; + } + + float InstanceInformation::GetPosition(size_t i) const + { + assert(hasPosition_ && i < 3); + return position_[i]; + } + + float InstanceInformation::GetNormal(size_t i) const + { + assert(hasPosition_ && i < 3); + return normal_[i]; + } + + int InstanceInformation::GetIndexInSeries() const + { + assert(hasIndex_); + return index_; + } + + void InstanceInformation::Serialize(std::string& result) const + { + Json::Value value = Json::objectValue; + + if (hasPosition_) + { + value["Normal"] = Json::arrayValue; + value["Normal"][0] = normal_[0]; + value["Normal"][1] = normal_[1]; + value["Normal"][2] = normal_[2]; + + value["Position"] = Json::arrayValue; + value["Position"][0] = position_[0]; + value["Position"][1] = position_[1]; + value["Position"][2] = position_[2]; + } + + if (hasIndex_) + { + value["Index"] = index_; + } + + Json::FastWriter writer; + result = writer.write(value); + } + + + void InstanceInformation::Deserialize(InstanceInformation& result, + const std::string& serialized) + { + result = InstanceInformation(); + + Json::Reader reader; + Json::Value value; + + if (!reader.parse(serialized, value) || + value.type() != Json::objectValue) + { + return; + } + + if (value.isMember("Normal")) + { + std::vector<float> normal(3), position(3); + normal[0] = value["Normal"][0].asFloat(); + normal[1] = value["Normal"][1].asFloat(); + normal[2] = value["Normal"][2].asFloat(); + position[0] = value["Position"][0].asFloat(); + position[1] = value["Position"][1].asFloat(); + position[2] = value["Position"][2].asFloat(); + result.SetPosition(normal, position); + } + + if (value.isMember("Index")) + { + result.SetIndexInSeries(value["Index"].asInt()); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugin/InstanceInformation.h Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,73 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include <vector> +#include <string> + +namespace OrthancPlugins +{ + class InstanceInformation + { + private: + bool hasPosition_; + float normal_[3]; + float position_[3]; + bool hasIndex_; + int index_; + + public: + InstanceInformation() : hasPosition_(false), hasIndex_(false) + { + } + + InstanceInformation(const std::string& serialized) + { + Deserialize(*this, serialized); + } + + void SetPosition(const std::vector<float>& normal, + const std::vector<float>& position); + + void SetIndexInSeries(int index); + + bool HasPosition() const + { + return hasPosition_; + } + + bool HasIndexInSeries() const + { + return hasIndex_; + } + + float GetPosition(size_t i) const; + + float GetNormal(size_t i) const; + + int GetIndexInSeries() const; + + void Serialize(std::string& result) const; + + static void Deserialize(InstanceInformation& result, + const std::string& serialized); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugin/InstanceInformationAdapter.cpp Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,81 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "InstanceInformationAdapter.h" + +#include "ViewerToolbox.h" +#include "InstanceInformation.h" + +#include <json/value.h> + +static const char* IMAGE_ORIENTATION_PATIENT = "ImageOrientationPatient"; +static const char* IMAGE_POSITION_PATIENT = "ImagePositionPatient"; +static const char* INDEX_IN_SERIES = "IndexInSeries"; + + +namespace OrthancPlugins +{ + bool InstanceInformationAdapter::Create(std::string& content, + const std::string& instanceId) + { + std::string message = "Creating spatial information for instance: " + instanceId; + OrthancPluginLogInfo(context_, message.c_str()); + + std::string uri = "/instances/" + instanceId; + + Json::Value instance, tags; + if (!GetJsonFromOrthanc(instance, context_, uri) || + !GetJsonFromOrthanc(tags, context_, uri + "/tags?simplify") || + instance.type() != Json::objectValue || + tags.type() != Json::objectValue) + { + return false; + } + + InstanceInformation info; + + if (tags.isMember(IMAGE_ORIENTATION_PATIENT) && + tags.isMember(IMAGE_POSITION_PATIENT) && + tags[IMAGE_ORIENTATION_PATIENT].type() == Json::stringValue && + tags[IMAGE_POSITION_PATIENT].type() == Json::stringValue) + { + std::vector<float> cosines, position; + if (TokenizeVector(cosines, tags[IMAGE_ORIENTATION_PATIENT].asString(), 6) && + TokenizeVector(position, tags[IMAGE_POSITION_PATIENT].asString(), 3)) + { + std::vector<float> normal(3); + normal[0] = cosines[1] * cosines[5] - cosines[2] * cosines[4]; + normal[1] = cosines[2] * cosines[3] - cosines[0] * cosines[5]; + normal[2] = cosines[0] * cosines[4] - cosines[1] * cosines[3]; + + info.SetPosition(normal, position); + } + } + + if (instance.isMember(INDEX_IN_SERIES) && + instance[INDEX_IN_SERIES].type() == Json::intValue) + { + info.SetIndexInSeries(instance[INDEX_IN_SERIES].asInt()); + } + + info.Serialize(content); + return true; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugin/InstanceInformationAdapter.h Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,42 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "Cache/ICacheFactory.h" + +#include <orthanc/OrthancCPlugin.h> + +namespace OrthancPlugins +{ + class InstanceInformationAdapter : public ICacheFactory + { + private: + OrthancPluginContext* context_; + + public: + InstanceInformationAdapter(OrthancPluginContext* context) : context_(context) + { + } + + virtual bool Create(std::string& content, + const std::string& instanceId); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugin/JpegWriter.cpp Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,246 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "JpegWriter.h" + +#include "../Orthanc/OrthancException.h" + +#include <jpeglib.h> +#include <setjmp.h> +#include <stdio.h> +#include <vector> +#include <string.h> +#include <stdlib.h> + +namespace OrthancPlugins +{ + namespace + { + class ErrorManager + { + private: + struct jpeg_error_mgr pub; /* "public" fields */ + jmp_buf setjmp_buffer; /* for return to caller */ + std::string message; + + static void OutputMessage(j_common_ptr cinfo) + { + char message[JMSG_LENGTH_MAX]; + (*cinfo->err->format_message) (cinfo, message); + + ErrorManager* that = reinterpret_cast<ErrorManager*>(cinfo->err); + that->message = std::string(message); + } + + + static void ErrorExit(j_common_ptr cinfo) + { + (*cinfo->err->output_message) (cinfo); + + ErrorManager* that = reinterpret_cast<ErrorManager*>(cinfo->err); + longjmp(that->setjmp_buffer, 1); + } + + + public: + ErrorManager() + { + memset(&pub, 0, sizeof(struct jpeg_error_mgr)); + memset(&setjmp_buffer, 0, sizeof(jmp_buf)); + + jpeg_std_error(&pub); + pub.error_exit = ErrorExit; + pub.output_message = OutputMessage; + } + + struct jpeg_error_mgr* GetPublic() + { + return &pub; + } + + jmp_buf& GetJumpBuffer() + { + return setjmp_buffer; + } + + const std::string& GetMessage() const + { + return message; + } + }; + } + + + static void GetLines(std::vector<uint8_t*>& lines, + unsigned int height, + unsigned int pitch, + Orthanc::PixelFormat format, + const void* buffer) + { + if (format != Orthanc::PixelFormat_Grayscale8 && + format != Orthanc::PixelFormat_RGB24) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + lines.resize(height); + + uint8_t* base = const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(buffer)); + for (unsigned int y = 0; y < height; y++) + { + lines[y] = base + static_cast<intptr_t>(y) * static_cast<intptr_t>(pitch); + } + } + + + static void Compress(struct jpeg_compress_struct& cinfo, + std::vector<uint8_t*>& lines, + unsigned int width, + unsigned int height, + Orthanc::PixelFormat format, + int quality) + { + cinfo.image_width = width; + cinfo.image_height = height; + + switch (format) + { + case Orthanc::PixelFormat_Grayscale8: + cinfo.input_components = 1; + cinfo.in_color_space = JCS_GRAYSCALE; + break; + + case Orthanc::PixelFormat_RGB24: + cinfo.input_components = 3; + cinfo.in_color_space = JCS_RGB; + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + jpeg_set_defaults(&cinfo); + jpeg_set_quality(&cinfo, quality, TRUE); + jpeg_start_compress(&cinfo, TRUE); + jpeg_write_scanlines(&cinfo, &lines[0], height); + jpeg_finish_compress(&cinfo); + jpeg_destroy_compress(&cinfo); + } + + + void JpegWriter::SetQuality(uint8_t quality) + { + if (quality <= 0 || quality > 100) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + quality_ = quality; + } + + + void JpegWriter::WriteToFile(const char* filename, + unsigned int width, + unsigned int height, + unsigned int pitch, + Orthanc::PixelFormat format, + const void* buffer) + { + FILE* fp = fopen(filename, "wb"); + if (fp == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_FullStorage); + } + + std::vector<uint8_t*> lines; + GetLines(lines, height, pitch, format, buffer); + + struct jpeg_compress_struct cinfo; + memset(&cinfo, 0, sizeof(struct jpeg_compress_struct)); + + ErrorManager jerr; + cinfo.err = jerr.GetPublic(); + + if (setjmp(jerr.GetJumpBuffer())) + { + /* If we get here, the JPEG code has signaled an error. + * We need to clean up the JPEG object, close the input file, and return. + */ + jpeg_destroy_compress(&cinfo); + fclose(fp); + throw Orthanc::OrthancException("Error during JPEG encoding: " + jerr.GetMessage()); + } + + // Do not allocate data on the stack below this line! + + jpeg_create_compress(&cinfo); + jpeg_stdio_dest(&cinfo, fp); + Compress(cinfo, lines, width, height, format, quality_); + + // Everything went fine, "setjmp()" didn't get called + + fclose(fp); + } + + + void JpegWriter::WriteToMemory(std::string& jpeg, + unsigned int width, + unsigned int height, + unsigned int pitch, + Orthanc::PixelFormat format, + const void* buffer) + { + std::vector<uint8_t*> lines; + GetLines(lines, height, pitch, format, buffer); + + struct jpeg_compress_struct cinfo; + memset(&cinfo, 0, sizeof(struct jpeg_compress_struct)); + + ErrorManager jerr; + + unsigned char* data = NULL; + unsigned long size; + + if (setjmp(jerr.GetJumpBuffer())) + { + jpeg_destroy_compress(&cinfo); + + if (data != NULL) + { + free(data); + } + + throw Orthanc::OrthancException("Error during JPEG encoding: " + jerr.GetMessage()); + } + + // Do not allocate data on the stack below this line! + + jpeg_create_compress(&cinfo); + cinfo.err = jerr.GetPublic(); + jpeg_mem_dest(&cinfo, &data, &size); + + Compress(cinfo, lines, width, height, format, quality_); + + // Everything went fine, "setjmp()" didn't get called + + jpeg.assign(reinterpret_cast<const char*>(data), size); + free(data); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugin/JpegWriter.h Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,75 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../Orthanc/ImageFormats/ImageAccessor.h" + +#include <string> +#include <stdint.h> + +namespace OrthancPlugins +{ + class JpegWriter + { + private: + int quality_; + + public: + JpegWriter() : quality_(90) + { + } + + void SetQuality(uint8_t quality); + + uint8_t GetQuality() const + { + return quality_; + } + + void WriteToFile(const char* filename, + unsigned int width, + unsigned int height, + unsigned int pitch, + Orthanc::PixelFormat format, + const void* buffer); + + void WriteToMemory(std::string& jpeg, + unsigned int width, + unsigned int height, + unsigned int pitch, + Orthanc::PixelFormat format, + const void* buffer); + + void WriteToFile(const char* filename, + const Orthanc::ImageAccessor& accessor) + { + WriteToFile(filename, accessor.GetWidth(), accessor.GetHeight(), + accessor.GetPitch(), accessor.GetFormat(), accessor.GetConstBuffer()); + } + + void WriteToMemory(std::string& jpeg, + const Orthanc::ImageAccessor& accessor) + { + WriteToMemory(jpeg, accessor.GetWidth(), accessor.GetHeight(), + accessor.GetPitch(), accessor.GetFormat(), accessor.GetConstBuffer()); + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugin/ParsedDicomImage.cpp Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,477 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "ParsedDicomImage.h" + +#include "../Orthanc/OrthancException.h" +#include "../Orthanc/Toolbox.h" +#include "../Orthanc/ImageFormats/ImageProcessing.h" +#include "../Orthanc/ImageFormats/ImageBuffer.h" +#include "JpegWriter.h" +#include "ViewerToolbox.h" + +#include <gdcmImageReader.h> +#include <gdcmImageChangePlanarConfiguration.h> +#include <gdcmImageChangePhotometricInterpretation.h> +#include <boost/lexical_cast.hpp> +#include <boost/math/special_functions/round.hpp> + +#include "../Resources/ThirdParty/base64/base64.h" + + +namespace OrthancPlugins +{ + struct ParsedDicomImage::PImpl + { + gdcm::ImageReader reader_; + std::auto_ptr<gdcm::ImageChangePhotometricInterpretation> photometric_; + std::auto_ptr<gdcm::ImageChangePlanarConfiguration> interleaved_; + std::string decoded_; + + const gdcm::Image& GetImage() const + { + if (interleaved_.get() != NULL) + { + return interleaved_->GetOutput(); + } + + if (photometric_.get() != NULL) + { + return photometric_->GetOutput(); + } + + return reader_.GetImage(); + } + + + const gdcm::DataSet& GetDataSet() const + { + return reader_.GetFile().GetDataSet(); + } + }; + + + template <typename TargetType, typename SourceType> + static void ChangeDynamics(Orthanc::ImageAccessor& target, + const Orthanc::ImageAccessor& source, + SourceType source1, TargetType target1, + SourceType source2, TargetType target2) + { + if (source.GetWidth() != target.GetWidth() || + source.GetHeight() != target.GetHeight()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize); + } + + float scale = static_cast<float>(target2 - target1) / static_cast<float>(source2 - source1); + float offset = static_cast<float>(target1) - scale * static_cast<float>(source1); + + const float minValue = static_cast<float>(std::numeric_limits<TargetType>::min()); + const float maxValue = static_cast<float>(std::numeric_limits<TargetType>::max()); + + for (unsigned int y = 0; y < source.GetHeight(); y++) + { + const SourceType* p = reinterpret_cast<const SourceType*>(source.GetConstRow(y)); + TargetType* q = reinterpret_cast<TargetType*>(target.GetRow(y)); + + for (unsigned int x = 0; x < source.GetWidth(); x++, p++, q++) + { + float v = (scale * static_cast<float>(*p)) + offset; + + if (v > maxValue) + { + *q = std::numeric_limits<TargetType>::max(); + } + else if (v < minValue) + { + *q = std::numeric_limits<TargetType>::min(); + } + else + { + *q = static_cast<TargetType>(boost::math::iround(v)); + } + } + } + } + + + void ParsedDicomImage::Setup(const std::string& dicom) + { + // Prepare a memory stream over the DICOM instance + std::stringstream stream(dicom); + + // Parse the DICOM instance using GDCM + pimpl_->reader_.SetStream(stream); + if (!pimpl_->reader_.Read()) + { + throw Orthanc::OrthancException("GDCM cannot extract an image from this DICOM instance"); + } + + // Change photometric interpretation, if required + { + const gdcm::Image& image = pimpl_->GetImage(); + if (image.GetPixelFormat().GetSamplesPerPixel() == 1) + { + if (image.GetPhotometricInterpretation() != gdcm::PhotometricInterpretation::MONOCHROME1 && + image.GetPhotometricInterpretation() != gdcm::PhotometricInterpretation::MONOCHROME2) + { + pimpl_->photometric_.reset(new gdcm::ImageChangePhotometricInterpretation()); + pimpl_->photometric_->SetInput(image); + pimpl_->photometric_->SetPhotometricInterpretation(gdcm::PhotometricInterpretation::MONOCHROME2); + if (!pimpl_->photometric_->Change()) + { + throw Orthanc::OrthancException("GDCM cannot change the photometric interpretation"); + } + } + } + else + { + if (image.GetPixelFormat().GetSamplesPerPixel() == 3 && + image.GetPhotometricInterpretation() != gdcm::PhotometricInterpretation::RGB) + { + pimpl_->photometric_.reset(new gdcm::ImageChangePhotometricInterpretation()); + pimpl_->photometric_->SetInput(image); + pimpl_->photometric_->SetPhotometricInterpretation(gdcm::PhotometricInterpretation::RGB); + if (!pimpl_->photometric_->Change()) + { + throw Orthanc::OrthancException("GDCM cannot change the photometric interpretation"); + } + } + } + } + + // Possibly convert planar configuration to interleaved + { + const gdcm::Image& image = pimpl_->GetImage(); + if (image.GetPlanarConfiguration() != 0 && + image.GetPixelFormat().GetSamplesPerPixel() != 1) + { + pimpl_->interleaved_.reset(new gdcm::ImageChangePlanarConfiguration()); + pimpl_->interleaved_->SetInput(image); + if (!pimpl_->interleaved_->Change()) + { + throw Orthanc::OrthancException("GDCM cannot change the planar configuration to interleaved"); + } + } + } + + // Decode the image to the memory buffer + { + const gdcm::Image& image = pimpl_->GetImage(); + pimpl_->decoded_.resize(image.GetBufferLength()); + + if (pimpl_->decoded_.size() > 0) + { + image.GetBuffer(&pimpl_->decoded_[0]); + } + } + } + + + ParsedDicomImage::ParsedDicomImage(const std::string& dicom) : pimpl_(new PImpl) + { + Setup(dicom); + } + + + bool ParsedDicomImage::GetTag(std::string& result, + uint16_t group, + uint16_t element, + bool stripSpaces) + { + const gdcm::Tag tag(group, element); + + if (pimpl_->GetDataSet().FindDataElement(tag)) + { + const gdcm::ByteValue* value = pimpl_->GetDataSet().GetDataElement(tag).GetByteValue(); + if (value) + { + result = std::string(value->GetPointer(), value->GetLength()); + + if (stripSpaces) + { + result = Orthanc::Toolbox::StripSpaces(result); + } + + return true; + } + } + + return false; + } + + + bool ParsedDicomImage::GetAccessor(Orthanc::ImageAccessor& accessor) + { + const gdcm::Image& image = pimpl_->GetImage(); + + size_t size = pimpl_->decoded_.size(); + void* buffer = (size ? &pimpl_->decoded_[0] : NULL); + unsigned int height = image.GetRows(); + unsigned int width = image.GetColumns(); + + if (image.GetPixelFormat().GetSamplesPerPixel() == 1 && + (image.GetPhotometricInterpretation() == gdcm::PhotometricInterpretation::MONOCHROME1 || + image.GetPhotometricInterpretation() == gdcm::PhotometricInterpretation::MONOCHROME2)) + { + switch (image.GetPixelFormat()) + { + case gdcm::PixelFormat::UINT16: + accessor.AssignWritable(Orthanc::PixelFormat_Grayscale16, width, height, 2 * width, buffer); + return true; + + case gdcm::PixelFormat::INT16: + accessor.AssignWritable(Orthanc::PixelFormat_SignedGrayscale16, width, height, 2 * width, buffer); + return true; + + case gdcm::PixelFormat::UINT8: + accessor.AssignWritable(Orthanc::PixelFormat_Grayscale8, width, height, width, buffer); + return true; + } + } + else if (image.GetPixelFormat().GetSamplesPerPixel() == 3 && + image.GetPhotometricInterpretation() == gdcm::PhotometricInterpretation::RGB) + { + switch (image.GetPixelFormat()) + { + case gdcm::PixelFormat::UINT8: + accessor.AssignWritable(Orthanc::PixelFormat_RGB24, width, height, 3 * width, buffer); + return true; + } + } + + return false; + } + + bool ParsedDicomImage::GetCornerstoneMetadata(Json::Value& json) + { + using namespace Orthanc; + + ImageAccessor accessor; + if (!GetAccessor(accessor)) + { + return false; + } + + float windowCenter, windowWidth; + + switch (accessor.GetFormat()) + { + case PixelFormat_Grayscale8: + case PixelFormat_Grayscale16: + case PixelFormat_SignedGrayscale16: + { + int64_t a, b; + Orthanc::ImageProcessing::GetMinMaxValue(a, b, accessor); + json["minPixelValue"] = (a < 0 ? static_cast<int32_t>(a) : 0); + json["maxPixelValue"] = (b > 0 ? static_cast<int32_t>(b) : 1); + json["color"] = false; + + windowCenter = static_cast<float>(a + b) / 2.0f; + + if (a == b) + { + windowWidth = 127.5f; // Arbitrary value + } + else + { + windowWidth = static_cast<float>(b - a) / 2.0f; + } + + break; + } + + case PixelFormat_RGB24: + json["minPixelValue"] = 0; + json["maxPixelValue"] = 255; + json["color"] = true; + windowCenter = 127.5f; + windowWidth = 127.5f; + break; + + default: + return false; + } + + const gdcm::Image& image = pimpl_->GetImage(); + json["slope"] = image.GetSlope(); + json["intercept"] = image.GetIntercept(); + json["rows"] = image.GetRows(); + json["columns"] = image.GetColumns(); + json["height"] = image.GetRows(); + json["width"] = image.GetColumns(); + json["columnPixelSpacing"] = image.GetSpacing(1); + json["rowPixelSpacing"] = image.GetSpacing(0); + + json["windowCenter"] = windowCenter * image.GetSlope() + image.GetIntercept(); + json["windowWidth"] = windowWidth * image.GetSlope(); + + try + { + std::string width, center; + if (GetTag(center, 0x0028, 0x1050 /*DICOM_TAG_WINDOW_CENTER*/) && + GetTag(width, 0x0028, 0x1051 /*DICOM_TAG_WINDOW_WIDTH*/)) + { + float a = boost::lexical_cast<float>(width); + float b = boost::lexical_cast<float>(center); + json["windowWidth"] = a; + json["windowCenter"] = b; + } + } + catch (boost::bad_lexical_cast&) + { + } + + return true; + } + + + bool ParsedDicomImage::EncodeUsingDeflate(Json::Value& result, + uint8_t compressionLevel /* between 0 and 9 */) + { + using namespace Orthanc; + + ImageAccessor accessor; + if (!GetAccessor(accessor)) + { + return false; + } + + result = Json::objectValue; + result["Orthanc"] = Json::objectValue; + if (!GetCornerstoneMetadata(result)) + { + return false; + } + + ImageBuffer buffer; + buffer.SetMinimalPitchForced(true); + + ImageAccessor converted; + + + switch (accessor.GetFormat()) + { + case Orthanc::PixelFormat_RGB24: + converted = accessor; + break; + + case Orthanc::PixelFormat_Grayscale8: + case Orthanc::PixelFormat_Grayscale16: + buffer.SetFormat(Orthanc::PixelFormat_SignedGrayscale16); + buffer.SetWidth(accessor.GetWidth()); + buffer.SetHeight(accessor.GetHeight()); + converted = buffer.GetAccessor(); + ImageProcessing::Convert(converted, accessor); + break; + + case Orthanc::PixelFormat_SignedGrayscale16: + converted = accessor; + break; + + default: + // Unsupported pixel format + return false; + } + + // Sanity check: The pitch must be minimal + assert(converted.GetSize() == converted.GetWidth() * converted.GetHeight() * + GetBytesPerPixel(converted.GetFormat())); + result["Orthanc"]["Compression"] = "Deflate"; + result["sizeInBytes"] = converted.GetSize(); + + std::string z; + if (!CompressUsingDeflate(z, converted.GetConstBuffer(), converted.GetSize(), compressionLevel)) + { + return false; + } + + result["Orthanc"]["PixelData"] = base64_encode(z); + + return true; + } + + + + bool ParsedDicomImage::EncodeUsingJpeg(Json::Value& result, + uint8_t quality /* between 0 and 100 */) + { + using namespace Orthanc; + + ImageAccessor accessor; + if (!GetAccessor(accessor)) + { + return false; + } + + result = Json::objectValue; + result["Orthanc"] = Json::objectValue; + GetCornerstoneMetadata(result); + + ImageBuffer buffer; + buffer.SetMinimalPitchForced(true); + + ImageAccessor converted; + + if (accessor.GetFormat() == Orthanc::PixelFormat_Grayscale8 || + accessor.GetFormat() == Orthanc::PixelFormat_RGB24) + { + result["Orthanc"]["Stretched"] = false; + converted = accessor; + } + else if (accessor.GetFormat() == Orthanc::PixelFormat_Grayscale16 || + accessor.GetFormat() == Orthanc::PixelFormat_SignedGrayscale16) + { + result["Orthanc"]["Stretched"] = true; + buffer.SetFormat(Orthanc::PixelFormat_Grayscale8); + buffer.SetWidth(accessor.GetWidth()); + buffer.SetHeight(accessor.GetHeight()); + converted = buffer.GetAccessor(); + + int64_t a, b; + Orthanc::ImageProcessing::GetMinMaxValue(a, b, accessor); + result["Orthanc"]["StretchLow"] = static_cast<int32_t>(a); + result["Orthanc"]["StretchHigh"] = static_cast<int32_t>(b); + + if (accessor.GetFormat() == Orthanc::PixelFormat_Grayscale16) + { + ChangeDynamics<uint8_t, uint16_t>(converted, accessor, a, 0, b, 255); + } + else + { + ChangeDynamics<uint8_t, int16_t>(converted, accessor, a, 0, b, 255); + } + } + else + { + return false; + } + + result["Orthanc"]["Compression"] = "Jpeg"; + result["sizeInBytes"] = converted.GetSize(); + + std::string jpeg; + OrthancPlugins::JpegWriter writer; + writer.SetQuality(quality); + writer.WriteToMemory(jpeg, converted); + result["Orthanc"]["PixelData"] = base64_encode(jpeg); + return true; + } +};
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugin/ParsedDicomImage.h Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,58 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../Orthanc/ImageFormats/ImageAccessor.h" + +#include <stdint.h> +#include <boost/noncopyable.hpp> +#include <boost/shared_ptr.hpp> +#include <json/value.h> + +namespace OrthancPlugins +{ + class ParsedDicomImage : public boost::noncopyable + { + private: + struct PImpl; + boost::shared_ptr<PImpl> pimpl_; + + void Setup(const std::string& dicom); + + public: + ParsedDicomImage(const std::string& dicom); + + bool GetTag(std::string& result, + uint16_t group, + uint16_t element, + bool stripSpaces = true); + + bool GetAccessor(Orthanc::ImageAccessor& accessor); + + bool GetCornerstoneMetadata(Json::Value& json); + + bool EncodeUsingDeflate(Json::Value& result, + uint8_t compressionLevel /* between 0 and 9 */); + + bool EncodeUsingJpeg(Json::Value& result, + uint8_t quality /* between 0 and 100 */); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugin/Plugin.cpp Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,350 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include <boost/thread.hpp> +#include <boost/lexical_cast.hpp> +#include <EmbeddedResources.h> + +#include "../Orthanc/OrthancException.h" +#include "ViewerToolbox.h" +#include "ViewerPrefetchPolicy.h" +#include "DecodedImageAdapter.h" +#include "InstanceInformationAdapter.h" +#include "SeriesInformationAdapter.h" + + + +class CacheContext +{ +private: + std::auto_ptr<Orthanc::FilesystemStorage> storage_; + std::auto_ptr<Orthanc::SQLite::Connection> db_; + std::auto_ptr<OrthancPlugins::CacheManager> cache_; + std::auto_ptr<OrthancPlugins::CacheScheduler> scheduler_; + +public: + CacheContext(const std::string& path) + { + boost::filesystem::path p(path); + + storage_.reset(new Orthanc::FilesystemStorage(path)); + db_.reset(new Orthanc::SQLite::Connection()); + db_->Open((p / "cache.db").string()); + + cache_.reset(new OrthancPlugins::CacheManager(*db_, *storage_)); + //cache_->SetSanityCheckEnabled(true); // For debug + + scheduler_.reset(new OrthancPlugins::CacheScheduler(*cache_, 100)); + } + + OrthancPlugins::CacheScheduler& GetScheduler() + { + return *scheduler_; + } +}; + + +static OrthancPluginContext* context_ = NULL; +static CacheContext* cache_ = NULL; + + + +static int32_t OnChangeCallback(OrthancPluginChangeType changeType, + OrthancPluginResourceType resourceType, + const char* resourceId) +{ + try + { + if (changeType == OrthancPluginChangeType_NewInstance && + resourceType == OrthancPluginResourceType_Instance) + { + // On the reception of a new instance, precompute its spatial position + cache_->GetScheduler().Prefetch(OrthancPlugins::CacheBundle_InstanceInformation, resourceId); + + // Indalidate the parent series of the instance + std::string uri = "/instances/" + std::string(resourceId); + Json::Value instance; + if (OrthancPlugins::GetJsonFromOrthanc(instance, context_, uri)) + { + std::string seriesId = instance["ParentSeries"].asString(); + cache_->GetScheduler().Invalidate(OrthancPlugins::CacheBundle_SeriesInformation, seriesId); + } + } + + return 0; + } + catch (std::runtime_error& e) + { + OrthancPluginLogError(context_, e.what()); + return 0; // Ignore error + } +} + + + +template <enum OrthancPlugins::CacheBundle bundle> +int32_t ServeCache(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request) +{ + try + { + if (request->method != OrthancPluginHttpMethod_Get) + { + OrthancPluginSendMethodNotAllowed(context_, output, "GET"); + return 0; + } + + const std::string id = request->groups[0]; + std::string content; + + if (cache_->GetScheduler().Access(content, bundle, id)) + { + OrthancPluginAnswerBuffer(context_, output, content.c_str(), content.size(), "application/json"); + } + else + { + OrthancPluginSendHttpStatusCode(context_, output, 404); + } + + return 0; + } + catch (Orthanc::OrthancException& e) + { + OrthancPluginLogError(context_, e.What()); + return -1; + } + catch (std::runtime_error& e) + { + OrthancPluginLogError(context_, e.what()); + return -1; + } + catch (boost::bad_lexical_cast&) + { + OrthancPluginLogError(context_, "Bad lexical cast"); + return -1; + } +} + + + + +#if ORTHANC_STANDALONE == 0 +static int32_t ServeWebViewer(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request) +{ + if (request->method != OrthancPluginHttpMethod_Get) + { + OrthancPluginSendMethodNotAllowed(context_, output, "GET"); + return 0; + } + + const std::string path = std::string(WEB_VIEWER_PATH) + std::string(request->groups[0]); + const char* mime = OrthancPlugins::GetMimeType(path); + + std::string s; + if (OrthancPlugins::ReadFile(s, path)) + { + const char* resource = s.size() ? s.c_str() : NULL; + OrthancPluginAnswerBuffer(context_, output, resource, s.size(), mime); + } + else + { + std::string s = "Inexistent file in served folder: " + path; + OrthancPluginLogError(context_, s.c_str()); + OrthancPluginSendHttpStatusCode(context_, output, 404); + } + + return 0; +} +#endif + + + +template <enum OrthancPlugins::EmbeddedResources::DirectoryResourceId folder> +static int32_t ServeEmbeddedFolder(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request) +{ + if (request->method != OrthancPluginHttpMethod_Get) + { + OrthancPluginSendMethodNotAllowed(context_, output, "GET"); + return 0; + } + + std::string path = "/" + std::string(request->groups[0]); + const char* mime = OrthancPlugins::GetMimeType(path); + + try + { + std::string s; + OrthancPlugins::EmbeddedResources::GetDirectoryResource(s, folder, path.c_str()); + + const char* resource = s.size() ? s.c_str() : NULL; + OrthancPluginAnswerBuffer(context_, output, resource, s.size(), mime); + + return 0; + } + catch (std::runtime_error&) + { + std::string s = "Unknown static resource in plugin: " + std::string(request->groups[0]); + OrthancPluginLogError(context_, s.c_str()); + OrthancPluginSendHttpStatusCode(context_, output, 404); + return 0; + } +} + + + + +extern "C" +{ + ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* context) + { + using namespace OrthancPlugins; + + context_ = context; + OrthancPluginLogWarning(context_, "Initializing the Web viewer"); + + + /* Check the version of the Orthanc core */ + if (OrthancPluginCheckVersion(context_) == 0) + { + char info[1024]; + sprintf(info, "Your version of Orthanc (%s) must be above %d.%d.%d to run this plugin", + context_->orthancVersion, + ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER, + ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER, + ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER); + OrthancPluginLogError(context_, info); + return -1; + } + + OrthancPluginSetDescription(context_, "Provides a Web viewer of DICOM series within Orthanc."); + + + /* By default, use half of the available processing cores for the decoding of DICOM images */ + int decodingThreads = boost::thread::hardware_concurrency() / 2; + if (decodingThreads == 0) + { + decodingThreads = 1; + } + + + try + { + /* Read the configuration of the Web viewer */ + Json::Value configuration; + if (!ReadConfiguration(configuration, context)) + { + OrthancPluginLogError(context_, "Unable to read the configuration file of Orthanc"); + return -1; + } + + std::string cachePath = "WebViewerCache"; + + if (configuration.isMember("WebViewer")) + { + cachePath = GetStringValue(configuration["WebViewer"], "Cache", cachePath); + decodingThreads = GetIntegerValue(configuration["WebViewer"], "Threads", decodingThreads); + } + + std::string message = ("Web viewer using " + boost::lexical_cast<std::string>(decodingThreads) + + " threads for the decoding of the DICOM images"); + OrthancPluginLogWarning(context_, message.c_str()); + + if (decodingThreads <= 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + message = "Storing the cache of the Web viewer in folder: " + cachePath; + OrthancPluginLogWarning(context_, message.c_str()); + + + /* Create the cache */ + cache_ = new CacheContext(cachePath); + cache_->GetScheduler().RegisterPolicy(new ViewerPrefetchPolicy(context_)); + cache_->GetScheduler().Register(CacheBundle_SeriesInformation, + new SeriesInformationAdapter(context_, cache_->GetScheduler()), 1); + cache_->GetScheduler().Register(CacheBundle_InstanceInformation, + new InstanceInformationAdapter(context_), 1); + cache_->GetScheduler().Register(CacheBundle_DecodedImage, + new DecodedImageAdapter(context_), decodingThreads); + } + catch (std::runtime_error& e) + { + OrthancPluginLogError(context_, e.what()); + return -1; + } + catch (Orthanc::OrthancException& e) + { + OrthancPluginLogError(context_, e.What()); + return -1; + } + + + /* Install the callbacks */ + OrthancPluginRegisterRestCallback(context_, "/web-viewer/series/(.*)", ServeCache<CacheBundle_SeriesInformation>); + OrthancPluginRegisterRestCallback(context_, "/web-viewer/instances/(.*)", ServeCache<CacheBundle_DecodedImage>); + OrthancPluginRegisterRestCallback(context, "/web-viewer/libs/(.*)", ServeEmbeddedFolder<EmbeddedResources::JAVASCRIPT_LIBS>); + +#if ORTHANC_STANDALONE == 1 + OrthancPluginRegisterRestCallback(context, "/web-viewer/app/(.*)", ServeEmbeddedFolder<EmbeddedResources::WEB_VIEWER>); +#else + OrthancPluginRegisterRestCallback(context, "/web-viewer/app/(.*)", ServeWebViewer); +#endif + + OrthancPluginRegisterOnChangeCallback(context, OnChangeCallback); + + + /* Extend the default Orthanc Explorer with custom JavaScript */ + std::string explorer; + EmbeddedResources::GetFileResource(explorer, EmbeddedResources::ORTHANC_EXPLORER); + OrthancPluginExtendOrthancExplorer(context_, explorer.c_str()); + + return 0; + } + + + ORTHANC_PLUGINS_API void OrthancPluginFinalize() + { + OrthancPluginLogWarning(context_, "Finalizing the Web viewer"); + + if (cache_ != NULL) + { + delete cache_; + cache_ = NULL; + } + } + + + ORTHANC_PLUGINS_API const char* OrthancPluginGetName() + { + return "web-viewer"; + } + + + ORTHANC_PLUGINS_API const char* OrthancPluginGetVersion() + { + return "1.0"; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugin/SeriesInformationAdapter.cpp Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,80 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "SeriesInformationAdapter.h" + +#include "ViewerToolbox.h" +#include "SeriesVolumeSorter.h" + +#include "../Orthanc/OrthancException.h" + +namespace OrthancPlugins +{ + bool SeriesInformationAdapter::Create(std::string& content, + const std::string& seriesId) + { + std::string message = "Ordering instances of series: " + seriesId; + OrthancPluginLogInfo(context_, message.c_str()); + + Json::Value series, study, patient; + if (!GetJsonFromOrthanc(series, context_, "/series/" + seriesId) || + !GetJsonFromOrthanc(study, context_, "/studies/" + series["ID"].asString() + "/module?simplify") || + !GetJsonFromOrthanc(patient, context_, "/studies/" + series["ID"].asString() + "/module-patient?simplify") || + !series.isMember("Instances") || + series["Instances"].type() != Json::arrayValue) + { + return false; + } + + Json::Value result; + result["ID"] = seriesId; + result["SeriesDescription"] = series["MainDicomTags"]["SeriesDescription"].asString(); + result["StudyDescription"] = study["StudyDescription"].asString(); + result["PatientID"] = patient["PatientID"].asString(); + result["PatientName"] = patient["PatientName"].asString(); + result["SortedInstances"] = Json::arrayValue; + + SeriesVolumeSorter sorter; + sorter.Reserve(series["Instances"].size()); + + for (Json::Value::ArrayIndex i = 0; i < series["Instances"].size(); i++) + { + const std::string instanceId = series["Instances"][i].asString(); + std::string tmp; + + if (!cache_.Access(tmp, CacheBundle_InstanceInformation, instanceId)) + { + throw Orthanc::OrthancException("The cache is corrupted. Delete it to reconstruct it."); + } + + InstanceInformation instance(tmp); + sorter.AddInstance(instanceId, instance); + } + + for (size_t i = 0; i < sorter.GetSize(); i++) + { + result["SortedInstances"].append(sorter.GetInstance(i)); + } + + content = result.toStyledString(); + + return true; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugin/SeriesInformationAdapter.h Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,47 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "Cache/ICacheFactory.h" +#include "Cache/CacheScheduler.h" + +#include <orthanc/OrthancCPlugin.h> + +namespace OrthancPlugins +{ + class SeriesInformationAdapter : public ICacheFactory + { + private: + OrthancPluginContext* context_; + CacheScheduler& cache_; + + public: + SeriesInformationAdapter(OrthancPluginContext* context, + CacheScheduler& cache) : + context_(context), + cache_(cache) + { + } + + virtual bool Create(std::string& content, + const std::string& seriesId); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugin/SeriesVolumeSorter.cpp Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,131 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "SeriesVolumeSorter.h" + +#include <algorithm> +#include <limits> +#include <math.h> +#include <cassert> + +namespace OrthancPlugins +{ + SeriesVolumeSorter::SeriesVolumeSorter() : + isVolume_(true), + sorted_(true) + { + } + + + void SeriesVolumeSorter::Reserve(size_t countInstances) + { + positions_.reserve(countInstances); + indexes_.reserve(countInstances); + } + + + void SeriesVolumeSorter::AddInstance(const std::string& instanceId, + const InstanceInformation& instance) + { + if (instance.HasIndexInSeries()) + { + indexes_.push_back(std::make_pair(instanceId, instance.GetIndexInSeries())); + } + + if (!isVolume_ || + !instance.HasPosition()) + { + isVolume_ = false; + } + else + { + if (positions_.size() == 0) + { + // This is the first slice in a possible 3D volume. Remember its normal. + normal_[0] = instance.GetNormal(0); + normal_[1] = instance.GetNormal(1); + normal_[2] = instance.GetNormal(2); + } + else + { + static const float THRESHOLD = 10.0f * std::numeric_limits<float>::epsilon(); + + // This is still a possible 3D volume. Check whether the normal + // is constant wrt. the previous slices. + if (fabs(normal_[0] - instance.GetNormal(0)) > THRESHOLD || + fabs(normal_[1] - instance.GetNormal(1)) > THRESHOLD || + fabs(normal_[2] - instance.GetNormal(2)) > THRESHOLD) + { + // The normal is not constant, not a 3D volume. + isVolume_ = false; + positions_.clear(); + } + } + + if (isVolume_) + { + float distance = (normal_[0] * instance.GetPosition(0) + + normal_[1] * instance.GetPosition(1) + + normal_[2] * instance.GetPosition(2)); + positions_.push_back(std::make_pair(instanceId, distance)); + } + } + + sorted_ = false; + } + + + std::string SeriesVolumeSorter::GetInstance(size_t index) + { + if (!sorted_) + { + if (isVolume_) + { + assert(indexes_.size() == positions_.size()); + std::sort(positions_.begin(), positions_.end(), ComparePosition); + + float a = positions_.front().second; + float b = positions_.back().second; + assert(a <= b); + + if (fabs(b - a) <= 10.0f * std::numeric_limits<float>::epsilon()) + { + // Not enough difference between the minimum and maximum + // positions along the normal of the volume + isVolume_ = false; + } + } + + if (!isVolume_) + { + std::sort(indexes_.begin(), indexes_.end(), CompareIndex); + } + } + + if (isVolume_) + { + return positions_[index].first; + } + else + { + return indexes_[index].first; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugin/SeriesVolumeSorter.h Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,70 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "InstanceInformation.h" + +#include <json/reader.h> +#include <boost/noncopyable.hpp> + +namespace OrthancPlugins +{ + class SeriesVolumeSorter : public boost::noncopyable + { + private: + typedef std::pair<std::string, float> InstanceWithPosition; + typedef std::pair<std::string, int> InstanceWithIndex; + + static bool ComparePosition(const InstanceWithPosition& a, + const InstanceWithPosition& b) + { + return a.second < b.second; + } + + static bool CompareIndex(const InstanceWithIndex& a, + const InstanceWithIndex& b) + { + return a.second < b.second; + } + + bool sorted_; + bool isVolume_; + float normal_[3]; + + std::vector<InstanceWithPosition> positions_; + std::vector<InstanceWithIndex> indexes_; + + public: + SeriesVolumeSorter(); + + void Reserve(size_t countInstances); + + void AddInstance(const std::string& instanceId, + const InstanceInformation& instance); + + size_t GetSize() const + { + return indexes_.size(); + } + + std::string GetInstance(size_t index); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugin/ViewerPrefetchPolicy.cpp Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,160 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "ViewerPrefetchPolicy.h" + +#include "ViewerToolbox.h" +#include "Cache/CacheScheduler.h" + +#include <json/value.h> +#include <json/reader.h> + + + +static const Json::Value::ArrayIndex PREFETCH_FORWARD = 10; +static const Json::Value::ArrayIndex PREFETCH_BACKWARD = 3; + + +namespace OrthancPlugins +{ + void ViewerPrefetchPolicy::ApplySeries(std::list<CacheIndex>& toPrefetch, + CacheScheduler& cache, + const std::string& series, + const std::string& content) + { + Json::Value json; + Json::Reader reader; + if (!reader.parse(content, json) || + !json.isMember("SortedInstances")) + { + return; + } + + const Json::Value& instances = json["SortedInstances"]; + if (instances.type() != Json::arrayValue) + { + return; + } + + for (Json::Value::ArrayIndex i = 0; + i < instances.size() && i < PREFETCH_FORWARD; + i++) + { + std::string item = "jpeg95-" + instances[i].asString(); + toPrefetch.push_back(CacheIndex(CacheBundle_DecodedImage, item)); + } + } + + + void ViewerPrefetchPolicy::ApplyInstance(std::list<CacheIndex>& toPrefetch, + CacheScheduler& cache, + const std::string& path) + { + size_t separator = path.find('-'); + if (separator == std::string::npos) + { + return; + } + + std::string compression = path.substr(0, separator + 1); + std::string instanceId = path.substr(separator + 1); + + Json::Value instance; + if (!GetJsonFromOrthanc(instance, context_, "/instances/" + instanceId) || + !instance.isMember("ParentSeries")) + { + return; + } + + std::string tmp; + if (!cache.Access(tmp, CacheBundle_SeriesInformation, instance["ParentSeries"].asString())) + { + return; + } + + Json::Value series; + Json::Reader reader; + if (!reader.parse(tmp, series) || + !series.isMember("SortedInstances")) + { + return; + } + + const Json::Value& instances = series["SortedInstances"]; + if (instances.type() != Json::arrayValue) + { + return; + } + + Json::Value::ArrayIndex position = 0; + while (position < instances.size()) + { + if (instances[position] == instanceId) + { + break; + } + + position++; + } + + if (position == instances.size()) + { + return; + } + + for (Json::Value::ArrayIndex i = position; + i < instances.size() && i < position + PREFETCH_FORWARD; + i++) + { + std::string item = compression + instances[i].asString(); + toPrefetch.push_back(CacheIndex(CacheBundle_DecodedImage, item)); + } + + for (Json::Value::ArrayIndex i = position; + i >= 0 && i > position - PREFETCH_BACKWARD; ) + { + i--; + std::string item = compression + instances[i].asString(); + toPrefetch.push_back(CacheIndex(CacheBundle_DecodedImage, item)); + } + } + + + void ViewerPrefetchPolicy::Apply(std::list<CacheIndex>& toPrefetch, + CacheScheduler& cache, + const CacheIndex& accessed, + const std::string& content) + { + switch (accessed.GetBundle()) + { + case CacheBundle_SeriesInformation: + ApplySeries(toPrefetch, cache, accessed.GetItem(), content); + return; + + case CacheBundle_DecodedImage: + ApplyInstance(toPrefetch, cache, accessed.GetItem()); + return; + + default: + return; + } + } +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugin/ViewerPrefetchPolicy.h Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,53 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "Cache/IPrefetchPolicy.h" + +#include <orthanc/OrthancCPlugin.h> + +namespace OrthancPlugins +{ + class ViewerPrefetchPolicy : public IPrefetchPolicy + { + private: + OrthancPluginContext* context_; + + void ApplySeries(std::list<CacheIndex>& toPrefetch, + CacheScheduler& cache, + const std::string& series, + const std::string& content); + + void ApplyInstance(std::list<CacheIndex>& toPrefetch, + CacheScheduler& cache, + const std::string& path); + + public: + ViewerPrefetchPolicy(OrthancPluginContext* context) : context_(context) + { + } + + virtual void Apply(std::list<CacheIndex>& toPrefetch, + CacheScheduler& cache, + const CacheIndex& accessed, + const std::string& content); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugin/ViewerToolbox.cpp Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,329 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "ViewerToolbox.h" + +#include "../Orthanc/OrthancException.h" +#include "../Orthanc/Toolbox.h" + +#include <json/reader.h> +#include <zlib.h> +#include <stdexcept> +#include <boost/lexical_cast.hpp> +#include <fstream> +#include <sys/stat.h> + +namespace OrthancPlugins +{ + bool GetStringFromOrthanc(std::string& content, + OrthancPluginContext* context, + const std::string& uri) + { + OrthancPluginMemoryBuffer answer; + + if (OrthancPluginRestApiGet(context, &answer, uri.c_str())) + { + return false; + } + + if (answer.size) + { + try + { + content.assign(reinterpret_cast<const char*>(answer.data), answer.size); + } + catch (std::bad_alloc&) + { + OrthancPluginFreeMemoryBuffer(context, &answer); + throw Orthanc::OrthancException("Not enough memory"); + } + } + + OrthancPluginFreeMemoryBuffer(context, &answer); + return true; + } + + + bool GetJsonFromOrthanc(Json::Value& json, + OrthancPluginContext* context, + const std::string& uri) + { + OrthancPluginMemoryBuffer answer; + + if (OrthancPluginRestApiGet(context, &answer, uri.c_str())) + { + return false; + } + + if (answer.size) + { + try + { + const char* data = reinterpret_cast<const char*>(answer.data); + Json::Reader reader; + if (!reader.parse(data, data + answer.size, json, + false /* don't collect comments */)) + { + return false; + } + } + catch (std::runtime_error&) + { + OrthancPluginFreeMemoryBuffer(context, &answer); + return false; + } + } + + OrthancPluginFreeMemoryBuffer(context, &answer); + return true; + } + + + + + bool TokenizeVector(std::vector<float>& result, + const std::string& value, + unsigned int expectedSize) + { + std::vector<std::string> tokens; + Orthanc::Toolbox::TokenizeString(tokens, value, '\\'); + + if (tokens.size() != expectedSize) + { + return false; + } + + result.resize(tokens.size()); + + for (size_t i = 0; i < tokens.size(); i++) + { + try + { + result[i] = boost::lexical_cast<float>(tokens[i]); + } + catch (boost::bad_lexical_cast&) + { + return false; + } + } + + return true; + } + + + bool CompressUsingDeflate(std::string& compressed, + const void* uncompressed, + size_t uncompressedSize, + uint8_t compressionLevel) + { + if (uncompressedSize == 0) + { + compressed.clear(); + return true; + } + + uLongf compressedSize = compressBound(uncompressedSize); + compressed.resize(compressedSize); + + int error = compress2 + (reinterpret_cast<uint8_t*>(&compressed[0]), + &compressedSize, + const_cast<Bytef *>(static_cast<const Bytef *>(uncompressed)), + uncompressedSize, + compressionLevel); + + if (error == Z_OK) + { + compressed.resize(compressedSize); + return true; + } + else + { + compressed.clear(); + return false; + } + } + + + + const char* GetMimeType(const std::string& path) + { + size_t dot = path.find_last_of('.'); + + std::string extension = (dot == std::string::npos) ? "" : path.substr(dot); + std::transform(extension.begin(), extension.end(), extension.begin(), tolower); + + if (extension == ".html") + { + return "text/html"; + } + else if (extension == ".css") + { + return "text/css"; + } + else if (extension == ".js") + { + return "application/javascript"; + } + else if (extension == ".gif") + { + return "image/gif"; + } + else if (extension == ".svg") + { + return "image/svg+xml"; + } + else if (extension == ".json") + { + return "application/json"; + } + else if (extension == ".xml") + { + return "application/xml"; + } + else if (extension == ".png") + { + return "image/png"; + } + else if (extension == ".jpg" || extension == ".jpeg") + { + return "image/jpeg"; + } + else + { + return "application/octet-stream"; + } + } + + + bool ReadConfiguration(Json::Value& configuration, + OrthancPluginContext* context) + { + std::string path; + + { + char* pathTmp = OrthancPluginGetConfigurationPath(context); + if (pathTmp == NULL) + { + OrthancPluginLogError(context, "No configuration file is provided"); + return false; + } + + path = std::string(pathTmp); + + OrthancPluginFreeString(context, pathTmp); + } + + std::ifstream f(path.c_str()); + + Json::Reader reader; + if (!reader.parse(f, configuration) || + configuration.type() != Json::objectValue) + { + std::string s = "Unable to parse the configuration file: " + std::string(path); + OrthancPluginLogError(context, s.c_str()); + return false; + } + + return true; + } + + + std::string GetStringValue(const Json::Value& configuration, + const std::string& key, + const std::string& defaultValue) + { + if (configuration.type() != Json::objectValue || + !configuration.isMember(key) || + configuration[key].type() != Json::stringValue) + { + return defaultValue; + } + else + { + return configuration[key].asString(); + } + } + + + int GetIntegerValue(const Json::Value& configuration, + const std::string& key, + int defaultValue) + { + if (configuration.type() != Json::objectValue || + !configuration.isMember(key) || + configuration[key].type() != Json::intValue) + { + return defaultValue; + } + else + { + return configuration[key].asInt(); + } + } + + + bool ReadFile(std::string& content, + const std::string& path) + { + struct stat s; + if (stat(path.c_str(), &s) != 0 || + !(s.st_mode & S_IFREG)) + { + // Either the path does not exist, or it is not a regular file + return false; + } + + FILE* fp = fopen(path.c_str(), "rb"); + if (fp == NULL) + { + return false; + } + + long size; + + if (fseek(fp, 0, SEEK_END) == -1 || + (size = ftell(fp)) < 0) + { + fclose(fp); + return false; + } + + content.resize(size); + + if (fseek(fp, 0, SEEK_SET) == -1) + { + fclose(fp); + return false; + } + + bool ok = true; + + if (size > 0 && + fread(&content[0], size, 1, fp) != 1) + { + ok = false; + } + + fclose(fp); + + return ok; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugin/ViewerToolbox.h Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,68 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include <string> +#include <json/value.h> +#include <orthanc/OrthancCPlugin.h> + +namespace OrthancPlugins +{ + enum CacheBundle + { + CacheBundle_DecodedImage = 1, + CacheBundle_InstanceInformation = 2, + CacheBundle_SeriesInformation = 3 + }; + + bool GetStringFromOrthanc(std::string& content, + OrthancPluginContext* context, + const std::string& uri); + + bool GetJsonFromOrthanc(Json::Value& json, + OrthancPluginContext* context, + const std::string& uri); + + bool TokenizeVector(std::vector<float>& result, + const std::string& value, + unsigned int expectedSize); + + bool CompressUsingDeflate(std::string& compressed, + const void* uncompressed, + size_t uncompressedSize, + uint8_t compressionLevel); + + const char* GetMimeType(const std::string& path); + + bool ReadConfiguration(Json::Value& configuration, + OrthancPluginContext* context); + + std::string GetStringValue(const Json::Value& configuration, + const std::string& key, + const std::string& defaultValue); + + int GetIntegerValue(const Json::Value& configuration, + const std::string& key, + int defaultValue); + + bool ReadFile(std::string& content, + const std::string& path); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/README Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,64 @@ +Web Viewer plugin for Orthanc +============================= + + +General Information +------------------- + +This repository contains the source code of a plugin implementing a +Web viewer for Orthanc, the lightweight, RESTful DICOM server. + + +Dependencies +------------ + +The Web viewer is based upon the following projects: + +* Cornerstone, a client-side JavaScript library to display medical + images in Web browsers, by Chris Hafey <chafey@gmail.com>: + https://github.com/chafey/cornerstone + +* GDCM, an open-source implementation of the DICOM standard with + advanced features for image decoding, by Mathieu Malaterre + <mathieu.malaterre@gmail.com>: + http://sourceforge.net/projects/gdcm/ + + +Supported Platforms +------------------- + +Currently, the supported platforms are: + +* Linux 32bit. +* Linux 64bit. +* Windows 32bit. + + +Build Instructions +------------------ + +See: ./Resources/BuildInstructions.txt + + +Licensing +--------- + +The Web viewer plugin for Orthanc is licensed under the AGPL license. + +We also kindly ask scientific works and clinical studies that make +use of Orthanc to cite Orthanc in their associated publications. +Similarly, we ask open-source and closed-source products that make +use of Orthanc to warn us about this use. You can cite our work +using the following BibTeX entry: + +@inproceedings{Jodogne:ISBI2013, + author = {Jodogne, S. and Bernard, C. and Devillers, M. and Lenaerts, E. and Coucke, P.}, + title = {Orthanc -- {A} Lightweight, {REST}ful {DICOM} Server for Healthcare and Medical Research}, + booktitle={Biomedical Imaging ({ISBI}), {IEEE} 10th International Symposium on}, + year={2013}, + pages={190-193}, + ISSN={1945-7928}, + month=apr, + url={http://ieeexplore.ieee.org/xpl/articleDetails.jsp?tp=&arnumber=6556444}, + address={San Francisco, {CA}, {USA}} +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/BuildInstructions.txt Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,29 @@ +Generic Linux (static linking) +============================== + +# mkdir Build +# cd Build +# cmake .. -DCMAKE_BUILD_TYPE=Debug -DALLOW_DOWNLOADS=ON -DSTATIC_BUILD=ON +# make + + +Debian Sid (dynamic linking) +============================ + +# sudo apt-get install build-essential unzip cmake libgdcm2-dev libjpeg-dev \ + uuid-dev libgtest-dev libpng-dev libsqlite3-dev \ + zlib1g-dev libboost-all-dev libjsoncpp-dev + +# mkdir Build +# cd Build +# cmake .. -DCMAKE_BUILD_TYPE=Debug -DALLOW_DOWNLOADS=ON -DUSE_SYSTEM_GOOGLE_TEST=OFF -DSTANDALONE_BUILD=ON +# make + + +Cross-compiling for Windows from Linux using MinGW +================================================== + +# mkdir Build +# cd Build +# cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TOOLCHAIN_FILE=../Resources/MinGWToolchain.cmake +# make
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CMake/AutoGeneratedCode.cmake Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,59 @@ +# Orthanc - A Lightweight, RESTful DICOM Store +# Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics +# Department, University Hospital of Liege, Belgium +# +# This program is free software: you can redistribute it and/or +# modify it under the terms of the GNU Affero General Public License +# as published by the Free Software Foundation, either version 3 of +# the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + +set(AUTOGENERATED_DIR "${CMAKE_CURRENT_BINARY_DIR}/AUTOGENERATED") +set(AUTOGENERATED_SOURCES) + +file(MAKE_DIRECTORY ${AUTOGENERATED_DIR}) +include_directories(${AUTOGENERATED_DIR}) + +macro(EmbedResources) + # Convert a semicolon separated list to a whitespace separated string + set(SCRIPT_ARGUMENTS) + set(DEPENDENCIES) + set(IS_PATH_NAME false) + foreach(arg ${ARGN}) + if (${IS_PATH_NAME}) + list(APPEND SCRIPT_ARGUMENTS "${arg}") + list(APPEND DEPENDENCIES "${arg}") + set(IS_PATH_NAME false) + else() + list(APPEND SCRIPT_ARGUMENTS "${arg}") + set(IS_PATH_NAME true) + endif() + endforeach() + + set(TARGET_BASE "${AUTOGENERATED_DIR}/EmbeddedResources") + add_custom_command( + OUTPUT + "${TARGET_BASE}.h" + "${TARGET_BASE}.cpp" + COMMAND + python + "${CMAKE_CURRENT_SOURCE_DIR}/Resources/EmbedResources.py" + "${AUTOGENERATED_DIR}/EmbeddedResources" + ${SCRIPT_ARGUMENTS} + DEPENDS + "${CMAKE_CURRENT_SOURCE_DIR}/Resources/EmbedResources.py" + ${DEPENDENCIES} + ) + + list(APPEND AUTOGENERATED_SOURCES + "${AUTOGENERATED_DIR}/EmbeddedResources.cpp" + ) +endmacro()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CMake/BoostConfiguration.cmake Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,124 @@ +# Orthanc - A Lightweight, RESTful DICOM Store +# Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics +# Department, University Hospital of Liege, Belgium +# +# This program is free software: you can redistribute it and/or +# modify it under the terms of the GNU Affero General Public License +# as published by the Free Software Foundation, either version 3 of +# the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + +if (STATIC_BUILD OR NOT USE_SYSTEM_BOOST) + set(BOOST_STATIC 1) +else() + include(FindBoost) + set(BOOST_STATIC 0) + find_package(Boost COMPONENTS system thread filesystem) + + if (NOT Boost_FOUND) + message(FATAL_ERROR "Unable to locate Boost on this system") + endif() + + include_directories(${Boost_INCLUDE_DIRS}) + link_libraries(${Boost_LIBRARIES}) +endif() + + +if (BOOST_STATIC) + # Parameters for Boost 1.55.0 + set(BOOST_NAME boost_1_55_0) + set(BOOST_BCP_SUFFIX bcpdigest-0.7.4) + set(BOOST_MD5 "409f7a0e4fb1f5659d07114f3133b67b") + set(BOOST_FILESYSTEM_SOURCES_DIR "${BOOST_NAME}/libs/filesystem/src") + + set(BOOST_SOURCES_DIR ${CMAKE_BINARY_DIR}/${BOOST_NAME}) + DownloadPackage( + "${BOOST_MD5}" + "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/${BOOST_NAME}_${BOOST_BCP_SUFFIX}.tar.gz" + "${BOOST_SOURCES_DIR}" + ) + + add_definitions( + # Static build of Boost + -DBOOST_ALL_NO_LIB + -DBOOST_ALL_NOLIB + -DBOOST_DATE_TIME_NO_LIB + -DBOOST_THREAD_BUILD_LIB + -DBOOST_PROGRAM_OPTIONS_NO_LIB + -DBOOST_REGEX_NO_LIB + -DBOOST_SYSTEM_NO_LIB + -DBOOST_LOCALE_NO_LIB + ) + + if (${CMAKE_COMPILER_IS_GNUCXX}) + add_definitions(-isystem ${BOOST_SOURCES_DIR}) + endif() + + include_directories( + ${BOOST_SOURCES_DIR} + ) + + list(APPEND BOOST_SOURCES + ${BOOST_SOURCES_DIR}/libs/system/src/error_code.cpp + ) + + + ## Boost::thread + + if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR + ${CMAKE_SYSTEM_NAME} STREQUAL "Darwin" OR + ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD") + list(APPEND BOOST_SOURCES + ${BOOST_SOURCES_DIR}/libs/thread/src/pthread/once.cpp + ${BOOST_SOURCES_DIR}/libs/thread/src/pthread/thread.cpp + ) + + if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase") + add_definitions(-DBOOST_HAS_SCHED_YIELD=1) + endif() + + elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Windows") + list(APPEND BOOST_SOURCES + ${BOOST_SOURCES_DIR}/libs/thread/src/win32/tss_dll.cpp + ${BOOST_SOURCES_DIR}/libs/thread/src/win32/thread.cpp + ${BOOST_SOURCES_DIR}/libs/thread/src/win32/tss_pe.cpp + ) + endif() + + + ## Boost::filesystem + + list(APPEND BOOST_SOURCES + ${BOOST_FILESYSTEM_SOURCES_DIR}/codecvt_error_category.cpp + ${BOOST_FILESYSTEM_SOURCES_DIR}/operations.cpp + ${BOOST_FILESYSTEM_SOURCES_DIR}/path.cpp + ${BOOST_FILESYSTEM_SOURCES_DIR}/path_traits.cpp + ) + + if (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") + list(APPEND BOOST_SOURCES + ${BOOST_SOURCES_DIR}/libs/filesystem/src/utf8_codecvt_facet.cpp + ) + elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Windows") + list(APPEND BOOST_SOURCES + ${BOOST_FILESYSTEM_SOURCES_DIR}/windows_file_codecvt.cpp + ) + endif() + + + source_group(ThirdParty\\Boost REGULAR_EXPRESSION ${BOOST_SOURCES_DIR}/.*) +endif() + + +add_definitions( + -DBOOST_HAS_FILESYSTEM_V3=1 + -DBOOST_HAS_LOCALE=1 + )
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CMake/DownloadPackage.cmake Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,156 @@ +# Orthanc - A Lightweight, RESTful DICOM Store +# Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics +# Department, University Hospital of Liege, Belgium +# +# This program is free software: you can redistribute it and/or +# modify it under the terms of the GNU Affero General Public License +# as published by the Free Software Foundation, either version 3 of +# the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + +macro(GetUrlFilename TargetVariable Url) + string(REGEX REPLACE "^.*/" "" ${TargetVariable} "${Url}") +endmacro() + + +macro(GetUrlExtension TargetVariable Url) + #string(REGEX REPLACE "^.*/[^.]*\\." "" TMP "${Url}") + string(REGEX REPLACE "^.*\\." "" TMP "${Url}") + string(TOLOWER "${TMP}" "${TargetVariable}") +endmacro() + + +## +## Check the existence of the required decompression tools +## + +if ("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Windows") + find_program(ZIP_EXECUTABLE 7z + PATHS + "$ENV{ProgramFiles}/7-Zip" + "$ENV{ProgramW6432}/7-Zip" + ) + + if (${ZIP_EXECUTABLE} MATCHES "ZIP_EXECUTABLE-NOTFOUND") + message(FATAL_ERROR "Please install the '7-zip' software (http://www.7-zip.org/)") + endif() + +else() + find_program(UNZIP_EXECUTABLE unzip) + if (${UNZIP_EXECUTABLE} MATCHES "UNZIP_EXECUTABLE-NOTFOUND") + message(FATAL_ERROR "Please install the 'unzip' package") + endif() + + find_program(TAR_EXECUTABLE tar) + if (${TAR_EXECUTABLE} MATCHES "TAR_EXECUTABLE-NOTFOUND") + message(FATAL_ERROR "Please install the 'tar' package") + endif() +endif() + + +macro(DownloadPackage MD5 Url TargetDirectory) + if (NOT IS_DIRECTORY "${TargetDirectory}") + GetUrlFilename(TMP_FILENAME "${Url}") + + set(TMP_PATH "${CMAKE_SOURCE_DIR}/ThirdPartyDownloads/${TMP_FILENAME}") + if (NOT EXISTS "${TMP_PATH}") + message("Downloading ${Url}") + + # This fixes issue 6: "I think cmake shouldn't download the + # packages which are not in the system, it should stop and let + # user know." + # https://code.google.com/p/orthanc/issues/detail?id=6 + if (NOT STATIC_BUILD AND NOT ALLOW_DOWNLOADS) + message(FATAL_ERROR "CMake is not allowed to download from Internet. Please set the ALLOW_DOWNLOADS option to ON") + endif() + + file(DOWNLOAD "${Url}" "${TMP_PATH}" SHOW_PROGRESS EXPECTED_MD5 "${MD5}") + else() + message("Using local copy of ${Url}") + endif() + + GetUrlExtension(TMP_EXTENSION "${Url}") + #message(${TMP_EXTENSION}) + message("Uncompressing ${TMP_FILENAME}") + + if ("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Windows") + # How to silently extract files using 7-zip + # http://superuser.com/questions/331148/7zip-command-line-extract-silently-quietly + + if (("${TMP_EXTENSION}" STREQUAL "gz") OR ("${TMP_EXTENSION}" STREQUAL "tgz")) + execute_process( + COMMAND ${ZIP_EXECUTABLE} e -y ${TMP_PATH} + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + RESULT_VARIABLE Failure + OUTPUT_QUIET + ) + + if (Failure) + message(FATAL_ERROR "Error while running the uncompression tool") + endif() + + if ("${TMP_EXTENSION}" STREQUAL "tgz") + string(REGEX REPLACE ".tgz$" ".tar" TMP_FILENAME2 "${TMP_FILENAME}") + else() + string(REGEX REPLACE ".gz$" "" TMP_FILENAME2 "${TMP_FILENAME}") + endif() + + execute_process( + COMMAND ${ZIP_EXECUTABLE} x -y ${TMP_FILENAME2} + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + RESULT_VARIABLE Failure + OUTPUT_QUIET + ) + elseif ("${TMP_EXTENSION}" STREQUAL "zip") + execute_process( + COMMAND ${ZIP_EXECUTABLE} x -y ${TMP_PATH} + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + RESULT_VARIABLE Failure + OUTPUT_QUIET + ) + else() + message(FATAL_ERROR "Support your platform here") + endif() + + else() + if ("${TMP_EXTENSION}" STREQUAL "zip") + execute_process( + COMMAND sh -c "${UNZIP_EXECUTABLE} -q ${TMP_PATH}" + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + RESULT_VARIABLE Failure + ) + elseif (("${TMP_EXTENSION}" STREQUAL "gz") OR ("${TMP_EXTENSION}" STREQUAL "tgz")) + #message("tar xvfz ${TMP_PATH}") + execute_process( + COMMAND sh -c "${TAR_EXECUTABLE} xfz ${TMP_PATH}" + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + RESULT_VARIABLE Failure + ) + elseif ("${TMP_EXTENSION}" STREQUAL "bz2") + execute_process( + COMMAND sh -c "${TAR_EXECUTABLE} xfj ${TMP_PATH}" + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + RESULT_VARIABLE Failure + ) + else() + message(FATAL_ERROR "Unknown package format.") + endif() + endif() + + if (Failure) + message(FATAL_ERROR "Error while running the uncompression tool") + endif() + + if (NOT IS_DIRECTORY "${TargetDirectory}") + message(FATAL_ERROR "The package was not uncompressed at the proper location. Check the CMake instructions.") + endif() + endif() +endmacro()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CMake/GdcmConfiguration.cmake Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,86 @@ +# Orthanc - A Lightweight, RESTful DICOM Store +# Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics +# Department, University Hospital of Liege, Belgium +# +# This program is free software: you can redistribute it and/or +# modify it under the terms of the GNU Affero General Public License +# as published by the Free Software Foundation, either version 3 of +# the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + +if (STATIC_BUILD OR NOT USE_SYSTEM_GDCM) + # If using gcc, build GDCM with the "-fPIC" argument to allow its + # embedding into the shared library containing the Orthanc plugin + if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + set(Flags -DCMAKE_CXX_FLAGS:STRING=-fPIC -DCMAKE_C_FLAGS:STRING=-fPIC) + else() + endif() + + if (CMAKE_TOOLCHAIN_FILE) + list(APPEND Flags -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}) + endif() + + include(ExternalProject) + externalproject_add(GDCM + URL "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/gdcm-2.4.4.tar.gz" + URL_MD5 "5dca87a061c536b6fa377263b7839dcb" + CMAKE_ARGS -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE} ${Flags} + #-DLIBRARY_OUTPUT_PATH=${CMAKE_CURRENT_BINARY_DIR} + INSTALL_COMMAND "" # Skip the install step + ) + + if(MSVC) + set(Suffix ".lib") + else() + set(Suffix ".a") + endif() + + list(GET CMAKE_FIND_LIBRARY_PREFIXES 0 Prefix) + set(GDCM_LIBRARIES + ${Prefix}gdcmMSFF${Suffix} + ${Prefix}gdcmcharls${Suffix} + ${Prefix}gdcmDICT${Suffix} + ${Prefix}gdcmDSED${Suffix} + ${Prefix}gdcmIOD${Suffix} + ${Prefix}gdcmjpeg8${Suffix} + ${Prefix}gdcmjpeg12${Suffix} + ${Prefix}gdcmjpeg16${Suffix} + ${Prefix}gdcmMEXD${Suffix} + ${Prefix}gdcmopenjpeg${Suffix} + ${Prefix}gdcmzlib${Suffix} + ${Prefix}socketxx${Suffix} + ${Prefix}gdcmCommon${Suffix} + ${Prefix}gdcmexpat${Suffix} + + #${Prefix}gdcmgetopt${Suffix} + #${Prefix}gdcmuuid${Suffix} + ) + + ExternalProject_Get_Property(GDCM binary_dir) + include_directories(${binary_dir}/Source/Common) + link_directories(${binary_dir}/bin) + + ExternalProject_Get_Property(GDCM source_dir) + include_directories( + ${source_dir}/Source/Common + ${source_dir}/Source/MediaStorageAndFileFormat + ${source_dir}/Source/DataStructureAndEncodingDefinition + ) + +else() + find_package(GDCM REQUIRED) + if (GDCM_FOUND) + include(${GDCM_USE_FILE}) + set(GDCM_LIBRARIES gdcmCommon gdcmMSFF) + else(GDCM_FOUND) + message(FATAL_ERROR "Cannot find GDCM, did you set GDCM_DIR?") + endif(GDCM_FOUND) +endif()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CMake/GoogleTestConfiguration.cmake Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,57 @@ +# Orthanc - A Lightweight, RESTful DICOM Store +# Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics +# Department, University Hospital of Liege, Belgium +# +# This program is free software: you can redistribute it and/or +# modify it under the terms of the GNU Affero General Public License +# as published by the Free Software Foundation, either version 3 of +# the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + +if (USE_GTEST_DEBIAN_SOURCE_PACKAGE) + set(GTEST_SOURCES /usr/src/gtest/src/gtest-all.cc) + include_directories(/usr/src/gtest) + + if (NOT EXISTS /usr/include/gtest/gtest.h OR + NOT EXISTS ${GTEST_SOURCES}) + message(FATAL_ERROR "Please install the libgtest-dev package") + endif() + +elseif (STATIC_BUILD OR NOT USE_SYSTEM_GOOGLE_TEST) + set(GTEST_SOURCES_DIR ${CMAKE_BINARY_DIR}/gtest-1.7.0) + DownloadPackage( + "2d6ec8ccdf5c46b05ba54a9fd1d130d7" + "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/gtest-1.7.0.zip" + "${GTEST_SOURCES_DIR}") + + include_directories( + ${GTEST_SOURCES_DIR}/include + ${GTEST_SOURCES_DIR} + ) + + set(GTEST_SOURCES + ${GTEST_SOURCES_DIR}/src/gtest-all.cc + ) + + # https://code.google.com/p/googletest/issues/detail?id=412 + if (MSVC) # VS2012 does not support tuples correctly yet + add_definitions(/D _VARIADIC_MAX=10) + endif() + +else() + include(FindGTest) + if (NOT GTEST_FOUND) + message(FATAL_ERROR "Unable to find GoogleTest") + endif() + + include_directories(${GTEST_INCLUDE_DIRS}) + link_libraries(${GTEST_LIBRARIES}) +endif()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CMake/JavaScriptLibraries.cmake Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,68 @@ +# Orthanc - A Lightweight, RESTful DICOM Store +# Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics +# Department, University Hospital of Liege, Belgium +# +# This program is free software: you can redistribute it and/or +# modify it under the terms of the GNU Affero General Public License +# as published by the Free Software Foundation, either version 3 of +# the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + +set(BASE_URL "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/WebViewer") + +DownloadPackage( + "eef2d2e8c057d33c20ab55fdd06a73c7" + "${BASE_URL}/cornerstone-0.7.1.zip" + "cornerstone-0.7.1") + +DownloadPackage( + "cb943ac26be9ee755e8741ea232389e2" + "${BASE_URL}/jquery-ui-1.11.3.zip" + "jquery-ui-1.11.3") + +DownloadPackage( + "8f27231a78218b959159e37daa3d86b3" + "${BASE_URL}/jsPanel-2.3.3.zip" + "jspanel") + +DownloadPackage( + "8392ad105d913c3a83a7787c8f148055" + "${BASE_URL}/pako-0.2.5.zip" + "pako-0.2.5") + +DownloadPackage( + "7ebea0b624cd62445a124d264dfa2a53" + "${BASE_URL}/js-url-1.8.6.zip" + "js-url-1.8.6") + + +set(JAVASCRIPT_LIBS_DIR ${CMAKE_CURRENT_BINARY_DIR}/javascript-libs) +file(MAKE_DIRECTORY ${JAVASCRIPT_LIBS_DIR}) + +file(COPY + ${CMAKE_CURRENT_BINARY_DIR}/cornerstone-0.7.1/dist/cornerstone.min.css + ${CMAKE_CURRENT_BINARY_DIR}/cornerstone-0.7.1/dist/cornerstone.min.js + ${CMAKE_CURRENT_BINARY_DIR}/jquery-ui-1.11.3/external/jquery/jquery.js + ${CMAKE_CURRENT_BINARY_DIR}/jquery-ui-1.11.3/images + ${CMAKE_CURRENT_BINARY_DIR}/jquery-ui-1.11.3/jquery-ui.min.css + ${CMAKE_CURRENT_BINARY_DIR}/jquery-ui-1.11.3/jquery-ui.min.js + ${CMAKE_CURRENT_BINARY_DIR}/jquery-ui-1.11.3/jquery-ui.theme.min.css + ${CMAKE_CURRENT_BINARY_DIR}/js-url-1.8.6/url.min.js + ${CMAKE_CURRENT_BINARY_DIR}/jspanel/fonts + ${CMAKE_CURRENT_BINARY_DIR}/jspanel/images + ${CMAKE_CURRENT_BINARY_DIR}/jspanel/jquery.jspanel.min.css + ${CMAKE_CURRENT_BINARY_DIR}/jspanel/jquery.jspanel.min.js + ${CMAKE_CURRENT_BINARY_DIR}/vendor/jquery.ui.touch-punch.min.js + ${CMAKE_CURRENT_BINARY_DIR}/vendor/mobile-detect.min.js + ${CMAKE_CURRENT_BINARY_DIR}/pako-0.2.5/dist/pako_inflate.min.js + DESTINATION + ${JAVASCRIPT_LIBS_DIR} + )
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CMake/JsonCppConfiguration.cmake Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,47 @@ +# Orthanc - A Lightweight, RESTful DICOM Store +# Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics +# Department, University Hospital of Liege, Belgium +# +# This program is free software: you can redistribute it and/or +# modify it under the terms of the GNU Affero General Public License +# as published by the Free Software Foundation, either version 3 of +# the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + +if (STATIC_BUILD OR NOT USE_SYSTEM_JSONCPP) + set(JSONCPP_SOURCES_DIR ${CMAKE_BINARY_DIR}/jsoncpp-src-0.6.0-rc2) + DownloadPackage( + "363e2f4cbd3aeb63bf4e571f377400fb" + "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/jsoncpp-src-0.6.0-rc2.tar.gz" + "${JSONCPP_SOURCES_DIR}") + + list(APPEND JSONCPP_SOURCES + ${JSONCPP_SOURCES_DIR}/src/lib_json/json_reader.cpp + ${JSONCPP_SOURCES_DIR}/src/lib_json/json_value.cpp + ${JSONCPP_SOURCES_DIR}/src/lib_json/json_writer.cpp + ) + + include_directories( + ${JSONCPP_SOURCES_DIR}/include + ) + + source_group(ThirdParty\\JsonCpp REGULAR_EXPRESSION ${JSONCPP_SOURCES_DIR}/.*) + +else() + CHECK_INCLUDE_FILE_CXX(jsoncpp/json/reader.h HAVE_JSONCPP_H) + if (NOT HAVE_JSONCPP_H) + message(FATAL_ERROR "Please install the libjsoncpp-dev package") + endif() + + include_directories(/usr/include/jsoncpp) + link_libraries(jsoncpp) + +endif()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CMake/LibJpegConfiguration.cmake Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,104 @@ +# Orthanc - A Lightweight, RESTful DICOM Store +# Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics +# Department, University Hospital of Liege, Belgium +# +# This program is free software: you can redistribute it and/or +# modify it under the terms of the GNU Affero General Public License +# as published by the Free Software Foundation, either version 3 of +# the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + +if (STATIC_BUILD OR NOT USE_SYSTEM_LIBJPEG) + set(LIBJPEG_SOURCES_DIR ${CMAKE_BINARY_DIR}/jpeg-9a) + DownloadPackage( + "3353992aecaee1805ef4109aadd433e7" + "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/jpegsrc.v9a.tar.gz" + "${LIBJPEG_SOURCES_DIR}") + + include_directories( + ${LIBJPEG_SOURCES_DIR}/ + ) + + list(APPEND LIBJPEG_SOURCES + ${LIBJPEG_SOURCES_DIR}/jaricom.c + ${LIBJPEG_SOURCES_DIR}/jcapimin.c + ${LIBJPEG_SOURCES_DIR}/jcapistd.c + ${LIBJPEG_SOURCES_DIR}/jcarith.c + ${LIBJPEG_SOURCES_DIR}/jccoefct.c + ${LIBJPEG_SOURCES_DIR}/jccolor.c + ${LIBJPEG_SOURCES_DIR}/jcdctmgr.c + ${LIBJPEG_SOURCES_DIR}/jchuff.c + ${LIBJPEG_SOURCES_DIR}/jcinit.c + ${LIBJPEG_SOURCES_DIR}/jcmarker.c + ${LIBJPEG_SOURCES_DIR}/jcmaster.c + ${LIBJPEG_SOURCES_DIR}/jcomapi.c + ${LIBJPEG_SOURCES_DIR}/jcparam.c + ${LIBJPEG_SOURCES_DIR}/jcprepct.c + ${LIBJPEG_SOURCES_DIR}/jcsample.c + ${LIBJPEG_SOURCES_DIR}/jctrans.c + ${LIBJPEG_SOURCES_DIR}/jdapimin.c + ${LIBJPEG_SOURCES_DIR}/jdapistd.c + ${LIBJPEG_SOURCES_DIR}/jdarith.c + ${LIBJPEG_SOURCES_DIR}/jdatadst.c + ${LIBJPEG_SOURCES_DIR}/jdatasrc.c + ${LIBJPEG_SOURCES_DIR}/jdcoefct.c + ${LIBJPEG_SOURCES_DIR}/jdcolor.c + ${LIBJPEG_SOURCES_DIR}/jddctmgr.c + ${LIBJPEG_SOURCES_DIR}/jdhuff.c + ${LIBJPEG_SOURCES_DIR}/jdinput.c + ${LIBJPEG_SOURCES_DIR}/jcmainct.c + ${LIBJPEG_SOURCES_DIR}/jdmainct.c + ${LIBJPEG_SOURCES_DIR}/jdmarker.c + ${LIBJPEG_SOURCES_DIR}/jdmaster.c + ${LIBJPEG_SOURCES_DIR}/jdmerge.c + ${LIBJPEG_SOURCES_DIR}/jdpostct.c + ${LIBJPEG_SOURCES_DIR}/jdsample.c + ${LIBJPEG_SOURCES_DIR}/jdtrans.c + ${LIBJPEG_SOURCES_DIR}/jerror.c + ${LIBJPEG_SOURCES_DIR}/jfdctflt.c + ${LIBJPEG_SOURCES_DIR}/jfdctfst.c + ${LIBJPEG_SOURCES_DIR}/jfdctint.c + ${LIBJPEG_SOURCES_DIR}/jidctflt.c + ${LIBJPEG_SOURCES_DIR}/jidctfst.c + ${LIBJPEG_SOURCES_DIR}/jidctint.c + #${LIBJPEG_SOURCES_DIR}/jmemansi.c + #${LIBJPEG_SOURCES_DIR}/jmemdos.c + #${LIBJPEG_SOURCES_DIR}/jmemmac.c + ${LIBJPEG_SOURCES_DIR}/jmemmgr.c + #${LIBJPEG_SOURCES_DIR}/jmemname.c + ${LIBJPEG_SOURCES_DIR}/jmemnobs.c + ${LIBJPEG_SOURCES_DIR}/jquant1.c + ${LIBJPEG_SOURCES_DIR}/jquant2.c + ${LIBJPEG_SOURCES_DIR}/jutils.c + + # ${LIBJPEG_SOURCES_DIR}/rdbmp.c + # ${LIBJPEG_SOURCES_DIR}/rdcolmap.c + # ${LIBJPEG_SOURCES_DIR}/rdgif.c + # ${LIBJPEG_SOURCES_DIR}/rdppm.c + # ${LIBJPEG_SOURCES_DIR}/rdrle.c + # ${LIBJPEG_SOURCES_DIR}/rdswitch.c + # ${LIBJPEG_SOURCES_DIR}/rdtarga.c + # ${LIBJPEG_SOURCES_DIR}/transupp.c + # ${LIBJPEG_SOURCES_DIR}/wrbmp.c + # ${LIBJPEG_SOURCES_DIR}/wrgif.c + # ${LIBJPEG_SOURCES_DIR}/wrppm.c + # ${LIBJPEG_SOURCES_DIR}/wrrle.c + # ${LIBJPEG_SOURCES_DIR}/wrtarga.c + ) + + configure_file( + ${LIBJPEG_SOURCES_DIR}/jconfig.txt + ${LIBJPEG_SOURCES_DIR}/jconfig.h COPYONLY + ) + +else() + link_libraries(jpeg) +endif()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CMake/LibPngConfiguration.cmake Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,78 @@ +# Orthanc - A Lightweight, RESTful DICOM Store +# Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics +# Department, University Hospital of Liege, Belgium +# +# This program is free software: you can redistribute it and/or +# modify it under the terms of the GNU Affero General Public License +# as published by the Free Software Foundation, either version 3 of +# the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + +if (STATIC_BUILD OR NOT USE_SYSTEM_LIBPNG) + SET(LIBPNG_SOURCES_DIR ${CMAKE_BINARY_DIR}/libpng-1.5.12) + DownloadPackage( + "8ea7f60347a306c5faf70b977fa80e28" + "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/libpng-1.5.12.tar.gz" + "${LIBPNG_SOURCES_DIR}") + + include_directories( + ${LIBPNG_SOURCES_DIR} + ) + + configure_file( + ${LIBPNG_SOURCES_DIR}/scripts/pnglibconf.h.prebuilt + ${LIBPNG_SOURCES_DIR}/pnglibconf.h + COPY_ONLY) + + set(LIBPNG_SOURCES + #${LIBPNG_SOURCES_DIR}/example.c + ${LIBPNG_SOURCES_DIR}/png.c + ${LIBPNG_SOURCES_DIR}/pngerror.c + ${LIBPNG_SOURCES_DIR}/pngget.c + ${LIBPNG_SOURCES_DIR}/pngmem.c + ${LIBPNG_SOURCES_DIR}/pngpread.c + ${LIBPNG_SOURCES_DIR}/pngread.c + ${LIBPNG_SOURCES_DIR}/pngrio.c + ${LIBPNG_SOURCES_DIR}/pngrtran.c + ${LIBPNG_SOURCES_DIR}/pngrutil.c + ${LIBPNG_SOURCES_DIR}/pngset.c + #${LIBPNG_SOURCES_DIR}/pngtest.c + ${LIBPNG_SOURCES_DIR}/pngtrans.c + ${LIBPNG_SOURCES_DIR}/pngwio.c + ${LIBPNG_SOURCES_DIR}/pngwrite.c + ${LIBPNG_SOURCES_DIR}/pngwtran.c + ${LIBPNG_SOURCES_DIR}/pngwutil.c + ) + + #set_property( + # SOURCE ${LIBPNG_SOURCES} + # PROPERTY COMPILE_FLAGS -UHAVE_CONFIG_H) + + list(APPEND THIRD_PARTY_SOURCES ${LIBPNG_SOURCES}) + + add_definitions( + -DPNG_NO_CONSOLE_IO=1 + -DPNG_NO_STDIO=1 + ) + + source_group(ThirdParty\\Libpng REGULAR_EXPRESSION ${LIBPNG_SOURCES_DIR}/.*) + +else() + include(FindPNG) + + if (NOT ${PNG_FOUND}) + message(FATAL_ERROR "Unable to find LibPNG") + endif() + + include_directories(${PNG_INCLUDE_DIRS}) + link_libraries(${PNG_LIBRARIES}) + add_definitions(${PNG_DEFINITIONS}) +endif()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CMake/SQLiteConfiguration.cmake Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,61 @@ +# Orthanc - A Lightweight, RESTful DICOM Store +# Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics +# Department, University Hospital of Liege, Belgium +# +# This program is free software: you can redistribute it and/or +# modify it under the terms of the GNU Affero General Public License +# as published by the Free Software Foundation, either version 3 of +# the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + +if (STATIC_BUILD OR NOT USE_SYSTEM_SQLITE) + SET(SQLITE_SOURCES_DIR ${CMAKE_BINARY_DIR}/sqlite-amalgamation-3071300) + DownloadPackage( + "5fbeff9645ab035a1f580e90b279a16d" + "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/sqlite-amalgamation-3071300.zip" + "${SQLITE_SOURCES_DIR}") + + list(APPEND SQLITE_SOURCES + ${SQLITE_SOURCES_DIR}/sqlite3.c + ) + + add_definitions( + # For SQLite to run in the "Serialized" thread-safe mode + # http://www.sqlite.org/threadsafe.html + -DSQLITE_THREADSAFE=1 + -DSQLITE_OMIT_LOAD_EXTENSION # Disable SQLite plugins + ) + + include_directories( + ${SQLITE_SOURCES_DIR} + ) + + source_group(ThirdParty\\SQLite REGULAR_EXPRESSION ${SQLITE_SOURCES_DIR}/.*) + +else() + CHECK_INCLUDE_FILE_CXX(sqlite3.h HAVE_SQLITE_H) + if (NOT HAVE_SQLITE_H) + message(FATAL_ERROR "Please install the libsqlite3-dev package") + endif() + + # Autodetection of the version of SQLite + file(STRINGS "/usr/include/sqlite3.h" SQLITE_VERSION_NUMBER1 REGEX "#define SQLITE_VERSION_NUMBER.*$") + string(REGEX REPLACE "#define SQLITE_VERSION_NUMBER(.*)$" "\\1" SQLITE_VERSION_NUMBER ${SQLITE_VERSION_NUMBER1}) + + message("Detected version of SQLite: ${SQLITE_VERSION_NUMBER}") + + IF (${SQLITE_VERSION_NUMBER} LESS 3007000) + # "sqlite3_create_function_v2" is not defined in SQLite < 3.7.0 + message(FATAL_ERROR "SQLite version must be above 3.7.0. Please set the CMake variable USE_SYSTEM_SQLITE to OFF.") + ENDIF() + + link_libraries(sqlite3) +endif()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CMake/ZlibConfiguration.cmake Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,60 @@ +# Orthanc - A Lightweight, RESTful DICOM Store +# Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics +# Department, University Hospital of Liege, Belgium +# +# This program is free software: you can redistribute it and/or +# modify it under the terms of the GNU Affero General Public License +# as published by the Free Software Foundation, either version 3 of +# the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + +# This is the minizip distribution to create ZIP files +list(APPEND THIRD_PARTY_SOURCES + ${ORTHANC_ROOT}/Resources/ThirdParty/minizip/ioapi.c + ${ORTHANC_ROOT}/Resources/ThirdParty/minizip/zip.c + ) + +if (STATIC_BUILD OR NOT USE_SYSTEM_ZLIB) + SET(ZLIB_SOURCES_DIR ${CMAKE_BINARY_DIR}/zlib-1.2.7) + DownloadPackage( + "60df6a37c56e7c1366cca812414f7b85" + "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/zlib-1.2.7.tar.gz" + "${ZLIB_SOURCES_DIR}") + + include_directories( + ${ZLIB_SOURCES_DIR} + ) + + list(APPEND ZLIB_SOURCES + ${ZLIB_SOURCES_DIR}/adler32.c + ${ZLIB_SOURCES_DIR}/compress.c + ${ZLIB_SOURCES_DIR}/crc32.c + ${ZLIB_SOURCES_DIR}/deflate.c + ${ZLIB_SOURCES_DIR}/gzclose.c + ${ZLIB_SOURCES_DIR}/gzlib.c + ${ZLIB_SOURCES_DIR}/gzread.c + ${ZLIB_SOURCES_DIR}/gzwrite.c + ${ZLIB_SOURCES_DIR}/infback.c + ${ZLIB_SOURCES_DIR}/inffast.c + ${ZLIB_SOURCES_DIR}/inflate.c + ${ZLIB_SOURCES_DIR}/inftrees.c + ${ZLIB_SOURCES_DIR}/trees.c + ${ZLIB_SOURCES_DIR}/uncompr.c + ${ZLIB_SOURCES_DIR}/zutil.c + ) + +else() + include(FindZLIB) + include_directories(${ZLIB_INCLUDE_DIRS}) + link_libraries(${ZLIB_LIBRARIES}) +endif() + +source_group(ThirdParty\\ZLib REGULAR_EXPRESSION ${ZLIB_SOURCES_DIR}/.*)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/EmbedResources.py Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,391 @@ +# Orthanc - A Lightweight, RESTful DICOM Store +# Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics +# Department, University Hospital of Liege, Belgium +# +# This program is free software: you can redistribute it and/or +# modify it under the terms of the GNU Affero General Public License +# as published by the Free Software Foundation, either version 3 of +# the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + +import sys +import os +import os.path +import pprint +import re + +UPCASE_CHECK = True +ARGS = [] +for i in range(len(sys.argv)): + if not sys.argv[i].startswith('--'): + ARGS.append(sys.argv[i]) + elif sys.argv[i].lower() == '--no-upcase-check': + UPCASE_CHECK = False + +if len(ARGS) < 2 or len(ARGS) % 2 != 0: + print ('Usage:') + print ('python %s [--no-upcase-check] <TargetBaseFilename> [ <Name> <Source> ]*' % sys.argv[0]) + exit(-1) + +TARGET_BASE_FILENAME = ARGS[1] +SOURCES = ARGS[2:] + +try: + # Make sure the destination directory exists + os.makedirs(os.path.normpath(os.path.join(TARGET_BASE_FILENAME, '..'))) +except: + pass + + +##################################################################### +## Read each resource file +##################################################################### + +def CheckNoUpcase(s): + global UPCASE_CHECK + if (UPCASE_CHECK and + re.search('[A-Z]', s) != None): + raise Exception("Path in a directory with an upcase letter: %s" % s) + +resources = {} + +counter = 0 +i = 0 +while i < len(SOURCES): + resourceName = SOURCES[i].upper() + pathName = SOURCES[i + 1] + + if not os.path.exists(pathName): + raise Exception("Non existing path: %s" % pathName) + + if resourceName in resources: + raise Exception("Twice the same resource: " + resourceName) + + if os.path.isdir(pathName): + # The resource is a directory: Recursively explore its files + content = {} + for root, dirs, files in os.walk(pathName): + base = os.path.relpath(root, pathName) + + # Fix issue #24 (Build fails on OSX when directory has .DS_Store files): + # Ignore folders whose name starts with a dot (".") + if base.find('/.') != -1: + print('Ignoring folder: %s' % root) + continue + + for f in files: + if f.find('~') == -1: # Ignore Emacs backup files + if base == '.': + r = f + else: + r = os.path.join(base, f) + + CheckNoUpcase(r) + r = '/' + r.replace('\\', '/') + if r in content: + raise Exception("Twice the same filename (check case): " + r) + + content[r] = { + 'Filename' : os.path.join(root, f), + 'Index' : counter + } + counter += 1 + + resources[resourceName] = { + 'Type' : 'Directory', + 'Files' : content + } + + elif os.path.isfile(pathName): + resources[resourceName] = { + 'Type' : 'File', + 'Index' : counter, + 'Filename' : pathName + } + counter += 1 + + else: + raise Exception("Not a regular file, nor a directory: " + pathName) + + i += 2 + +#pprint.pprint(resources) + + +##################################################################### +## Write .h header +##################################################################### + +header = open(TARGET_BASE_FILENAME + '.h', 'w') + +header.write(""" +#pragma once + +#include <string> +#include <list> + +namespace OrthancPlugins +{ + namespace EmbeddedResources + { + enum FileResourceId + { +""") + +isFirst = True +for name in resources: + if resources[name]['Type'] == 'File': + if isFirst: + isFirst = False + else: + header.write(',\n') + header.write(' %s' % name) + +header.write(""" + }; + + enum DirectoryResourceId + { +""") + +isFirst = True +for name in resources: + if resources[name]['Type'] == 'Directory': + if isFirst: + isFirst = False + else: + header.write(',\n') + header.write(' %s' % name) + +header.write(""" + }; + + const void* GetFileResourceBuffer(FileResourceId id); + size_t GetFileResourceSize(FileResourceId id); + void GetFileResource(std::string& result, FileResourceId id); + + const void* GetDirectoryResourceBuffer(DirectoryResourceId id, const char* path); + size_t GetDirectoryResourceSize(DirectoryResourceId id, const char* path); + void GetDirectoryResource(std::string& result, DirectoryResourceId id, const char* path); + + void ListResources(std::list<std::string>& result, DirectoryResourceId id); + } +} +""") +header.close() + + + +##################################################################### +## Write the resource content in the .cpp source +##################################################################### + +PYTHON_MAJOR_VERSION = sys.version_info[0] + +def WriteResource(cpp, item): + cpp.write(' static const uint8_t resource%dBuffer[] = {' % item['Index']) + + f = open(item['Filename'], "rb") + content = f.read() + f.close() + + # http://stackoverflow.com/a/1035360 + pos = 0 + for b in content: + if PYTHON_MAJOR_VERSION == 2: + c = ord(b[0]) + else: + c = b + + if pos > 0: + cpp.write(', ') + + if (pos % 16) == 0: + cpp.write('\n ') + + if c < 0: + raise Exception("Internal error") + + cpp.write("0x%02x" % c) + pos += 1 + + cpp.write(' };\n') + cpp.write(' static const size_t resource%dSize = %d;\n' % (item['Index'], pos)) + + +cpp = open(TARGET_BASE_FILENAME + '.cpp', 'w') + +cpp.write(""" +#include "%s.h" + +#include <stdexcept> +#include <stdint.h> +#include <string.h> + +namespace OrthancPlugins +{ + namespace EmbeddedResources + { +""" % (os.path.basename(TARGET_BASE_FILENAME))) + + +for name in resources: + if resources[name]['Type'] == 'File': + WriteResource(cpp, resources[name]) + else: + for f in resources[name]['Files']: + WriteResource(cpp, resources[name]['Files'][f]) + + + +##################################################################### +## Write the accessors to the file resources in .cpp +##################################################################### + +cpp.write(""" + const void* GetFileResourceBuffer(FileResourceId id) + { + switch (id) + { +""") +for name in resources: + if resources[name]['Type'] == 'File': + cpp.write(' case %s:\n' % name) + cpp.write(' return resource%dBuffer;\n' % resources[name]['Index']) + +cpp.write(""" + default: + throw std::runtime_error("Parameter out of range"); + } + } + + size_t GetFileResourceSize(FileResourceId id) + { + switch (id) + { +""") + +for name in resources: + if resources[name]['Type'] == 'File': + cpp.write(' case %s:\n' % name) + cpp.write(' return resource%dSize;\n' % resources[name]['Index']) + +cpp.write(""" + default: + throw std::runtime_error("Parameter out of range"); + } + } +""") + + + +##################################################################### +## Write the accessors to the directory resources in .cpp +##################################################################### + +cpp.write(""" + const void* GetDirectoryResourceBuffer(DirectoryResourceId id, const char* path) + { + switch (id) + { +""") + +for name in resources: + if resources[name]['Type'] == 'Directory': + cpp.write(' case %s:\n' % name) + isFirst = True + for path in resources[name]['Files']: + cpp.write(' if (!strcmp(path, "%s"))\n' % path) + cpp.write(' return resource%dBuffer;\n' % resources[name]['Files'][path]['Index']) + cpp.write(' throw std::runtime_error("Unknown path in a directory resource");\n\n') + +cpp.write(""" default: + throw std::runtime_error("Parameter out of range"); + } + } + + size_t GetDirectoryResourceSize(DirectoryResourceId id, const char* path) + { + switch (id) + { +""") + +for name in resources: + if resources[name]['Type'] == 'Directory': + cpp.write(' case %s:\n' % name) + isFirst = True + for path in resources[name]['Files']: + cpp.write(' if (!strcmp(path, "%s"))\n' % path) + cpp.write(' return resource%dSize;\n' % resources[name]['Files'][path]['Index']) + cpp.write(' throw std::runtime_error("Unknown path in a directory resource");\n\n') + +cpp.write(""" default: + throw std::runtime_error("Parameter out of range"); + } + } +""") + + + + +##################################################################### +## List the resources in a directory +##################################################################### + +cpp.write(""" + void ListResources(std::list<std::string>& result, DirectoryResourceId id) + { + result.clear(); + + switch (id) + { +""") + +for name in resources: + if resources[name]['Type'] == 'Directory': + cpp.write(' case %s:\n' % name) + for path in sorted(resources[name]['Files']): + cpp.write(' result.push_back("%s");\n' % path) + cpp.write(' break;\n\n') + +cpp.write(""" default: + throw std::runtime_error("Parameter out of range"); + } + } +""") + + + + +##################################################################### +## Write the convenience wrappers in .cpp +##################################################################### + +cpp.write(""" + void GetFileResource(std::string& result, FileResourceId id) + { + size_t size = GetFileResourceSize(id); + result.resize(size); + if (size > 0) + memcpy(&result[0], GetFileResourceBuffer(id), size); + } + + void GetDirectoryResource(std::string& result, DirectoryResourceId id, const char* path) + { + size_t size = GetDirectoryResourceSize(id, path); + result.resize(size); + if (size > 0) + memcpy(&result[0], GetDirectoryResourceBuffer(id, path), size); + } + } +} +""") +cpp.close()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/ImplementationNotes.txt Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,17 @@ +Possible combinations returned by the plugin: + +Compression | Color | Depth (bpp) | Colorspace | Stretched +------------+-------+-------------+------------+----------- +Deflate | False | 16 | int16_t | Never +JPEG | False | 8 | uint8_t | Possible +Deflate | True | 8 | RGB24 | Never +JPEG | True | 8 | RGB24 | Never + + +In the viewer, grayscale images are always converted to int16_t. + + + +Note for Cornerstone < 0.7.1: 1 must be added to "maxPixelValue", as +the range [minPixelValue, maxPixelValue[ is taken into consideration +by Cornerstone (i.e. "maxPixelValue" is not inclusive).
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/MinGWToolchain.cmake Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,37 @@ +# Orthanc - A Lightweight, RESTful DICOM Store +# Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics +# Department, University Hospital of Liege, Belgium +# +# This program is free software: you can redistribute it and/or +# modify it under the terms of the GNU Affero General Public License +# as published by the Free Software Foundation, either version 3 of +# the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + +# http://www.vtk.org/Wiki/CmakeMingw + +# the name of the target operating system +set(CMAKE_SYSTEM_NAME Windows) + +# which compilers to use for C and C++ +set(CMAKE_C_COMPILER i586-mingw32msvc-gcc) +set(CMAKE_CXX_COMPILER i586-mingw32msvc-g++) +set(CMAKE_RC_COMPILER i586-mingw32msvc-windres) + +# here is the target environment located +set(CMAKE_FIND_ROOT_PATH /usr/i586-mingw32msvc) + +# adjust the default behaviour of the FIND_XXX() commands: +# search headers and libraries in the target environment, search +# programs in the host environment +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/OrthancExplorer.js Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,18 @@ +$('#series').live('pagebeforecreate', function() { + $('#series-preview').parent().remove(); + + var b = $('<a>') + .attr('data-role', 'button') + .attr('href', '#') + .attr('data-icon', 'search') + .attr('data-theme', 'e') + .text('Orthanc Web Viewer'); + + b.insertBefore($('#series-delete').parent().parent()); + b.click(function() { + if ($.mobile.pageData) { + var series = $.mobile.pageData.uuid; + window.open('/web-viewer/app/viewer.html?series=' + series); + } + }); +});
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/ThirdParty/base64/base64.cpp Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,128 @@ +/* + base64.cpp and base64.h + + Copyright (C) 2004-2008 René Nyffenegger + + This source code is provided 'as-is', without any express or implied + warranty. In no event will the author be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this source code must not be misrepresented; you must not + claim that you wrote the original source code. If you use this source code + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original source code. + + 3. This notice may not be removed or altered from any source distribution. + + René Nyffenegger rene.nyffenegger@adp-gmbh.ch + +*/ + +#include "base64.h" +#include <string.h> + +static const std::string base64_chars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + + +static inline bool is_base64(unsigned char c) { + return (isalnum(c) || (c == '+') || (c == '/')); +} + +std::string base64_encode(const std::string& stringToEncode) +{ + const unsigned char* bytes_to_encode = reinterpret_cast<const unsigned char*> + (stringToEncode.size() > 0 ? &stringToEncode[0] : NULL); + unsigned int in_len = stringToEncode.size(); + + std::string ret; + int i = 0; + int j = 0; + unsigned char char_array_3[3]; + unsigned char char_array_4[4]; + + while (in_len--) { + char_array_3[i++] = *(bytes_to_encode++); + if (i == 3) { + char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; + char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); + char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); + char_array_4[3] = char_array_3[2] & 0x3f; + + for(i = 0; (i <4) ; i++) + ret += base64_chars[char_array_4[i]]; + i = 0; + } + } + + if (i) + { + for(j = i; j < 3; j++) + char_array_3[j] = '\0'; + + char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; + char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); + char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); + char_array_4[3] = char_array_3[2] & 0x3f; + + for (j = 0; (j < i + 1); j++) + ret += base64_chars[char_array_4[j]]; + + while((i++ < 3)) + ret += '='; + + } + + return ret; +} + + +std::string base64_decode(const std::string& encoded_string) { + int in_len = encoded_string.size(); + int i = 0; + int j = 0; + int in_ = 0; + unsigned char char_array_4[4], char_array_3[3]; + std::string ret; + + while (in_len-- && ( encoded_string[in_] != '=') && is_base64(encoded_string[in_])) { + char_array_4[i++] = encoded_string[in_]; in_++; + if (i ==4) { + for (i = 0; i <4; i++) + char_array_4[i] = base64_chars.find(char_array_4[i]); + + char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); + char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); + char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; + + for (i = 0; (i < 3); i++) + ret += char_array_3[i]; + i = 0; + } + } + + if (i) { + for (j = i; j <4; j++) + char_array_4[j] = 0; + + for (j = 0; j <4; j++) + char_array_4[j] = base64_chars.find(char_array_4[j]); + + char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); + char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); + char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; + + for (j = 0; (j < i - 1); j++) ret += char_array_3[j]; + } + + return ret; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/ThirdParty/base64/base64.h Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,4 @@ +#include <string> + +std::string base64_encode(const std::string& stringToEncode); +std::string base64_decode(const std::string& s);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/VersionScript.map Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,12 @@ +# This is a version-script for Orthanc plugins + +{ +global: + OrthancPluginInitialize; + OrthancPluginFinalize; + OrthancPluginGetName; + OrthancPluginGetVersion; + +local: + *; +};
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UnitTestsSources/UnitTestsMain.cpp Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,223 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include <gtest/gtest.h> +#include <boost/lexical_cast.hpp> + +static int argc_; +static char** argv_; + +#include "../Orthanc/OrthancException.h" +#include "../Orthanc/Toolbox.h" +#include "../Orthanc/ImageFormats/ImageBuffer.h" +#include "../Orthanc/ImageFormats/PngWriter.h" +#include "../Plugin/Cache/CacheManager.h" +#include "../Plugin/Cache/CacheScheduler.h" +#include "../Plugin/Cache/ICacheFactory.h" +#include "../Plugin/Cache/ICacheFactory.h" +#include "../Plugin/JpegWriter.h" + +using namespace OrthancPlugins; + + +class CacheManagerTest : public testing::Test +{ +private: + std::auto_ptr<Orthanc::FilesystemStorage> storage_; + std::auto_ptr<Orthanc::SQLite::Connection> db_; + std::auto_ptr<CacheManager> cache_; + +public: + virtual void SetUp() + { + storage_.reset(new Orthanc::FilesystemStorage("UnitTestsResults")); + storage_->Clear(); + Orthanc::Toolbox::RemoveFile("UnitTestsResults/cache.db"); + + db_.reset(new Orthanc::SQLite::Connection()); + db_->Open("UnitTestsResults/cache.db"); + + cache_.reset(new CacheManager(*db_, *storage_)); + cache_->SetSanityCheckEnabled(true); + } + + virtual void TearDown() + { + cache_.reset(NULL); + db_.reset(NULL); + storage_.reset(NULL); + } + + CacheManager& GetCache() + { + return *cache_; + } + + Orthanc::FilesystemStorage& GetStorage() + { + return *storage_; + } +}; + + + +class TestF : public ICacheFactory +{ +private: + int bundle_; + +public: + TestF(int bundle) : bundle_(bundle) + { + } + + virtual bool Create(std::string& content, + const std::string& key) + { + content = "Bundle " + boost::lexical_cast<std::string>(bundle_) + ", item " + key; + return true; + } +}; + + +TEST_F(CacheManagerTest, DefaultQuota) +{ + std::set<std::string> f; + GetStorage().ListAllFiles(f); + ASSERT_EQ(0, f.size()); + + GetCache().SetDefaultQuota(10, 0); + for (int i = 0; i < 30; i++) + { + GetStorage().ListAllFiles(f); + ASSERT_EQ(i >= 10 ? 10 : i, f.size()); + std::string s = boost::lexical_cast<std::string>(i); + GetCache().Store(0, s, "Test " + s); + } + + GetStorage().ListAllFiles(f); + ASSERT_EQ(10, f.size()); + + for (int i = 0; i < 30; i++) + { + ASSERT_EQ(i >= 20, GetCache().IsCached(0, boost::lexical_cast<std::string>(i))); + } + + GetCache().SetDefaultQuota(5, 0); + GetStorage().ListAllFiles(f); + ASSERT_EQ(5, f.size()); + for (int i = 0; i < 30; i++) + { + ASSERT_EQ(i >= 25, GetCache().IsCached(0, boost::lexical_cast<std::string>(i))); + } + + for (int i = 0; i < 15; i++) + { + std::string s = boost::lexical_cast<std::string>(i); + GetCache().Store(0, s, "Test " + s); + } + + GetStorage().ListAllFiles(f); + ASSERT_EQ(5, f.size()); + + for (int i = 0; i < 50; i++) + { + std::string s = boost::lexical_cast<std::string>(i); + if (i >= 10 && i < 15) + { + std::string tmp; + ASSERT_TRUE(GetCache().IsCached(0, s)); + ASSERT_TRUE(GetCache().Access(tmp, 0, s)); + ASSERT_EQ("Test " + s, tmp); + } + else + { + ASSERT_FALSE(GetCache().IsCached(0, s)); + } + } +} + + + +TEST_F(CacheManagerTest, Invalidate) +{ + GetCache().SetDefaultQuota(10, 0); + for (int i = 0; i < 30; i++) + { + std::string s = boost::lexical_cast<std::string>(i); + GetCache().Store(0, s, "Test " + s); + } + + std::set<std::string> f; + GetStorage().ListAllFiles(f); + ASSERT_EQ(10, f.size()); + + GetCache().Invalidate(0, "25"); + GetStorage().ListAllFiles(f); + ASSERT_EQ(9, f.size()); + for (int i = 0; i < 50; i++) + { + std::string s = boost::lexical_cast<std::string>(i); + ASSERT_EQ((i >= 20 && i < 30 && i != 25), GetCache().IsCached(0, s)); + } + + for (int i = 0; i < 50; i++) + { + GetCache().Invalidate(0, boost::lexical_cast<std::string>(i)); + } + + GetStorage().ListAllFiles(f); + ASSERT_EQ(0, f.size()); +} + + + +TEST(JpegWriter, Basic) +{ + Orthanc::ImageBuffer img(16, 16, Orthanc::PixelFormat_Grayscale8); + Orthanc::ImageAccessor accessor = img.GetAccessor(); + for (int y = 0, value = 0; y < img.GetHeight(); y++) + { + uint8_t* p = reinterpret_cast<uint8_t*>(accessor.GetRow(y)); + for (int x = 0; x < img.GetWidth(); x++, p++) + { + *p = value++; + } + } + + JpegWriter w; + w.WriteToFile("UnitTestsResults/hello.jpg", accessor); + + std::string s; + w.WriteToMemory(s, accessor); + Orthanc::Toolbox::WriteFile(s, "UnitTestsResults/hello2.jpg"); +} + + + +int main(int argc, char **argv) +{ + argc_ = argc; + argv_ = argv; + + ::testing::InitGoogleTest(&argc, argv); + + return RUN_ALL_TESTS(); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebApplication/jpeg-decoder.js Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,984 @@ +/** + * SOURCE: https://github.com/notmasteryet/jpgjs/blob/master/jpg.js + **/ + + +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- / +/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ +/* + Copyright 2011 notmasteryet + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +// - The JPEG specification can be found in the ITU CCITT Recommendation T.81 +// (www.w3.org/Graphics/JPEG/itu-t81.pdf) +// - The JFIF specification can be found in the JPEG File Interchange Format +// (www.w3.org/Graphics/JPEG/jfif3.pdf) +// - The Adobe Application-Specific JPEG markers in the Supporting the DCT Filters +// in PostScript Level 2, Technical Note #5116 +// (partners.adobe.com/public/developer/en/ps/sdk/5116.DCT_Filter.pdf) + +var JpegImage = (function jpegImage() { + "use strict"; + var dctZigZag = new Int32Array([ + 0, + 1, 8, + 16, 9, 2, + 3, 10, 17, 24, + 32, 25, 18, 11, 4, + 5, 12, 19, 26, 33, 40, + 48, 41, 34, 27, 20, 13, 6, + 7, 14, 21, 28, 35, 42, 49, 56, + 57, 50, 43, 36, 29, 22, 15, + 23, 30, 37, 44, 51, 58, + 59, 52, 45, 38, 31, + 39, 46, 53, 60, + 61, 54, 47, + 55, 62, + 63 + ]); + + var dctCos1 = 4017 // cos(pi/16) + var dctSin1 = 799 // sin(pi/16) + var dctCos3 = 3406 // cos(3*pi/16) + var dctSin3 = 2276 // sin(3*pi/16) + var dctCos6 = 1567 // cos(6*pi/16) + var dctSin6 = 3784 // sin(6*pi/16) + var dctSqrt2 = 5793 // sqrt(2) + var dctSqrt1d2 = 2896 // sqrt(2) / 2 + + function constructor() { + } + + function buildHuffmanTable(codeLengths, values) { + var k = 0, code = [], i, j, length = 16; + while (length > 0 && !codeLengths[length - 1]) + length--; + code.push({children: [], index: 0}); + var p = code[0], q; + for (i = 0; i < length; i++) { + for (j = 0; j < codeLengths[i]; j++) { + p = code.pop(); + p.children[p.index] = values[k]; + while (p.index > 0) { + p = code.pop(); + } + p.index++; + code.push(p); + while (code.length <= i) { + code.push(q = {children: [], index: 0}); + p.children[p.index] = q.children; + p = q; + } + k++; + } + if (i + 1 < length) { + // p here points to last code + code.push(q = {children: [], index: 0}); + p.children[p.index] = q.children; + p = q; + } + } + return code[0].children; + } + + function getBlockBufferOffset(component, row, col) { + return 64 * ((component.blocksPerLine + 1) * row + col); + } + + function decodeScan(data, offset, + frame, components, resetInterval, + spectralStart, spectralEnd, + successivePrev, successive) { + var precision = frame.precision; + var samplesPerLine = frame.samplesPerLine; + var scanLines = frame.scanLines; + var mcusPerLine = frame.mcusPerLine; + var progressive = frame.progressive; + var maxH = frame.maxH, maxV = frame.maxV; + + var startOffset = offset, bitsData = 0, bitsCount = 0; + + function readBit() { + if (bitsCount > 0) { + bitsCount--; + return (bitsData >> bitsCount) & 1; + } + bitsData = data[offset++]; + if (bitsData == 0xFF) { + var nextByte = data[offset++]; + if (nextByte) { + throw "unexpected marker: " + ((bitsData << 8) | nextByte).toString(16); + } + // unstuff 0 + } + bitsCount = 7; + return bitsData >>> 7; + } + + function decodeHuffman(tree) { + var node = tree; + var bit; + while ((bit = readBit()) !== null) { + node = node[bit]; + if (typeof node === 'number') + return node; + if (typeof node !== 'object') + throw "invalid huffman sequence"; + } + return null; + } + + function receive(length) { + var n = 0; + while (length > 0) { + var bit = readBit(); + if (bit === null) return; + n = (n << 1) | bit; + length--; + } + return n; + } + + function receiveAndExtend(length) { + var n = receive(length); + if (n >= 1 << (length - 1)) + return n; + return n + (-1 << length) + 1; + } + + function decodeBaseline(component, offset) { + var t = decodeHuffman(component.huffmanTableDC); + var diff = t === 0 ? 0 : receiveAndExtend(t); + component.blockData[offset] = (component.pred += diff); + var k = 1; + while (k < 64) { + var rs = decodeHuffman(component.huffmanTableAC); + var s = rs & 15, r = rs >> 4; + if (s === 0) { + if (r < 15) + break; + k += 16; + continue; + } + k += r; + var z = dctZigZag[k]; + component.blockData[offset + z] = receiveAndExtend(s); + k++; + } + } + + function decodeDCFirst(component, offset) { + var t = decodeHuffman(component.huffmanTableDC); + var diff = t === 0 ? 0 : (receiveAndExtend(t) << successive); + component.blockData[offset] = (component.pred += diff); + } + + function decodeDCSuccessive(component, offset) { + component.blockData[offset] |= readBit() << successive; + } + + var eobrun = 0; + function decodeACFirst(component, offset) { + if (eobrun > 0) { + eobrun--; + return; + } + var k = spectralStart, e = spectralEnd; + while (k <= e) { + var rs = decodeHuffman(component.huffmanTableAC); + var s = rs & 15, r = rs >> 4; + if (s === 0) { + if (r < 15) { + eobrun = receive(r) + (1 << r) - 1; + break; + } + k += 16; + continue; + } + k += r; + var z = dctZigZag[k]; + component.blockData[offset + z] = receiveAndExtend(s) * (1 << successive); + k++; + } + } + + var successiveACState = 0, successiveACNextValue; + function decodeACSuccessive(component, offset) { + var k = spectralStart, e = spectralEnd, r = 0; + while (k <= e) { + var z = dctZigZag[k]; + switch (successiveACState) { + case 0: // initial state + var rs = decodeHuffman(component.huffmanTableAC); + var s = rs & 15, r = rs >> 4; + if (s === 0) { + if (r < 15) { + eobrun = receive(r) + (1 << r); + successiveACState = 4; + } else { + r = 16; + successiveACState = 1; + } + } else { + if (s !== 1) + throw "invalid ACn encoding"; + successiveACNextValue = receiveAndExtend(s); + successiveACState = r ? 2 : 3; + } + continue; + case 1: // skipping r zero items + case 2: + if (component.blockData[offset + z]) { + component.blockData[offset + z] += (readBit() << successive); + } else { + r--; + if (r === 0) + successiveACState = successiveACState == 2 ? 3 : 0; + } + break; + case 3: // set value for a zero item + if (component.blockData[offset + z]) { + component.blockData[offset + z] += (readBit() << successive); + } else { + component.blockData[offset + z] = successiveACNextValue << successive; + successiveACState = 0; + } + break; + case 4: // eob + if (component.blockData[offset + z]) { + component.blockData[offset + z] += (readBit() << successive); + } + break; + } + k++; + } + if (successiveACState === 4) { + eobrun--; + if (eobrun === 0) + successiveACState = 0; + } + } + + function decodeMcu(component, decode, mcu, row, col) { + var mcuRow = (mcu / mcusPerLine) | 0; + var mcuCol = mcu % mcusPerLine; + var blockRow = mcuRow * component.v + row; + var blockCol = mcuCol * component.h + col; + var offset = getBlockBufferOffset(component, blockRow, blockCol); + decode(component, offset); + } + + function decodeBlock(component, decode, mcu) { + var blockRow = (mcu / component.blocksPerLine) | 0; + var blockCol = mcu % component.blocksPerLine; + var offset = getBlockBufferOffset(component, blockRow, blockCol); + decode(component, offset); + } + + var componentsLength = components.length; + var component, i, j, k, n; + var decodeFn; + if (progressive) { + if (spectralStart === 0) + decodeFn = successivePrev === 0 ? decodeDCFirst : decodeDCSuccessive; + else + decodeFn = successivePrev === 0 ? decodeACFirst : decodeACSuccessive; + } else { + decodeFn = decodeBaseline; + } + + var mcu = 0, marker; + var mcuExpected; + if (componentsLength == 1) { + mcuExpected = components[0].blocksPerLine * components[0].blocksPerColumn; + } else { + mcuExpected = mcusPerLine * frame.mcusPerColumn; + } + if (!resetInterval) { + resetInterval = mcuExpected; + } + + var h, v; + while (mcu < mcuExpected) { + // reset interval stuff + for (i = 0; i < componentsLength; i++) { + components[i].pred = 0; + } + eobrun = 0; + + if (componentsLength == 1) { + component = components[0]; + for (n = 0; n < resetInterval; n++) { + decodeBlock(component, decodeFn, mcu); + mcu++; + } + } else { + for (n = 0; n < resetInterval; n++) { + for (i = 0; i < componentsLength; i++) { + component = components[i]; + h = component.h; + v = component.v; + for (j = 0; j < v; j++) { + for (k = 0; k < h; k++) { + decodeMcu(component, decodeFn, mcu, j, k); + } + } + } + mcu++; + } + } + + // find marker + bitsCount = 0; + marker = (data[offset] << 8) | data[offset + 1]; + if (marker <= 0xFF00) { + throw "marker was not found"; + } + + if (marker >= 0xFFD0 && marker <= 0xFFD7) { // RSTx + offset += 2; + } else { + break; + } + } + + return offset - startOffset; + } + + // A port of poppler's IDCT method which in turn is taken from: + // Christoph Loeffler, Adriaan Ligtenberg, George S. Moschytz, + // "Practical Fast 1-D DCT Algorithms with 11 Multiplications", + // IEEE Intl. Conf. on Acoustics, Speech & Signal Processing, 1989, + // 988-991. + function quantizeAndInverse(component, blockBufferOffset, p) { + var qt = component.quantizationTable; + var v0, v1, v2, v3, v4, v5, v6, v7, t; + var i; + + // dequant + for (i = 0; i < 64; i++) { + p[i] = component.blockData[blockBufferOffset + i] * qt[i]; + } + + // inverse DCT on rows + for (i = 0; i < 8; ++i) { + var row = 8 * i; + + // check for all-zero AC coefficients + if (p[1 + row] == 0 && p[2 + row] == 0 && p[3 + row] == 0 && + p[4 + row] == 0 && p[5 + row] == 0 && p[6 + row] == 0 && + p[7 + row] == 0) { + t = (dctSqrt2 * p[0 + row] + 512) >> 10; + p[0 + row] = t; + p[1 + row] = t; + p[2 + row] = t; + p[3 + row] = t; + p[4 + row] = t; + p[5 + row] = t; + p[6 + row] = t; + p[7 + row] = t; + continue; + } + + // stage 4 + v0 = (dctSqrt2 * p[0 + row] + 128) >> 8; + v1 = (dctSqrt2 * p[4 + row] + 128) >> 8; + v2 = p[2 + row]; + v3 = p[6 + row]; + v4 = (dctSqrt1d2 * (p[1 + row] - p[7 + row]) + 128) >> 8; + v7 = (dctSqrt1d2 * (p[1 + row] + p[7 + row]) + 128) >> 8; + v5 = p[3 + row] << 4; + v6 = p[5 + row] << 4; + + // stage 3 + t = (v0 - v1+ 1) >> 1; + v0 = (v0 + v1 + 1) >> 1; + v1 = t; + t = (v2 * dctSin6 + v3 * dctCos6 + 128) >> 8; + v2 = (v2 * dctCos6 - v3 * dctSin6 + 128) >> 8; + v3 = t; + t = (v4 - v6 + 1) >> 1; + v4 = (v4 + v6 + 1) >> 1; + v6 = t; + t = (v7 + v5 + 1) >> 1; + v5 = (v7 - v5 + 1) >> 1; + v7 = t; + + // stage 2 + t = (v0 - v3 + 1) >> 1; + v0 = (v0 + v3 + 1) >> 1; + v3 = t; + t = (v1 - v2 + 1) >> 1; + v1 = (v1 + v2 + 1) >> 1; + v2 = t; + t = (v4 * dctSin3 + v7 * dctCos3 + 2048) >> 12; + v4 = (v4 * dctCos3 - v7 * dctSin3 + 2048) >> 12; + v7 = t; + t = (v5 * dctSin1 + v6 * dctCos1 + 2048) >> 12; + v5 = (v5 * dctCos1 - v6 * dctSin1 + 2048) >> 12; + v6 = t; + + // stage 1 + p[0 + row] = v0 + v7; + p[7 + row] = v0 - v7; + p[1 + row] = v1 + v6; + p[6 + row] = v1 - v6; + p[2 + row] = v2 + v5; + p[5 + row] = v2 - v5; + p[3 + row] = v3 + v4; + p[4 + row] = v3 - v4; + } + + // inverse DCT on columns + for (i = 0; i < 8; ++i) { + var col = i; + + // check for all-zero AC coefficients + if (p[1*8 + col] == 0 && p[2*8 + col] == 0 && p[3*8 + col] == 0 && + p[4*8 + col] == 0 && p[5*8 + col] == 0 && p[6*8 + col] == 0 && + p[7*8 + col] == 0) { + t = (dctSqrt2 * p[i+0] + 8192) >> 14; + p[0*8 + col] = t; + p[1*8 + col] = t; + p[2*8 + col] = t; + p[3*8 + col] = t; + p[4*8 + col] = t; + p[5*8 + col] = t; + p[6*8 + col] = t; + p[7*8 + col] = t; + continue; + } + + // stage 4 + v0 = (dctSqrt2 * p[0*8 + col] + 2048) >> 12; + v1 = (dctSqrt2 * p[4*8 + col] + 2048) >> 12; + v2 = p[2*8 + col]; + v3 = p[6*8 + col]; + v4 = (dctSqrt1d2 * (p[1*8 + col] - p[7*8 + col]) + 2048) >> 12; + v7 = (dctSqrt1d2 * (p[1*8 + col] + p[7*8 + col]) + 2048) >> 12; + v5 = p[3*8 + col]; + v6 = p[5*8 + col]; + + // stage 3 + t = (v0 - v1 + 1) >> 1; + v0 = (v0 + v1 + 1) >> 1; + v1 = t; + t = (v2 * dctSin6 + v3 * dctCos6 + 2048) >> 12; + v2 = (v2 * dctCos6 - v3 * dctSin6 + 2048) >> 12; + v3 = t; + t = (v4 - v6 + 1) >> 1; + v4 = (v4 + v6 + 1) >> 1; + v6 = t; + t = (v7 + v5 + 1) >> 1; + v5 = (v7 - v5 + 1) >> 1; + v7 = t; + + // stage 2 + t = (v0 - v3 + 1) >> 1; + v0 = (v0 + v3 + 1) >> 1; + v3 = t; + t = (v1 - v2 + 1) >> 1; + v1 = (v1 + v2 + 1) >> 1; + v2 = t; + t = (v4 * dctSin3 + v7 * dctCos3 + 2048) >> 12; + v4 = (v4 * dctCos3 - v7 * dctSin3 + 2048) >> 12; + v7 = t; + t = (v5 * dctSin1 + v6 * dctCos1 + 2048) >> 12; + v5 = (v5 * dctCos1 - v6 * dctSin1 + 2048) >> 12; + v6 = t; + + // stage 1 + p[0*8 + col] = v0 + v7; + p[7*8 + col] = v0 - v7; + p[1*8 + col] = v1 + v6; + p[6*8 + col] = v1 - v6; + p[2*8 + col] = v2 + v5; + p[5*8 + col] = v2 - v5; + p[3*8 + col] = v3 + v4; + p[4*8 + col] = v3 - v4; + } + + // convert to 8-bit integers + for (i = 0; i < 64; ++i) { + var index = blockBufferOffset + i; + var q = p[i]; + q = (q <= -2056) ? 0 : (q >= 2024) ? 255 : (q + 2056) >> 4; + component.blockData[index] = q; + } + } + + function buildComponentData(frame, component) { + var lines = []; + var blocksPerLine = component.blocksPerLine; + var blocksPerColumn = component.blocksPerColumn; + var samplesPerLine = blocksPerLine << 3; + var computationBuffer = new Int32Array(64); + + var i, j, ll = 0; + for (var blockRow = 0; blockRow < blocksPerColumn; blockRow++) { + for (var blockCol = 0; blockCol < blocksPerLine; blockCol++) { + var offset = getBlockBufferOffset(component, blockRow, blockCol) + quantizeAndInverse(component, offset, computationBuffer); + } + } + return component.blockData; + } + + function clampToUint8(a) { + return a <= 0 ? 0 : a >= 255 ? 255 : a | 0; + } + + constructor.prototype = { + load: function load(path) { + var handleData = (function(data) { + this.parse(data); + if (this.onload) + this.onload(); + }).bind(this); + + if (path.indexOf("data:") > -1) { + var offset = path.indexOf("base64,")+7; + var data = atob(path.substring(offset)); + var arr = new Uint8Array(data.length); + for (var i = data.length - 1; i >= 0; i--) { + arr[i] = data.charCodeAt(i); + } + handleData(data); + } else { + var xhr = new XMLHttpRequest(); + xhr.open("GET", path, true); + xhr.responseType = "arraybuffer"; + xhr.onload = (function() { + // TODO catch parse error + var data = new Uint8Array(xhr.response); + handleData(data); + }).bind(this); + xhr.send(null); + } + }, + + parse: function parse(data) { + + function readUint16() { + var value = (data[offset] << 8) | data[offset + 1]; + offset += 2; + return value; + } + + function readDataBlock() { + var length = readUint16(); + var array = data.subarray(offset, offset + length - 2); + offset += array.length; + return array; + } + + function prepareComponents(frame) { + var mcusPerLine = Math.ceil(frame.samplesPerLine / 8 / frame.maxH); + var mcusPerColumn = Math.ceil(frame.scanLines / 8 / frame.maxV); + for (var i = 0; i < frame.components.length; i++) { + component = frame.components[i]; + var blocksPerLine = Math.ceil(Math.ceil(frame.samplesPerLine / 8) * component.h / frame.maxH); + var blocksPerColumn = Math.ceil(Math.ceil(frame.scanLines / 8) * component.v / frame.maxV); + var blocksPerLineForMcu = mcusPerLine * component.h; + var blocksPerColumnForMcu = mcusPerColumn * component.v; + + var blocksBufferSize = 64 * blocksPerColumnForMcu + * (blocksPerLineForMcu + 1); + component.blockData = new Int16Array(blocksBufferSize); + component.blocksPerLine = blocksPerLine; + component.blocksPerColumn = blocksPerColumn; + } + frame.mcusPerLine = mcusPerLine; + frame.mcusPerColumn = mcusPerColumn; + } + + var offset = 0, length = data.length; + var jfif = null; + var adobe = null; + var pixels = null; + var frame, resetInterval; + var quantizationTables = []; + var huffmanTablesAC = [], huffmanTablesDC = []; + var fileMarker = readUint16(); + if (fileMarker != 0xFFD8) { // SOI (Start of Image) + throw "SOI not found"; + } + + fileMarker = readUint16(); + while (fileMarker != 0xFFD9) { // EOI (End of image) + var i, j, l; + switch(fileMarker) { + case 0xFFE0: // APP0 (Application Specific) + case 0xFFE1: // APP1 + case 0xFFE2: // APP2 + case 0xFFE3: // APP3 + case 0xFFE4: // APP4 + case 0xFFE5: // APP5 + case 0xFFE6: // APP6 + case 0xFFE7: // APP7 + case 0xFFE8: // APP8 + case 0xFFE9: // APP9 + case 0xFFEA: // APP10 + case 0xFFEB: // APP11 + case 0xFFEC: // APP12 + case 0xFFED: // APP13 + case 0xFFEE: // APP14 + case 0xFFEF: // APP15 + case 0xFFFE: // COM (Comment) + var appData = readDataBlock(); + + if (fileMarker === 0xFFE0) { + if (appData[0] === 0x4A && appData[1] === 0x46 && appData[2] === 0x49 && + appData[3] === 0x46 && appData[4] === 0) { // 'JFIF\x00' + jfif = { + version: { major: appData[5], minor: appData[6] }, + densityUnits: appData[7], + xDensity: (appData[8] << 8) | appData[9], + yDensity: (appData[10] << 8) | appData[11], + thumbWidth: appData[12], + thumbHeight: appData[13], + thumbData: appData.subarray(14, 14 + 3 * appData[12] * appData[13]) + }; + } + } + // TODO APP1 - Exif + if (fileMarker === 0xFFEE) { + if (appData[0] === 0x41 && appData[1] === 0x64 && appData[2] === 0x6F && + appData[3] === 0x62 && appData[4] === 0x65 && appData[5] === 0) { // 'Adobe\x00' + adobe = { + version: appData[6], + flags0: (appData[7] << 8) | appData[8], + flags1: (appData[9] << 8) | appData[10], + transformCode: appData[11] + }; + } + } + break; + + case 0xFFDB: // DQT (Define Quantization Tables) + var quantizationTablesLength = readUint16(); + var quantizationTablesEnd = quantizationTablesLength + offset - 2; + while (offset < quantizationTablesEnd) { + var quantizationTableSpec = data[offset++]; + var tableData = new Int32Array(64); + if ((quantizationTableSpec >> 4) === 0) { // 8 bit values + for (j = 0; j < 64; j++) { + var z = dctZigZag[j]; + tableData[z] = data[offset++]; + } + } else if ((quantizationTableSpec >> 4) === 1) { //16 bit + for (j = 0; j < 64; j++) { + var z = dctZigZag[j]; + tableData[z] = readUint16(); + } + } else + throw "DQT: invalid table spec"; + quantizationTables[quantizationTableSpec & 15] = tableData; + } + break; + + case 0xFFC0: // SOF0 (Start of Frame, Baseline DCT) + case 0xFFC1: // SOF1 (Start of Frame, Extended DCT) + case 0xFFC2: // SOF2 (Start of Frame, Progressive DCT) + if (frame) { + throw "Only single frame JPEGs supported"; + } + readUint16(); // skip data length + frame = {}; + frame.extended = (fileMarker === 0xFFC1); + frame.progressive = (fileMarker === 0xFFC2); + frame.precision = data[offset++]; + frame.scanLines = readUint16(); + frame.samplesPerLine = readUint16(); + frame.components = []; + frame.componentIds = {}; + var componentsCount = data[offset++], componentId; + var maxH = 0, maxV = 0; + for (i = 0; i < componentsCount; i++) { + componentId = data[offset]; + var h = data[offset + 1] >> 4; + var v = data[offset + 1] & 15; + if (maxH < h) maxH = h; + if (maxV < v) maxV = v; + var qId = data[offset + 2]; + var l = frame.components.push({ + h: h, + v: v, + quantizationTable: quantizationTables[qId] + }); + frame.componentIds[componentId] = l - 1; + offset += 3; + } + frame.maxH = maxH; + frame.maxV = maxV; + prepareComponents(frame); + break; + + case 0xFFC4: // DHT (Define Huffman Tables) + var huffmanLength = readUint16(); + for (i = 2; i < huffmanLength;) { + var huffmanTableSpec = data[offset++]; + var codeLengths = new Uint8Array(16); + var codeLengthSum = 0; + for (j = 0; j < 16; j++, offset++) + codeLengthSum += (codeLengths[j] = data[offset]); + var huffmanValues = new Uint8Array(codeLengthSum); + for (j = 0; j < codeLengthSum; j++, offset++) + huffmanValues[j] = data[offset]; + i += 17 + codeLengthSum; + + ((huffmanTableSpec >> 4) === 0 ? + huffmanTablesDC : huffmanTablesAC)[huffmanTableSpec & 15] = + buildHuffmanTable(codeLengths, huffmanValues); + } + break; + + case 0xFFDD: // DRI (Define Restart Interval) + readUint16(); // skip data length + resetInterval = readUint16(); + break; + + case 0xFFDA: // SOS (Start of Scan) + var scanLength = readUint16(); + var selectorsCount = data[offset++]; + var components = [], component; + for (i = 0; i < selectorsCount; i++) { + var componentIndex = frame.componentIds[data[offset++]]; + component = frame.components[componentIndex]; + var tableSpec = data[offset++]; + component.huffmanTableDC = huffmanTablesDC[tableSpec >> 4]; + component.huffmanTableAC = huffmanTablesAC[tableSpec & 15]; + components.push(component); + } + var spectralStart = data[offset++]; + var spectralEnd = data[offset++]; + var successiveApproximation = data[offset++]; + var processed = decodeScan(data, offset, + frame, components, resetInterval, + spectralStart, spectralEnd, + successiveApproximation >> 4, successiveApproximation & 15); + offset += processed; + break; + default: + if (data[offset - 3] == 0xFF && + data[offset - 2] >= 0xC0 && data[offset - 2] <= 0xFE) { + // could be incorrect encoding -- last 0xFF byte of the previous + // block was eaten by the encoder + offset -= 3; + break; + } + throw "unknown JPEG marker " + fileMarker.toString(16); + } + fileMarker = readUint16(); + } + + this.width = frame.samplesPerLine; + this.height = frame.scanLines; + this.jfif = jfif; + this.adobe = adobe; + this.components = []; + for (var i = 0; i < frame.components.length; i++) { + var component = frame.components[i]; + this.components.push({ + output: buildComponentData(frame, component), + scaleX: component.h / frame.maxH, + scaleY: component.v / frame.maxV, + blocksPerLine: component.blocksPerLine, + blocksPerColumn: component.blocksPerColumn + }); + } + }, + + getData: function getData(width, height) { + var scaleX = this.width / width, scaleY = this.height / height; + + var component, componentScaleX, componentScaleY; + var x, y, i; + var offset = 0; + var Y, Cb, Cr, K, C, M, Ye, R, G, B; + var colorTransform; + var numComponents = this.components.length; + var dataLength = width * height * numComponents; + var data = new Uint8Array(dataLength); + var componentLine; + + // lineData is reused for all components. Assume first component is + // the biggest + var lineData = new Uint8Array((this.components[0].blocksPerLine << 3) * + this.components[0].blocksPerColumn * 8); + + // First construct image data ... + for (i = 0; i < numComponents; i++) { + component = this.components[i]; + var blocksPerLine = component.blocksPerLine; + var blocksPerColumn = component.blocksPerColumn; + var samplesPerLine = blocksPerLine << 3; + + var j, k, ll = 0; + var lineOffset = 0; + for (var blockRow = 0; blockRow < blocksPerColumn; blockRow++) { + var scanLine = blockRow << 3; + for (var blockCol = 0; blockCol < blocksPerLine; blockCol++) { + var bufferOffset = getBlockBufferOffset(component, blockRow, blockCol); + var offset = 0, sample = blockCol << 3; + for (j = 0; j < 8; j++) { + var lineOffset = (scanLine + j) * samplesPerLine; + for (k = 0; k < 8; k++) { + lineData[lineOffset + sample + k] = + component.output[bufferOffset + offset++]; + } + } + } + } + + componentScaleX = component.scaleX * scaleX; + componentScaleY = component.scaleY * scaleY; + offset = i; + + var cx, cy; + var index; + for (y = 0; y < height; y++) { + for (x = 0; x < width; x++) { + cy = 0 | (y * componentScaleY); + cx = 0 | (x * componentScaleX); + index = cy * samplesPerLine + cx; + data[offset] = lineData[index]; + offset += numComponents; + } + } + } + + // ... then transform colors, if necessary + switch (numComponents) { + case 1: case 2: break; + // no color conversion for one or two compoenents + + case 3: + // The default transform for three components is true + colorTransform = true; + // The adobe transform marker overrides any previous setting + if (this.adobe && this.adobe.transformCode) + colorTransform = true; + else if (typeof this.colorTransform !== 'undefined') + colorTransform = !!this.colorTransform; + + if (colorTransform) { + for (i = 0; i < dataLength; i += numComponents) { + Y = data[i ]; + Cb = data[i + 1]; + Cr = data[i + 2]; + + R = clampToUint8(Y - 179.456 + 1.402 * Cr); + G = clampToUint8(Y + 135.459 - 0.344 * Cb - 0.714 * Cr); + B = clampToUint8(Y - 226.816 + 1.772 * Cb); + + data[i ] = R; + data[i + 1] = G; + data[i + 2] = B; + } + } + break; + case 4: + if (!this.adobe) + throw 'Unsupported color mode (4 components)'; + // The default transform for four components is false + colorTransform = false; + // The adobe transform marker overrides any previous setting + if (this.adobe && this.adobe.transformCode) + colorTransform = true; + else if (typeof this.colorTransform !== 'undefined') + colorTransform = !!this.colorTransform; + + if (colorTransform) { + for (i = 0; i < dataLength; i += numComponents) { + Y = data[i]; + Cb = data[i + 1]; + Cr = data[i + 2]; + + C = clampToUint8(434.456 - Y - 1.402 * Cr); + M = clampToUint8(119.541 - Y + 0.344 * Cb + 0.714 * Cr); + Y = clampToUint8(481.816 - Y - 1.772 * Cb); + + data[i ] = C; + data[i + 1] = M; + data[i + 2] = Y; + // K is unchanged + } + } + break; + default: + throw 'Unsupported color mode'; + } + return data; + }, + copyToImageData: function copyToImageData(imageData) { + var width = imageData.width, height = imageData.height; + var imageDataBytes = width * height * 4; + var imageDataArray = imageData.data; + var data = this.getData(width, height); + var i = 0, j = 0, k0, k1; + var Y, K, C, M, R, G, B; + switch (this.components.length) { + case 1: + while (j < imageDataBytes) { + Y = data[i++]; + + imageDataArray[j++] = Y; + imageDataArray[j++] = Y; + imageDataArray[j++] = Y; + imageDataArray[j++] = 255; + } + break; + case 3: + while (j < imageDataBytes) { + R = data[i++]; + G = data[i++]; + B = data[i++]; + + imageDataArray[j++] = R; + imageDataArray[j++] = G; + imageDataArray[j++] = B; + imageDataArray[j++] = 255; + } + break; + case 4: + while (j < imageDataBytes) { + C = data[i++]; + M = data[i++]; + Y = data[i++]; + K = data[i++]; + + k0 = 255 - K; + k1 = k0 / 255; + + + R = clampToUint8(k0 - C * k1); + G = clampToUint8(k0 - M * k1); + B = clampToUint8(k0 - Y * k1); + + imageDataArray[j++] = R; + imageDataArray[j++] = G; + imageDataArray[j++] = B; + imageDataArray[j++] = 255; + } + break; + default: + throw 'Unsupported color mode'; + } + } + }; + + return constructor; +})();
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebApplication/viewer.css Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,121 @@ +.jsPanel-hdr-r { + display: none; +} + +.ui-button-icon-only { + padding-top: 6px; + padding-bottom: 6px; +} + +.jsPanel-content { + padding: 4px; + overflow-y: visible !important; +} + +.jsPanel-hdr img { + padding-top: 4px; + padding-bottom: 4px; + display: block; + margin-left: auto; + margin-right: auto; +} + +.ui-slider .ui-slider-handle { + height: 20px; +} + +.ui-icon-custom-default { + background-image: url(images/default.png) !important; +} + +.ui-icon-custom-orthanc { + background-image: url(images/orthanc-icon.png) !important; +} + +.ui-icon-custom-stretch { + background-image: url(images/stretch.png) !important; +} + +.ui-icon-custom-bone { + background-image: url(images/bone.png) !important; +} + +.ui-icon-custom-lung { + background-image: url(images/lung.png) !important; +} + +.ui-icon-custom-interpolation { + background-image: url(images/interpolation.png) !important; +} + +.ui-icon-custom-inversion { + background-image: url(images/inversion.png) !important; +} + +#dicomImageWrapper { + top: 0px; + left: 0px; + width: 100%; + height: 100%; + position: absolute; + background-color: black; + color: white; + overflow: hidden; + font-family: Arial, Helvetica, sans-serif; +} + +#dicomImage { + width: 100%; + height: 100%; + top: 0px; + left: 0px; + position: absolute; +} + +#topleft { + position: absolute; + top: 10px; + left: 10px; +} + +#topright { + position: absolute; + top: 10px; + right: 10px; + text-align: right; +} + +#bottomright { + position: absolute; + bottom:25px; + right:10px; +} + +#bottomleft { + position: absolute; + bottom: 25px; + left: 10px; +} + +#bottomcenter { + position: absolute; + bottom: 25px; + left: 10px; + right: 10px; + width: 100%; + text-align: center; +} + +#bottom { + position: absolute; + bottom: 0px; + left: 0px; + height: 20px; + width: 100%; +} + +.alert { + color: #f00; + font-weight: bold; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebApplication/viewer.html Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,81 @@ +<!doctype html> + +<html lang="us"> + <head> + <meta charset="utf-8" /> + <title>Orthanc Web Viewer</title> + <link href="../libs/jquery-ui.min.css" rel="stylesheet" /> + <link href="../libs/jquery-ui.theme.min.css" rel="stylesheet" /> + <link href="../libs/cornerstone.min.css" rel="stylesheet" /> + <link href="../libs/jquery.jspanel.min.css" rel="stylesheet" /> + <link href="viewer.css" rel="stylesheet" /> + </head> + <body> + <div id="dicomImageWrapper" + class="cornerstone-enabled-image" + oncontextmenu="return false" + unselectable="on" + onselectstart="return false;" + onmousedown="return false;"> + <div id="dicomImage" oncontextmenu="return false" /> + </div> + + <div id="topleft"> + <button id="open-toolbar"></button> + </div> + + <div id="topright">Render Time:</div> + <div id="bottomright">Zoom:</div> + <div id="bottomleft">WW/WC:</div> + <div id="bottomcenter" class="alert">Not for diagnostic purpose</div> + <div id="bottom"> + <div id="slider"></div> + </div> + + <div id="toolbar-content" style="display:none"> + View<br/> + <div class="toolbar-view"> + <button title="Series information"></button> + <button title="Reverse contrast"></button> + <button title="Interpolation"></button> + <button title="Download DICOM"></button> + </div> + Zoom<br/> + <div class="toolbar-zoom"> + <button title="Adjust"></button> + <button title="Zoom in"></button> + <button title="Zoom out"></button> + </div> + Windowing<br/> + <div class="toolbar-windowing"> + <button title="Default windowing"></button> + <button title="Stretch contrast"></button> + <button title="Lung"></button> + <button title="Bone"></button> + </div> + Quality<br/> + <div class="toolbar-quality"> + <button title="Low quality"></button> + <button title="Medium quality"></button> + <button title="Best quality (lossless)"></button> + </div> + </div> + + <!-- jQuery, jQuery UI and jsPanel --> + <script src="../libs/jquery.js" type="text/javascript"></script> + <script src="../libs/jquery-ui.min.js" type="text/javascript"></script> + <script src="../libs/mobile-detect.min.js" type="text/javascript"></script> + <script src="../libs/jquery.ui.touch-punch.min.js" type="text/javascript"></script> + <script src="../libs/jquery.jspanel.min.js" type="text/javascript"></script> + + <!-- Cornerstone --> + <script src="../libs/cornerstone.min.js" type="text/javascript"></script> + + <!-- For zlib (deflate) and JPEG decompression --> + <script src="../libs/pako_inflate.min.js" type="text/javascript"></script> + <script src="jpeg-decoder.js" type="text/javascript"></script> + + <script src="../libs/url.min.js" type="text/javascript"></script> + <script src="viewer.js" type="text/javascript"></script> + </body> +</html>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebApplication/viewer.js Wed Feb 25 13:45:35 2015 +0100 @@ -0,0 +1,578 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +var compression = 'jpeg95'; + + + +// Prevent the access to IE +if(navigator.appVersion.indexOf("MSIE ") != -1) +{ + alert("Please use Mozilla Firefox or Google Chrome. Microsoft Internet Explorer is not supported."); +} + + +function ResizeCornerstone() +{ + $('#dicomImage').height($(window).height() - $('#slider').parent().height()); + var element = $('#dicomImage').get(0); + cornerstone.resize(element, true); +} + + +function SetWindowing(center, width) +{ + var element = $('#dicomImage').get(0); + var viewport = cornerstone.getViewport(element); + viewport.voi.windowCenter = center; + viewport.voi.windowWidth = width; + cornerstone.setViewport(element, viewport); + UpdateViewportInformation(); +} + + +function SetFullWindowing() +{ + var element = $('#dicomImage').get(0); + var viewport = cornerstone.getViewport(element); + var image = cornerstone.getEnabledElement(element).image; + + if (image.color) { + // Ignore color images + return; + } + + var minValue = image.minPixelValue; + var maxValue = image.maxPixelValue; + if (minValue == undefined || + maxValue == undefined || + minValue == maxValue) { + return; + } + + if (image.slope != undefined && + image.intercept != undefined) { + minValue = minValue * image.slope + image.intercept; + maxValue = maxValue * image.slope + image.intercept; + } + + viewport.voi.windowCenter = (minValue + maxValue) / 2.0; + viewport.voi.windowWidth = (maxValue - minValue) / 2.0; + cornerstone.setViewport(element, viewport); + UpdateViewportInformation(); +} + + +function SetDefaultWindowing() +{ + var element = $('#dicomImage').get(0); + var viewport = cornerstone.getViewport(element); + var image = cornerstone.getEnabledElement(element).image; + + viewport.voi.windowCenter = image.windowCenter; + viewport.voi.windowWidth = image.windowWidth; + cornerstone.setViewport(element, viewport); + UpdateViewportInformation(); +} + + +function SetBoneWindowing() +{ + SetWindowing(300, 2000); +} + + +function SetLungWindowing() +{ + SetWindowing(-600, 1600); +} + + +function UpdateViewportInformation() +{ + var element = $('#dicomImage').get(0); + var viewport = cornerstone.getViewport(element); + + $('#bottomleft').text('WW/WL:' + Math.round(viewport.voi.windowWidth) + '/' + Math.round(viewport.voi.windowCenter)); + $('#bottomright').text('Zoom: ' + viewport.scale.toFixed(2) + 'x'); +} + + +function ToggleSeriesInformation() +{ + $('#topright').toggle(); +} + + +function ToggleInterpolation() +{ + var element = $('#dicomImage').get(0); + var viewport = cornerstone.getViewport(element); + if (viewport.pixelReplication === true) { + viewport.pixelReplication = false; + } else { + viewport.pixelReplication = true; + } + cornerstone.setViewport(element, viewport); +} + + +function ToggleInversion() +{ + var element = $('#dicomImage').get(0); + var viewport = cornerstone.getViewport(element); + if (viewport.invert === true) { + viewport.invert = false; + } else { + viewport.invert = true; + } + cornerstone.setViewport(element, viewport); +} + + +function DownloadInstance(instance) +{ + // http://stackoverflow.com/a/3749395/881731 + var hiddenIFrameID = 'hiddenDownloader', + iframe = document.getElementById(hiddenIFrameID); + if (iframe === null) { + iframe = document.createElement('iframe'); + iframe.id = hiddenIFrameID; + iframe.style.display = 'none'; + document.body.appendChild(iframe); + } + iframe.src = '../../instances/' + instance + '/file'; +} + + +function AdjustZoom() +{ + var element = $('#dicomImage').get(0); + cornerstone.fitToWindow(element); +} + + +function ZoomIn() +{ + var element = $('#dicomImage').get(0); + var viewport = cornerstone.getViewport(element); + viewport.scale /= 0.5; + cornerstone.setViewport(element, viewport); + UpdateViewportInformation(); +} + + +function ZoomOut() +{ + var element = $('#dicomImage').get(0); + var viewport = cornerstone.getViewport(element); + viewport.scale *= 0.5; + cornerstone.setViewport(element, viewport); + UpdateViewportInformation(); +} + + + +(function (cornerstone) { + 'use strict'; + + function PrintRange(pixels) + { + var a = Infinity; + var b = -Infinity; + + for (var i = 0, length = pixels.length; i < length; i++) { + if (pixels[i] < a) + a = pixels[i]; + if (pixels[i] > b) + b = pixels[i]; + } + + console.log(a + ' ' + b); + } + + function ChangeDynamics(pixels, source1, target1, source2, target2) + { + var scale = (target2 - target1) / (source2 - source1); + var offset = (target1) - scale * source1; + + for (var i = 0, length = pixels.length; i < length; i++) { + pixels[i] = scale * pixels[i] + offset; + } + } + + + function getPixelDataDeflate(image) { + // Decompresses the base64 buffer that was compressed with Deflate + var s = pako.inflate(window.atob(image.Orthanc.PixelData)); + var pixels = null; + + if (image.color) { + var buf = new ArrayBuffer(s.length / 3 * 4); // RGB32 + pixels = new Uint8Array(buf); + var index = 0; + for (var i = 0, length = s.length; i < length; i += 3) { + pixels[index++] = s[i]; + pixels[index++] = s[i + 1]; + pixels[index++] = s[i + 2]; + pixels[index++] = 255; // Alpha channel + } + } else { + var buf = new ArrayBuffer(s.length * 2); // int16_t + pixels = new Int16Array(buf); + var index = 0; + for (var i = 0, length = s.length; i < length; i += 2) { + var lower = s[i]; + var upper = s[i + 1]; + pixels[index] = lower + upper * 256; + index++; + } + } + + return pixels; + } + + + // http://stackoverflow.com/a/11058858/881731 + function str2ab(str) { + var buf = new ArrayBuffer(str.length); + var pixels = new Uint8Array(buf); + for (var i = 0, strLen=str.length; i<strLen; i++) { + pixels[i] = str.charCodeAt(i); + } + return pixels; + } + + function getPixelDataJpeg(image) { + var jpegReader = new JpegImage(); + var jpeg = str2ab(window.atob(image.Orthanc.PixelData)); + jpegReader.parse(jpeg); + var s = jpegReader.getData(image.width, image.height); + var pixels = null; + + if (image.color) { + var buf = new ArrayBuffer(s.length / 3 * 4); // RGB32 + pixels = new Uint8Array(buf); + var index = 0; + for (var i = 0, length = s.length; i < length; i += 3) { + pixels[index++] = s[i]; + pixels[index++] = s[i + 1]; + pixels[index++] = s[i + 2]; + pixels[index++] = 255; // Alpha channel + } + } else { + var buf = new ArrayBuffer(s.length * 2); // uint8_t + pixels = new Int16Array(buf); + var index = 0; + for (var i = 0, length = s.length; i < length; i++) { + pixels[index] = s[i]; + index++; + } + + if (image.Orthanc.Stretched) { + ChangeDynamics(pixels, 0, image.Orthanc.StretchLow, 255, image.Orthanc.StretchHigh); + } + } + + return pixels; + } + + + function getOrthancImage(imageId) { + var result = null; + + $.ajax({ + type: 'GET', + url: '../instances/' + compression + '-' + imageId, + dataType: 'json', + cache: true, + async: false, + success: function(image) { + image.imageId = imageId; + if (image.color) + image.render = cornerstone.renderColorImage; + else + image.render = cornerstone.renderGrayscaleImage; + + image.getPixelData = function() { + if (image.Orthanc.Compression == 'Deflate') + return getPixelDataDeflate(this); + + if (image.Orthanc.Compression == 'Jpeg') + return getPixelDataJpeg(this); + + // Unknown compression + return null; + } + + result = image; + }, + error: function() { + return null; + } + }); + + var deferred = $.Deferred(); + deferred.resolve(result); + return deferred; + } + + // register our imageLoader plugin with cornerstone + cornerstone.registerImageLoader('', getOrthancImage); + +}(cornerstone)); + + +$(document).ready(function() { + $('#open-toolbar').button({ + icons: { primary: 'ui-icon-custom-orthanc' }, + text: false + }); + + var series = window.url('?series', window.location.search); + if (series == null) + return; + + console.log('Displaying series: ' + series); + var instances = [ ]; + + $.ajax({ + type: 'GET', + url: '../series/' + series, + dataType: 'json', + cache: false, + async: false, + success: function(volume) { + if (volume.SortedInstances.length != 0) { + instances = volume.SortedInstances; + $('#topright').html([ + $('<span>').text(volume.PatientID), + $('<br>'), + $('<span>').text(volume.PatientName), + $('<br>'), + $('<span>').text(volume.StudyDescription), + $('<br>'), + $('<span>').text(volume.SeriesDescription) + ]); + } + } + }); + + if (instances.length == 0) + { + console.log('No image in this series'); + return; + } + + + var currentImageIndex = 0; + + // updates the image display + function updateTheImage(imageIndex) { + return cornerstone.loadAndCacheImage(instances[imageIndex]).then(function(image) { + currentImageIndex = imageIndex; + var viewport = cornerstone.getViewport(element); + cornerstone.displayImage(element, image, viewport); + }); + } + + // image enable the element + var element = $('#dicomImage').get(0); + cornerstone.enable(element); + + // set event handlers + /*function onImageRendered(e, eventData) { + $('#topright').text('Render Time:' + eventData.renderTimeInMs + ' ms'); + } + $(element).on('CornerstoneImageRendered', onImageRendered);*/ + + // load and display the image + var imagePromise = updateTheImage(0); + + // add handlers for mouse events once the image is loaded. + imagePromise.then(function() { + viewport = cornerstone.getViewport(element); + UpdateViewportInformation(); + + // add event handlers to pan image on mouse move + $('#dicomImage').mousedown(function (e) { + var lastX = e.pageX; + var lastY = e.pageY; + var mouseButton = e.which; + + $(toolbar).hide(); + + $(document).mousemove(function (e) { + var deltaX = e.pageX - lastX, + deltaY = e.pageY - lastY; + lastX = e.pageX; + lastY = e.pageY; + + if (mouseButton == 1) { + var viewport = cornerstone.getViewport(element); + viewport.voi.windowWidth += (deltaX / viewport.scale); + viewport.voi.windowCenter += (deltaY / viewport.scale); + cornerstone.setViewport(element, viewport); + UpdateViewportInformation(); + } + else if (mouseButton == 2) { + var viewport = cornerstone.getViewport(element); + viewport.translation.x += (deltaX / viewport.scale); + viewport.translation.y += (deltaY / viewport.scale); + cornerstone.setViewport(element, viewport); + } + else if (mouseButton == 3) { + var viewport = cornerstone.getViewport(element); + viewport.scale += (deltaY / 100); + cornerstone.setViewport(element, viewport); + UpdateViewportInformation(); + } + }); + + $(document).mouseup(function (e) { + $(document).unbind('mousemove'); + $(document).unbind('mouseup'); + }); + }); + + $('#dicomImage').on('mousewheel DOMMouseScroll', function (e) { + // Firefox e.originalEvent.detail > 0 scroll back, < 0 scroll forward + // chrome/safari e.originalEvent.wheelDelta < 0 scroll back, > 0 scroll forward + if (e.originalEvent.wheelDelta < 0 || e.originalEvent.detail > 0) { + currentImageIndex ++; + if (currentImageIndex >= instances.length) { + currentImageIndex = instances.length - 1; + } + } else { + currentImageIndex --; + if (currentImageIndex < 0) { + currentImageIndex = 0; + } + } + + updateTheImage(currentImageIndex); + $('#slider').slider("option", "value", currentImageIndex); + + //prevent page fom scrolling + return false; + }); + }); + + + $('#slider').slider({ + min: 0, + max: instances.length - 1, + slide: function(event, ui) { + updateTheImage(ui.value); + } + }); + + var toolbar = $.jsPanel({ + position: { top: 50, left: 10 }, + size: { width: 155, height: 200 }, + content: $('#toolbar-content').clone().show(), + controls: { buttons: 'none' }, + title: '<a target="_blank" href="http://www.orthanc-server.com/"><img src="images/orthanc-logo.png" /></a>' + }); + + $('#open-toolbar').click(function() { + toolbar.toggle(); + }); + + $(toolbar).hide(); + + $('.toolbar-view', toolbar).buttonset() + .children().first().button({ + icons: { primary: 'ui-icon-info' }, + text: false + }).click(ToggleSeriesInformation).next().button({ + icons: { primary: 'ui-icon-custom-inversion' }, + text: false + }).click(ToggleInversion).next().button({ + icons: { primary: 'ui-icon-custom-interpolation' }, + text: false + }).click(ToggleInterpolation).next().button({ + icons: { primary: 'ui-icon-circle-triangle-s' }, + text: false + }).click(function() { + DownloadInstance(instances[currentImageIndex]); + }); + + $('.toolbar-zoom', toolbar).buttonset() + .children().first().button({ + icons: { primary: 'ui-icon-image' }, + text: false + }).click(AdjustZoom).next().button({ + icons: { primary: 'ui-icon-zoomin' }, + text: false + }).click(ZoomIn).next().button({ + icons: { primary: 'ui-icon-zoomout' }, + text: false + }).click(ZoomOut); + + $('.toolbar-windowing', toolbar).buttonset() + .children().first().button({ + icons: { primary: 'ui-icon-custom-default' }, + text: false + }).click(SetDefaultWindowing).next().button({ + icons: { primary: 'ui-icon-custom-stretch' }, + text: false + }).click(SetFullWindowing).next().button({ + icons: { primary: 'ui-icon-custom-lung' }, + text: false + }).click(SetLungWindowing).next().button({ + icons: { primary: 'ui-icon-custom-bone' }, + text: false + }).click(SetBoneWindowing); + + + function SetCompression(c) + { + compression = c; + cornerstone.imageCache.purgeCache(); + updateTheImage(currentImageIndex); + cornerstone.invalidateImageId(instances[currentImageIndex]); + } + + $('.toolbar-quality', toolbar).buttonset() + .children().first().button({ + label: 'L' + }).click(function() { + SetCompression('jpeg80'); + }).next().button({ + label: 'M' + }).click(function() { + SetCompression('jpeg95'); + }).next().button({ + label: 'H' + }).click(function() { + SetCompression('deflate'); + }); + + + ResizeCornerstone(); + $(window).resize(function(e) { + if (!$(e.target).hasClass('jsPanel')) // Ignore toolbar resizing + { + ResizeCornerstone(); + } + }); + +});