changeset 5350:17165a5540f6

integration Orthanc-1.12.0->mainline
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 04 Jul 2023 12:20:41 +0200
parents 0223315871e8 (diff) ca6df6e6c7a6 (current diff)
children e2746201996a 65b4e6ae2703
files
diffstat 97 files changed, 3198 insertions(+), 1170 deletions(-) [+]
line wrap: on
line diff
--- a/LinuxCompilation.txt	Tue May 16 07:09:06 2023 +0200
+++ b/LinuxCompilation.txt	Tue Jul 04 12:20:41 2023 +0200
@@ -161,7 +161,7 @@
        	       	       uuid-dev libcurl4-openssl-dev liblua5.3-dev \
        	       	       libgtest-dev libpng-dev libsqlite3-dev libssl-dev libjpeg-dev \
 		       zlib1g-dev libdcmtk-dev libboost-all-dev libwrap0-dev \
-                       libcharls-dev libjsoncpp-dev libpugixml-dev locales
+                       libcharls-dev libjsoncpp-dev libpugixml-dev locales protobuf-compiler
 
 # cd ./Build
 # cmake -DALLOW_DOWNLOADS=ON \
--- a/NEWS	Tue May 16 07:09:06 2023 +0200
+++ b/NEWS	Tue Jul 04 12:20:41 2023 +0200
@@ -1,6 +1,47 @@
 Pending changes in the mainline
 ===============================
 
+General
+-------
+
+* Orthanc now anonymizes according to Basic Profile of PS 3.15-2023b Table E.1-1
+* Added metrics:
+  - "orthanc_storage_read_bytes" 
+  - "orthanc_storage_written_bytes"
+  - "orthanc_memory_trimming_duration_ms"
+
+REST API
+--------
+
+* API version upgraded to 21
+* "/tools/create-dicom" can now be used to create Encapsulated 3D
+  Manufacturing Model IODs (MTL, OBJ, or STL)
+* Added a route to delete the output of an asynchronous job (right now
+  only for archive jobs): e.g. DELETE /jobs/../archive
+
+Plugins
+-------
+
+* Added "OrthancPluginLoadDicomInstance()" to load DICOM instances from the database
+* Added "OrthancPluginSetMetricsIntegerValue()" to track metrics with integer values
+
+Maintenance
+-----------
+
+* Fix decoding of YBR_FULL RLE images for which the "Planar Configuration" 
+  tag (0028,0006) equals 1
+* Made Orthanc more resilient to common spelling errors in SpecificCharacterSet
+* Modality worklists plugin: Allow searching on private tags (exact match only)
+* Fix orphan files remaining in storage when working with MaximumStorageSize
+  (https://discourse.orthanc-server.org/t/issue-with-deleting-incoming-dicoms-when-maximumstoragesize-is-reached/3510)
+* When deleting a resource, the "LastUpdate" metadata of its parents are now updated
+* Reduced the memory usage when downloading archives when "ZipLoaderThreads" > 0
+* Metrics can be stored either as floating-point numbers, or as integers
+* Upgraded dependencies for static builds:
+  - boost 1.82.0
+* Reduce the frequency of memory trimming from 100ms to 30s to avoid high idle
+  CPU load (https://discourse.orthanc-server.org/t/onchange-callbacks-and-cpu-loads/3534).
+
 
 Version 1.12.0 (2023-04-14)
 ===========================
--- a/OrthancFramework/Resources/CMake/BoostConfiguration.cmake	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancFramework/Resources/CMake/BoostConfiguration.cmake	Tue Jul 04 12:20:41 2023 +0200
@@ -90,10 +90,10 @@
   ## Parameters for static compilation of Boost 
   ##
   
-  set(BOOST_NAME boost_1_80_0)
-  set(BOOST_VERSION 1.80.0)
-  set(BOOST_BCP_SUFFIX bcpdigest-1.11.2)
-  set(BOOST_MD5 "7734e19f9a39a4411b807a9913e4a5ff")
+  set(BOOST_NAME boost_1_82_0)
+  set(BOOST_VERSION 1.82.0)
+  set(BOOST_BCP_SUFFIX bcpdigest-1.12.1)
+  set(BOOST_MD5 "9d02d026c61870b1838b53293692326f")
   set(BOOST_URL "https://orthanc.uclouvain.be/third-party-downloads/${BOOST_NAME}_${BOOST_BCP_SUFFIX}.tar.gz")
   set(BOOST_SOURCES_DIR ${CMAKE_BINARY_DIR}/${BOOST_NAME})
 
@@ -310,12 +310,14 @@
       ${BOOST_SOURCES_DIR}/libs/locale/src/boost/locale/shared/date_time.cpp
       ${BOOST_SOURCES_DIR}/libs/locale/src/boost/locale/shared/formatting.cpp
       ${BOOST_SOURCES_DIR}/libs/locale/src/boost/locale/shared/generator.cpp
+      ${BOOST_SOURCES_DIR}/libs/locale/src/boost/locale/shared/iconv_codecvt.cpp
       ${BOOST_SOURCES_DIR}/libs/locale/src/boost/locale/shared/ids.cpp
       ${BOOST_SOURCES_DIR}/libs/locale/src/boost/locale/shared/localization_backend.cpp
       ${BOOST_SOURCES_DIR}/libs/locale/src/boost/locale/shared/message.cpp
       ${BOOST_SOURCES_DIR}/libs/locale/src/boost/locale/shared/mo_lambda.cpp
       ${BOOST_SOURCES_DIR}/libs/locale/src/boost/locale/util/codecvt_converter.cpp
       ${BOOST_SOURCES_DIR}/libs/locale/src/boost/locale/util/default_locale.cpp
+      ${BOOST_SOURCES_DIR}/libs/locale/src/boost/locale/util/encoding.cpp
       ${BOOST_SOURCES_DIR}/libs/locale/src/boost/locale/util/gregorian.cpp
       ${BOOST_SOURCES_DIR}/libs/locale/src/boost/locale/util/info.cpp
       ${BOOST_SOURCES_DIR}/libs/locale/src/boost/locale/util/locale_data.cpp
--- a/OrthancFramework/Resources/CMake/BoostConfiguration.sh	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancFramework/Resources/CMake/BoostConfiguration.sh	Tue Jul 04 12:20:41 2023 +0200
@@ -22,10 +22,11 @@
 ##   - Orthanc between 1.4.0 and 1.4.2: Boost 1.67.0
 ##   - Orthanc between 1.5.0 and 1.5.4: Boost 1.68.0
 ##   - Orthanc between 1.5.5 and 1.11.1: Boost 1.69.0
-##   - Orthanc >= 1.11.2: Boost 1.80.0
+##   - Orthanc between 1.11.2 and 1.12.0: Boost 1.80.0
+##   - Orthanc >= 1.12.1: Boost 1.82.0
 
-BOOST_VERSION=1_80_0
-ORTHANC_VERSION=1.11.2
+BOOST_VERSION=1_82_0
+ORTHANC_VERSION=1.12.1
 
 rm -rf /tmp/boost_${BOOST_VERSION}
 rm -rf /tmp/bcp/boost_${BOOST_VERSION}
--- a/OrthancFramework/Resources/CMake/DownloadOrthancFramework.cmake	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancFramework/Resources/CMake/DownloadOrthancFramework.cmake	Tue Jul 04 12:20:41 2023 +0200
@@ -154,6 +154,8 @@
         set(ORTHANC_FRAMEWORK_MD5 "ede3de356493a8868545f8cb4b8bc8b5")
       elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.11.3")
         set(ORTHANC_FRAMEWORK_MD5 "5c1b11009d782f248739919db6bf7f7a")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.12.0")
+        set(ORTHANC_FRAMEWORK_MD5 "d32a0cde03b6eb603d8dd2b33d38bf1b")
 
       # Below this point are development snapshots that were used to
       # release some plugin, before an official release of the Orthanc
--- a/OrthancFramework/Resources/CMake/DownloadPackage.cmake	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancFramework/Resources/CMake/DownloadPackage.cmake	Tue Jul 04 12:20:41 2023 +0200
@@ -101,19 +101,26 @@
       message(FATAL_ERROR "CMake is not allowed to download from Internet. Please set the ALLOW_DOWNLOADS option to ON")
     endif()
 
-    if ("${MD5}" STREQUAL "no-check")
-      message(WARNING "Not checking the MD5 of: ${Url}")
-      file(DOWNLOAD "${Url}" "${TMP_PATH}"
-        SHOW_PROGRESS TIMEOUT 300 INACTIVITY_TIMEOUT 60
-        STATUS Failure)
-    else()
-      file(DOWNLOAD "${Url}" "${TMP_PATH}"
-        SHOW_PROGRESS TIMEOUT 300 INACTIVITY_TIMEOUT 60
-        EXPECTED_MD5 "${MD5}" STATUS Failure)
-    endif()
+    foreach (retry RANGE 1 5)   # Retries 5 times
+      if ("${MD5}" STREQUAL "no-check")
+        message(WARNING "Not checking the MD5 of: ${Url}")
+        file(DOWNLOAD "${Url}" "${TMP_PATH}"
+          SHOW_PROGRESS TIMEOUT 30 INACTIVITY_TIMEOUT 10
+          STATUS Failure)
+      else()
+        file(DOWNLOAD "${Url}" "${TMP_PATH}"
+          SHOW_PROGRESS TIMEOUT 30 INACTIVITY_TIMEOUT 10
+          EXPECTED_MD5 "${MD5}" STATUS Failure)
+      endif()
 
-    list(GET Failure 0 Status)
+      list(GET Failure 0 Status)
+      if (Status EQUAL 0)
+        break()  # Successful download
+      endif()
+    endforeach()
+
     if (NOT Status EQUAL 0)
+      file(REMOVE ${TMP_PATH})
       message(FATAL_ERROR "Cannot download file: ${Url}")
     endif()
     
--- a/OrthancFramework/Resources/CMake/OrthancFrameworkParameters.cmake	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancFramework/Resources/CMake/OrthancFrameworkParameters.cmake	Tue Jul 04 12:20:41 2023 +0200
@@ -24,7 +24,7 @@
 #####################################################################
 
 # Version of the build, should always be "mainline" except in release branches
-set(ORTHANC_VERSION "1.12.0")
+set(ORTHANC_VERSION "mainline")
 
 # Version of the database schema. History:
 #   * Orthanc 0.1.0 -> Orthanc 0.3.0 = no versioning
@@ -38,7 +38,7 @@
 # Version of the Orthanc API, can be retrieved from "/system" URI in
 # order to check whether new URI endpoints are available even if using
 # the mainline version of Orthanc
-set(ORTHANC_API_VERSION "20")
+set(ORTHANC_API_VERSION "21")
 
 
 #####################################################################
--- a/OrthancFramework/Resources/Patches/boost-1.65.1-linux-standard-base.patch	Tue May 16 07:09:06 2023 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,12 +0,0 @@
-diff -urEb boost_1_65_1.orig/boost/move/adl_move_swap.hpp boost_1_65_1/boost/move/adl_move_swap.hpp
---- boost_1_65_1.orig/boost/move/adl_move_swap.hpp	2017-11-08 17:43:20.000000000 +0100
-+++ boost_1_65_1/boost/move/adl_move_swap.hpp	2018-01-02 15:34:48.829052917 +0100
-@@ -28,6 +28,8 @@
- //Try to avoid including <algorithm>, as it's quite big
- #if defined(_MSC_VER) && defined(BOOST_DINKUMWARE_STDLIB)
-    #include <utility>   //Dinkum libraries define std::swap in utility which is lighter than algorithm
-+#elif defined(__LSB_VERSION__)
-+#  include <utility>
- #elif defined(BOOST_GNU_STDLIB)
-    //For non-GCC compilers, where GNUC version is not very reliable, or old GCC versions
-    //use the good old stl_algobase header, which is quite lightweight
--- a/OrthancFramework/Resources/Patches/boost-1.66.0-linux-standard-base.patch	Tue May 16 07:09:06 2023 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,13 +0,0 @@
-diff -urEb boost_1_66_0.orig/boost/move/adl_move_swap.hpp boost_1_66_0/boost/move/adl_move_swap.hpp
---- boost_1_66_0.orig/boost/move/adl_move_swap.hpp	2018-04-11 11:56:16.761768726 +0200
-+++ boost_1_66_0/boost/move/adl_move_swap.hpp	2018-04-11 11:57:01.073881330 +0200
-@@ -28,6 +28,8 @@
- //Try to avoid including <algorithm>, as it's quite big
- #if defined(_MSC_VER) && defined(BOOST_DINKUMWARE_STDLIB)
-    #include <utility>   //Dinkum libraries define std::swap in utility which is lighter than algorithm
-+#elif defined(__LSB_VERSION__)
-+#  include <utility>
- #elif defined(BOOST_GNU_STDLIB)
-    //For non-GCC compilers, where GNUC version is not very reliable, or old GCC versions
-    //use the good old stl_algobase header, which is quite lightweight
-Only in boost_1_66_0/boost/move: adl_move_swap.hpp~
--- a/OrthancFramework/Resources/Patches/boost-1.67.0-linux-standard-base.patch	Tue May 16 07:09:06 2023 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,74 +0,0 @@
-diff -urEb boost_1_67_0.orig/boost/move/adl_move_swap.hpp boost_1_67_0/boost/move/adl_move_swap.hpp
---- boost_1_67_0.orig/boost/move/adl_move_swap.hpp	2018-06-20 17:42:27.000000000 +0200
-+++ boost_1_67_0/boost/move/adl_move_swap.hpp	2018-10-12 14:27:41.368076902 +0200
-@@ -28,6 +28,8 @@
- //Try to avoid including <algorithm>, as it's quite big
- #if defined(_MSC_VER) && defined(BOOST_DINKUMWARE_STDLIB)
-    #include <utility>   //Dinkum libraries define std::swap in utility which is lighter than algorithm
-+#elif defined(__LSB_VERSION__)
-+#  include <utility>
- #elif defined(BOOST_GNU_STDLIB)
-    //For non-GCC compilers, where GNUC version is not very reliable, or old GCC versions
-    //use the good old stl_algobase header, which is quite lightweight
-diff -urEb boost_1_67_0.orig/boost/thread/detail/config.hpp boost_1_67_0/boost/thread/detail/config.hpp
---- boost_1_67_0.orig/boost/thread/detail/config.hpp	2018-06-20 17:42:27.000000000 +0200
-+++ boost_1_67_0/boost/thread/detail/config.hpp	2018-10-12 14:27:41.372076898 +0200
-@@ -417,6 +417,8 @@
-   #define BOOST_THREAD_INTERNAL_CLOCK_IS_MONO
- #elif defined(BOOST_THREAD_CHRONO_MAC_API)
-   #define BOOST_THREAD_HAS_MONO_CLOCK
-+#elif defined(__LSB_VERSION__) || defined(__ANDROID__)
-+  #define BOOST_THREAD_HAS_MONO_CLOCK
- #else
-   #include <time.h> // check for CLOCK_MONOTONIC
-   #if defined(CLOCK_MONOTONIC)
-diff -urEb boost_1_67_0.orig/boost/type_traits/detail/has_postfix_operator.hpp boost_1_67_0/boost/type_traits/detail/has_postfix_operator.hpp
---- boost_1_67_0.orig/boost/type_traits/detail/has_postfix_operator.hpp	2018-06-20 17:42:27.000000000 +0200
-+++ boost_1_67_0/boost/type_traits/detail/has_postfix_operator.hpp	2018-10-12 14:31:27.539874170 +0200
-@@ -32,8 +32,11 @@
- namespace boost {
- namespace detail {
- 
-+// https://stackoverflow.com/a/15474269
-+#ifndef Q_MOC_RUN
- // This namespace ensures that argument-dependent name lookup does not mess things up.
- namespace BOOST_JOIN(BOOST_TT_TRAIT_NAME,_impl) {
-+#endif
- 
- // 1. a function to have an instance of type T without requiring T to be default
- // constructible
-@@ -181,7 +184,9 @@
-    BOOST_STATIC_CONSTANT(bool, value = (trait_impl1 < Lhs_noref, Ret, BOOST_TT_FORBIDDEN_IF >::value));
- };
- 
-+#ifndef Q_MOC_RUN
- } // namespace impl
-+#endif
- } // namespace detail
- 
- // this is the accessible definition of the trait to end user
-diff -urEb boost_1_67_0.orig/boost/type_traits/detail/has_prefix_operator.hpp boost_1_67_0/boost/type_traits/detail/has_prefix_operator.hpp
---- boost_1_67_0.orig/boost/type_traits/detail/has_prefix_operator.hpp	2018-06-20 17:42:27.000000000 +0200
-+++ boost_1_67_0/boost/type_traits/detail/has_prefix_operator.hpp	2018-10-12 14:31:40.991862281 +0200
-@@ -45,8 +45,11 @@
- namespace boost {
- namespace detail {
- 
-+// https://stackoverflow.com/a/15474269
-+#ifndef Q_MOC_RUN
- // This namespace ensures that argument-dependent name lookup does not mess things up.
- namespace BOOST_JOIN(BOOST_TT_TRAIT_NAME,_impl) {
-+#endif
- 
- // 1. a function to have an instance of type T without requiring T to be default
- // constructible
-@@ -194,7 +197,9 @@
-    BOOST_STATIC_CONSTANT(bool, value = (trait_impl1 < Rhs_noref, Ret, BOOST_TT_FORBIDDEN_IF >::value));
- };
- 
-+#ifndef Q_MOC_RUN
- } // namespace impl
-+#endif
- } // namespace detail
- 
- // this is the accessible definition of the trait to end user
--- a/OrthancFramework/Resources/Patches/boost-1.68.0-linux-standard-base.patch	Tue May 16 07:09:06 2023 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,76 +0,0 @@
-diff -urEb boost_1_68_0.orig/boost/move/adl_move_swap.hpp boost_1_68_0/boost/move/adl_move_swap.hpp
---- boost_1_68_0.orig/boost/move/adl_move_swap.hpp	2018-11-13 16:08:32.214434915 +0100
-+++ boost_1_68_0/boost/move/adl_move_swap.hpp	2018-11-13 16:09:03.558399048 +0100
-@@ -28,6 +28,8 @@
- //Try to avoid including <algorithm>, as it's quite big
- #if defined(_MSC_VER) && defined(BOOST_DINKUMWARE_STDLIB)
-    #include <utility>   //Dinkum libraries define std::swap in utility which is lighter than algorithm
-+#elif defined(__LSB_VERSION__)
-+#  include <utility>
- #elif defined(BOOST_GNU_STDLIB)
-    //For non-GCC compilers, where GNUC version is not very reliable, or old GCC versions
-    //use the good old stl_algobase header, which is quite lightweight
-diff -urEb boost_1_68_0.orig/boost/thread/detail/config.hpp boost_1_68_0/boost/thread/detail/config.hpp
---- boost_1_68_0.orig/boost/thread/detail/config.hpp	2018-11-13 16:08:32.210434920 +0100
-+++ boost_1_68_0/boost/thread/detail/config.hpp	2018-11-13 16:10:03.386329911 +0100
-@@ -417,7 +417,7 @@
-   #define BOOST_THREAD_INTERNAL_CLOCK_IS_MONO
- #elif defined(BOOST_THREAD_CHRONO_MAC_API)
-   #define BOOST_THREAD_HAS_MONO_CLOCK
--#elif defined(__ANDROID__)
-+#elif defined(__LSB_VERSION__) || defined(__ANDROID__)
-   #define BOOST_THREAD_HAS_MONO_CLOCK
-   #if defined(__ANDROID_API__) && __ANDROID_API__ >= 21
-     #define BOOST_THREAD_INTERNAL_CLOCK_IS_MONO
-diff -urEb boost_1_68_0.orig/boost/type_traits/detail/has_postfix_operator.hpp boost_1_68_0/boost/type_traits/detail/has_postfix_operator.hpp
---- boost_1_68_0.orig/boost/type_traits/detail/has_postfix_operator.hpp	2018-11-13 16:08:32.206434924 +0100
-+++ boost_1_68_0/boost/type_traits/detail/has_postfix_operator.hpp	2018-11-13 16:11:08.374253901 +0100
-@@ -32,8 +32,11 @@
- namespace boost {
- namespace detail {
- 
-+// https://stackoverflow.com/a/15474269
-+#ifndef Q_MOC_RUN
- // This namespace ensures that argument-dependent name lookup does not mess things up.
- namespace BOOST_JOIN(BOOST_TT_TRAIT_NAME,_impl) {
-+#endif
- 
- // 1. a function to have an instance of type T without requiring T to be default
- // constructible
-@@ -181,7 +184,9 @@
-    BOOST_STATIC_CONSTANT(bool, value = (trait_impl1 < Lhs_noref, Ret, BOOST_TT_FORBIDDEN_IF >::value));
- };
- 
-+#ifndef Q_MOC_RUN
- } // namespace impl
-+#endif
- } // namespace detail
- 
- // this is the accessible definition of the trait to end user
-diff -urEb boost_1_68_0.orig/boost/type_traits/detail/has_prefix_operator.hpp boost_1_68_0/boost/type_traits/detail/has_prefix_operator.hpp
---- boost_1_68_0.orig/boost/type_traits/detail/has_prefix_operator.hpp	2018-11-13 16:08:32.206434924 +0100
-+++ boost_1_68_0/boost/type_traits/detail/has_prefix_operator.hpp	2018-11-13 16:14:30.278012856 +0100
-@@ -45,8 +45,11 @@
- namespace boost {
- namespace detail {
- 
-+// https://stackoverflow.com/a/15474269
-+#ifndef Q_MOC_RUN
- // This namespace ensures that argument-dependent name lookup does not mess things up.
- namespace BOOST_JOIN(BOOST_TT_TRAIT_NAME,_impl) {
-+#endif
- 
- // 1. a function to have an instance of type T without requiring T to be default
- // constructible
-@@ -194,7 +197,10 @@
-    BOOST_STATIC_CONSTANT(bool, value = (trait_impl1 < Rhs_noref, Ret, BOOST_TT_FORBIDDEN_IF >::value));
- };
- 
-+
-+#ifndef Q_MOC_RUN
- } // namespace impl
-+#endif
- } // namespace detail
- 
- // this is the accessible definition of the trait to end user
-Only in boost_1_68_0/boost/type_traits/detail: has_prefix_operator.hpp~
--- a/OrthancFramework/Resources/Patches/civetweb-1.11.patch	Tue May 16 07:09:06 2023 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,98 +0,0 @@
-diff -urEb civetweb-1.11.orig/include/civetweb.h civetweb-1.11/include/civetweb.h
---- civetweb-1.11.orig/include/civetweb.h	2019-01-17 21:09:41.844888908 +0100
-+++ civetweb-1.11/include/civetweb.h	2019-01-21 12:05:08.138998659 +0100
-@@ -1507,6 +1507,10 @@
- #endif
- 
- 
-+// Added by SJ
-+CIVETWEB_API void mg_disable_keep_alive(struct mg_connection *conn);
-+
-+
- #ifdef __cplusplus
- }
- #endif /* __cplusplus */
-diff -urEb civetweb-1.11.orig/src/civetweb.c civetweb-1.11/src/civetweb.c
---- civetweb-1.11.orig/src/civetweb.c	2019-01-17 21:09:41.852888857 +0100
-+++ civetweb-1.11/src/civetweb.c	2019-01-21 12:06:35.826868284 +0100
-@@ -59,6 +59,9 @@
- #if defined(__linux__) && !defined(_XOPEN_SOURCE)
- #define _XOPEN_SOURCE 600 /* For flockfile() on Linux */
- #endif
-+#if defined(__LSB_VERSION__)
-+#define NEED_TIMEGM
-+#endif
- #if !defined(_LARGEFILE_SOURCE)
- #define _LARGEFILE_SOURCE /* For fseeko(), ftello() */
- #endif
-@@ -129,6 +132,12 @@
- 
- 
- /* Alternative queue is well tested and should be the new default */
-+#if defined(__LSB_VERSION__)
-+/* Function "eventfd()" is not available in Linux Standard Base, can't
-+ * use the alternative queue */
-+#define NO_ALTERNATIVE_QUEUE
-+#endif
-+
- #if defined(NO_ALTERNATIVE_QUEUE)
- #if defined(ALTERNATIVE_QUEUE)
- #error "Define ALTERNATIVE_QUEUE or NO_ALTERNATIVE_QUEUE or none, but not both"
-@@ -536,6 +545,10 @@
- #if !defined(EWOULDBLOCK)
- #define EWOULDBLOCK WSAEWOULDBLOCK
- #endif /* !EWOULDBLOCK */
-+#if !defined(ECONNRESET)
-+/* This macro is not defined e.g. in Visual Studio 2008 */
-+#define ECONNRESET WSAECONNRESET
-+#endif /* !ECONNRESET */
- #define _POSIX_
- #define INT64_FMT "I64d"
- #define UINT64_FMT "I64u"
-@@ -2939,6 +2952,13 @@
- #endif
- 
- 
-+#if defined(__LSB_VERSION__)
-+static void
-+mg_set_thread_name(const char *threadName)
-+{
-+  /* prctl() does not seem to be available in Linux Standard Base */
-+}
-+#else
- static void
- mg_set_thread_name(const char *name)
- {
-@@ -2980,6 +3000,7 @@
- 	(void)prctl(PR_SET_NAME, threadName, 0, 0, 0);
- #endif
- }
-+#endif
- #else /* !defined(NO_THREAD_NAME) */
- void
- mg_set_thread_name(const char *threadName)
-@@ -16919,6 +16940,10 @@
- 	/* Message is a valid request */
- 
- 	/* Is there a "host" ? */
-+        /* https://github.com/civetweb/civetweb/pull/675/commits/96e3e8c50acb4b8e0c946d02b5f880a3e62986e1 */
-+	if (conn->host!=NULL) {
-+		mg_free((void *)conn->host);
-+	}
- 	conn->host = alloc_get_host(conn);
- 	if (!conn->host) {
- 		mg_snprintf(conn,
-@@ -19857,4 +19882,13 @@
- }
- 
- 
-+// Added by SJ
-+void mg_disable_keep_alive(struct mg_connection *conn)
-+{
-+  if (conn != NULL) {
-+    conn->must_close = 1;
-+  }
-+}
-+
-+
- /* End of civetweb.c */
--- a/OrthancFramework/Resources/Patches/civetweb-1.12.patch	Tue May 16 07:09:06 2023 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,41 +0,0 @@
-diff -urEb civetweb-1.12.orig/include/civetweb.h civetweb-1.12/include/civetweb.h
---- civetweb-1.12.orig/include/civetweb.h	2020-10-06 12:39:10.634902843 +0200
-+++ civetweb-1.12/include/civetweb.h	2020-10-06 12:39:30.630872089 +0200
-@@ -1614,6 +1614,9 @@
-                                   struct mg_error_data *error);
- #endif
- 
-+// Added by SJ
-+CIVETWEB_API void mg_disable_keep_alive(struct mg_connection *conn);
-+
- #ifdef __cplusplus
- }
- #endif /* __cplusplus */
-diff -urEb civetweb-1.12.orig/src/civetweb.c civetweb-1.12/src/civetweb.c
---- civetweb-1.12.orig/src/civetweb.c	2020-10-06 12:39:10.638902837 +0200
-+++ civetweb-1.12/src/civetweb.c	2020-10-06 12:41:40.110671929 +0200
-@@ -10525,6 +10525,11 @@
-     /* + MicroSoft extensions
-      * https://msdn.microsoft.com/en-us/library/aa142917.aspx */
- 
-+    /* Added by SJ, for write access to WebDAV on Windows >= 7 */
-+    {"LOCK", 1, 1, 0, 0, 0},
-+    {"UNLOCK", 1, 0, 0, 0, 0},
-+    {"PROPPATCH", 1, 1, 0, 0, 0},
-+    
-     /* REPORT method (RFC 3253) */
-     {"REPORT", 1, 1, 1, 1, 1},
-     /* REPORT method only allowed for CGI/Lua/LSP and callbacks. */
-@@ -20704,5 +20709,12 @@
- 	return 1;
- }
- 
-+// Added by SJ
-+void mg_disable_keep_alive(struct mg_connection *conn)
-+{
-+  if (conn != NULL) {
-+    conn->must_close = 1;
-+  }
-+}
- 
- /* End of civetweb.c */
--- a/OrthancFramework/Sources/Cache/SharedArchive.cpp	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancFramework/Sources/Cache/SharedArchive.cpp	Tue Jul 04 12:20:41 2023 +0200
@@ -102,7 +102,7 @@
 
   std::string SharedArchive::Add(IDynamicObject* obj)
   {
-    boost::mutex::scoped_lock lock(mutex_);
+    boost::recursive_mutex::scoped_lock lock(mutex_);
 
     if (archive_.size() == maxSize_)
     {
@@ -122,7 +122,7 @@
 
   void SharedArchive::Remove(const std::string& id)
   {
-    boost::mutex::scoped_lock lock(mutex_);
+    boost::recursive_mutex::scoped_lock lock(mutex_);
     RemoveInternal(id);      
   }
 
@@ -132,7 +132,7 @@
     items.clear();
 
     {
-      boost::mutex::scoped_lock lock(mutex_);
+      boost::recursive_mutex::scoped_lock lock(mutex_);
 
       for (Archive::const_iterator it = archive_.begin();
            it != archive_.end(); ++it)
--- a/OrthancFramework/Sources/Cache/SharedArchive.h	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancFramework/Sources/Cache/SharedArchive.h	Tue Jul 04 12:20:41 2023 +0200
@@ -44,9 +44,9 @@
   private:
     typedef std::map<std::string, IDynamicObject*>  Archive;
 
-    size_t         maxSize_;
-    boost::mutex   mutex_;
-    Archive        archive_;
+    size_t                  maxSize_;
+    boost::recursive_mutex  mutex_;
+    Archive                 archive_;
     LeastRecentlyUsedIndex<std::string> lru_;
 
     void RemoveInternal(const std::string& id);
@@ -55,8 +55,8 @@
     class ORTHANC_PUBLIC Accessor : public boost::noncopyable
     {
     private:
-      boost::mutex::scoped_lock  lock_;
-      IDynamicObject*            item_;
+      boost::recursive_mutex::scoped_lock lock_;
+      IDynamicObject*                     item_;
 
     public:
       Accessor(SharedArchive& that,
--- a/OrthancFramework/Sources/DicomFormat/DicomArray.cpp	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancFramework/Sources/DicomFormat/DicomArray.cpp	Tue Jul 04 12:20:41 2023 +0200
@@ -86,7 +86,21 @@
     {
       DicomTag t = elements_[i]->GetTag();
       const DicomValue& v = elements_[i]->GetValue();
-      std::string s = v.IsNull() ? "(null)" : v.GetContent();
+
+      std::string s;
+      if (v.IsNull())
+      {
+        s = "(null)";
+      }
+      else if (v.IsSequence())
+      {
+        s = "(sequence)";
+      }
+      else
+      {
+        s = v.GetContent();
+      }
+
       printf("0x%04x 0x%04x [%s]\n", t.GetGroup(), t.GetElement(), s.c_str());
     }
   }
--- a/OrthancFramework/Sources/DicomFormat/DicomImageInformation.cpp	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancFramework/Sources/DicomFormat/DicomImageInformation.cpp	Tue Jul 04 12:20:41 2023 +0200
@@ -423,4 +423,46 @@
   {
     return 256;
   }
+
+
+  ValueRepresentation DicomImageInformation::GuessPixelDataValueRepresentation(const DicomTransferSyntax& transferSyntax,
+                                                                               unsigned int bitsAllocated)
+  {
+    /**
+     * This approach is validated in "Tests/GuessPixelDataVR.py":
+     * https://hg.orthanc-server.com/orthanc-tests/file/tip/Tests/GuessPixelDataVR.py
+     **/
+
+    if (transferSyntax == DicomTransferSyntax_LittleEndianExplicit ||
+        transferSyntax == DicomTransferSyntax_BigEndianExplicit)
+    {
+      /**
+       * Same rules apply to Little Endian Explicit and Big Endian
+       * Explicit (now retired). The VR of the pixel data directly
+       * depends upon the "Bits Allocated (0028,0100)" tag:
+       * https://dicom.nema.org/medical/dicom/current/output/chtml/part05/sect_A.2.html
+       * https://dicom.nema.org/medical/dicom/2016b/output/chtml/part05/sect_A.3.html
+       **/
+      if (bitsAllocated > 8)
+      {
+        return ValueRepresentation_OtherWord;
+      }
+      else
+      {
+        return ValueRepresentation_OtherByte;
+      }
+    }
+    else if (transferSyntax == DicomTransferSyntax_LittleEndianImplicit)
+    {
+      // Assume "OW" for DICOM Implicit VR Little Endian Transfer Syntax
+      // https://dicom.nema.org/medical/dicom/current/output/chtml/part05/chapter_A.html#sect_A.1
+      return ValueRepresentation_OtherWord;
+    }
+    else
+    {
+      // Assume "OB" for all the compressed transfer syntaxes
+      // https://dicom.nema.org/medical/dicom/current/output/chtml/part05/sect_A.4.html
+      return ValueRepresentation_OtherByte;
+    }
+  }
 }
--- a/OrthancFramework/Sources/DicomFormat/DicomImageInformation.h	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancFramework/Sources/DicomFormat/DicomImageInformation.h	Tue Jul 04 12:20:41 2023 +0200
@@ -95,5 +95,8 @@
      * was implicitly used in Orthanc <= 1.7.2.
      **/
     static unsigned int GetUsefulTagLength();
+
+    static ValueRepresentation GuessPixelDataValueRepresentation(const DicomTransferSyntax& transferSyntax,
+                                                                 unsigned int bitsAllocated);
   };
 }
--- a/OrthancFramework/Sources/DicomFormat/DicomMap.cpp	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancFramework/Sources/DicomFormat/DicomMap.cpp	Tue Jul 04 12:20:41 2023 +0200
@@ -34,6 +34,7 @@
 #include "../OrthancException.h"
 #include "../Toolbox.h"
 #include "DicomArray.h"
+#include "DicomImageInformation.h"
 
 #if ORTHANC_ENABLE_DCMTK == 1
 #include "../DicomParsing/FromDcmtkBridge.h"
@@ -418,7 +419,7 @@
     SetValueInternal(group, element, new DicomValue(str, isBinary));
   }
 
-  void DicomMap::SetValue(const DicomTag& tag, const Json::Value& value)
+  void DicomMap::SetSequenceValue(const DicomTag& tag, const Json::Value& value)
   {
     SetValueInternal(tag.GetGroup(), tag.GetElement(), new DicomValue(value));
   }
@@ -1456,7 +1457,7 @@
         }
         else
         {
-          SetValue(tag, value["Value"]);
+          SetSequenceValue(tag, value["Value"]);
         }
       }
     }
@@ -1530,7 +1531,7 @@
     {
       if (it->second->IsSequence())
       {
-        result.SetValue(it->first, it->second->GetSequenceContent());
+        result.SetSequenceValue(it->first, it->second->GetSequenceContent());
       }
     }
   }
@@ -1808,6 +1809,19 @@
   }
   
 
+  ValueRepresentation DicomMap::GuessPixelDataValueRepresentation(DicomTransferSyntax transferSyntax) const
+  {
+    const DicomValue* value = TestAndGetValue(DICOM_TAG_BITS_ALLOCATED);
+
+    uint32_t bitsAllocated;
+    if (value == NULL ||
+        !value->ParseUnsignedInteger32(bitsAllocated))
+    {
+      bitsAllocated = 8;
+    }
+
+    return DicomImageInformation::GuessPixelDataValueRepresentation(transferSyntax, bitsAllocated);
+  }
   
 
   void DicomMap::Print(FILE* fp) const
--- a/OrthancFramework/Sources/DicomFormat/DicomMap.h	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancFramework/Sources/DicomFormat/DicomMap.h	Tue Jul 04 12:20:41 2023 +0200
@@ -85,8 +85,8 @@
                   const std::string& str,
                   bool isBinary);
 
-    void SetValue(const DicomTag& tag,
-                  const Json::Value& value);
+    void SetSequenceValue(const DicomTag& tag,
+                          const Json::Value& value);
 
     bool HasTag(uint16_t group, uint16_t element) const;
 
@@ -230,6 +230,8 @@
     void DumpMainDicomTags(Json::Value& target,
                            ResourceType level) const;
 
+    ValueRepresentation GuessPixelDataValueRepresentation(DicomTransferSyntax transferSyntax) const;
+
     void Print(FILE* fp) const;  // For debugging only
   };
 }
--- a/OrthancFramework/Sources/DicomFormat/DicomStreamReader.cpp	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancFramework/Sources/DicomFormat/DicomStreamReader.cpp	Tue Jul 04 12:20:41 2023 +0200
@@ -579,13 +579,17 @@
   class DicomStreamReader::PixelDataVisitor : public DicomStreamReader::IVisitor
   {
   private:
-    bool      hasPixelData_;
-    uint64_t  pixelDataOffset_;
+    bool                 hasPixelData_;
+    uint64_t             pixelDataOffset_;
+    ValueRepresentation  pixelDataVR_;
+    DicomTransferSyntax  transferSyntax_;
     
   public:
     PixelDataVisitor() :
       hasPixelData_(false),
-      pixelDataOffset_(0)
+      pixelDataOffset_(0),
+      pixelDataVR_(ValueRepresentation_Unknown),
+      transferSyntax_(DicomTransferSyntax_LittleEndianImplicit) // Default DICOM transfer syntax
     {
     }
     
@@ -597,6 +601,7 @@
 
     virtual void VisitTransferSyntax(DicomTransferSyntax transferSyntax) ORTHANC_OVERRIDE
     {
+      transferSyntax_ = transferSyntax;
     }
     
     virtual bool VisitDatasetTag(const DicomTag& tag,
@@ -609,6 +614,23 @@
       {
         hasPixelData_ = true;
         pixelDataOffset_ = fileOffset;
+
+        if (transferSyntax_ == DicomTransferSyntax_LittleEndianImplicit)
+        {
+          // Implicit Little Endian has always "OW" VR for pixel data
+          // https://dicom.nema.org/medical/dicom/current/output/chtml/part05/chapter_A.html
+          pixelDataVR_ = ValueRepresentation_OtherWord;
+        }
+        else if (transferSyntax_ == DicomTransferSyntax_LittleEndianExplicit ||
+                 transferSyntax_ == DicomTransferSyntax_BigEndianExplicit)
+        {
+          pixelDataVR_ = vr;
+        }
+        else
+        {
+          // Compressed transfer syntaxes must always be OB
+          pixelDataVR_ = ValueRepresentation_OtherByte;
+        }
       }
 
       // Stop processing once pixel data has been passed
@@ -625,7 +647,13 @@
       return pixelDataOffset_;
     }
 
+    ValueRepresentation GetPixelDataVR() const
+    {
+      return pixelDataVR_;
+    }
+
     static bool LookupPixelDataOffset(uint64_t& offset,
+                                      ValueRepresentation& vr,
                                       std::istream& stream)
     {
       PixelDataVisitor visitor;
@@ -672,6 +700,7 @@
             s[3] == char(0x00))
         {
           offset = visitor.GetPixelDataOffset();
+          vr = visitor.GetPixelDataVR();
           return true;
         }
         else
@@ -688,20 +717,22 @@
 
   
   bool DicomStreamReader::LookupPixelDataOffset(uint64_t& offset,
+                                                ValueRepresentation& vr,
                                                 const std::string& dicom)
   {
     std::stringstream stream(dicom);
-    return PixelDataVisitor::LookupPixelDataOffset(offset, stream);
+    return PixelDataVisitor::LookupPixelDataOffset(offset, vr, stream);
   }
   
 
   bool DicomStreamReader::LookupPixelDataOffset(uint64_t& offset,
+                                                ValueRepresentation& vr,
                                                 const void* buffer,
                                                 size_t size)
   {
     boost::iostreams::array_source source(reinterpret_cast<const char*>(buffer), size);
     boost::iostreams::stream<boost::iostreams::array_source> stream(source);
-    return PixelDataVisitor::LookupPixelDataOffset(offset, stream);
+    return PixelDataVisitor::LookupPixelDataOffset(offset, vr, stream);
   }
 }
 
--- a/OrthancFramework/Sources/DicomFormat/DicomStreamReader.h	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancFramework/Sources/DicomFormat/DicomStreamReader.h	Tue Jul 04 12:20:41 2023 +0200
@@ -128,10 +128,12 @@
 
     uint64_t GetProcessedBytes() const;
 
-    static bool LookupPixelDataOffset(uint64_t& offset,
+    static bool LookupPixelDataOffset(uint64_t& offset /* out */,
+                                      ValueRepresentation& vr /* out */,
                                       const std::string& dicom);
 
-    static bool LookupPixelDataOffset(uint64_t& offset,
+    static bool LookupPixelDataOffset(uint64_t& offset /* out */,
+                                      ValueRepresentation& vr /* out */,
                                       const void* buffer,
                                       size_t size);
   };
--- a/OrthancFramework/Sources/DicomFormat/DicomValue.cpp	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancFramework/Sources/DicomFormat/DicomValue.cpp	Tue Jul 04 12:20:41 2023 +0200
@@ -66,6 +66,10 @@
     type_(Type_SequenceAsJson),
     sequenceJson_(value)
   {
+    if (value.type() != Json::arrayValue)
+    {
+      throw OrthancException(ErrorCode_BadParameterType);
+    }
   }
   
   const std::string& DicomValue::GetContent() const
--- a/OrthancFramework/Sources/DicomParsing/DicomModification.cpp	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancFramework/Sources/DicomParsing/DicomModification.cpp	Tue Jul 04 12:20:41 2023 +0200
@@ -43,6 +43,9 @@
 static const std::string ORTHANC_DEIDENTIFICATION_METHOD_2021b =
   "Orthanc " ORTHANC_VERSION " - PS 3.15-2021b Table E.1-1 Basic Profile";
 
+static const std::string ORTHANC_DEIDENTIFICATION_METHOD_2023b =
+  "Orthanc " ORTHANC_VERSION " - PS 3.15-2023b Table E.1-1 Basic Profile";
+
 namespace Orthanc
 {
   namespace
@@ -427,7 +430,8 @@
 
       if (it->second->asString() == ORTHANC_DEIDENTIFICATION_METHOD_2008 ||
           it->second->asString() == ORTHANC_DEIDENTIFICATION_METHOD_2017c ||
-          it->second->asString() == ORTHANC_DEIDENTIFICATION_METHOD_2021b)
+          it->second->asString() == ORTHANC_DEIDENTIFICATION_METHOD_2021b ||
+          it->second->asString() == ORTHANC_DEIDENTIFICATION_METHOD_2023b)
       {
         delete it->second;
         replacements_.erase(it);
@@ -831,9 +835,6 @@
      * generated automatically by calling:
      * "../../../OrthancServer/Resources/GenerateAnonymizationProfile.py
      * https://raw.githubusercontent.com/jodogne/dicom-specification/master/2021b/part15.xml"
-     *
-     * http://dicom.nema.org/medical/dicom/2021b/output/chtml/part15/chapter_E.html#table_E.1-1a
-     * http://dicom.nema.org/medical/dicom/2021b/output/chtml/part15/chapter_E.html#table_E.1-1
      **/
     
 #include "DicomModification_Anonymization2021b.impl.h"
@@ -843,6 +844,26 @@
   }
   
 
+  void DicomModification::SetupAnonymization2023b()
+  {
+    /**
+     * This is Table E.1-1 from PS 3.15-2023b (DICOM Part 15: Security
+     * and System Management Profiles), "basic profile" column. It was
+     * generated automatically by calling:
+     * "../../../OrthancServer/Resources/GenerateAnonymizationProfile.py
+     * https://raw.githubusercontent.com/jodogne/dicom-specification/master/2023b/part15.xml"
+     *
+     * http://dicom.nema.org/medical/dicom/current/output/chtml/part15/chapter_E.html#table_E.1-1a
+     * http://dicom.nema.org/medical/dicom/current/output/chtml/part15/chapter_E.html#table_E.1-1
+     **/
+    
+#include "DicomModification_Anonymization2023b.impl.h"
+    
+    // Set the DeidentificationMethod tag
+    ReplaceInternal(DICOM_TAG_DEIDENTIFICATION_METHOD, ORTHANC_DEIDENTIFICATION_METHOD_2023b);
+  }
+  
+
   void DicomModification::SetupAnonymization(DicomVersion version)
   {
     isAnonymization_ = true;
@@ -874,6 +895,10 @@
         SetupAnonymization2021b();
         break;
 
+      case DicomVersion_2023b:
+        SetupAnonymization2023b();
+        break;
+
       default:
         throw OrthancException(ErrorCode_ParameterOutOfRange);
     }
@@ -1344,7 +1369,9 @@
       
     // DicomVersion version = DicomVersion_2008;   // For Orthanc <= 1.2.0
     // DicomVersion version = DicomVersion_2017c;  // For Orthanc between 1.3.0 and 1.9.3
-    DicomVersion version = DicomVersion_2021b;     // For Orthanc >= 1.9.4
+    // DicomVersion version = DicomVersion_2021b;  // For Orthanc >= 1.9.4
+    DicomVersion version = DicomVersion_2023b;     // For Orthanc >= 1.12.1
+    
     if (request.isMember("DicomVersion"))
     {
       if (request["DicomVersion"].type() != Json::stringValue)
--- a/OrthancFramework/Sources/DicomParsing/DicomModification.h	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancFramework/Sources/DicomParsing/DicomModification.h	Tue Jul 04 12:20:41 2023 +0200
@@ -179,6 +179,8 @@
 
     void SetupAnonymization2021b();
 
+    void SetupAnonymization2023b();
+
     void UnserializeUidMap(ResourceType level,
                            const Json::Value& serialized,
                            const char* field);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancFramework/Sources/DicomParsing/DicomModification_Anonymization2023b.impl.h	Tue Jul 04 12:20:41 2023 +0200
@@ -0,0 +1,610 @@
+// RelationshipsVisitor handles (0x0008, 0x1140)  /* X/Z/U* */   // Referenced Image Sequence
+// RelationshipsVisitor handles (0x0008, 0x2112)  /* X/Z/U* */   // Source Image Sequence
+// Tag (0x0008, 0x0018) is set in Apply()         /* U */        // SOP Instance UID
+// Tag (0x0010, 0x0010) is set below (*)          /* Z */        // Patient's Name
+// Tag (0x0010, 0x0020) is set below (*)          /* Z */        // Patient ID
+// Tag (0x0020, 0x000d) is set in Apply()         /* U */        // Study Instance UID
+// Tag (0x0020, 0x000e) is set in Apply()         /* U */        // Series Instance UID
+clearings_.insert(DicomTag(0x0008, 0x0020));                     // Study Date
+clearings_.insert(DicomTag(0x0008, 0x0023));  /* Z/D */          // Content Date
+clearings_.insert(DicomTag(0x0008, 0x0030));                     // Study Time
+clearings_.insert(DicomTag(0x0008, 0x0033));  /* Z/D */          // Content Time
+clearings_.insert(DicomTag(0x0008, 0x0050));                     // Accession Number
+clearings_.insert(DicomTag(0x0008, 0x0090));                     // Referring Physician's Name
+clearings_.insert(DicomTag(0x0008, 0x009c));                     // Consulting Physician's Name
+clearings_.insert(DicomTag(0x0008, 0x0106));  /* D */            // Context Group Version
+clearings_.insert(DicomTag(0x0008, 0x0107));  /* D */            // Context Group Local Version
+clearings_.insert(DicomTag(0x0010, 0x0030));                     // Patient's Birth Date
+clearings_.insert(DicomTag(0x0010, 0x0040));                     // Patient's Sex
+clearings_.insert(DicomTag(0x0012, 0x0010));  /* D */            // Clinical Trial Sponsor Name
+clearings_.insert(DicomTag(0x0012, 0x0020));  /* D */            // Clinical Trial Protocol ID
+clearings_.insert(DicomTag(0x0012, 0x0021));                     // Clinical Trial Protocol Name
+clearings_.insert(DicomTag(0x0012, 0x0030));                     // Clinical Trial Site ID
+clearings_.insert(DicomTag(0x0012, 0x0031));                     // Clinical Trial Site Name
+clearings_.insert(DicomTag(0x0012, 0x0040));  /* D */            // Clinical Trial Subject ID
+clearings_.insert(DicomTag(0x0012, 0x0042));  /* D */            // Clinical Trial Subject Reading ID
+clearings_.insert(DicomTag(0x0012, 0x0050));                     // Clinical Trial Time Point ID
+clearings_.insert(DicomTag(0x0012, 0x0060));                     // Clinical Trial Coordinating Center Name
+clearings_.insert(DicomTag(0x0012, 0x0081));  /* D */            // Clinical Trial Protocol Ethics Committee Name
+clearings_.insert(DicomTag(0x0018, 0x0010));  /* Z/D */          // Contrast/Bolus Agent
+clearings_.insert(DicomTag(0x0018, 0x11bb));  /* D */            // Acquisition Field Of View Label
+clearings_.insert(DicomTag(0x0018, 0x1203));                     // Calibration DateTime
+clearings_.insert(DicomTag(0x0018, 0x9074));  /* D */            // Frame Acquisition DateTime
+clearings_.insert(DicomTag(0x0018, 0x9151));  /* D */            // Frame Reference DateTime
+clearings_.insert(DicomTag(0x0018, 0x9367));  /* D */            // X-Ray Source ID
+clearings_.insert(DicomTag(0x0018, 0x9369));  /* D */            // Source Start DateTime
+clearings_.insert(DicomTag(0x0018, 0x936a));  /* D */            // Source End DateTime
+clearings_.insert(DicomTag(0x0018, 0x9371));  /* D */            // X-Ray Detector ID
+clearings_.insert(DicomTag(0x0018, 0x9623));  /* D */            // Functional Sync Pulse
+clearings_.insert(DicomTag(0x0018, 0x9701));  /* D */            // Decay Correction DateTime
+clearings_.insert(DicomTag(0x0018, 0x9804));  /* D */            // Exclusion Start DateTime
+clearings_.insert(DicomTag(0x0018, 0x9919));  /* Z/D */          // Instruction Performed DateTime
+clearings_.insert(DicomTag(0x0020, 0x0010));                     // Study ID
+clearings_.insert(DicomTag(0x0034, 0x0001));  /* D */            // Flow Identifier Sequence
+clearings_.insert(DicomTag(0x0034, 0x0002));  /* D */            // Flow Identifier
+clearings_.insert(DicomTag(0x0034, 0x0005));  /* D */            // Source Identifier
+clearings_.insert(DicomTag(0x0034, 0x0007));  /* D */            // Frame Origin Timestamp
+clearings_.insert(DicomTag(0x003a, 0x0314));  /* D */            // Impedance Measurement DateTime
+clearings_.insert(DicomTag(0x0040, 0x0512));  /* D */            // Container Identifier
+clearings_.insert(DicomTag(0x0040, 0x0513));                     // Issuer of the Container Identifier Sequence
+clearings_.insert(DicomTag(0x0040, 0x0551));  /* D */            // Specimen Identifier
+clearings_.insert(DicomTag(0x0040, 0x0562));                     // Issuer of the Specimen Identifier Sequence
+clearings_.insert(DicomTag(0x0040, 0x0610));                     // Specimen Preparation Sequence
+clearings_.insert(DicomTag(0x0040, 0x1101));  /* D */            // Person Identification Code Sequence
+clearings_.insert(DicomTag(0x0040, 0x2016));                     // Placer Order Number / Imaging Service Request
+clearings_.insert(DicomTag(0x0040, 0x2017));                     // Filler Order Number / Imaging Service Request
+clearings_.insert(DicomTag(0x0040, 0xa027));  /* D */            // Verifying Organization
+clearings_.insert(DicomTag(0x0040, 0xa030));  /* D */            // Verification DateTime
+clearings_.insert(DicomTag(0x0040, 0xa073));  /* D */            // Verifying Observer Sequence
+clearings_.insert(DicomTag(0x0040, 0xa075));  /* D */            // Verifying Observer Name
+clearings_.insert(DicomTag(0x0040, 0xa082));                     // Participation DateTime
+clearings_.insert(DicomTag(0x0040, 0xa088));                     // Verifying Observer Identification Code Sequence
+clearings_.insert(DicomTag(0x0040, 0xa120));  /* D */            // DateTime
+clearings_.insert(DicomTag(0x0040, 0xa121));  /* D */            // Date
+clearings_.insert(DicomTag(0x0040, 0xa122));  /* D */            // Time
+clearings_.insert(DicomTag(0x0040, 0xa123));  /* D */            // Person Name
+clearings_.insert(DicomTag(0x0040, 0xa13a));  /* D */            // Referenced DateTime
+clearings_.insert(DicomTag(0x0040, 0xa730));  /* D */            // Content Sequence
+clearings_.insert(DicomTag(0x0042, 0x0011));  /* D */            // Encapsulated Document
+clearings_.insert(DicomTag(0x0044, 0x0104));  /* D */            // Assertion DateTime
+clearings_.insert(DicomTag(0x0068, 0x6226));  /* D */            // Effective DateTime
+clearings_.insert(DicomTag(0x0068, 0x6270));  /* D */            // Information Issue DateTime
+clearings_.insert(DicomTag(0x006a, 0x0003));  /* D */            // Annotation Group UID
+clearings_.insert(DicomTag(0x006a, 0x0005));  /* D */            // Annotation Group Label
+clearings_.insert(DicomTag(0x0070, 0x0001));  /* D */            // Graphic Annotation Sequence
+clearings_.insert(DicomTag(0x0070, 0x0084));  /* Z/D */          // Content Creator's Name
+clearings_.insert(DicomTag(0x0072, 0x000a));  /* D */            // Hanging Protocol Creation DateTime
+clearings_.insert(DicomTag(0x0072, 0x005e));  /* D */            // Selector AE Value
+clearings_.insert(DicomTag(0x0072, 0x005f));  /* D */            // Selector AS Value
+clearings_.insert(DicomTag(0x0072, 0x0061));  /* D */            // Selector DA Value
+clearings_.insert(DicomTag(0x0072, 0x0063));  /* D */            // Selector DT Value
+clearings_.insert(DicomTag(0x0072, 0x0065));  /* D */            // Selector OB Value
+clearings_.insert(DicomTag(0x0072, 0x0066));  /* D */            // Selector LO Value
+clearings_.insert(DicomTag(0x0072, 0x0068));  /* D */            // Selector LT Value
+clearings_.insert(DicomTag(0x0072, 0x006a));  /* D */            // Selector PN Value
+clearings_.insert(DicomTag(0x0072, 0x006b));  /* D */            // Selector TM Value
+clearings_.insert(DicomTag(0x0072, 0x006c));  /* D */            // Selector SH Value
+clearings_.insert(DicomTag(0x0072, 0x006d));  /* D */            // Selector UN Value
+clearings_.insert(DicomTag(0x0072, 0x006e));  /* D */            // Selector ST Value
+clearings_.insert(DicomTag(0x0072, 0x0070));  /* D */            // Selector UT Value
+clearings_.insert(DicomTag(0x0072, 0x0071));  /* D */            // Selector UR Value
+clearings_.insert(DicomTag(0x0400, 0x0105));  /* D */            // Digital Signature DateTime
+clearings_.insert(DicomTag(0x0400, 0x0115));  /* D */            // Certificate of Signer
+clearings_.insert(DicomTag(0x0400, 0x0562));  /* D */            // Attribute Modification DateTime
+clearings_.insert(DicomTag(0x0400, 0x0563));  /* D */            // Modifying System
+clearings_.insert(DicomTag(0x0400, 0x0564));                     // Source of Previous Values
+clearings_.insert(DicomTag(0x0400, 0x0565));  /* D */            // Reason for the Attribute Modification
+clearings_.insert(DicomTag(0x2100, 0x0140));  /* D */            // Destination AE
+clearings_.insert(DicomTag(0x3006, 0x0002));  /* D */            // Structure Set Label
+clearings_.insert(DicomTag(0x3006, 0x0008));                     // Structure Set Date
+clearings_.insert(DicomTag(0x3006, 0x0009));                     // Structure Set Time
+clearings_.insert(DicomTag(0x3006, 0x0026));                     // ROI Name
+clearings_.insert(DicomTag(0x3006, 0x00a6));                     // ROI Interpreter
+clearings_.insert(DicomTag(0x3008, 0x0024));  /* D */            // Treatment Control Point Date
+clearings_.insert(DicomTag(0x3008, 0x0025));  /* D */            // Treatment Control Point Time
+clearings_.insert(DicomTag(0x3008, 0x0162));  /* D */            // Safe Position Exit Date
+clearings_.insert(DicomTag(0x3008, 0x0164));  /* D */            // Safe Position Exit Time
+clearings_.insert(DicomTag(0x3008, 0x0166));  /* D */            // Safe Position Return Date
+clearings_.insert(DicomTag(0x3008, 0x0168));  /* D */            // Safe Position Return Time
+clearings_.insert(DicomTag(0x300a, 0x0002));  /* D */            // RT Plan Label
+clearings_.insert(DicomTag(0x300a, 0x022c));  /* D */            // Source Strength Reference Date
+clearings_.insert(DicomTag(0x300a, 0x022e));  /* D */            // Source Strength Reference Time
+clearings_.insert(DicomTag(0x300a, 0x0608));  /* D */            // Treatment Position Group Label
+clearings_.insert(DicomTag(0x300a, 0x0611));                     // RT Accessory Holder Slot ID
+clearings_.insert(DicomTag(0x300a, 0x0615));                     // RT Accessory Device Slot ID
+clearings_.insert(DicomTag(0x300a, 0x0619));  /* D */            // Radiation Dose Identification Label
+clearings_.insert(DicomTag(0x300a, 0x0623));  /* D */            // Radiation Dose In-Vivo Measurement Label
+clearings_.insert(DicomTag(0x300a, 0x062a));  /* D */            // RT Tolerance Set Label
+clearings_.insert(DicomTag(0x300a, 0x067c));  /* D */            // Radiation Generation Mode Label
+clearings_.insert(DicomTag(0x300a, 0x067d));                     // Radiation Generation Mode Description
+clearings_.insert(DicomTag(0x300a, 0x0734));  /* D */            // Treatment Tolerance Violation Description
+clearings_.insert(DicomTag(0x300a, 0x0736));  /* D */            // Treatment Tolerance Violation DateTime
+clearings_.insert(DicomTag(0x300a, 0x073a));  /* D */            // Recorded RT Control Point DateTime
+clearings_.insert(DicomTag(0x300a, 0x0741));  /* D */            // Interlock DateTime
+clearings_.insert(DicomTag(0x300a, 0x0742));  /* D */            // Interlock Description
+clearings_.insert(DicomTag(0x300a, 0x0760));  /* D */            // Override DateTime
+clearings_.insert(DicomTag(0x300a, 0x0783));  /* D */            // Interlock Origin Description
+clearings_.insert(DicomTag(0x300c, 0x0127));  /* D */            // Beam Hold Transition DateTime
+clearings_.insert(DicomTag(0x300e, 0x0004));                     // Review Date
+clearings_.insert(DicomTag(0x300e, 0x0005));                     // Review Time
+clearings_.insert(DicomTag(0x3010, 0x000f));                     // Conceptual Volume Combination Description
+clearings_.insert(DicomTag(0x3010, 0x0017));                     // Conceptual Volume Description
+clearings_.insert(DicomTag(0x3010, 0x001b));                     // Device Alternate Identifier
+clearings_.insert(DicomTag(0x3010, 0x002d));  /* D */            // Device Label
+clearings_.insert(DicomTag(0x3010, 0x0033));  /* D */            // User Content Label
+clearings_.insert(DicomTag(0x3010, 0x0034));  /* D */            // User Content Long Label
+clearings_.insert(DicomTag(0x3010, 0x0035));  /* D */            // Entity Label
+clearings_.insert(DicomTag(0x3010, 0x0038));  /* D */            // Entity Long Label
+clearings_.insert(DicomTag(0x3010, 0x0043));                     // Manufacturer's Device Identifier
+clearings_.insert(DicomTag(0x3010, 0x0054));  /* D */            // RT Prescription Label
+clearings_.insert(DicomTag(0x3010, 0x005a));                     // RT Physician Intent Narrative
+clearings_.insert(DicomTag(0x3010, 0x005c));                     // Reason for Superseding
+clearings_.insert(DicomTag(0x3010, 0x007a));                     // Treatment Technique Notes
+clearings_.insert(DicomTag(0x3010, 0x007b));                     // Prescription Notes
+clearings_.insert(DicomTag(0x3010, 0x007f));                     // Fractionation Notes
+clearings_.insert(DicomTag(0x3010, 0x0081));                     // Prescription Notes Sequence
+removals_.insert(DicomTag(0x0000, 0x1000));                      // Affected SOP Instance UID
+removals_.insert(DicomTag(0x0008, 0x0012));   /* X/D */          // Instance Creation Date
+removals_.insert(DicomTag(0x0008, 0x0013));   /* X/Z/D */        // Instance Creation Time
+removals_.insert(DicomTag(0x0008, 0x0015));                      // Instance Coercion DateTime
+removals_.insert(DicomTag(0x0008, 0x0021));   /* X/D */          // Series Date
+removals_.insert(DicomTag(0x0008, 0x0022));   /* X/Z */          // Acquisition Date
+removals_.insert(DicomTag(0x0008, 0x0024));                      // Overlay Date
+removals_.insert(DicomTag(0x0008, 0x0025));                      // Curve Date
+removals_.insert(DicomTag(0x0008, 0x002a));   /* X/Z/D */        // Acquisition DateTime
+removals_.insert(DicomTag(0x0008, 0x0031));   /* X/D */          // Series Time
+removals_.insert(DicomTag(0x0008, 0x0032));   /* X/Z */          // Acquisition Time
+removals_.insert(DicomTag(0x0008, 0x0034));                      // Overlay Time
+removals_.insert(DicomTag(0x0008, 0x0035));                      // Curve Time
+removals_.insert(DicomTag(0x0008, 0x0054));                      // Retrieve AE Title
+removals_.insert(DicomTag(0x0008, 0x0055));                      // Station AE Title
+removals_.insert(DicomTag(0x0008, 0x0080));   /* X/Z/D */        // Institution Name
+removals_.insert(DicomTag(0x0008, 0x0081));                      // Institution Address
+removals_.insert(DicomTag(0x0008, 0x0082));   /* X/Z/D */        // Institution Code Sequence
+removals_.insert(DicomTag(0x0008, 0x0092));                      // Referring Physician's Address
+removals_.insert(DicomTag(0x0008, 0x0094));                      // Referring Physician's Telephone Numbers
+removals_.insert(DicomTag(0x0008, 0x0096));                      // Referring Physician Identification Sequence
+removals_.insert(DicomTag(0x0008, 0x009d));                      // Consulting Physician Identification Sequence
+removals_.insert(DicomTag(0x0008, 0x0201));                      // Timezone Offset From UTC
+removals_.insert(DicomTag(0x0008, 0x1000));                      // Network ID
+removals_.insert(DicomTag(0x0008, 0x1010));   /* X/Z/D */        // Station Name
+removals_.insert(DicomTag(0x0008, 0x1030));                      // Study Description
+removals_.insert(DicomTag(0x0008, 0x103e));                      // Series Description
+removals_.insert(DicomTag(0x0008, 0x1040));                      // Institutional Department Name
+removals_.insert(DicomTag(0x0008, 0x1041));                      // Institutional Department Type Code Sequence
+removals_.insert(DicomTag(0x0008, 0x1048));                      // Physician(s) of Record
+removals_.insert(DicomTag(0x0008, 0x1049));                      // Physician(s) of Record Identification Sequence
+removals_.insert(DicomTag(0x0008, 0x1050));                      // Performing Physician's Name
+removals_.insert(DicomTag(0x0008, 0x1052));                      // Performing Physician Identification Sequence
+removals_.insert(DicomTag(0x0008, 0x1060));                      // Name of Physician(s) Reading Study
+removals_.insert(DicomTag(0x0008, 0x1062));                      // Physician(s) Reading Study Identification Sequence
+removals_.insert(DicomTag(0x0008, 0x1070));   /* X/Z/D */        // Operators' Name
+removals_.insert(DicomTag(0x0008, 0x1072));   /* X/D */          // Operator Identification Sequence
+removals_.insert(DicomTag(0x0008, 0x1080));                      // Admitting Diagnoses Description
+removals_.insert(DicomTag(0x0008, 0x1084));                      // Admitting Diagnoses Code Sequence
+removals_.insert(DicomTag(0x0008, 0x1088));                      // Pyramid Description
+removals_.insert(DicomTag(0x0008, 0x1110));   /* X/Z */          // Referenced Study Sequence
+removals_.insert(DicomTag(0x0008, 0x1111));   /* X/Z/D */        // Referenced Performed Procedure Step Sequence
+removals_.insert(DicomTag(0x0008, 0x1120));                      // Referenced Patient Sequence
+removals_.insert(DicomTag(0x0008, 0x2111));                      // Derivation Description
+removals_.insert(DicomTag(0x0008, 0x4000));                      // Identifying Comments
+removals_.insert(DicomTag(0x0010, 0x0021));                      // Issuer of Patient ID
+removals_.insert(DicomTag(0x0010, 0x0032));                      // Patient's Birth Time
+removals_.insert(DicomTag(0x0010, 0x0050));                      // Patient's Insurance Plan Code Sequence
+removals_.insert(DicomTag(0x0010, 0x0101));                      // Patient's Primary Language Code Sequence
+removals_.insert(DicomTag(0x0010, 0x0102));                      // Patient's Primary Language Modifier Code Sequence
+removals_.insert(DicomTag(0x0010, 0x1000));                      // Other Patient IDs
+removals_.insert(DicomTag(0x0010, 0x1001));                      // Other Patient Names
+removals_.insert(DicomTag(0x0010, 0x1002));                      // Other Patient IDs Sequence
+removals_.insert(DicomTag(0x0010, 0x1005));                      // Patient's Birth Name
+removals_.insert(DicomTag(0x0010, 0x1010));                      // Patient's Age
+removals_.insert(DicomTag(0x0010, 0x1020));                      // Patient's Size
+removals_.insert(DicomTag(0x0010, 0x1030));                      // Patient's Weight
+removals_.insert(DicomTag(0x0010, 0x1040));                      // Patient's Address
+removals_.insert(DicomTag(0x0010, 0x1050));                      // Insurance Plan Identification
+removals_.insert(DicomTag(0x0010, 0x1060));                      // Patient's Mother's Birth Name
+removals_.insert(DicomTag(0x0010, 0x1080));                      // Military Rank
+removals_.insert(DicomTag(0x0010, 0x1081));                      // Branch of Service
+removals_.insert(DicomTag(0x0010, 0x1090));                      // Medical Record Locator
+removals_.insert(DicomTag(0x0010, 0x1100));                      // Referenced Patient Photo Sequence
+removals_.insert(DicomTag(0x0010, 0x2000));                      // Medical Alerts
+removals_.insert(DicomTag(0x0010, 0x2110));                      // Allergies
+removals_.insert(DicomTag(0x0010, 0x2150));                      // Country of Residence
+removals_.insert(DicomTag(0x0010, 0x2152));                      // Region of Residence
+removals_.insert(DicomTag(0x0010, 0x2154));                      // Patient's Telephone Numbers
+removals_.insert(DicomTag(0x0010, 0x2155));                      // Patient's Telecom Information
+removals_.insert(DicomTag(0x0010, 0x2160));                      // Ethnic Group
+removals_.insert(DicomTag(0x0010, 0x2180));                      // Occupation
+removals_.insert(DicomTag(0x0010, 0x21a0));                      // Smoking Status
+removals_.insert(DicomTag(0x0010, 0x21b0));                      // Additional Patient History
+removals_.insert(DicomTag(0x0010, 0x21c0));                      // Pregnancy Status
+removals_.insert(DicomTag(0x0010, 0x21d0));                      // Last Menstrual Date
+removals_.insert(DicomTag(0x0010, 0x21f0));                      // Patient's Religious Preference
+removals_.insert(DicomTag(0x0010, 0x2203));   /* X/Z */          // Patient's Sex Neutered
+removals_.insert(DicomTag(0x0010, 0x2297));                      // Responsible Person
+removals_.insert(DicomTag(0x0010, 0x2299));                      // Responsible Organization
+removals_.insert(DicomTag(0x0010, 0x4000));                      // Patient Comments
+removals_.insert(DicomTag(0x0012, 0x0051));                      // Clinical Trial Time Point Description
+removals_.insert(DicomTag(0x0012, 0x0071));                      // Clinical Trial Series ID
+removals_.insert(DicomTag(0x0012, 0x0072));                      // Clinical Trial Series Description
+removals_.insert(DicomTag(0x0012, 0x0082));                      // Clinical Trial Protocol Ethics Committee Approval Number
+removals_.insert(DicomTag(0x0012, 0x0086));                      // Ethics Committee Approval Effectiveness Start Date
+removals_.insert(DicomTag(0x0012, 0x0087));                      // Ethics Committee Approval Effectiveness End Date
+removals_.insert(DicomTag(0x0014, 0x407c));                      // Calibration Time
+removals_.insert(DicomTag(0x0014, 0x407e));                      // Calibration Date
+removals_.insert(DicomTag(0x0016, 0x002b));                      // Maker Note
+removals_.insert(DicomTag(0x0016, 0x004b));                      // Device Setting Description
+removals_.insert(DicomTag(0x0016, 0x004d));                      // Camera Owner Name
+removals_.insert(DicomTag(0x0016, 0x004e));                      // Lens Specification
+removals_.insert(DicomTag(0x0016, 0x004f));                      // Lens Make
+removals_.insert(DicomTag(0x0016, 0x0050));                      // Lens Model
+removals_.insert(DicomTag(0x0016, 0x0051));                      // Lens Serial Number
+removals_.insert(DicomTag(0x0016, 0x0070));                      // GPS Version ID
+removals_.insert(DicomTag(0x0016, 0x0071));                      // GPS Latitude Ref
+removals_.insert(DicomTag(0x0016, 0x0072));                      // GPS Latitude
+removals_.insert(DicomTag(0x0016, 0x0073));                      // GPS Longitude Ref
+removals_.insert(DicomTag(0x0016, 0x0074));                      // GPS Longitude
+removals_.insert(DicomTag(0x0016, 0x0075));                      // GPS Altitude Ref
+removals_.insert(DicomTag(0x0016, 0x0076));                      // GPS Altitude
+removals_.insert(DicomTag(0x0016, 0x0077));                      // GPS Time Stamp
+removals_.insert(DicomTag(0x0016, 0x0078));                      // GPS Satellites
+removals_.insert(DicomTag(0x0016, 0x0079));                      // GPS Status
+removals_.insert(DicomTag(0x0016, 0x007a));                      // GPS Measure Mode
+removals_.insert(DicomTag(0x0016, 0x007b));                      // GPS DOP
+removals_.insert(DicomTag(0x0016, 0x007c));                      // GPS Speed Ref
+removals_.insert(DicomTag(0x0016, 0x007d));                      // GPS Speed
+removals_.insert(DicomTag(0x0016, 0x007e));                      // GPS Track Ref
+removals_.insert(DicomTag(0x0016, 0x007f));                      // GPS Track
+removals_.insert(DicomTag(0x0016, 0x0080));                      // GPS Img Direction Ref
+removals_.insert(DicomTag(0x0016, 0x0081));                      // GPS Img Direction
+removals_.insert(DicomTag(0x0016, 0x0082));                      // GPS Map Datum
+removals_.insert(DicomTag(0x0016, 0x0083));                      // GPS Dest Latitude Ref
+removals_.insert(DicomTag(0x0016, 0x0084));                      // GPS Dest Latitude
+removals_.insert(DicomTag(0x0016, 0x0085));                      // GPS Dest Longitude Ref
+removals_.insert(DicomTag(0x0016, 0x0086));                      // GPS Dest Longitude
+removals_.insert(DicomTag(0x0016, 0x0087));                      // GPS Dest Bearing Ref
+removals_.insert(DicomTag(0x0016, 0x0088));                      // GPS Dest Bearing
+removals_.insert(DicomTag(0x0016, 0x0089));                      // GPS Dest Distance Ref
+removals_.insert(DicomTag(0x0016, 0x008a));                      // GPS Dest Distance
+removals_.insert(DicomTag(0x0016, 0x008b));                      // GPS Processing Method
+removals_.insert(DicomTag(0x0016, 0x008c));                      // GPS Area Information
+removals_.insert(DicomTag(0x0016, 0x008d));                      // GPS Date Stamp
+removals_.insert(DicomTag(0x0016, 0x008e));                      // GPS Differential
+removals_.insert(DicomTag(0x0018, 0x0027));                      // Intervention Drug Stop Time
+removals_.insert(DicomTag(0x0018, 0x0035));                      // Intervention Drug Start Time
+removals_.insert(DicomTag(0x0018, 0x1000));   /* X/Z/D */        // Device Serial Number
+removals_.insert(DicomTag(0x0018, 0x1004));                      // Plate ID
+removals_.insert(DicomTag(0x0018, 0x1005));                      // Generator ID
+removals_.insert(DicomTag(0x0018, 0x1007));                      // Cassette ID
+removals_.insert(DicomTag(0x0018, 0x1008));                      // Gantry ID
+removals_.insert(DicomTag(0x0018, 0x1009));                      // Unique Device Identifier
+removals_.insert(DicomTag(0x0018, 0x100a));                      // UDI Sequence
+removals_.insert(DicomTag(0x0018, 0x1012));                      // Date of Secondary Capture
+removals_.insert(DicomTag(0x0018, 0x1014));                      // Time of Secondary Capture
+removals_.insert(DicomTag(0x0018, 0x1030));   /* X/D */          // Protocol Name
+removals_.insert(DicomTag(0x0018, 0x1042));                      // Contrast/Bolus Start Time
+removals_.insert(DicomTag(0x0018, 0x1043));                      // Contrast/Bolus Stop Time
+removals_.insert(DicomTag(0x0018, 0x1072));                      // Radiopharmaceutical Start Time
+removals_.insert(DicomTag(0x0018, 0x1073));                      // Radiopharmaceutical Stop Time
+removals_.insert(DicomTag(0x0018, 0x1078));                      // Radiopharmaceutical Start DateTime
+removals_.insert(DicomTag(0x0018, 0x1079));                      // Radiopharmaceutical Stop DateTime
+removals_.insert(DicomTag(0x0018, 0x1200));                      // Date of Last Calibration
+removals_.insert(DicomTag(0x0018, 0x1201));                      // Time of Last Calibration
+removals_.insert(DicomTag(0x0018, 0x1202));                      // DateTime of Last Calibration
+removals_.insert(DicomTag(0x0018, 0x1400));   /* X/D */          // Acquisition Device Processing Description
+removals_.insert(DicomTag(0x0018, 0x4000));                      // Acquisition Comments
+removals_.insert(DicomTag(0x0018, 0x5011));                      // Transducer Identification Sequence
+removals_.insert(DicomTag(0x0018, 0x700a));   /* X/D */          // Detector ID
+removals_.insert(DicomTag(0x0018, 0x700c));   /* X/D */          // Date of Last Detector Calibration
+removals_.insert(DicomTag(0x0018, 0x700e));   /* X/D */          // Time of Last Detector Calibration
+removals_.insert(DicomTag(0x0018, 0x9185));                      // Respiratory Motion Compensation Technique Description
+removals_.insert(DicomTag(0x0018, 0x9373));                      // X-Ray Detector Label
+removals_.insert(DicomTag(0x0018, 0x937b));                      // Multi-energy Acquisition Description
+removals_.insert(DicomTag(0x0018, 0x937f));                      // Decomposition Description
+removals_.insert(DicomTag(0x0018, 0x9424));                      // Acquisition Protocol Description
+removals_.insert(DicomTag(0x0018, 0x9516));   /* X/D */          // Start Acquisition DateTime
+removals_.insert(DicomTag(0x0018, 0x9517));   /* X/D */          // End Acquisition DateTime
+removals_.insert(DicomTag(0x0018, 0x9937));                      // Requested Series Description
+removals_.insert(DicomTag(0x0018, 0xa002));                      // Contribution DateTime
+removals_.insert(DicomTag(0x0018, 0xa003));                      // Contribution Description
+removals_.insert(DicomTag(0x0020, 0x0027));                      // Pyramid Label
+removals_.insert(DicomTag(0x0020, 0x3401));                      // Modifying Device ID
+removals_.insert(DicomTag(0x0020, 0x3403));                      // Modified Image Date
+removals_.insert(DicomTag(0x0020, 0x3405));                      // Modified Image Time
+removals_.insert(DicomTag(0x0020, 0x3406));                      // Modified Image Description
+removals_.insert(DicomTag(0x0020, 0x4000));                      // Image Comments
+removals_.insert(DicomTag(0x0020, 0x9158));                      // Frame Comments
+removals_.insert(DicomTag(0x0028, 0x4000));                      // Image Presentation Comments
+removals_.insert(DicomTag(0x0032, 0x0012));                      // Study ID Issuer
+removals_.insert(DicomTag(0x0032, 0x0032));                      // Study Verified Date
+removals_.insert(DicomTag(0x0032, 0x0033));                      // Study Verified Time
+removals_.insert(DicomTag(0x0032, 0x0034));                      // Study Read Date
+removals_.insert(DicomTag(0x0032, 0x0035));                      // Study Read Time
+removals_.insert(DicomTag(0x0032, 0x1000));                      // Scheduled Study Start Date
+removals_.insert(DicomTag(0x0032, 0x1001));                      // Scheduled Study Start Time
+removals_.insert(DicomTag(0x0032, 0x1010));                      // Scheduled Study Stop Date
+removals_.insert(DicomTag(0x0032, 0x1011));                      // Scheduled Study Stop Time
+removals_.insert(DicomTag(0x0032, 0x1020));                      // Scheduled Study Location
+removals_.insert(DicomTag(0x0032, 0x1021));                      // Scheduled Study Location AE Title
+removals_.insert(DicomTag(0x0032, 0x1021));                      // Scheduled Study Location AE Title
+removals_.insert(DicomTag(0x0032, 0x1030));                      // Reason for Study
+removals_.insert(DicomTag(0x0032, 0x1032));                      // Requesting Physician
+removals_.insert(DicomTag(0x0032, 0x1033));                      // Requesting Service
+removals_.insert(DicomTag(0x0032, 0x1040));                      // Study Arrival Date
+removals_.insert(DicomTag(0x0032, 0x1041));                      // Study Arrival Time
+removals_.insert(DicomTag(0x0032, 0x1050));                      // Study Completion Date
+removals_.insert(DicomTag(0x0032, 0x1051));                      // Study Completion Time
+removals_.insert(DicomTag(0x0032, 0x1060));   /* X/Z */          // Requested Procedure Description
+removals_.insert(DicomTag(0x0032, 0x1066));                      // Reason for Visit
+removals_.insert(DicomTag(0x0032, 0x1067));                      // Reason for Visit Code Sequence
+removals_.insert(DicomTag(0x0032, 0x1070));                      // Requested Contrast Agent
+removals_.insert(DicomTag(0x0032, 0x4000));                      // Study Comments
+removals_.insert(DicomTag(0x0038, 0x0004));                      // Referenced Patient Alias Sequence
+removals_.insert(DicomTag(0x0038, 0x0010));                      // Admission ID
+removals_.insert(DicomTag(0x0038, 0x0011));                      // Issuer of Admission ID
+removals_.insert(DicomTag(0x0038, 0x0014));                      // Issuer of Admission ID Sequence
+removals_.insert(DicomTag(0x0038, 0x001a));                      // Scheduled Admission Date
+removals_.insert(DicomTag(0x0038, 0x001b));                      // Scheduled Admission Time
+removals_.insert(DicomTag(0x0038, 0x001c));                      // Scheduled Discharge Date
+removals_.insert(DicomTag(0x0038, 0x001d));                      // Scheduled Discharge Time
+removals_.insert(DicomTag(0x0038, 0x001e));                      // Scheduled Patient Institution Residence
+removals_.insert(DicomTag(0x0038, 0x0020));                      // Admitting Date
+removals_.insert(DicomTag(0x0038, 0x0021));                      // Admitting Time
+removals_.insert(DicomTag(0x0038, 0x0030));                      // Discharge Date
+removals_.insert(DicomTag(0x0038, 0x0032));                      // Discharge Time
+removals_.insert(DicomTag(0x0038, 0x0040));                      // Discharge Diagnosis Description
+removals_.insert(DicomTag(0x0038, 0x0050));                      // Special Needs
+removals_.insert(DicomTag(0x0038, 0x0060));                      // Service Episode ID
+removals_.insert(DicomTag(0x0038, 0x0061));                      // Issuer of Service Episode ID
+removals_.insert(DicomTag(0x0038, 0x0062));                      // Service Episode Description
+removals_.insert(DicomTag(0x0038, 0x0064));                      // Issuer of Service Episode ID Sequence
+removals_.insert(DicomTag(0x0038, 0x0300));                      // Current Patient Location
+removals_.insert(DicomTag(0x0038, 0x0400));                      // Patient's Institution Residence
+removals_.insert(DicomTag(0x0038, 0x0500));                      // Patient State
+removals_.insert(DicomTag(0x0038, 0x4000));                      // Visit Comments
+removals_.insert(DicomTag(0x003a, 0x0329));                      // Waveform Filter Description
+removals_.insert(DicomTag(0x003a, 0x032b));                      // Filter Lookup Table Description
+removals_.insert(DicomTag(0x0040, 0x0001));                      // Scheduled Station AE Title
+removals_.insert(DicomTag(0x0040, 0x0001));                      // Scheduled Station AE Title
+removals_.insert(DicomTag(0x0040, 0x0002));                      // Scheduled Procedure Step Start Date
+removals_.insert(DicomTag(0x0040, 0x0003));                      // Scheduled Procedure Step Start Time
+removals_.insert(DicomTag(0x0040, 0x0004));                      // Scheduled Procedure Step End Date
+removals_.insert(DicomTag(0x0040, 0x0005));                      // Scheduled Procedure Step End Time
+removals_.insert(DicomTag(0x0040, 0x0006));                      // Scheduled Performing Physician's Name
+removals_.insert(DicomTag(0x0040, 0x0007));                      // Scheduled Procedure Step Description
+removals_.insert(DicomTag(0x0040, 0x0009));                      // Scheduled Procedure Step ID
+removals_.insert(DicomTag(0x0040, 0x000b));                      // Scheduled Performing Physician Identification Sequence
+removals_.insert(DicomTag(0x0040, 0x0010));                      // Scheduled Station Name
+removals_.insert(DicomTag(0x0040, 0x0011));                      // Scheduled Procedure Step Location
+removals_.insert(DicomTag(0x0040, 0x0012));                      // Pre-Medication
+removals_.insert(DicomTag(0x0040, 0x0241));                      // Performed Station AE Title
+removals_.insert(DicomTag(0x0040, 0x0241));                      // Performed Station AE Title
+removals_.insert(DicomTag(0x0040, 0x0242));                      // Performed Station Name
+removals_.insert(DicomTag(0x0040, 0x0243));                      // Performed Location
+removals_.insert(DicomTag(0x0040, 0x0244));                      // Performed Procedure Step Start Date
+removals_.insert(DicomTag(0x0040, 0x0245));                      // Performed Procedure Step Start Time
+removals_.insert(DicomTag(0x0040, 0x0250));                      // Performed Procedure Step End Date
+removals_.insert(DicomTag(0x0040, 0x0251));                      // Performed Procedure Step End Time
+removals_.insert(DicomTag(0x0040, 0x0253));                      // Performed Procedure Step ID
+removals_.insert(DicomTag(0x0040, 0x0254));                      // Performed Procedure Step Description
+removals_.insert(DicomTag(0x0040, 0x0275));                      // Request Attributes Sequence
+removals_.insert(DicomTag(0x0040, 0x0280));                      // Comments on the Performed Procedure Step
+removals_.insert(DicomTag(0x0040, 0x0310));                      // Comments on Radiation Dose
+removals_.insert(DicomTag(0x0040, 0x050a));                      // Specimen Accession Number
+removals_.insert(DicomTag(0x0040, 0x051a));                      // Container Description
+removals_.insert(DicomTag(0x0040, 0x0555));   /* X/Z */          // Acquisition Context Sequence
+removals_.insert(DicomTag(0x0040, 0x0600));                      // Specimen Short Description
+removals_.insert(DicomTag(0x0040, 0x0602));                      // Specimen Detailed Description
+removals_.insert(DicomTag(0x0040, 0x06fa));                      // Slide Identifier
+removals_.insert(DicomTag(0x0040, 0x1001));                      // Requested Procedure ID
+removals_.insert(DicomTag(0x0040, 0x1002));                      // Reason for the Requested Procedure
+removals_.insert(DicomTag(0x0040, 0x1004));                      // Patient Transport Arrangements
+removals_.insert(DicomTag(0x0040, 0x1005));                      // Requested Procedure Location
+removals_.insert(DicomTag(0x0040, 0x100a));                      // Reason for Requested Procedure Code Sequence
+removals_.insert(DicomTag(0x0040, 0x1010));                      // Names of Intended Recipients of Results
+removals_.insert(DicomTag(0x0040, 0x1011));                      // Intended Recipients of Results Identification Sequence
+removals_.insert(DicomTag(0x0040, 0x1102));                      // Person's Address
+removals_.insert(DicomTag(0x0040, 0x1103));                      // Person's Telephone Numbers
+removals_.insert(DicomTag(0x0040, 0x1104));                      // Person's Telecom Information
+removals_.insert(DicomTag(0x0040, 0x1400));                      // Requested Procedure Comments
+removals_.insert(DicomTag(0x0040, 0x2001));                      // Reason for the Imaging Service Request
+removals_.insert(DicomTag(0x0040, 0x2004));                      // Issue Date of Imaging Service Request
+removals_.insert(DicomTag(0x0040, 0x2005));                      // Issue Time of Imaging Service Request
+removals_.insert(DicomTag(0x0040, 0x2008));                      // Order Entered By
+removals_.insert(DicomTag(0x0040, 0x2009));                      // Order Enterer's Location
+removals_.insert(DicomTag(0x0040, 0x2010));                      // Order Callback Phone Number
+removals_.insert(DicomTag(0x0040, 0x2011));                      // Order Callback Telecom Information
+removals_.insert(DicomTag(0x0040, 0x2400));                      // Imaging Service Request Comments
+removals_.insert(DicomTag(0x0040, 0x3001));                      // Confidentiality Constraint on Patient Data Description
+removals_.insert(DicomTag(0x0040, 0x4005));                      // Scheduled Procedure Step Start DateTime
+removals_.insert(DicomTag(0x0040, 0x4008));                      // Scheduled Procedure Step Expiration DateTime
+removals_.insert(DicomTag(0x0040, 0x4010));                      // Scheduled Procedure Step Modification DateTime
+removals_.insert(DicomTag(0x0040, 0x4011));                      // Expected Completion DateTime
+removals_.insert(DicomTag(0x0040, 0x4025));                      // Scheduled Station Name Code Sequence
+removals_.insert(DicomTag(0x0040, 0x4027));                      // Scheduled Station Geographic Location Code Sequence
+removals_.insert(DicomTag(0x0040, 0x4028));                      // Performed Station Name Code Sequence
+removals_.insert(DicomTag(0x0040, 0x4030));                      // Performed Station Geographic Location Code Sequence
+removals_.insert(DicomTag(0x0040, 0x4034));                      // Scheduled Human Performers Sequence
+removals_.insert(DicomTag(0x0040, 0x4035));                      // Actual Human Performers Sequence
+removals_.insert(DicomTag(0x0040, 0x4036));                      // Human Performer's Organization
+removals_.insert(DicomTag(0x0040, 0x4037));                      // Human Performer's Name
+removals_.insert(DicomTag(0x0040, 0x4050));                      // Performed Procedure Step Start DateTime
+removals_.insert(DicomTag(0x0040, 0x4051));                      // Performed Procedure Step End DateTime
+removals_.insert(DicomTag(0x0040, 0x4052));                      // Procedure Step Cancellation DateTime
+removals_.insert(DicomTag(0x0040, 0xa023));                      // Findings Group Recording Date (Trial)
+removals_.insert(DicomTag(0x0040, 0xa024));                      // Findings Group Recording Time (Trial)
+removals_.insert(DicomTag(0x0040, 0xa032));   /* X/D */          // Observation DateTime
+removals_.insert(DicomTag(0x0040, 0xa033));                      // Observation Start DateTime
+removals_.insert(DicomTag(0x0040, 0xa078));                      // Author Observer Sequence
+removals_.insert(DicomTag(0x0040, 0xa07a));                      // Participant Sequence
+removals_.insert(DicomTag(0x0040, 0xa07c));                      // Custodial Organization Sequence
+removals_.insert(DicomTag(0x0040, 0xa110));                      // Date of Document or Verbal Transaction (Trial)
+removals_.insert(DicomTag(0x0040, 0xa112));                      // Time of Document or Verbal Transaction (Trial)
+removals_.insert(DicomTag(0x0040, 0xa192));                      // Observation Date (Trial)
+removals_.insert(DicomTag(0x0040, 0xa193));                      // Observation Time (Trial)
+removals_.insert(DicomTag(0x0040, 0xa307));                      // Current Observer (Trial)
+removals_.insert(DicomTag(0x0040, 0xa352));                      // Verbal Source (Trial)
+removals_.insert(DicomTag(0x0040, 0xa353));                      // Address (Trial)
+removals_.insert(DicomTag(0x0040, 0xa354));                      // Telephone Number (Trial)
+removals_.insert(DicomTag(0x0040, 0xa358));                      // Verbal Source Identifier Code Sequence (Trial)
+removals_.insert(DicomTag(0x0040, 0xdb06));                      // Template Version
+removals_.insert(DicomTag(0x0040, 0xdb07));                      // Template Local Version
+removals_.insert(DicomTag(0x0040, 0xe004));                      // HL7 Document Effective Time
+removals_.insert(DicomTag(0x0044, 0x0004));                      // Approval Status DateTime
+removals_.insert(DicomTag(0x0044, 0x000b));                      // Product Expiration DateTime
+removals_.insert(DicomTag(0x0044, 0x0010));                      // Substance Administration DateTime
+removals_.insert(DicomTag(0x0044, 0x0105));                      // Assertion Expiration DateTime
+removals_.insert(DicomTag(0x0050, 0x001b));                      // Container Component ID
+removals_.insert(DicomTag(0x0050, 0x0020));                      // Device Description
+removals_.insert(DicomTag(0x0050, 0x0021));                      // Long Device Description
+removals_.insert(DicomTag(0x006a, 0x0006));                      // Annotation Group Description
+removals_.insert(DicomTag(0x0070, 0x0082));                      // Presentation Creation Date
+removals_.insert(DicomTag(0x0070, 0x0083));                      // Presentation Creation Time
+removals_.insert(DicomTag(0x0070, 0x0086));                      // Content Creator's Identification Code Sequence
+removals_.insert(DicomTag(0x0074, 0x1234));                      // Receiving AE
+removals_.insert(DicomTag(0x0074, 0x1236));                      // Requesting AE
+removals_.insert(DicomTag(0x0088, 0x0200));                      // Icon Image Sequence
+removals_.insert(DicomTag(0x0088, 0x0904));                      // Topic Title
+removals_.insert(DicomTag(0x0088, 0x0906));                      // Topic Subject
+removals_.insert(DicomTag(0x0088, 0x0910));                      // Topic Author
+removals_.insert(DicomTag(0x0088, 0x0912));                      // Topic Keywords
+removals_.insert(DicomTag(0x0100, 0x0420));                      // SOP Authorization DateTime
+removals_.insert(DicomTag(0x0400, 0x0310));                      // Certified Timestamp
+removals_.insert(DicomTag(0x0400, 0x0402));                      // Referenced Digital Signature Sequence
+removals_.insert(DicomTag(0x0400, 0x0403));                      // Referenced SOP Instance MAC Sequence
+removals_.insert(DicomTag(0x0400, 0x0404));                      // MAC
+removals_.insert(DicomTag(0x0400, 0x0550));                      // Modified Attributes Sequence
+removals_.insert(DicomTag(0x0400, 0x0551));                      // Nonconforming Modified Attributes Sequence
+removals_.insert(DicomTag(0x0400, 0x0552));                      // Nonconforming Data Element Value
+removals_.insert(DicomTag(0x0400, 0x0561));                      // Original Attributes Sequence
+removals_.insert(DicomTag(0x0400, 0x0600));                      // Instance Origin Status
+removals_.insert(DicomTag(0x2030, 0x0020));                      // Text String
+removals_.insert(DicomTag(0x2100, 0x0040));                      // Creation Date
+removals_.insert(DicomTag(0x2100, 0x0050));                      // Creation Time
+removals_.insert(DicomTag(0x2100, 0x0070));                      // Originator
+removals_.insert(DicomTag(0x2200, 0x0002));   /* X/Z */          // Label Text
+removals_.insert(DicomTag(0x2200, 0x0005));   /* X/Z */          // Barcode Value
+removals_.insert(DicomTag(0x3002, 0x0121));                      // Position Acquisition Template Name
+removals_.insert(DicomTag(0x3002, 0x0123));                      // Position Acquisition Template Description
+removals_.insert(DicomTag(0x3006, 0x0004));                      // Structure Set Name
+removals_.insert(DicomTag(0x3006, 0x0006));                      // Structure Set Description
+removals_.insert(DicomTag(0x3006, 0x0028));                      // ROI Description
+removals_.insert(DicomTag(0x3006, 0x0038));                      // ROI Generation Description
+removals_.insert(DicomTag(0x3006, 0x0085));                      // ROI Observation Label
+removals_.insert(DicomTag(0x3006, 0x0088));                      // ROI Observation Description
+removals_.insert(DicomTag(0x3008, 0x0054));   /* X/D */          // First Treatment Date
+removals_.insert(DicomTag(0x3008, 0x0056));   /* X/D */          // Most Recent Treatment Date
+removals_.insert(DicomTag(0x3008, 0x0105));   /* X/Z */          // Source Serial Number
+removals_.insert(DicomTag(0x3008, 0x0250));   /* X/D */          // Treatment Date
+removals_.insert(DicomTag(0x3008, 0x0251));   /* X/D */          // Treatment Time
+removals_.insert(DicomTag(0x300a, 0x0003));                      // RT Plan Name
+removals_.insert(DicomTag(0x300a, 0x0004));                      // RT Plan Description
+removals_.insert(DicomTag(0x300a, 0x0006));   /* X/D */          // RT Plan Date
+removals_.insert(DicomTag(0x300a, 0x0007));   /* X/D */          // RT Plan Time
+removals_.insert(DicomTag(0x300a, 0x000b));                      // Treatment Sites
+removals_.insert(DicomTag(0x300a, 0x000e));                      // Prescription Description
+removals_.insert(DicomTag(0x300a, 0x0016));                      // Dose Reference Description
+removals_.insert(DicomTag(0x300a, 0x0072));                      // Fraction Group Description
+removals_.insert(DicomTag(0x300a, 0x00b2));   /* X/Z */          // Treatment Machine Name
+removals_.insert(DicomTag(0x300a, 0x00c3));                      // Beam Description
+removals_.insert(DicomTag(0x300a, 0x00dd));                      // Bolus Description
+removals_.insert(DicomTag(0x300a, 0x0196));                      // Fixation Device Description
+removals_.insert(DicomTag(0x300a, 0x01a6));                      // Shielding Device Description
+removals_.insert(DicomTag(0x300a, 0x01b2));                      // Setup Technique Description
+removals_.insert(DicomTag(0x300a, 0x0216));                      // Source Manufacturer
+removals_.insert(DicomTag(0x300a, 0x02eb));                      // Compensator Description
+removals_.insert(DicomTag(0x300a, 0x0676));                      // Equipment Frame of Reference Description
+removals_.insert(DicomTag(0x300a, 0x078e));                      // Patient Treatment Preparation Procedure Parameter Description
+removals_.insert(DicomTag(0x300a, 0x0792));                      // Patient Treatment Preparation Method Description
+removals_.insert(DicomTag(0x300a, 0x0794));                      // Patient Setup Photo Description
+removals_.insert(DicomTag(0x300a, 0x079a));                      // Displacement Reference Label
+removals_.insert(DicomTag(0x300c, 0x0113));                      // Reason for Omission Description
+removals_.insert(DicomTag(0x300e, 0x0008));   /* X/Z */          // Reviewer Name
+removals_.insert(DicomTag(0x3010, 0x0036));                      // Entity Name
+removals_.insert(DicomTag(0x3010, 0x0037));                      // Entity Description
+removals_.insert(DicomTag(0x3010, 0x004c));   /* X/D */          // Intended Phase Start Date
+removals_.insert(DicomTag(0x3010, 0x004d));   /* X/D */          // Intended Phase End Date
+removals_.insert(DicomTag(0x3010, 0x0056));   /* X/D */          // RT Treatment Approach Label
+removals_.insert(DicomTag(0x3010, 0x0061));                      // Prior Treatment Dose Description
+removals_.insert(DicomTag(0x3010, 0x0077));   /* X/D */          // Treatment Site
+removals_.insert(DicomTag(0x3010, 0x0085));                      // Intended Fraction Start Time
+removals_.insert(DicomTag(0x4000, 0x0010));                      // Arbitrary
+removals_.insert(DicomTag(0x4000, 0x4000));                      // Text Comments
+removals_.insert(DicomTag(0x4008, 0x0040));                      // Results ID
+removals_.insert(DicomTag(0x4008, 0x0042));                      // Results ID Issuer
+removals_.insert(DicomTag(0x4008, 0x0100));                      // Interpretation Recorded Date
+removals_.insert(DicomTag(0x4008, 0x0101));                      // Interpretation Recorded Time
+removals_.insert(DicomTag(0x4008, 0x0102));                      // Interpretation Recorder
+removals_.insert(DicomTag(0x4008, 0x0108));                      // Interpretation Transcription Date
+removals_.insert(DicomTag(0x4008, 0x0109));                      // Interpretation Transcription Time
+removals_.insert(DicomTag(0x4008, 0x010a));                      // Interpretation Transcriber
+removals_.insert(DicomTag(0x4008, 0x010b));                      // Interpretation Text
+removals_.insert(DicomTag(0x4008, 0x010c));                      // Interpretation Author
+removals_.insert(DicomTag(0x4008, 0x0111));                      // Interpretation Approver Sequence
+removals_.insert(DicomTag(0x4008, 0x0112));                      // Interpretation Approval Date
+removals_.insert(DicomTag(0x4008, 0x0113));                      // Interpretation Approval Time
+removals_.insert(DicomTag(0x4008, 0x0114));                      // Physician Approving Interpretation
+removals_.insert(DicomTag(0x4008, 0x0115));                      // Interpretation Diagnosis Description
+removals_.insert(DicomTag(0x4008, 0x0118));                      // Results Distribution List Sequence
+removals_.insert(DicomTag(0x4008, 0x0119));                      // Distribution Name
+removals_.insert(DicomTag(0x4008, 0x011a));                      // Distribution Address
+removals_.insert(DicomTag(0x4008, 0x0200));                      // Interpretation ID
+removals_.insert(DicomTag(0x4008, 0x0202));                      // Interpretation ID Issuer
+removals_.insert(DicomTag(0x4008, 0x0300));                      // Impressions
+removals_.insert(DicomTag(0x4008, 0x4000));                      // Results Comments
+removals_.insert(DicomTag(0xfffa, 0xfffa));                      // Digital Signatures Sequence
+removals_.insert(DicomTag(0xfffc, 0xfffc));                      // Data Set Trailing Padding
+removedRanges_.push_back(DicomTagRange(0x5000, 0x50ff, 0x0000, 0xffff));  // Curve Data
+removedRanges_.push_back(DicomTagRange(0x6000, 0x60ff, 0x3000, 0x3000));  // Overlay Data
+removedRanges_.push_back(DicomTagRange(0x6000, 0x60ff, 0x4000, 0x4000));  // Overlay Comments
+uids_.insert(DicomTag(0x0000, 0x1001));                          // Requested SOP Instance UID
+uids_.insert(DicomTag(0x0002, 0x0003));                          // Media Storage SOP Instance UID
+uids_.insert(DicomTag(0x0004, 0x1511));                          // Referenced SOP Instance UID in File
+uids_.insert(DicomTag(0x0008, 0x0014));                          // Instance Creator UID
+uids_.insert(DicomTag(0x0008, 0x0017));                          // Acquisition UID
+uids_.insert(DicomTag(0x0008, 0x0019));                          // Pyramid UID
+uids_.insert(DicomTag(0x0008, 0x0058));                          // Failed SOP Instance UID List
+uids_.insert(DicomTag(0x0008, 0x1155));                          // Referenced SOP Instance UID
+uids_.insert(DicomTag(0x0008, 0x1195));                          // Transaction UID
+uids_.insert(DicomTag(0x0008, 0x3010));                          // Irradiation Event UID
+uids_.insert(DicomTag(0x0018, 0x1002));                          // Device UID
+uids_.insert(DicomTag(0x0018, 0x100b));                          // Manufacturer's Device Class UID
+uids_.insert(DicomTag(0x0018, 0x2042));                          // Target UID
+uids_.insert(DicomTag(0x0020, 0x0052));                          // Frame of Reference UID
+uids_.insert(DicomTag(0x0020, 0x0200));                          // Synchronization Frame of Reference UID
+uids_.insert(DicomTag(0x0020, 0x9161));                          // Concatenation UID
+uids_.insert(DicomTag(0x0020, 0x9164));                          // Dimension Organization UID
+uids_.insert(DicomTag(0x0028, 0x1199));                          // Palette Color Lookup Table UID
+uids_.insert(DicomTag(0x0028, 0x1214));                          // Large Palette Color Lookup Table UID
+uids_.insert(DicomTag(0x003a, 0x0310));                          // Multiplex Group UID
+uids_.insert(DicomTag(0x0040, 0x0554));                          // Specimen UID
+uids_.insert(DicomTag(0x0040, 0x4023));                          // Referenced General Purpose Scheduled Procedure Step Transaction UID
+uids_.insert(DicomTag(0x0040, 0xa124));                          // UID
+uids_.insert(DicomTag(0x0040, 0xa171));                          // Observation UID
+uids_.insert(DicomTag(0x0040, 0xa172));                          // Referenced Observation UID (Trial)
+uids_.insert(DicomTag(0x0040, 0xa402));                          // Observation Subject UID (Trial)
+uids_.insert(DicomTag(0x0040, 0xdb0c));                          // Template Extension Organization UID
+uids_.insert(DicomTag(0x0040, 0xdb0d));                          // Template Extension Creator UID
+uids_.insert(DicomTag(0x0062, 0x0021));                          // Tracking UID
+uids_.insert(DicomTag(0x0064, 0x0003));                          // Source Frame of Reference UID
+uids_.insert(DicomTag(0x0070, 0x031a));                          // Fiducial UID
+uids_.insert(DicomTag(0x0070, 0x1101));                          // Presentation Display Collection UID
+uids_.insert(DicomTag(0x0070, 0x1102));                          // Presentation Sequence Collection UID
+uids_.insert(DicomTag(0x0088, 0x0140));                          // Storage Media File-set UID
+uids_.insert(DicomTag(0x0400, 0x0100));                          // Digital Signature UID
+uids_.insert(DicomTag(0x3006, 0x0024));                          // Referenced Frame of Reference UID
+uids_.insert(DicomTag(0x3006, 0x00c2));                          // Related Frame of Reference UID
+uids_.insert(DicomTag(0x300a, 0x0013));                          // Dose Reference UID
+uids_.insert(DicomTag(0x300a, 0x0083));                          // Referenced Dose Reference UID
+uids_.insert(DicomTag(0x300a, 0x0609));                          // Treatment Position Group UID
+uids_.insert(DicomTag(0x300a, 0x0650));                          // Patient Setup UID
+uids_.insert(DicomTag(0x300a, 0x0700));                          // Treatment Session UID
+uids_.insert(DicomTag(0x300a, 0x0785));                          // Referenced Treatment Position Group UID
+uids_.insert(DicomTag(0x3010, 0x0006));                          // Conceptual Volume UID
+uids_.insert(DicomTag(0x3010, 0x000b));                          // Referenced Conceptual Volume UID
+uids_.insert(DicomTag(0x3010, 0x0013));                          // Constituent Conceptual Volume UID
+uids_.insert(DicomTag(0x3010, 0x0015));                          // Source Conceptual Volume UID
+uids_.insert(DicomTag(0x3010, 0x0031));                          // Referenced Fiducials UID
+uids_.insert(DicomTag(0x3010, 0x003b));                          // RT Treatment Phase UID
+uids_.insert(DicomTag(0x3010, 0x006e));                          // Dosimetric Objective UID
+uids_.insert(DicomTag(0x3010, 0x006f));                          // Referenced Dosimetric Objective UID
--- a/OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp	Tue Jul 04 12:20:41 2023 +0200
@@ -582,8 +582,8 @@
                           ignoreTagLength, 1);
           }
 
-          target.SetValue(DicomTag(element->getTag().getGTag(), element->getTag().getETag()),
-                          jsonSequence);
+          target.SetSequenceValue(DicomTag(element->getTag().getGTag(), element->getTag().getETag()),
+                                  jsonSequence);
         }
       }
     }
@@ -1981,7 +1981,14 @@
         case EVR_OL:  // other long (requires byte-swapping)
 #endif
         {
-          ok = element.putUint32(boost::lexical_cast<Uint32>(*decoded)).good();
+          if (decoded->find('\\') != std::string::npos)
+          {
+            ok = element.putString(decoded->c_str()).good();
+          }
+          else
+          {
+            ok = element.putUint32(boost::lexical_cast<Uint32>(*decoded)).good();
+          }
           break;
         }
 
@@ -2460,7 +2467,7 @@
 
 #if ORTHANC_ENABLE_DCMTK_JPEG == 1
     CLOG(INFO, DICOM) << "Registering JPEG codecs in DCMTK";
-    DJDecoderRegistration::registerCodecs(); 
+    DJDecoderRegistration::registerCodecs();
 # if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
     DJEncoderRegistration::registerCodecs();
 # endif
--- a/OrthancFramework/Sources/DicomParsing/Internals/DicomImageDecoder.cpp	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancFramework/Sources/DicomParsing/Internals/DicomImageDecoder.cpp	Tue Jul 04 12:20:41 2023 +0200
@@ -745,11 +745,20 @@
                                "Cannot decode a non-palette image");
       }
 
+      std::string colorModel = Orthanc::Toolbox::StripSpaces(decompressedColorModel.c_str());
+
       if (target->GetFormat() == PixelFormat_RGB24 &&
-          Orthanc::Toolbox::StripSpaces(decompressedColorModel.c_str()) == "RGB" &&
+          (colorModel == "RGB" || colorModel == "YBR_FULL") &&
           info.IsPlanar())
       {
-        return DecodePlanarConfiguration(*target);
+        std::unique_ptr<ImageAccessor> output(DecodePlanarConfiguration(*target));
+
+        if (colorModel == "YBR_FULL")
+        {
+          ImageProcessing::ConvertJpegYCbCrToRgb(*output);
+        }
+
+        return output.release();
       }
       else
       {
--- a/OrthancFramework/Sources/DicomParsing/ParsedDicomFile.cpp	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancFramework/Sources/DicomParsing/ParsedDicomFile.cpp	Tue Jul 04 12:20:41 2023 +0200
@@ -76,6 +76,7 @@
 #include "Internals/DicomImageDecoder.h"
 #include "ToDcmtkBridge.h"
 
+#include "../DicomFormat/DicomImageInformation.h"
 #include "../Images/Image.h"
 #include "../Images/ImageProcessing.h"
 #include "../Images/PamReader.h"
@@ -1202,7 +1203,42 @@
         break;
 
       case MimeType_Pdf:
-        EmbedPdf(content);
+      {
+        if (content.size() < 5 ||  // (*)
+            strncmp("%PDF-", content.c_str(), 5) != 0)
+        {
+          throw OrthancException(ErrorCode_BadFileFormat, "Not a PDF file");
+        }
+        
+        EncapsulateDocument(MimeType_Pdf, content);
+
+        // In Orthanc <= 1.9.7, the "Modality" would have always be overwritten as "OT"
+        // https://groups.google.com/g/orthanc-users/c/eNSddNrQDtM/m/wc1HahimAAAJ
+
+        SetIfAbsent(DICOM_TAG_SOP_CLASS_UID, UID_EncapsulatedPDFStorage);
+        SetIfAbsent(DICOM_TAG_MODALITY, "OT");
+        SetIfAbsent(FromDcmtkBridge::Convert(DCM_ConversionType), "WSD");
+        //SetIfAbsent(FromDcmtkBridge::Convert(DCM_SeriesNumber), "1");
+
+        break;
+      }
+
+      case MimeType_Mtl:
+        EncapsulateDocument(mime, content);
+        SetIfAbsent(DICOM_TAG_SOP_CLASS_UID, "1.2.840.10008.5.1.4.1.1.104.5");
+        SetIfAbsent(DICOM_TAG_MODALITY, "M3D");
+        break;
+
+      case MimeType_Obj:
+        EncapsulateDocument(mime, content);
+        SetIfAbsent(DICOM_TAG_SOP_CLASS_UID, "1.2.840.10008.5.1.4.1.1.104.4");
+        SetIfAbsent(DICOM_TAG_MODALITY, "M3D");
+        break;
+
+      case MimeType_Stl:
+        EncapsulateDocument(mime, content);
+        SetIfAbsent(DICOM_TAG_SOP_CLASS_UID, "1.2.840.10008.5.1.4.1.1.104.3");
+        SetIfAbsent(DICOM_TAG_MODALITY, "M3D");
         break;
 
       default:
@@ -1525,28 +1561,16 @@
   }
 
 
-  void ParsedDicomFile::EmbedPdf(const std::string& pdf)
+  void ParsedDicomFile::EncapsulateDocument(MimeType mime,
+                                            const std::string& document)
   {
-    if (pdf.size() < 5 ||  // (*)
-        strncmp("%PDF-", pdf.c_str(), 5) != 0)
-    {
-      throw OrthancException(ErrorCode_BadFileFormat, "Not a PDF file");
-    }
-
     InvalidateCache();
 
-    // In Orthanc <= 1.9.7, the "Modality" would have always be overwritten as "OT"
-    // https://groups.google.com/g/orthanc-users/c/eNSddNrQDtM/m/wc1HahimAAAJ
-    
-    ReplacePlainString(DICOM_TAG_SOP_CLASS_UID, UID_EncapsulatedPDFStorage);
-    SetIfAbsent(FromDcmtkBridge::Convert(DCM_Modality), "OT");
-    SetIfAbsent(FromDcmtkBridge::Convert(DCM_ConversionType), "WSD");
-    SetIfAbsent(FromDcmtkBridge::Convert(DCM_MIMETypeOfEncapsulatedDocument), MIME_PDF);
-    //SetIfAbsent(FromDcmtkBridge::Convert(DCM_SeriesNumber), "1");
+    ReplacePlainString(FromDcmtkBridge::Convert(DCM_MIMETypeOfEncapsulatedDocument), EnumerationToString(mime));
 
     std::unique_ptr<DcmPolymorphOBOW> element(new DcmPolymorphOBOW(DCM_EncapsulatedDocument));
 
-    size_t s = pdf.size();
+    size_t s = document.size();
     if (s & 1)
     {
       // The size of the buffer must be even
@@ -1560,10 +1584,12 @@
       throw OrthancException(ErrorCode_NotEnoughMemory);
     }
 
-    // Blank pad byte (no access violation, as "pdf.size() >= 5" because of (*) )
-    bytes[s - 1] = 0;
+    if (s > 0)
+    {
+      bytes[s - 1] = 0;
+    }
 
-    memcpy(bytes, pdf.c_str(), pdf.size());
+    memcpy(bytes, document.c_str(), document.size());
       
     DcmPolymorphOBOW* obj = element.release();
     result = GetDcmtkObject().getDataset()->insert(obj);
@@ -2121,6 +2147,84 @@
     }
   }
 
+  
+  void ParsedDicomFile::InjectEmptyPixelData(ValueRepresentation vr)
+  {
+    DcmItem& dataset = *GetDcmtkObject().getDataset();
+
+    DcmElement *element = NULL;
+    if (!dataset.findAndGetElement(DCM_PixelData, element).good() ||
+        element == NULL)
+    {
+      // The pixel data is indeed nonexistent, insert it now
+      switch (vr)
+      {
+        case ValueRepresentation_OtherByte:
+          if (!dataset.putAndInsertUint8Array(DCM_PixelData, NULL, 0).good())
+          {
+            throw OrthancException(ErrorCode_InternalError);
+          }
+          break;
+
+        case ValueRepresentation_OtherWord:
+          if (!dataset.putAndInsertUint16Array(DCM_PixelData, NULL, 0).good())
+          {
+            throw OrthancException(ErrorCode_InternalError);
+          }
+          break;
+
+        default:
+          throw OrthancException(ErrorCode_ParameterOutOfRange);
+      }
+    }
+  }
+
+
+  void ParsedDicomFile::RemoveFromPixelData()
+  {
+    DcmItem& dataset = *GetDcmtkObject().getDataset();
+
+    // We need to go backward, otherwise "dataset.card()" is invalidated
+    for (unsigned long i = dataset.card(); i > 0; i--)
+    {
+      DcmElement* element = dataset.getElement(i - 1);
+      if (element == NULL)
+      {
+        throw OrthancException(ErrorCode_InternalError);
+      }
+
+      if (element->getTag().getGroup() > DCM_PixelData.getGroup() ||
+          (element->getTag().getGroup() == DCM_PixelData.getGroup() &&
+           element->getTag().getElement() >= DCM_PixelData.getElement()))
+      {
+        std::unique_ptr<DcmElement> removal(dataset.remove(i - 1));
+      }
+    }
+  }
+  
+
+  ValueRepresentation ParsedDicomFile::GuessPixelDataValueRepresentation() const
+  {
+    DicomTransferSyntax ts;
+    if (LookupTransferSyntax(ts))
+    {
+      DcmItem& dataset = *GetDcmtkObjectConst().getDataset();
+
+      uint16_t bitsAllocated;
+      if (!dataset.findAndGetUint16(DCM_BitsAllocated, bitsAllocated).good())
+      {
+        bitsAllocated = 8;
+      }
+
+      return DicomImageInformation::GuessPixelDataValueRepresentation(ts, bitsAllocated);
+    }
+    else
+    {
+      // Assume "OB" if the transfer syntax is unknown
+      return ValueRepresentation_OtherByte;
+    }
+  }
+
 
 #if ORTHANC_BUILDING_FRAMEWORK_LIBRARY == 1
   // Alias for binary compatibility with Orthanc Framework 1.7.2 => don't use it anymore
--- a/OrthancFramework/Sources/DicomParsing/ParsedDicomFile.h	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancFramework/Sources/DicomParsing/ParsedDicomFile.h	Tue Jul 04 12:20:41 2023 +0200
@@ -97,6 +97,9 @@
 
     bool EmbedContentInternal(const std::string& dataUriScheme);
 
+    void EncapsulateDocument(MimeType mime,
+                             const std::string& document);
+
     // For internal use only, in order to provide const-correctness on
     // the top of DCMTK API
     DcmFileFormat& GetDcmtkObjectConst() const;
@@ -205,6 +208,7 @@
     void SaveToFile(const std::string& path);
 #endif
 
+    // This method must only be used on the PixelData and EncapsulatedDocument tags
     void EmbedContent(const std::string& dataUriScheme);
 
     void EmbedImage(const ImageAccessor& accessor);
@@ -236,8 +240,6 @@
 
     bool HasTag(const DicomTag& tag) const;
 
-    void EmbedPdf(const std::string& pdf);
-
     bool ExtractPdf(std::string& pdf) const;
 
     void GetRawFrame(std::string& target, // OUT
@@ -309,5 +311,12 @@
 
     ImageAccessor* DecodeAllOverlays(int& originX,
                                      int& originY) const;
+
+    void InjectEmptyPixelData(ValueRepresentation vr);
+
+    // Remove all the tags after pixel data
+    void RemoveFromPixelData();
+
+    ValueRepresentation GuessPixelDataValueRepresentation() const;
   };
 }
--- a/OrthancFramework/Sources/Enumerations.cpp	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancFramework/Sources/Enumerations.cpp	Tue Jul 04 12:20:41 2023 +0200
@@ -31,6 +31,7 @@
 #include <boost/thread/mutex.hpp>
 #include <string.h>
 #include <cassert>
+#include <boost/algorithm/string/replace.hpp>
 
 namespace Orthanc
 {
@@ -877,15 +878,15 @@
     {
       case DicomVersion_2008:
         return "2008";
-        break;
 
       case DicomVersion_2017c:
         return "2017c";
-        break;
 
       case DicomVersion_2021b:
         return "2021b";
-        break;
+
+      case DicomVersion_2023b:
+        return "2023b";
 
       default: 
         throw OrthancException(ErrorCode_ParameterOutOfRange);
@@ -1110,6 +1111,15 @@
       case MimeType_Ico:
         return MIME_ICO;
 
+      case MimeType_Obj:
+        return MIME_OBJ;
+
+      case MimeType_Mtl:
+        return MIME_MTL;
+
+      case MimeType_Stl:
+        return MIME_STL;
+
       default:
         throw OrthancException(ErrorCode_ParameterOutOfRange);
     }
@@ -1641,6 +1651,10 @@
     {
       return DicomVersion_2021b;
     }
+    else if (version == "2023b")
+    {
+      return DicomVersion_2023b;
+    }
     else
     {
       throw OrthancException(ErrorCode_ParameterOutOfRange,
@@ -1848,6 +1862,21 @@
       target = MimeType_Ico;
       return true;
     }
+    else if (source == MIME_OBJ)
+    {
+      target = MimeType_Obj;
+      return true;
+    }
+    else if (source == MIME_MTL)
+    {
+      target = MimeType_Mtl;
+      return true;
+    }
+    else if (source == MIME_STL)
+    {
+      target = MimeType_Stl;
+      return true;
+    }
     else
     {
       return false;
@@ -1933,6 +1962,11 @@
     std::string s = Toolbox::StripSpaces(specificCharacterSet);
     Toolbox::ToUpperCase(s);
 
+    // handle common spelling mistakes
+    boost::replace_all(s, "ISO_IR_", "ISO_IR ");
+    boost::replace_all(s, "ISO_2022_IR_", "ISO 2022 IR ");
+
+
     // http://dicom.nema.org/medical/dicom/current/output/html/part03.html#sect_C.12.1.1.2
     // https://github.com/dcm4che/dcm4che/blob/master/dcm4che-core/src/main/java/org/dcm4che3/data/SpecificCharacterSet.java
     if (s == "ISO_IR 6" ||
--- a/OrthancFramework/Sources/Enumerations.h	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancFramework/Sources/Enumerations.h	Tue Jul 04 12:20:41 2023 +0200
@@ -42,6 +42,11 @@
   static const char* const MIME_XML = "application/xml";
   static const char* const MIME_XML_UTF8 = "application/xml; charset=utf-8";
 
+  // Added in Orthanc 1.12.1
+  static const char* const MIME_OBJ = "model/obj";
+  static const char* const MIME_MTL = "model/mtl";
+  static const char* const MIME_STL = "model/stl";
+
   /**
    * "No Internet Media Type (aka MIME type, content type) for PBM has
    * been registered with IANA, but the unofficial value
@@ -79,7 +84,10 @@
     MimeType_PrometheusText,  // Prometheus text-based exposition format (for metrics)
     MimeType_DicomWebJson,
     MimeType_DicomWebXml,
-    MimeType_Ico
+    MimeType_Ico,
+    MimeType_Mtl,             // MTL - New in Orthanc 1.12.1
+    MimeType_Obj,             // OBJ - New in Orthanc 1.12.1
+    MimeType_Stl              // STL - New in Orthanc 1.12.1
   };
 
   
@@ -620,7 +628,8 @@
   {
     DicomVersion_2008,
     DicomVersion_2017c,
-    DicomVersion_2021b
+    DicomVersion_2021b,
+    DicomVersion_2023b
   };
 
   enum ModalityManufacturer
--- a/OrthancFramework/Sources/FileStorage/StorageAccessor.cpp	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancFramework/Sources/FileStorage/StorageAccessor.cpp	Tue Jul 04 12:20:41 2023 +0200
@@ -38,9 +38,11 @@
 #endif
 
 
-static const std::string METRICS_CREATE = "orthanc_storage_create_duration_ms";
-static const std::string METRICS_READ = "orthanc_storage_read_duration_ms";
-static const std::string METRICS_REMOVE = "orthanc_storage_remove_duration_ms";
+static const std::string METRICS_CREATE_DURATION = "orthanc_storage_create_duration_ms";
+static const std::string METRICS_READ_DURATION = "orthanc_storage_read_duration_ms";
+static const std::string METRICS_REMOVE_DURATION = "orthanc_storage_remove_duration_ms";
+static const std::string METRICS_READ_BYTES = "orthanc_storage_read_bytes";
+static const std::string METRICS_WRITTEN_BYTES = "orthanc_storage_written_bytes";
 
 
 namespace Orthanc
@@ -116,9 +118,15 @@
     {
       case CompressionType_None:
       {
-        MetricsTimer timer(*this, METRICS_CREATE);
+        {
+          MetricsTimer timer(*this, METRICS_CREATE_DURATION);
+          area_.Create(uuid, data, size, type);
+        }
 
-        area_.Create(uuid, data, size, type);
+        if (metrics_ != NULL)
+        {
+          metrics_->IncrementIntegerValue(METRICS_WRITTEN_BYTES, size);
+        }
         
         if (cache_ != NULL)
         {
@@ -143,7 +151,7 @@
         }
 
         {
-          MetricsTimer timer(*this, METRICS_CREATE);
+          MetricsTimer timer(*this, METRICS_CREATE_DURATION);
 
           if (compressed.size() > 0)
           {
@@ -155,6 +163,11 @@
           }
         }
 
+        if (metrics_ != NULL)
+        {
+          metrics_->IncrementIntegerValue(METRICS_WRITTEN_BYTES, compressed.size());
+        }
+
         if (cache_ != NULL)
         {
           cache_->Add(uuid, type, data, size);  // always add uncompressed data to cache
@@ -189,8 +202,18 @@
       {
         case CompressionType_None:
         {
-          MetricsTimer timer(*this, METRICS_READ);
-          std::unique_ptr<IMemoryBuffer> buffer(area_.Read(info.GetUuid(), info.GetContentType()));
+          std::unique_ptr<IMemoryBuffer> buffer;
+
+          {
+            MetricsTimer timer(*this, METRICS_READ_DURATION);
+            buffer.reset(area_.Read(info.GetUuid(), info.GetContentType()));
+          }
+
+          if (metrics_ != NULL)
+          {
+            metrics_->IncrementIntegerValue(METRICS_READ_BYTES, buffer->GetSize());
+          }
+
           buffer->MoveToString(content);
 
           break;
@@ -203,10 +226,15 @@
           std::unique_ptr<IMemoryBuffer> compressed;
           
           {
-            MetricsTimer timer(*this, METRICS_READ);
+            MetricsTimer timer(*this, METRICS_READ_DURATION);
             compressed.reset(area_.Read(info.GetUuid(), info.GetContentType()));
           }
           
+          if (metrics_ != NULL)
+          {
+            metrics_->IncrementIntegerValue(METRICS_READ_BYTES, compressed->GetSize());
+          }
+
           zlib.Uncompress(content, compressed->GetData(), compressed->GetSize());
 
           break;
@@ -234,8 +262,18 @@
   {
     if (cache_ == NULL || !cache_->Fetch(content, info.GetUuid(), info.GetContentType()))
     {
-      MetricsTimer timer(*this, METRICS_READ);
-      std::unique_ptr<IMemoryBuffer> buffer(area_.Read(info.GetUuid(), info.GetContentType()));
+      std::unique_ptr<IMemoryBuffer> buffer;
+
+      {
+        MetricsTimer timer(*this, METRICS_READ_DURATION);
+        buffer.reset(area_.Read(info.GetUuid(), info.GetContentType()));
+      }
+
+      if (metrics_ != NULL)
+      {
+        metrics_->IncrementIntegerValue(METRICS_READ_BYTES, buffer->GetSize());
+      }
+
       buffer->MoveToString(content);
     }
   }
@@ -250,7 +288,7 @@
     }
 
     {
-      MetricsTimer timer(*this, METRICS_REMOVE);
+      MetricsTimer timer(*this, METRICS_REMOVE_DURATION);
       area_.Remove(fileUuid, type);
     }
   }
@@ -269,9 +307,19 @@
   {
     if (cache_ == NULL || !cache_->FetchStartRange(target, fileUuid, contentType, end))
     {
-      MetricsTimer timer(*this, METRICS_READ);
-      std::unique_ptr<IMemoryBuffer> buffer(area_.ReadRange(fileUuid, contentType, 0, end));
-      assert(buffer->GetSize() == end);
+      std::unique_ptr<IMemoryBuffer> buffer;
+
+      {
+        MetricsTimer timer(*this, METRICS_READ_DURATION);
+        buffer.reset(area_.ReadRange(fileUuid, contentType, 0, end));
+        assert(buffer->GetSize() == end);
+      }
+
+      if (metrics_ != NULL)
+      {
+        metrics_->IncrementIntegerValue(METRICS_READ_BYTES, buffer->GetSize());
+      }
+
       buffer->MoveToString(target);
 
       if (cache_ != NULL)
--- a/OrthancFramework/Sources/HttpServer/HttpContentNegociation.cpp	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancFramework/Sources/HttpServer/HttpContentNegociation.cpp	Tue Jul 04 12:20:41 2023 +0200
@@ -49,28 +49,68 @@
     {
       return true;
     }
-        
-    if (subtype == "*" && type == type_)
+    else if (subtype == "*" && type == type_)
     {
       return true;
     }
-
-    return type == type_ && subtype == subtype_;
+    else
+    {
+      return type == type_ && subtype == subtype_;
+    }
   }
 
 
-  struct HttpContentNegociation::Reference : public boost::noncopyable
+  class HttpContentNegociation::Reference : public boost::noncopyable
   {
+  private:
     const Handler&  handler_;
     uint8_t         level_;
     float           quality_;
+    Dictionary      parameters_;
 
+    static float GetQuality(const Dictionary& parameters)
+    {
+      Dictionary::const_iterator found = parameters.find("q");
+
+      if (found != parameters.end())
+      {
+        float quality;
+        bool ok = false;
+
+        try
+        {
+          quality = boost::lexical_cast<float>(found->second);
+          ok = (quality >= 0.0f && quality <= 1.0f);
+        }
+        catch (boost::bad_lexical_cast&)
+        {
+        }
+
+        if (ok)
+        {
+          return quality;
+        }
+        else
+        {
+          throw OrthancException(
+            ErrorCode_BadRequest,
+            "Quality parameter out of range in a HTTP request (must be between 0 and 1): " + found->second);
+        }
+      }
+      else
+      {
+        return 1.0f;  // Default quality
+      }
+    }
+
+  public:
     Reference(const Handler& handler,
               const std::string& type,
               const std::string& subtype,
-              float quality) :
+              const Dictionary& parameters) :
       handler_(handler),
-      quality_(quality)
+      quality_(GetQuality(parameters)),
+      parameters_(parameters)
     {
       if (type == "*" && subtype == "*")
       {
@@ -85,6 +125,11 @@
         level_ = 2;
       }
     }
+
+    void Call() const
+    {
+      handler_.Call(parameters_);
+    }
       
     bool operator< (const Reference& other) const
     {
@@ -92,13 +137,14 @@
       {
         return true;
       }
-
-      if (level_ > other.level_)
+      else if (level_ > other.level_)
       {
         return false;
       }
-
-      return quality_ < other.quality_;
+      else
+      {
+        return quality_ < other.quality_;
+      }
     }
   };
 
@@ -123,58 +169,21 @@
   }
 
 
-  float HttpContentNegociation::GetQuality(const Tokens& parameters)
-  {
-    for (size_t i = 1; i < parameters.size(); i++)
-    {
-      std::string key, value;
-      if (SplitPair(key, value, parameters[i], '=') &&
-          key == "q")
-      {
-        float quality;
-        bool ok = false;
-
-        try
-        {
-          quality = boost::lexical_cast<float>(value);
-          ok = (quality >= 0.0f && quality <= 1.0f);
-        }
-        catch (boost::bad_lexical_cast&)
-        {
-        }
-
-        if (ok)
-        {
-          return quality;
-        }
-        else
-        {
-          throw OrthancException(
-            ErrorCode_BadRequest,
-            "Quality parameter out of range in a HTTP request (must be between 0 and 1): " + value);
-        }
-      }
-    }
-
-    return 1.0f;  // Default quality
-  }
-
-
-  void HttpContentNegociation::SelectBestMatch(std::unique_ptr<Reference>& best,
+  void HttpContentNegociation::SelectBestMatch(std::unique_ptr<Reference>& target,
                                                const Handler& handler,
                                                const std::string& type,
                                                const std::string& subtype,
-                                               float quality)
+                                               const Dictionary& parameters)
   {
-    std::unique_ptr<Reference> match(new Reference(handler, type, subtype, quality));
+    std::unique_ptr<Reference> match(new Reference(handler, type, subtype, parameters));
 
-    if (best.get() == NULL ||
-        *best < *match)
+    if (target.get() == NULL ||
+        *target < *match)
     {
 #if __cplusplus < 201103L
-      best.reset(match.release());
+      target.reset(match.release());
 #else
-      best = std::move(match);
+      target = std::move(match);
 #endif
     }
   }
@@ -198,9 +207,9 @@
   }
 
     
-  bool HttpContentNegociation::Apply(const HttpHeaders& headers)
+  bool HttpContentNegociation::Apply(const Dictionary& headers)
   {
-    HttpHeaders::const_iterator accept = headers.find("accept");
+    Dictionary::const_iterator accept = headers.find("accept");
     if (accept != headers.end())
     {
       return Apply(accept->second);
@@ -226,22 +235,44 @@
     for (Tokens::const_iterator it = mediaRanges.begin();
          it != mediaRanges.end(); ++it)
     {
-      Tokens parameters;
-      Toolbox::TokenizeString(parameters, *it, ';');
+      Tokens tokens;
+      Toolbox::TokenizeString(tokens, *it, ';');
 
-      if (parameters.size() > 0)
+      if (tokens.size() > 0)
       {
-        float quality = GetQuality(parameters);
+        Dictionary parameters;
+        for (size_t i = 1; i < tokens.size(); i++)
+        {
+          std::string key, value;
+          
+          if (SplitPair(key, value, tokens[i], '='))
+          {
+            // Remove the enclosing quotes, if present
+            if (!value.empty() &&
+                value[0] == '"' &&
+                value[value.size() - 1] == '"')
+            {
+              value = value.substr(1, value.size() - 2);
+            }
+          }
+          else
+          {
+            key = Toolbox::StripSpaces(tokens[i]);
+            value = "";
+          }
 
+          parameters[key] = value;
+        }
+        
         std::string type, subtype;
-        if (SplitPair(type, subtype, parameters[0], '/'))
+        if (SplitPair(type, subtype, tokens[0], '/'))
         {
           for (Handlers::const_iterator it2 = handlers_.begin();
                it2 != handlers_.end(); ++it2)
           {
             if (it2->IsMatch(type, subtype))
             {
-              SelectBestMatch(bestMatch, *it2, type, subtype, quality);
+              SelectBestMatch(bestMatch, *it2, type, subtype, parameters);
             }
           }
         }
@@ -254,7 +285,7 @@
     }
     else
     {
-      bestMatch->handler_.Call();
+      bestMatch->Call();
       return true;
     }
   }
--- a/OrthancFramework/Sources/HttpServer/HttpContentNegociation.h	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancFramework/Sources/HttpServer/HttpContentNegociation.h	Tue Jul 04 12:20:41 2023 +0200
@@ -39,7 +39,7 @@
   class ORTHANC_PUBLIC HttpContentNegociation : public boost::noncopyable
   {
   public:
-    typedef std::map<std::string, std::string>  HttpHeaders;
+    typedef std::map<std::string, std::string>  Dictionary;
 
     class IHandler : public boost::noncopyable
     {
@@ -49,7 +49,8 @@
       }
 
       virtual void Handle(const std::string& type,
-                          const std::string& subtype) = 0;
+                          const std::string& subtype,
+                          const Dictionary& parameters) = 0;
     };
 
   private:
@@ -66,9 +67,9 @@
       bool IsMatch(const std::string& type,
                    const std::string& subtype) const;
 
-      void Call() const
+      void Call(const Dictionary& parameters) const
       {
-        handler_.Handle(type_, subtype_);
+        handler_.Handle(type_, subtype_, parameters);
       }
    };
 
@@ -86,19 +87,17 @@
                           const std::string& source,
                           char separator);
 
-    static float GetQuality(const Tokens& parameters);
-
-    static void SelectBestMatch(std::unique_ptr<Reference>& best,
+    static void SelectBestMatch(std::unique_ptr<Reference>& target,
                                 const Handler& handler,
                                 const std::string& type,
                                 const std::string& subtype,
-                                float quality);
+                                const Dictionary& parameters);
 
   public:
     void Register(const std::string& mime,
                   IHandler& handler);
     
-    bool Apply(const HttpHeaders& headers);
+    bool Apply(const Dictionary& headers);
 
     bool Apply(const std::string& accept);
   };
--- a/OrthancFramework/Sources/Images/NumpyWriter.cpp	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancFramework/Sources/Images/NumpyWriter.cpp	Tue Jul 04 12:20:41 2023 +0200
@@ -154,7 +154,7 @@
   {
     if (compress)
     {
-#if ORTHANC_ENABLE_ZLIB == 1
+#if (ORTHANC_ENABLE_ZLIB == 1) && (ORTHANC_SANDBOXED == 0)
       // This is the default name of the first array if arrays are
       // specified as positional arguments in "numpy.savez()"
       // https://numpy.org/doc/stable/reference/generated/numpy.savez.html
@@ -172,7 +172,7 @@
       writer.Write(uncompressed);
       writer.Close();
 #else
-      throw OrthancException(ErrorCode_InternalError, "Orthanc was compiled without support for zlib");
+      throw OrthancException(ErrorCode_InternalError, "Orthanc was compiled without support for ZIP");
 #endif
     }
     else
--- a/OrthancFramework/Sources/JobsEngine/IJob.h	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancFramework/Sources/JobsEngine/IJob.h	Tue Jul 04 12:20:41 2023 +0200
@@ -62,5 +62,9 @@
                            MimeType& mime,
                            std::string& filename,
                            const std::string& key) = 0;
+
+    // This function can only be called if the job has reached its
+    // "success" state
+    virtual bool DeleteOutput(const std::string& key) = 0;
   };
 }
--- a/OrthancFramework/Sources/JobsEngine/JobsRegistry.cpp	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancFramework/Sources/JobsEngine/JobsRegistry.cpp	Tue Jul 04 12:20:41 2023 +0200
@@ -679,6 +679,33 @@
     }
   }
 
+  bool JobsRegistry::DeleteJobOutput(const std::string& job,
+                                     const std::string& key)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+    CheckInvariants();
+
+    JobsIndex::const_iterator found = jobsIndex_.find(job);
+
+    if (found == jobsIndex_.end())
+    {
+      return false;
+    }
+    else
+    {
+      const JobHandler& handler = *found->second;
+
+      if (handler.GetState() == JobState_Success)
+      {
+        return handler.GetJob().DeleteOutput(key);
+      }
+      else
+      {
+        return false;
+      }
+    }
+  }
+
 
   void JobsRegistry::SubmitInternal(std::string& id,
                                     JobHandler* handler)
--- a/OrthancFramework/Sources/JobsEngine/JobsRegistry.h	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancFramework/Sources/JobsEngine/JobsRegistry.h	Tue Jul 04 12:20:41 2023 +0200
@@ -153,6 +153,9 @@
                       const std::string& job,
                       const std::string& key);
 
+    bool DeleteJobOutput(const std::string& job,
+                         const std::string& key);
+
     void Serialize(Json::Value& target);
 
     void Submit(std::string& id,
--- a/OrthancFramework/Sources/JobsEngine/Operations/SequenceOfOperationsJob.cpp	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancFramework/Sources/JobsEngine/Operations/SequenceOfOperationsJob.cpp	Tue Jul 04 12:20:41 2023 +0200
@@ -447,13 +447,6 @@
     return true;
   }
 
-  bool SequenceOfOperationsJob::GetOutput(std::string& output,
-                                          MimeType& mime,
-                                          std::string& filename,
-                                          const std::string& key)
-  {
-    return false;
-  }
 
   void SequenceOfOperationsJob::AwakeTrailingSleep()
   {
--- a/OrthancFramework/Sources/JobsEngine/Operations/SequenceOfOperationsJob.h	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancFramework/Sources/JobsEngine/Operations/SequenceOfOperationsJob.h	Tue Jul 04 12:20:41 2023 +0200
@@ -127,7 +127,15 @@
     virtual bool GetOutput(std::string& output,
                            MimeType& mime,
                            std::string& filename,
-                           const std::string& key) ORTHANC_OVERRIDE;
+                           const std::string& key) ORTHANC_OVERRIDE
+    {
+      return false;
+    }
+
+    virtual bool DeleteOutput(const std::string& key) ORTHANC_OVERRIDE
+    {
+      return false;
+    }
 
     void AwakeTrailingSleep();
   };
--- a/OrthancFramework/Sources/JobsEngine/SetOfCommandsJob.cpp	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancFramework/Sources/JobsEngine/SetOfCommandsJob.cpp	Tue Jul 04 12:20:41 2023 +0200
@@ -269,14 +269,6 @@
     return true;
   }
 
-  bool SetOfCommandsJob::GetOutput(std::string &output,
-                                   MimeType &mime,
-                                   std::string& filename,
-                                   const std::string &key)
-  {
-    return false;
-  }
-
 
   SetOfCommandsJob::SetOfCommandsJob(ICommandUnserializer* unserializer,
                                      const Json::Value& source) :
--- a/OrthancFramework/Sources/JobsEngine/SetOfCommandsJob.h	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancFramework/Sources/JobsEngine/SetOfCommandsJob.h	Tue Jul 04 12:20:41 2023 +0200
@@ -106,6 +106,14 @@
     virtual bool GetOutput(std::string& output,
                            MimeType& mime,
                            std::string& filename,
-                           const std::string& key) ORTHANC_OVERRIDE;
+                           const std::string& key) ORTHANC_OVERRIDE
+    {
+      return false;
+    }
+
+    virtual bool DeleteOutput(const std::string& key) ORTHANC_OVERRIDE
+    {
+      return false;
+    }
   };
 }
--- a/OrthancFramework/Sources/Lua/LuaContext.cpp	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancFramework/Sources/Lua/LuaContext.cpp	Tue Jul 04 12:20:41 2023 +0200
@@ -396,10 +396,6 @@
       const std::string s = value.asString();
       lua_pushlstring(lua_, s.c_str(), s.size());
     }
-    else if (value.isDouble())
-    {
-      lua_pushnumber(lua_, value.asDouble());
-    }
     else if (value.isInt())
     {
       lua_pushinteger(lua_, value.asInt());
@@ -408,6 +404,10 @@
     {
       lua_pushinteger(lua_, value.asUInt());
     }
+    else if (value.isDouble())
+    {
+      lua_pushnumber(lua_, value.asDouble());
+    }
     else if (value.isBool())
     {
       lua_pushboolean(lua_, value.asBool());
--- a/OrthancFramework/Sources/MetricsRegistry.cpp	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancFramework/Sources/MetricsRegistry.cpp	Tue Jul 04 12:20:41 2023 +0200
@@ -28,6 +28,8 @@
 #include "Compatibility.h"
 #include "OrthancException.h"
 
+#include <boost/math/special_functions/round.hpp>
+
 namespace Orthanc
 {
   static const boost::posix_time::ptime GetNow()
@@ -35,134 +37,277 @@
     return boost::posix_time::microsec_clock::universal_time();
   }
 
-  class MetricsRegistry::Item
+  namespace
   {
-  private:
-    MetricsType               type_;
-    boost::posix_time::ptime  time_;
-    bool                      hasValue_;
-    float                     value_;
-    
-    void Touch(float value,
-               const boost::posix_time::ptime& now)
+    template <typename T>
+    class TimestampedValue : public boost::noncopyable
     {
-      hasValue_ = true;
-      value_ = value;
-      time_ = now;
-    }
+    private:
+      boost::posix_time::ptime  time_;
+      bool                      hasValue_;
+      T                         value_;
+
+      void SetValue(const T& value,
+                    const boost::posix_time::ptime& now)
+      {
+        hasValue_ = true;
+        value_ = value;
+        time_ = now;
+      }
 
-    void Touch(float value)
-    {
-      Touch(value, GetNow());
-    }
+      bool IsLargerOverPeriod(const T& value,
+                              int duration,
+                              const boost::posix_time::ptime& now) const
+      {
+        if (hasValue_)
+        {
+          return (value > value_ ||
+                  (now - time_).total_seconds() > duration /* old value has expired */);
+        }
+        else
+        {
+          return true;  // No value yet
+        }
+      }
 
-    void UpdateMax(float value,
-                   int duration)
-    {
-      if (hasValue_)
+      bool IsSmallerOverPeriod(const T& value,
+                               int duration,
+                               const boost::posix_time::ptime& now) const
+      {
+        if (hasValue_)
+        {
+          return (value < value_ ||
+                  (now - time_).total_seconds() > duration /* old value has expired */);
+        }
+        else
+        {
+          return true;  // No value yet
+        }
+      }
+
+    public:
+      explicit TimestampedValue() :
+        hasValue_(false),
+        value_(0)
+      {
+      }
+
+      void Update(const T& value,
+                  const MetricsUpdatePolicy& policy)
       {
         const boost::posix_time::ptime now = GetNow();
 
-        if (value > value_ ||
-            (now - time_).total_seconds() > duration)
+        switch (policy)
         {
-          Touch(value, now);
+          case MetricsUpdatePolicy_Directly:
+            SetValue(value, now);
+            break;
+          
+          case MetricsUpdatePolicy_MaxOver10Seconds:
+            if (IsLargerOverPeriod(value, 10, now))
+            {
+              SetValue(value, now);
+            }
+            break;
+
+          case MetricsUpdatePolicy_MaxOver1Minute:
+            if (IsLargerOverPeriod(value, 60, now))
+            {
+              SetValue(value, now);
+            }
+            break;
+
+          case MetricsUpdatePolicy_MinOver10Seconds:
+            if (IsSmallerOverPeriod(value, 10, now))
+            {
+              SetValue(value, now);
+            }
+            break;
+
+          case MetricsUpdatePolicy_MinOver1Minute:
+            if (IsSmallerOverPeriod(value, 60, now))
+            {
+              SetValue(value, now);
+            }
+            break;
+
+          default:
+            throw OrthancException(ErrorCode_NotImplemented);
         }
       }
-      else
+
+      void Increment(const T& delta)
+      {
+        if (hasValue_)
+        {
+          value_ += delta;
+        }
+        else
+        {
+          value_ = delta;
+        }
+      }
+
+      bool HasValue() const
+      {
+        return hasValue_;
+      }
+
+      const boost::posix_time::ptime& GetTime() const
       {
-        Touch(value);
+        if (hasValue_)
+        {
+          return time_;
+        }
+        else
+        {
+          throw OrthancException(ErrorCode_BadSequenceOfCalls);
+        }
       }
+
+      const T& GetValue() const
+      {
+        if (hasValue_)
+        {
+          return value_;
+        }
+        else
+        {
+          throw OrthancException(ErrorCode_BadSequenceOfCalls);
+        }
+      }
+    };
+  }
+
+
+  class MetricsRegistry::Item : public boost::noncopyable
+  {
+  private:
+    MetricsUpdatePolicy   policy_;
+    
+  public:
+    explicit Item(MetricsUpdatePolicy policy) :
+      policy_(policy)
+    {
     }
     
-    void UpdateMin(float value,
-                   int duration)
-    {
-      if (hasValue_)
-      {
-        const boost::posix_time::ptime now = GetNow();
-        
-        if (value < value_ ||
-            (now - time_).total_seconds() > duration)
-        {
-          Touch(value, now);
-        }
-      }
-      else
-      {
-        Touch(value);
-      }
-    }
-
-  public:
-    explicit Item(MetricsType type) :
-      type_(type),
-      hasValue_(false),
-      value_(0)
+    virtual ~Item()
     {
     }
 
-    MetricsType GetType() const
+    MetricsUpdatePolicy GetPolicy() const
     {
-      return type_;
+      return policy_;
+    }
+
+    virtual void UpdateFloat(float value) = 0;
+
+    virtual void UpdateInteger(int64_t value) = 0;
+
+    virtual void IncrementInteger(int64_t delta) = 0;
+
+    virtual MetricsDataType GetDataType() const = 0;
+
+    virtual bool HasValue() const = 0;
+
+    virtual const boost::posix_time::ptime& GetTime() const = 0;
+    
+    virtual std::string FormatValue() const = 0;
+  };
+
+  
+  class MetricsRegistry::FloatItem : public Item
+  {
+  private:
+    TimestampedValue<float>  value_;
+
+  public:
+    explicit FloatItem(MetricsUpdatePolicy policy) :
+      Item(policy)
+    {
+    }
+    
+    virtual void UpdateFloat(float value) ORTHANC_OVERRIDE
+    {
+      value_.Update(value, GetPolicy());
+    }
+
+    virtual void UpdateInteger(int64_t value) ORTHANC_OVERRIDE
+    {
+      value_.Update(static_cast<float>(value), GetPolicy());
+    }
+
+    virtual void IncrementInteger(int64_t delta) ORTHANC_OVERRIDE
+    {
+      value_.Increment(static_cast<float>(delta));
+    }
+
+    virtual MetricsDataType GetDataType() const ORTHANC_OVERRIDE
+    {
+      return MetricsDataType_Float;
+    }
+
+    virtual bool HasValue() const ORTHANC_OVERRIDE
+    {
+      return value_.HasValue();
     }
 
-    void Update(float value)
+    virtual const boost::posix_time::ptime& GetTime() const ORTHANC_OVERRIDE
     {
-      switch (type_)
-      {
-        case MetricsType_Default:
-          Touch(value);
-          break;
-          
-        case MetricsType_MaxOver10Seconds:
-          UpdateMax(value, 10);
-          break;
+      return value_.GetTime();
+    }
+    
+    virtual std::string FormatValue() const ORTHANC_OVERRIDE
+    {
+      return boost::lexical_cast<std::string>(value_.GetValue());
+    }
+  };
 
-        case MetricsType_MaxOver1Minute:
-          UpdateMax(value, 60);
-          break;
-
-        case MetricsType_MinOver10Seconds:
-          UpdateMin(value, 10);
-          break;
+  
+  class MetricsRegistry::IntegerItem : public Item
+  {
+  private:
+    TimestampedValue<int64_t>  value_;
 
-        case MetricsType_MinOver1Minute:
-          UpdateMin(value, 60);
-          break;
-
-        default:
-          throw OrthancException(ErrorCode_NotImplemented);
-      }
+  public:
+    explicit IntegerItem(MetricsUpdatePolicy policy) :
+      Item(policy)
+    {
+    }
+    
+    virtual void UpdateFloat(float value) ORTHANC_OVERRIDE
+    {
+      value_.Update(boost::math::llround(value), GetPolicy());
     }
 
-    bool HasValue() const
+    virtual void UpdateInteger(int64_t value) ORTHANC_OVERRIDE
     {
-      return hasValue_;
+      value_.Update(value, GetPolicy());
+    }
+
+    virtual void IncrementInteger(int64_t delta) ORTHANC_OVERRIDE
+    {
+      value_.Increment(delta);
     }
 
-    const boost::posix_time::ptime& GetTime() const
+    virtual MetricsDataType GetDataType() const ORTHANC_OVERRIDE
     {
-      if (hasValue_)
-      {
-        return time_;
-      }
-      else
-      {
-        throw OrthancException(ErrorCode_BadSequenceOfCalls);
-      }
+      return MetricsDataType_Integer;
     }
 
-    float GetValue() const
+    virtual bool HasValue() const ORTHANC_OVERRIDE
+    {
+      return value_.HasValue();
+    }
+
+    virtual const boost::posix_time::ptime& GetTime() const ORTHANC_OVERRIDE
     {
-      if (hasValue_)
-      {
-        return value_;
-      }
-      else
-      {
-        throw OrthancException(ErrorCode_BadSequenceOfCalls);
-      }
+      return value_.GetTime();
+    }
+    
+    virtual std::string FormatValue() const ORTHANC_OVERRIDE
+    {
+      return boost::lexical_cast<std::string>(value_.GetValue());
     }
   };
 
@@ -190,48 +335,53 @@
 
 
   void MetricsRegistry::Register(const std::string& name,
-                                 MetricsType type)
+                                 MetricsUpdatePolicy policy,
+                                 MetricsDataType type)
   {
     boost::mutex::scoped_lock lock(mutex_);
 
+    if (content_.find(name) != content_.end())
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls, "Cannot register twice the same metrics: " + name);
+    }
+    else
+    {
+      GetItemInternal(name, policy, type);
+    }
+  }
+
+
+  MetricsRegistry::Item& MetricsRegistry::GetItemInternal(const std::string& name,
+                                                          MetricsUpdatePolicy policy,
+                                                          MetricsDataType type)
+  {
     Content::iterator found = content_.find(name);
 
     if (found == content_.end())
     {
-      content_[name] = new Item(type);
+      Item* item = NULL;
+      
+      switch (type)
+      {
+        case MetricsDataType_Float:
+          item = new FloatItem(policy);
+          break;
+
+        case MetricsDataType_Integer:
+          item = new IntegerItem(policy);
+          break;
+
+        default:
+          throw OrthancException(ErrorCode_ParameterOutOfRange);
+      }
+
+      content_[name] = item;
+      return *item;
     }
     else
     {
       assert(found->second != NULL);
-
-      // This metrics already exists: Only recreate it if there is a
-      // mismatch in the type of metrics
-      if (found->second->GetType() != type)
-      {
-        delete found->second;
-        found->second = new Item(type);
-      }
-    }    
-  }
-
-  void MetricsRegistry::SetValueInternal(const std::string& name,
-                                         float value,
-                                         MetricsType type)
-  {
-    boost::mutex::scoped_lock lock(mutex_);
-
-    Content::iterator found = content_.find(name);
-
-    if (found == content_.end())
-    {
-      std::unique_ptr<Item> item(new Item(type));
-      item->Update(value);
-      content_[name] = item.release();
-    }
-    else
-    {
-      assert(found->second != NULL);
-      found->second->Update(value);
+      return *found->second;
     }
   }
 
@@ -241,29 +391,49 @@
   }
 
 
-  void MetricsRegistry::SetValue(const std::string &name,
-                                 float value,
-                                 MetricsType type)
+  void MetricsRegistry::SetFloatValue(const std::string& name,
+                                      float value,
+                                      MetricsUpdatePolicy policy)
   {
     // Inlining to avoid loosing time if metrics are disabled
     if (enabled_)
     {
-      SetValueInternal(name, value, type);
+      boost::mutex::scoped_lock lock(mutex_);
+      GetItemInternal(name, policy, MetricsDataType_Float).UpdateFloat(value);
+    }
+  }
+  
+
+  void MetricsRegistry::SetIntegerValue(const std::string &name,
+                                        int64_t value,
+                                        MetricsUpdatePolicy policy)
+  {
+    // Inlining to avoid loosing time if metrics are disabled
+    if (enabled_)
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+      GetItemInternal(name, policy, MetricsDataType_Integer).UpdateInteger(value);
     }
   }
 
 
-  void MetricsRegistry::SetValue(const std::string &name, float value)
+  void MetricsRegistry::IncrementIntegerValue(const std::string &name,
+                                              int64_t delta)
   {
-    SetValue(name, value, MetricsType_Default);
+    // Inlining to avoid loosing time if metrics are disabled
+    if (enabled_)
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+      GetItemInternal(name, MetricsUpdatePolicy_Directly, MetricsDataType_Integer).IncrementInteger(delta);
+    }
   }
 
 
-  MetricsType MetricsRegistry::GetMetricsType(const std::string& name)
+  MetricsUpdatePolicy MetricsRegistry::GetUpdatePolicy(const std::string& metrics)
   {
     boost::mutex::scoped_lock lock(mutex_);
 
-    Content::const_iterator found = content_.find(name);
+    Content::const_iterator found = content_.find(metrics);
 
     if (found == content_.end())
     {
@@ -272,7 +442,25 @@
     else
     {
       assert(found->second != NULL);
-      return found->second->GetType();
+      return found->second->GetPolicy();
+    }
+  }
+
+
+  MetricsDataType MetricsRegistry::GetDataType(const std::string& metrics)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    Content::const_iterator found = content_.find(metrics);
+
+    if (found == content_.end())
+    {
+      throw OrthancException(ErrorCode_InexistentItem);
+    }
+    else
+    {
+      assert(found->second != NULL);
+      return found->second->GetDataType();
     }
   }
 
@@ -303,7 +491,7 @@
         boost::posix_time::time_duration diff = it->second->GetTime() - EPOCH;
 
         std::string line = (it->first + " " +
-                            boost::lexical_cast<std::string>(it->second->GetValue()) + " " + 
+                            it->second->FormatValue() + " " + 
                             boost::lexical_cast<std::string>(diff.total_milliseconds()) + "\n");
 
         buffer.AddChunk(line);
@@ -316,18 +504,18 @@
 
   MetricsRegistry::SharedMetrics::SharedMetrics(MetricsRegistry &registry,
                                                 const std::string &name,
-                                                MetricsType type) :
+                                                MetricsUpdatePolicy policy) :
     registry_(registry),
     name_(name),
     value_(0)
   {
   }
 
-  void MetricsRegistry::SharedMetrics::Add(float delta)
+  void MetricsRegistry::SharedMetrics::Add(int64_t delta)
   {
     boost::mutex::scoped_lock lock(mutex_);
     value_ += delta;
-    registry_.SetValue(name_, value_);
+    registry_.SetIntegerValue(name_, value_);
   }
 
 
@@ -361,7 +549,7 @@
                                 const std::string &name) :
     registry_(registry),
     name_(name),
-    type_(MetricsType_MaxOver10Seconds)
+    policy_(MetricsUpdatePolicy_MaxOver10Seconds)
   {
     Start();
   }
@@ -369,10 +557,10 @@
 
   MetricsRegistry::Timer::Timer(MetricsRegistry &registry,
                                 const std::string &name,
-                                MetricsType type) :
+                                MetricsUpdatePolicy policy) :
     registry_(registry),
     name_(name),
-    type_(type)
+    policy_(policy)
   {
     Start();
   }
@@ -383,8 +571,7 @@
     if (active_)
     {
       boost::posix_time::time_duration diff = GetNow() - start_;
-      registry_.SetValue(
-            name_, static_cast<float>(diff.total_milliseconds()), type_);
+      registry_.SetIntegerValue(name_, static_cast<int64_t>(diff.total_milliseconds()), policy_);
     }
   }
 }
--- a/OrthancFramework/Sources/MetricsRegistry.h	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancFramework/Sources/MetricsRegistry.h	Tue Jul 04 12:20:41 2023 +0200
@@ -35,22 +35,31 @@
 
 #include <boost/thread/mutex.hpp>
 #include <boost/date_time/posix_time/posix_time.hpp>
+#include <stdint.h>
 
 namespace Orthanc
 {
-  enum MetricsType
+  enum MetricsUpdatePolicy
   {
-    MetricsType_Default,
-    MetricsType_MaxOver10Seconds,
-    MetricsType_MaxOver1Minute,
-    MetricsType_MinOver10Seconds,
-    MetricsType_MinOver1Minute
+    MetricsUpdatePolicy_Directly,
+    MetricsUpdatePolicy_MaxOver10Seconds,
+    MetricsUpdatePolicy_MaxOver1Minute,
+    MetricsUpdatePolicy_MinOver10Seconds,
+    MetricsUpdatePolicy_MinOver1Minute
+  };
+  
+  enum MetricsDataType
+  {
+    MetricsDataType_Float,
+    MetricsDataType_Integer
   };
   
   class ORTHANC_PUBLIC MetricsRegistry : public boost::noncopyable
   {
   private:
     class Item;
+    class FloatItem;
+    class IntegerItem;
 
     typedef std::map<std::string, Item*>   Content;
 
@@ -58,9 +67,10 @@
     boost::mutex  mutex_;
     Content       content_;
 
-    void SetValueInternal(const std::string& name,
-                          float value,
-                          MetricsType type);
+    // The mutex must be locked
+    Item& GetItemInternal(const std::string& name,
+                          MetricsUpdatePolicy policy,
+                          MetricsDataType type);
 
   public:
     MetricsRegistry();
@@ -72,16 +82,35 @@
     void SetEnabled(bool enabled);
 
     void Register(const std::string& name,
-                  MetricsType type);
+                  MetricsUpdatePolicy policy,
+                  MetricsDataType type);
 
-    void SetValue(const std::string& name,
-                  float value,
-                  MetricsType type);
+    void SetFloatValue(const std::string& name,
+                       float value,
+                       MetricsUpdatePolicy policy /* only used if this is a new metrics */);
+    
+    void SetFloatValue(const std::string& name,
+                       float value)
+    {
+      SetFloatValue(name, value, MetricsUpdatePolicy_Directly);
+    }
     
-    void SetValue(const std::string& name,
-                  float value);
+    void SetIntegerValue(const std::string& name,
+                         int64_t value,
+                         MetricsUpdatePolicy policy /* only used if this is a new metrics */);
+    
+    void SetIntegerValue(const std::string& name,
+                         int64_t value)
+    {
+      SetIntegerValue(name, value, MetricsUpdatePolicy_Directly);
+    }
+    
+    void IncrementIntegerValue(const std::string& name,
+                               int64_t delta);
 
-    MetricsType GetMetricsType(const std::string& name);
+    MetricsUpdatePolicy GetUpdatePolicy(const std::string& metrics);
+
+    MetricsDataType GetDataType(const std::string& metrics);
 
     // https://prometheus.io/docs/instrumenting/exposition_formats/#text-based-format
     void ExportPrometheusText(std::string& s);
@@ -93,14 +122,14 @@
       boost::mutex      mutex_;
       MetricsRegistry&  registry_;
       std::string       name_;
-      float             value_;
+      int64_t           value_;
 
     public:
       SharedMetrics(MetricsRegistry& registry,
                     const std::string& name,
-                    MetricsType type);
+                    MetricsUpdatePolicy policy);
 
-      void Add(float delta);
+      void Add(int64_t delta);
     };
 
 
@@ -121,7 +150,7 @@
     private:
       MetricsRegistry&          registry_;
       std::string               name_;
-      MetricsType               type_;
+      MetricsUpdatePolicy       policy_;
       bool                      active_;
       boost::posix_time::ptime  start_;
 
@@ -133,7 +162,7 @@
 
       Timer(MetricsRegistry& registry,
             const std::string& name,
-            MetricsType type);
+            MetricsUpdatePolicy policy);
 
       ~Timer();
     };
--- a/OrthancFramework/Sources/SystemToolbox.cpp	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancFramework/Sources/SystemToolbox.cpp	Tue Jul 04 12:20:41 2023 +0200
@@ -825,6 +825,18 @@
     {
       return MimeType_Zip;
     }
+    else if (extension == ".mtl")
+    {
+      return MimeType_Mtl;
+    }
+    else if (extension == ".obj")
+    {
+      return MimeType_Obj;
+    }
+    else if (extension == ".stl")
+    {
+      return MimeType_Stl;
+    }
 
     // Default type
     else
--- a/OrthancFramework/UnitTestsSources/DicomMapTests.cpp	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancFramework/UnitTestsSources/DicomMapTests.cpp	Tue Jul 04 12:20:41 2023 +0200
@@ -42,6 +42,7 @@
 #include "../Sources/DicomParsing/DicomWebJsonVisitor.h"
 
 #include <boost/lexical_cast.hpp>
+#include <boost/tuple/tuple.hpp>
 
 using namespace Orthanc;
 
@@ -1288,50 +1289,61 @@
 {
   static const std::string PATH = "/home/jodogne/Subversion/orthanc-tests/Database/TransferSyntaxes/";
 
-  typedef std::list< std::pair<std::string, uint64_t> >  Sources;
+  typedef boost::tuple<std::string, uint64_t, ValueRepresentation> Source;
+  typedef std::list<Source>  Sources;
+
+  // $ ~/Subversion/orthanc-tests/Tests/GetPixelDataVR.py ~/Subversion/orthanc-tests/Database/ColorTestMalaterre.dcm ~/Subversion/orthanc-tests/Database/ColorTestImageJ.dcm ~/Subversion/orthanc-tests/Database/Knee/T1/IM-0001-0001.dcm ~/Subversion/orthanc-tests/Database/TransferSyntaxes/*.dcm
 
   Sources sources;
-  sources.push_back(std::make_pair(PATH + "../ColorTestMalaterre.dcm", 0x03a0u));
-  sources.push_back(std::make_pair(PATH + "1.2.840.10008.1.2.1.dcm", 0x037cu));
-  sources.push_back(std::make_pair(PATH + "1.2.840.10008.1.2.2.dcm", 0x03e8u));  // Big Endian
-  sources.push_back(std::make_pair(PATH + "1.2.840.10008.1.2.4.50.dcm", 0x04acu));
-  sources.push_back(std::make_pair(PATH + "1.2.840.10008.1.2.4.51.dcm", 0x072cu));
-  sources.push_back(std::make_pair(PATH + "1.2.840.10008.1.2.4.57.dcm", 0x0620u));
-  sources.push_back(std::make_pair(PATH + "1.2.840.10008.1.2.4.70.dcm", 0x065au));
-  sources.push_back(std::make_pair(PATH + "1.2.840.10008.1.2.4.80.dcm", 0x0b46u));
-  sources.push_back(std::make_pair(PATH + "1.2.840.10008.1.2.4.81.dcm", 0x073eu));
-  sources.push_back(std::make_pair(PATH + "1.2.840.10008.1.2.4.90.dcm", 0x0b66u));
-  sources.push_back(std::make_pair(PATH + "1.2.840.10008.1.2.4.91.dcm", 0x19b8u));
-  sources.push_back(std::make_pair(PATH + "1.2.840.10008.1.2.5.dcm", 0x0b0au));
+  sources.push_back(Source(PATH + "../ColorTestMalaterre.dcm",   0x03a0u, ValueRepresentation_Unknown));  // This file has strange VR
+  sources.push_back(Source(PATH + "../ColorTestImageJ.dcm",      0x00924, ValueRepresentation_OtherByte));
+  sources.push_back(Source(PATH + "../Knee/T1/IM-0001-0001.dcm", 0x00c78, ValueRepresentation_OtherWord));
+  sources.push_back(Source(PATH + "1.2.840.10008.1.2.1.dcm",     0x037cu, ValueRepresentation_OtherByte));
+  sources.push_back(Source(PATH + "1.2.840.10008.1.2.2.dcm",     0x03e8u, ValueRepresentation_OtherByte));  // Big Endian
+  sources.push_back(Source(PATH + "1.2.840.10008.1.2.4.50.dcm",  0x04acu, ValueRepresentation_OtherByte));
+  sources.push_back(Source(PATH + "1.2.840.10008.1.2.4.51.dcm",  0x072cu, ValueRepresentation_OtherByte));
+  sources.push_back(Source(PATH + "1.2.840.10008.1.2.4.57.dcm",  0x0620u, ValueRepresentation_OtherByte));
+  sources.push_back(Source(PATH + "1.2.840.10008.1.2.4.70.dcm",  0x065au, ValueRepresentation_OtherByte));
+  sources.push_back(Source(PATH + "1.2.840.10008.1.2.4.80.dcm",  0x0b46u, ValueRepresentation_OtherByte));
+  sources.push_back(Source(PATH + "1.2.840.10008.1.2.4.81.dcm",  0x073eu, ValueRepresentation_OtherByte));
+  sources.push_back(Source(PATH + "1.2.840.10008.1.2.4.90.dcm",  0x0b66u, ValueRepresentation_OtherByte));
+  sources.push_back(Source(PATH + "1.2.840.10008.1.2.4.91.dcm",  0x19b8u, ValueRepresentation_OtherByte));
+  sources.push_back(Source(PATH + "1.2.840.10008.1.2.5.dcm",     0x0b0au, ValueRepresentation_OtherByte));
 
   {
     std::string dicom;
 
     uint64_t offset;
+    ValueRepresentation vr;
+
     // Not a DICOM image
     SystemToolbox::ReadFile(dicom, PATH + "1.2.840.10008.1.2.4.50.png", false);
-    ASSERT_FALSE(DicomStreamReader::LookupPixelDataOffset(offset, dicom));
+    ASSERT_FALSE(DicomStreamReader::LookupPixelDataOffset(offset, vr, dicom));
 
     // Image without valid DICOM preamble
     SystemToolbox::ReadFile(dicom, PATH + "1.2.840.10008.1.2.dcm", false);
-    ASSERT_FALSE(DicomStreamReader::LookupPixelDataOffset(offset, dicom));
+    ASSERT_FALSE(DicomStreamReader::LookupPixelDataOffset(offset, vr, dicom));
   }
   
   for (Sources::const_iterator it = sources.begin(); it != sources.end(); ++it)
   {
     std::string dicom;
-    SystemToolbox::ReadFile(dicom, it->first, false);
+    SystemToolbox::ReadFile(dicom, it->get<0>(), false);
 
     {
       uint64_t offset;
-      ASSERT_TRUE(DicomStreamReader::LookupPixelDataOffset(offset, dicom));
-      ASSERT_EQ(it->second, offset);
+      ValueRepresentation vr;
+      ASSERT_TRUE(DicomStreamReader::LookupPixelDataOffset(offset, vr, dicom));
+      ASSERT_EQ(it->get<1>(), offset);
+      ASSERT_EQ(it->get<2>(), vr);
     }
     
     {
       uint64_t offset;
-      ASSERT_TRUE(DicomStreamReader::LookupPixelDataOffset(offset, dicom.c_str(), dicom.size()));
-      ASSERT_EQ(it->second, offset);
+      ValueRepresentation vr;
+      ASSERT_TRUE(DicomStreamReader::LookupPixelDataOffset(offset, vr, dicom.c_str(), dicom.size()));
+      ASSERT_EQ(it->get<1>(), offset);
+      ASSERT_EQ(it->get<2>(), vr);
     }
     
     ParsedDicomFile a(dicom);
@@ -1355,7 +1367,7 @@
 
     r.Consume(visitor);
 
-    ASSERT_EQ(it->second, visitor.GetPixelDataOffset());
+    ASSERT_EQ(it->get<1>(), visitor.GetPixelDataOffset());
 
     // Truncate the original DICOM up to pixel data
     dicom.resize(visitor.GetPixelDataOffset());
--- a/OrthancFramework/UnitTestsSources/FrameworkTests.cpp	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancFramework/UnitTestsSources/FrameworkTests.cpp	Tue Jul 04 12:20:41 2023 +0200
@@ -363,6 +363,10 @@
   ASSERT_STREQ("image/svg+xml", EnumerationToString(SystemToolbox::AutodetectMimeType(".svg")));
   ASSERT_STREQ("text/css", EnumerationToString(SystemToolbox::AutodetectMimeType(".css")));
   ASSERT_STREQ("text/html", EnumerationToString(SystemToolbox::AutodetectMimeType(".html")));
+
+  ASSERT_STREQ("model/obj", EnumerationToString(SystemToolbox::AutodetectMimeType(".obj")));
+  ASSERT_STREQ("model/mtl", EnumerationToString(SystemToolbox::AutodetectMimeType(".mtl")));
+  ASSERT_STREQ("model/stl", EnumerationToString(SystemToolbox::AutodetectMimeType(".stl")));
 }
 #endif
 
@@ -749,6 +753,7 @@
   ASSERT_EQ(DicomVersion_2008, StringToDicomVersion(EnumerationToString(DicomVersion_2008)));
   ASSERT_EQ(DicomVersion_2017c, StringToDicomVersion(EnumerationToString(DicomVersion_2017c)));
   ASSERT_EQ(DicomVersion_2021b, StringToDicomVersion(EnumerationToString(DicomVersion_2021b)));
+  ASSERT_EQ(DicomVersion_2023b, StringToDicomVersion(EnumerationToString(DicomVersion_2023b)));
 
   for (int i = static_cast<int>(ValueRepresentation_ApplicationEntity);
        i < static_cast<int>(ValueRepresentation_NotSupported); i += 1)
@@ -790,6 +795,9 @@
   ASSERT_EQ(MimeType_Xml, StringToMimeType(EnumerationToString(MimeType_Xml)));
   ASSERT_EQ(MimeType_DicomWebJson, StringToMimeType(EnumerationToString(MimeType_DicomWebJson)));
   ASSERT_EQ(MimeType_DicomWebXml, StringToMimeType(EnumerationToString(MimeType_DicomWebXml)));
+  ASSERT_EQ(MimeType_Mtl, StringToMimeType(EnumerationToString(MimeType_Mtl)));
+  ASSERT_EQ(MimeType_Obj, StringToMimeType(EnumerationToString(MimeType_Obj)));
+  ASSERT_EQ(MimeType_Stl, StringToMimeType(EnumerationToString(MimeType_Stl)));
   ASSERT_THROW(StringToMimeType("nope"), OrthancException);
 
   ASSERT_TRUE(IsResourceLevelAboveOrEqual(ResourceType_Patient, ResourceType_Patient));
@@ -1311,7 +1319,7 @@
   {
     MetricsRegistry m;
     m.SetEnabled(false);
-    m.SetValue("hello.world", 42.5f);
+    m.SetIntegerValue("hello.world", 42);
     
     std::string s;
     m.ExportPrometheusText(s);
@@ -1320,7 +1328,7 @@
 
   {
     MetricsRegistry m;
-    m.Register("hello.world", MetricsType_Default);
+    m.Register("hello.world", MetricsUpdatePolicy_Directly, MetricsDataType_Integer);
     
     std::string s;
     m.ExportPrometheusText(s);
@@ -1329,9 +1337,9 @@
 
   {
     MetricsRegistry m;
-    m.SetValue("hello.world", 42.5f);
-    ASSERT_EQ(MetricsType_Default, m.GetMetricsType("hello.world"));
-    ASSERT_THROW(m.GetMetricsType("nope"), OrthancException);
+    m.SetIntegerValue("hello.world", -42);
+    ASSERT_EQ(MetricsUpdatePolicy_Directly, m.GetUpdatePolicy("hello.world"));
+    ASSERT_THROW(m.GetUpdatePolicy("nope"), OrthancException);
     
     std::string s;
     m.ExportPrometheusText(s);
@@ -1339,33 +1347,33 @@
     std::vector<std::string> t;
     Toolbox::TokenizeString(t, s, '\n');
     ASSERT_EQ(2u, t.size());
-    ASSERT_EQ("hello.world 42.5 ", t[0].substr(0, 17));
+    ASSERT_EQ("hello.world -42 ", t[0].substr(0, 16));
     ASSERT_TRUE(t[1].empty());
   }
 
   {
     MetricsRegistry m;
-    m.Register("hello.max", MetricsType_MaxOver10Seconds);
-    m.SetValue("hello.max", 10);
-    m.SetValue("hello.max", 20);
-    m.SetValue("hello.max", -10);
-    m.SetValue("hello.max", 5);
+    m.Register("hello.max", MetricsUpdatePolicy_MaxOver10Seconds, MetricsDataType_Integer);
+    m.SetIntegerValue("hello.max", 10);
+    m.SetIntegerValue("hello.max", 20);
+    m.SetIntegerValue("hello.max", -10);
+    m.SetIntegerValue("hello.max", 5);
 
-    m.Register("hello.min", MetricsType_MinOver10Seconds);
-    m.SetValue("hello.min", 10);
-    m.SetValue("hello.min", 20);
-    m.SetValue("hello.min", -10);
-    m.SetValue("hello.min", 5);
+    m.Register("hello.min", MetricsUpdatePolicy_MinOver10Seconds, MetricsDataType_Integer);
+    m.SetIntegerValue("hello.min", 10);
+    m.SetIntegerValue("hello.min", 20);
+    m.SetIntegerValue("hello.min", -10);
+    m.SetIntegerValue("hello.min", 5);
     
-    m.Register("hello.default", MetricsType_Default);
-    m.SetValue("hello.default", 10);
-    m.SetValue("hello.default", 20);
-    m.SetValue("hello.default", -10);
-    m.SetValue("hello.default", 5);
+    m.Register("hello.directly", MetricsUpdatePolicy_Directly, MetricsDataType_Integer);
+    m.SetIntegerValue("hello.directly", 10);
+    m.SetIntegerValue("hello.directly", 20);
+    m.SetIntegerValue("hello.directly", -10);
+    m.SetIntegerValue("hello.directly", 5);
     
-    ASSERT_EQ(MetricsType_MaxOver10Seconds, m.GetMetricsType("hello.max"));
-    ASSERT_EQ(MetricsType_MinOver10Seconds, m.GetMetricsType("hello.min"));
-    ASSERT_EQ(MetricsType_Default, m.GetMetricsType("hello.default"));
+    ASSERT_EQ(MetricsUpdatePolicy_MaxOver10Seconds, m.GetUpdatePolicy("hello.max"));
+    ASSERT_EQ(MetricsUpdatePolicy_MinOver10Seconds, m.GetUpdatePolicy("hello.min"));
+    ASSERT_EQ(MetricsUpdatePolicy_Directly, m.GetUpdatePolicy("hello.directly"));
 
     std::string s;
     m.ExportPrometheusText(s);
@@ -1385,25 +1393,25 @@
 
     ASSERT_EQ("20", u["hello.max"]);
     ASSERT_EQ("-10", u["hello.min"]);
-    ASSERT_EQ("5", u["hello.default"]);
+    ASSERT_EQ("5", u["hello.directly"]);
   }
 
   {
     MetricsRegistry m;
 
-    m.SetValue("a", 10);
-    m.SetValue("b", 10, MetricsType_MinOver10Seconds);
+    m.SetIntegerValue("a", 10);
+    m.SetIntegerValue("b", 10, MetricsUpdatePolicy_MinOver10Seconds);
 
-    m.Register("c", MetricsType_MaxOver10Seconds);
-    m.SetValue("c", 10, MetricsType_MinOver10Seconds);
+    m.Register("c", MetricsUpdatePolicy_MaxOver10Seconds, MetricsDataType_Integer);
+    m.SetIntegerValue("c", 10, MetricsUpdatePolicy_MinOver10Seconds);
 
-    m.Register("d", MetricsType_MaxOver10Seconds);
-    m.Register("d", MetricsType_Default);
+    m.Register("d", MetricsUpdatePolicy_MaxOver10Seconds, MetricsDataType_Integer);
+    ASSERT_THROW(m.Register("d", MetricsUpdatePolicy_Directly, MetricsDataType_Integer), OrthancException);
 
-    ASSERT_EQ(MetricsType_Default, m.GetMetricsType("a"));
-    ASSERT_EQ(MetricsType_MinOver10Seconds, m.GetMetricsType("b"));
-    ASSERT_EQ(MetricsType_MaxOver10Seconds, m.GetMetricsType("c"));
-    ASSERT_EQ(MetricsType_Default, m.GetMetricsType("d"));
+    ASSERT_EQ(MetricsUpdatePolicy_Directly, m.GetUpdatePolicy("a"));
+    ASSERT_EQ(MetricsUpdatePolicy_MinOver10Seconds, m.GetUpdatePolicy("b"));
+    ASSERT_EQ(MetricsUpdatePolicy_MaxOver10Seconds, m.GetUpdatePolicy("c"));
+    ASSERT_EQ(MetricsUpdatePolicy_MaxOver10Seconds, m.GetUpdatePolicy("d"));
   }
 
   {
@@ -1411,11 +1419,47 @@
 
     {
       MetricsRegistry::Timer t1(m, "a");
-      MetricsRegistry::Timer t2(m, "b", MetricsType_MinOver10Seconds);
+      MetricsRegistry::Timer t2(m, "b", MetricsUpdatePolicy_MinOver10Seconds);
     }
 
-    ASSERT_EQ(MetricsType_MaxOver10Seconds, m.GetMetricsType("a"));
-    ASSERT_EQ(MetricsType_MinOver10Seconds, m.GetMetricsType("b"));
+    ASSERT_EQ(MetricsUpdatePolicy_MaxOver10Seconds, m.GetUpdatePolicy("a"));
+    ASSERT_EQ(MetricsUpdatePolicy_MinOver10Seconds, m.GetUpdatePolicy("b"));
+  }
+
+  {
+    MetricsRegistry m;
+    m.Register("c", MetricsUpdatePolicy_MaxOver10Seconds, MetricsDataType_Integer);
+    m.SetFloatValue("c", 100, MetricsUpdatePolicy_MinOver10Seconds);
+
+    ASSERT_EQ(MetricsUpdatePolicy_MaxOver10Seconds, m.GetUpdatePolicy("c"));
+    ASSERT_EQ(MetricsDataType_Integer, m.GetDataType("c"));
+  }
+
+  {
+    MetricsRegistry m;
+    m.Register("c", MetricsUpdatePolicy_MaxOver10Seconds, MetricsDataType_Float);
+    m.SetIntegerValue("c", 100, MetricsUpdatePolicy_MinOver10Seconds);
+
+    ASSERT_EQ(MetricsUpdatePolicy_MaxOver10Seconds, m.GetUpdatePolicy("c"));
+    ASSERT_EQ(MetricsDataType_Float, m.GetDataType("c"));
+  }
+
+  {
+    MetricsRegistry m;
+    m.SetIntegerValue("c", 100, MetricsUpdatePolicy_MinOver10Seconds);
+    m.SetFloatValue("c", 101, MetricsUpdatePolicy_MaxOver10Seconds);
+
+    ASSERT_EQ(MetricsUpdatePolicy_MinOver10Seconds, m.GetUpdatePolicy("c"));
+    ASSERT_EQ(MetricsDataType_Integer, m.GetDataType("c"));
+  }
+
+  {
+    MetricsRegistry m;
+    m.SetIntegerValue("c", 100);
+    m.SetFloatValue("c", 101, MetricsUpdatePolicy_MaxOver10Seconds);
+
+    ASSERT_EQ(MetricsUpdatePolicy_Directly, m.GetUpdatePolicy("c"));
+    ASSERT_EQ(MetricsDataType_Integer, m.GetDataType("c"));
   }
 }
 #endif
--- a/OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp	Tue Jul 04 12:20:41 2023 +0200
@@ -37,6 +37,7 @@
 #include <gtest/gtest.h>
 
 #include "../Sources/Compatibility.h"
+#include "../Sources/DicomFormat/DicomImageInformation.h"
 #include "../Sources/DicomFormat/DicomPath.h"
 #include "../Sources/DicomNetworking/DicomFindAnswers.h"
 #include "../Sources/DicomParsing/DicomModification.h"
@@ -314,6 +315,10 @@
   ASSERT_TRUE(GetDicomEncoding(e, "ISO_IR 192"));  ASSERT_EQ(Encoding_Utf8, e);
   ASSERT_TRUE(GetDicomEncoding(e, "GB18030"));     ASSERT_EQ(Encoding_Chinese, e);
   ASSERT_TRUE(GetDicomEncoding(e, "GBK"));         ASSERT_EQ(Encoding_Chinese, e);
+
+  // common spelling mistakes
+  ASSERT_TRUE(GetDicomEncoding(e, "ISO_IR_100"));  ASSERT_EQ(Encoding_Latin1, e);
+  ASSERT_TRUE(GetDicomEncoding(e, "ISO_2022_IR_6"));  ASSERT_EQ(Encoding_Ascii, e);
 }
 
 
@@ -2767,7 +2772,7 @@
 
     {
       DicomModification modif;
-      modif.SetupAnonymization(DicomVersion_2021b);
+      modif.SetupAnonymization(DicomVersion_2023b);
       modif.Apply(*dicom1);
       modif.Apply(*dicom2);
     }
@@ -2794,7 +2799,7 @@
 
     {
       DicomModification modif;
-      modif.SetupAnonymization(DicomVersion_2021b);
+      modif.SetupAnonymization(DicomVersion_2023b);
       modif.Keep(DicomPath::Parse("ReferencedImageSequence[1].ReferencedSOPInstanceUID"));
       modif.Keep(DicomPath::Parse("RelatedSeriesSequence"));
       modif.Apply(*dicom);
@@ -3217,6 +3222,312 @@
 }
 
 
+TEST(ParsedDicomFile, InjectEmptyPixelData)
+{
+  static const char* PIXEL_DATA = "7FE00010";
+
+  {
+    ParsedDicomFile dicom(true);
+
+    DicomWebJsonVisitor visitor;
+    dicom.Apply(visitor);
+
+    ASSERT_FALSE(visitor.GetResult().isMember(PIXEL_DATA));
+  }
+
+  {
+    ParsedDicomFile dicom(true);
+    dicom.InjectEmptyPixelData(ValueRepresentation_OtherByte);
+    dicom.InjectEmptyPixelData(ValueRepresentation_OtherWord); // Must be ignored
+
+    DicomWebJsonVisitor visitor;
+    dicom.Apply(visitor);
+
+    ASSERT_TRUE(visitor.GetResult().isMember(PIXEL_DATA));
+    ASSERT_EQ(2u, visitor.GetResult() [PIXEL_DATA].size());
+    ASSERT_EQ("", visitor.GetResult() [PIXEL_DATA]["InlineBinary"].asString());
+    ASSERT_EQ("OB", visitor.GetResult() [PIXEL_DATA]["vr"].asString());
+  }
+
+  {
+    ParsedDicomFile dicom(true);
+    dicom.InjectEmptyPixelData(ValueRepresentation_OtherWord);
+    dicom.InjectEmptyPixelData(ValueRepresentation_OtherByte); // Must be ignored
+
+    DicomWebJsonVisitor visitor;
+    dicom.Apply(visitor);
+
+    ASSERT_TRUE(visitor.GetResult().isMember(PIXEL_DATA));
+    ASSERT_EQ(2u, visitor.GetResult() [PIXEL_DATA].size());
+    ASSERT_EQ("", visitor.GetResult() [PIXEL_DATA]["InlineBinary"].asString());
+    ASSERT_EQ("OW", visitor.GetResult() [PIXEL_DATA]["vr"].asString());
+  }
+}
+
+
+TEST(ParsedDicomFile, RemoveFromPixelData)
+{
+  ParsedDicomFile dicom(true);
+  ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertString(DcmTag(0x7fe0, 0x0000), "").good());
+  ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertString(DcmTag(0x7fe0, 0x0009), "").good());
+  ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertUint8Array(DcmTag(0x7fe0, 0x0010), NULL, 0).good());
+  ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertString(DcmTag(0x7fe0, 0x0011), "").good());
+  ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertString(DcmTag(0x7fe1, 0x0000), "").good());
+
+  {
+    DicomMap m;
+    dicom.ExtractDicomSummary(m, 0);
+
+    ASSERT_EQ(10u, m.GetSize());
+    ASSERT_TRUE(m.HasTag(DICOM_TAG_MEDIA_STORAGE_SOP_INSTANCE_UID));
+    ASSERT_TRUE(m.HasTag(DICOM_TAG_SOP_INSTANCE_UID));
+    ASSERT_TRUE(m.HasTag(DICOM_TAG_PATIENT_ID));
+    ASSERT_TRUE(m.HasTag(DICOM_TAG_SERIES_INSTANCE_UID));
+    ASSERT_TRUE(m.HasTag(DICOM_TAG_STUDY_INSTANCE_UID));
+    ASSERT_TRUE(m.HasTag(0x7fe0, 0x0000));
+    ASSERT_TRUE(m.HasTag(0x7fe0, 0x0009));
+    ASSERT_TRUE(m.HasTag(DICOM_TAG_PIXEL_DATA));
+    ASSERT_TRUE(m.HasTag(0x7fe0, 0x0011));
+    ASSERT_TRUE(m.HasTag(0x7fe1, 0x0000));
+  }
+
+  dicom.RemoveFromPixelData();
+
+  {
+    DicomMap m;
+    dicom.ExtractDicomSummary(m, 0);
+
+    ASSERT_EQ(7u, m.GetSize());
+    ASSERT_TRUE(m.HasTag(DICOM_TAG_MEDIA_STORAGE_SOP_INSTANCE_UID));
+    ASSERT_TRUE(m.HasTag(DICOM_TAG_SOP_INSTANCE_UID));
+    ASSERT_TRUE(m.HasTag(DICOM_TAG_PATIENT_ID));
+    ASSERT_TRUE(m.HasTag(DICOM_TAG_SERIES_INSTANCE_UID));
+    ASSERT_TRUE(m.HasTag(DICOM_TAG_STUDY_INSTANCE_UID));
+    ASSERT_TRUE(m.HasTag(0x7fe0, 0x0000));
+    ASSERT_TRUE(m.HasTag(0x7fe0, 0x0009));
+    ASSERT_FALSE(m.HasTag(DICOM_TAG_PIXEL_DATA));
+    ASSERT_FALSE(m.HasTag(0x7fe0, 0x0011));
+    ASSERT_FALSE(m.HasTag(0x7fe1, 0x0000));
+  }
+}
+
+
+TEST(ParsedDicomFile, GuessPixelDataValueRepresentation)
+{
+  typedef std::list< std::pair<E_TransferSyntax, DicomTransferSyntax> > Syntaxes;
+
+  // Create a list of the main non-retired transfer syntaxes, from:
+  // https://www.dicomlibrary.com/dicom/transfer-syntax/
+  Syntaxes compressedSyntaxes;
+  compressedSyntaxes.push_back(std::make_pair(EXS_DeflatedLittleEndianExplicit, DicomTransferSyntax_DeflatedLittleEndianExplicit));
+  compressedSyntaxes.push_back(std::make_pair(EXS_JPEGProcess1, DicomTransferSyntax_JPEGProcess1));
+  compressedSyntaxes.push_back(std::make_pair(EXS_JPEGProcess2_4, DicomTransferSyntax_JPEGProcess2_4));
+  compressedSyntaxes.push_back(std::make_pair(EXS_JPEGProcess14, DicomTransferSyntax_JPEGProcess14));
+  compressedSyntaxes.push_back(std::make_pair(EXS_JPEGProcess14SV1, DicomTransferSyntax_JPEGProcess14SV1));
+  compressedSyntaxes.push_back(std::make_pair(EXS_JPEGLSLossless, DicomTransferSyntax_JPEGLSLossless));
+  compressedSyntaxes.push_back(std::make_pair(EXS_JPEGLSLossy, DicomTransferSyntax_JPEGLSLossy));
+  compressedSyntaxes.push_back(std::make_pair(EXS_JPEG2000LosslessOnly, DicomTransferSyntax_JPEG2000LosslessOnly));
+  compressedSyntaxes.push_back(std::make_pair(EXS_JPEG2000, DicomTransferSyntax_JPEG2000));
+  compressedSyntaxes.push_back(std::make_pair(EXS_JPEG2000MulticomponentLosslessOnly, DicomTransferSyntax_JPEG2000MulticomponentLosslessOnly));
+  compressedSyntaxes.push_back(std::make_pair(EXS_JPEG2000Multicomponent, DicomTransferSyntax_JPEG2000Multicomponent));
+  compressedSyntaxes.push_back(std::make_pair(EXS_JPIPReferenced, DicomTransferSyntax_JPIPReferenced));
+  compressedSyntaxes.push_back(std::make_pair(EXS_JPIPReferencedDeflate, DicomTransferSyntax_JPIPReferencedDeflate));
+  compressedSyntaxes.push_back(std::make_pair(EXS_RLELossless, DicomTransferSyntax_RLELossless));
+  compressedSyntaxes.push_back(std::make_pair(EXS_MPEG2MainProfileAtMainLevel, DicomTransferSyntax_MPEG2MainProfileAtMainLevel));
+  compressedSyntaxes.push_back(std::make_pair(EXS_MPEG4HighProfileLevel4_1, DicomTransferSyntax_MPEG4HighProfileLevel4_1));
+  compressedSyntaxes.push_back(std::make_pair(EXS_MPEG4BDcompatibleHighProfileLevel4_1, DicomTransferSyntax_MPEG4BDcompatibleHighProfileLevel4_1));
+
+  for (unsigned int i = 0; i < 3; i++)
+  {
+    unsigned int bitsAllocated;
+    switch (i)
+    {
+      case 0: bitsAllocated = 1;   break;
+      case 1: bitsAllocated = 8;   break;
+      case 2: bitsAllocated = 16;  break;
+      default:
+        throw OrthancException(ErrorCode_InternalError);
+    }
+      
+    for (Syntaxes::const_iterator it = compressedSyntaxes.begin(); it != compressedSyntaxes.end(); ++it)
+    {
+      // All the compressed transfer syntaxes must have "OB" pixel data
+      ASSERT_EQ(ValueRepresentation_OtherByte, DicomImageInformation::GuessPixelDataValueRepresentation(it->second, bitsAllocated));
+
+      {
+        DicomMap dicom;
+        dicom.SetValue(DICOM_TAG_BITS_ALLOCATED, boost::lexical_cast<std::string>(bitsAllocated), false);
+        ASSERT_EQ(ValueRepresentation_OtherByte, dicom.GuessPixelDataValueRepresentation(it->second));
+      }
+
+      {
+        DicomMap dicom;
+        ASSERT_EQ(ValueRepresentation_OtherByte, dicom.GuessPixelDataValueRepresentation(it->second));
+      }
+
+      {
+        ParsedDicomFile dicom(true);
+        ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertUint16(DCM_BitsAllocated, bitsAllocated).good());
+        ASSERT_TRUE(dicom.GetDcmtkObject().chooseRepresentation(it->first, NULL).good());
+        dicom.GetDcmtkObject().removeAllButCurrentRepresentations();
+        DicomTransferSyntax ts;
+        ASSERT_TRUE(dicom.LookupTransferSyntax(ts));
+        ASSERT_EQ(ts, it->second);
+        ASSERT_EQ(ValueRepresentation_OtherByte, dicom.GuessPixelDataValueRepresentation());
+      }
+    }
+
+    {
+      // Little endian implicit is always OW
+      ASSERT_EQ(ValueRepresentation_OtherWord, DicomImageInformation::GuessPixelDataValueRepresentation(DicomTransferSyntax_LittleEndianImplicit, bitsAllocated));
+
+      {
+        DicomMap dicom;
+        dicom.SetValue(DICOM_TAG_BITS_ALLOCATED, boost::lexical_cast<std::string>(bitsAllocated), false);
+        ASSERT_EQ(ValueRepresentation_OtherWord, dicom.GuessPixelDataValueRepresentation(DicomTransferSyntax_LittleEndianImplicit));
+      }
+
+      {
+        DicomMap dicom;
+        ASSERT_EQ(ValueRepresentation_OtherWord, dicom.GuessPixelDataValueRepresentation(DicomTransferSyntax_LittleEndianImplicit));
+      }
+
+      {
+        ParsedDicomFile dicom(true);
+        ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertUint16(DCM_BitsAllocated, bitsAllocated).good());
+        ASSERT_TRUE(dicom.GetDcmtkObject().chooseRepresentation(EXS_LittleEndianImplicit, NULL).good());
+        dicom.GetDcmtkObject().removeAllButCurrentRepresentations();
+        ASSERT_EQ(ValueRepresentation_OtherWord, dicom.GuessPixelDataValueRepresentation());
+      }
+    }
+
+  }
+
+  // Explicit little and big endian with <= 8 bpp is OB
+
+  for (unsigned int i = 0; i < 2; i++)
+  {
+    unsigned int bitsAllocated;
+    switch (i)
+    {
+      case 0: bitsAllocated = 1;   break;
+      case 1: bitsAllocated = 8;   break;
+      default:
+        throw OrthancException(ErrorCode_InternalError);
+    }
+
+    ASSERT_EQ(ValueRepresentation_OtherByte, DicomImageInformation::GuessPixelDataValueRepresentation(DicomTransferSyntax_LittleEndianExplicit, bitsAllocated));
+    ASSERT_EQ(ValueRepresentation_OtherByte, DicomImageInformation::GuessPixelDataValueRepresentation(DicomTransferSyntax_BigEndianExplicit, bitsAllocated));
+
+    {
+      DicomMap dicom;
+      dicom.SetValue(DICOM_TAG_BITS_ALLOCATED, boost::lexical_cast<std::string>(bitsAllocated), false);
+      ASSERT_EQ(ValueRepresentation_OtherByte, dicom.GuessPixelDataValueRepresentation(DicomTransferSyntax_LittleEndianExplicit));
+      ASSERT_EQ(ValueRepresentation_OtherByte, dicom.GuessPixelDataValueRepresentation(DicomTransferSyntax_BigEndianExplicit));
+    }
+
+    {
+      DicomMap dicom;
+      ASSERT_EQ(ValueRepresentation_OtherByte, dicom.GuessPixelDataValueRepresentation(DicomTransferSyntax_LittleEndianExplicit));
+      ASSERT_EQ(ValueRepresentation_OtherByte, dicom.GuessPixelDataValueRepresentation(DicomTransferSyntax_BigEndianExplicit));
+    }
+    
+    {
+      ParsedDicomFile dicom(true);
+      ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertUint16(DCM_BitsAllocated, bitsAllocated).good());
+      ASSERT_TRUE(dicom.GetDcmtkObject().chooseRepresentation(EXS_LittleEndianExplicit, NULL).good());
+      dicom.GetDcmtkObject().removeAllButCurrentRepresentations();
+      ASSERT_EQ(ValueRepresentation_OtherByte, dicom.GuessPixelDataValueRepresentation());
+    }
+
+    {
+      ParsedDicomFile dicom(true);
+      ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertUint16(DCM_BitsAllocated, bitsAllocated).good());
+      ASSERT_TRUE(dicom.GetDcmtkObject().chooseRepresentation(EXS_BigEndianExplicit, NULL).good());
+      dicom.GetDcmtkObject().removeAllButCurrentRepresentations();
+      ASSERT_EQ(ValueRepresentation_OtherByte, dicom.GuessPixelDataValueRepresentation());
+    }
+  }
+
+  // Explicit little and big endian with > 8 bpp is OW
+  
+  ASSERT_EQ(ValueRepresentation_OtherWord, DicomImageInformation::GuessPixelDataValueRepresentation(DicomTransferSyntax_LittleEndianExplicit, 16));
+  ASSERT_EQ(ValueRepresentation_OtherWord, DicomImageInformation::GuessPixelDataValueRepresentation(DicomTransferSyntax_BigEndianExplicit, 16));
+
+  {
+    DicomMap dicom;
+    dicom.SetValue(DICOM_TAG_BITS_ALLOCATED, "16", false);
+    ASSERT_EQ(ValueRepresentation_OtherWord, dicom.GuessPixelDataValueRepresentation(DicomTransferSyntax_LittleEndianExplicit));
+    ASSERT_EQ(ValueRepresentation_OtherWord, dicom.GuessPixelDataValueRepresentation(DicomTransferSyntax_BigEndianExplicit));
+  }
+
+  {
+    ParsedDicomFile dicom(true);
+    ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertUint16(DCM_BitsAllocated, 16).good());
+    ASSERT_TRUE(dicom.GetDcmtkObject().chooseRepresentation(EXS_LittleEndianExplicit, NULL).good());
+    dicom.GetDcmtkObject().removeAllButCurrentRepresentations();
+    ASSERT_EQ(ValueRepresentation_OtherWord, dicom.GuessPixelDataValueRepresentation());
+  }
+
+  {
+    ParsedDicomFile dicom(true);
+    ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertUint16(DCM_BitsAllocated, 16).good());
+    ASSERT_TRUE(dicom.GetDcmtkObject().chooseRepresentation(EXS_BigEndianExplicit, NULL).good());
+    dicom.GetDcmtkObject().removeAllButCurrentRepresentations();
+    ASSERT_EQ(ValueRepresentation_OtherWord, dicom.GuessPixelDataValueRepresentation());
+  }
+}
+
+
+TEST(ParsedDicomFile, DISABLED_InjectEmptyPixelData2)
+{
+  static const char* PIXEL_DATA = "7FE00010";
+
+  for (int i = 0; i <= DicomTransferSyntax_XML; i++)
+  {
+    DicomTransferSyntax a = (DicomTransferSyntax) i;
+
+    std::string path = (std::string(getenv("HOME")) +
+                        "/Subversion/orthanc-tests/Database/TransferSyntaxes/" +
+                        std::string(GetTransferSyntaxUid(a)) + ".dcm");
+    if (Orthanc::SystemToolbox::IsRegularFile(path))
+    {
+      printf("\n======= %s\n", GetTransferSyntaxUid(a));
+
+      std::string source;
+      Orthanc::SystemToolbox::ReadFile(source, path);
+
+      ParsedDicomFile dicom(source);
+      std::unique_ptr<DcmElement> removal(dicom.GetDcmtkObject().getDataset()->remove(DCM_PixelData));
+
+      {
+        DicomWebJsonVisitor visitor;
+        dicom.Apply(visitor);
+        ASSERT_FALSE(visitor.GetResult().isMember(PIXEL_DATA));
+      }
+
+      {
+        DicomWebJsonVisitor visitor;
+        dicom.InjectEmptyPixelData(ValueRepresentation_OtherByte);
+        dicom.Apply(visitor);
+        ASSERT_TRUE(visitor.GetResult().isMember(PIXEL_DATA));
+        ASSERT_EQ("OB", visitor.GetResult() [PIXEL_DATA]["vr"].asString());
+      }
+
+      removal.reset(dicom.GetDcmtkObject().getDataset()->remove(DCM_PixelData));
+
+      {
+        DicomWebJsonVisitor visitor;
+        dicom.InjectEmptyPixelData(ValueRepresentation_OtherWord);
+        dicom.Apply(visitor);
+        ASSERT_TRUE(visitor.GetResult().isMember(PIXEL_DATA));
+        ASSERT_EQ("OW", visitor.GetResult() [PIXEL_DATA]["vr"].asString());
+      }
+    }
+  }
+}
+
+
 
 
 #if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
@@ -3236,11 +3547,13 @@
   DcmtkTranscoder transcoder;
 
   for (int j = 0; j < 2; j++)
+  {
     for (int i = 0; i <= DicomTransferSyntax_XML; i++)
     {
       DicomTransferSyntax a = (DicomTransferSyntax) i;
 
-      std::string path = ("/home/jodogne/Subversion/orthanc-tests/Database/TransferSyntaxes/" +
+      std::string path = (std::string(getenv("HOME")) +
+                          "/Subversion/orthanc-tests/Database/TransferSyntaxes/" +
                           std::string(GetTransferSyntaxUid(a)) + ".dcm");
       if (Orthanc::SystemToolbox::IsRegularFile(path))
       {
@@ -3268,6 +3581,7 @@
         }
       }
     }
+  }
 }
 
 
@@ -3277,7 +3591,8 @@
 
   {
     std::string source;
-    Orthanc::SystemToolbox::ReadFile(source, "/home/jodogne/Subversion/orthanc-tests/Database/KarstenHilbertRF.dcm");
+    Orthanc::SystemToolbox::ReadFile(source, std::string(getenv("HOME")) +
+                                     "/Subversion/orthanc-tests/Database/KarstenHilbertRF.dcm");
     toto.reset(FromDcmtkBridge::LoadFromMemoryBuffer(source.c_str(), source.size()));
   }
   
--- a/OrthancFramework/UnitTestsSources/JobsTests.cpp	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancFramework/UnitTestsSources/JobsTests.cpp	Tue Jul 04 12:20:41 2023 +0200
@@ -131,6 +131,11 @@
     {
       return false;
     }
+
+    virtual bool DeleteOutput(const std::string& key) ORTHANC_OVERRIDE
+    {
+      return false;
+    }
   };
 
 
--- a/OrthancFramework/UnitTestsSources/RestApiTests.cpp	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancFramework/UnitTestsSources/RestApiTests.cpp	Tue Jul 04 12:20:41 2023 +0200
@@ -384,6 +384,7 @@
   private:
     std::string type_;
     std::string subtype_;
+    HttpContentNegociation::Dictionary parameters_;
 
   public:
     AcceptHandler()
@@ -393,7 +394,8 @@
 
     void Reset()
     {
-      Handle("nope", "nope");
+      HttpContentNegociation::Dictionary parameters;
+      Handle("nope", "nope", parameters);
     }
 
     const std::string& GetType() const
@@ -406,11 +408,18 @@
       return subtype_;
     }
 
+    HttpContentNegociation::Dictionary& GetParameters()
+    {
+      return parameters_;
+    }
+
     virtual void Handle(const std::string& type,
-                        const std::string& subtype) ORTHANC_OVERRIDE
+                        const std::string& subtype,
+                        const HttpContentNegociation::Dictionary& parameters) ORTHANC_OVERRIDE
     {
       type_ = type;
       subtype_ = subtype;
+      parameters_ = parameters;
     }
   };
 }
@@ -430,22 +439,29 @@
     ASSERT_TRUE(d.Apply("audio/*; q=0.2, audio/basic"));
     ASSERT_EQ("audio", h.GetType());
     ASSERT_EQ("basic", h.GetSubType());
+    ASSERT_EQ(0u, h.GetParameters().size());
 
-    ASSERT_TRUE(d.Apply("audio/*; q=0.2, audio/nope"));
+    ASSERT_TRUE(d.Apply("audio/*; q=0.2 ;  type =   test   ;  hello  , audio/nope"));
     ASSERT_EQ("audio", h.GetType());
     ASSERT_EQ("mp3", h.GetSubType());
+    ASSERT_EQ(3u, h.GetParameters().size());
+    ASSERT_EQ("0.2", h.GetParameters() ["q"]);
+    ASSERT_EQ("test", h.GetParameters() ["type"]);
+    ASSERT_EQ("", h.GetParameters() ["hello"]);
     
     ASSERT_FALSE(d.Apply("application/*; q=0.2, application/pdf"));
     
-    ASSERT_TRUE(d.Apply("*/*; application/*; q=0.2, application/pdf"));
+    ASSERT_TRUE(d.Apply("*/*; hello=world, application/*; q=0.2, application/pdf"));
     ASSERT_EQ("audio", h.GetType());
+    ASSERT_EQ(1u, h.GetParameters().size());
+    ASSERT_EQ("world", h.GetParameters() ["hello"]);
   }
 
   // "This would be interpreted as "text/html and text/x-c are the
   // preferred media types, but if they do not exist, then send the
   // text/x-dvi entity, and if that does not exist, send the
   // text/plain entity.""
-  const std::string T1 = "text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c";
+  const std::string T1 = "text/plain; q=0.5, text/html ;  hello  = \"world\"   , text/x-dvi; q=0.8, text/x-c";
   
   {
     HttpContentNegociation d;
@@ -455,6 +471,8 @@
     ASSERT_TRUE(d.Apply(T1));
     ASSERT_EQ("text", h.GetType());
     ASSERT_EQ("html", h.GetSubType());
+    ASSERT_EQ(1u, h.GetParameters().size());
+    ASSERT_EQ("world", h.GetParameters() ["hello"]);
   }
   
   {
@@ -465,6 +483,7 @@
     ASSERT_TRUE(d.Apply(T1));
     ASSERT_EQ("text", h.GetType());
     ASSERT_EQ("x-c", h.GetSubType());
+    ASSERT_EQ(0u, h.GetParameters().size());
   }
   
   {
@@ -476,6 +495,15 @@
     ASSERT_TRUE(d.Apply(T1));
     ASSERT_EQ("text", h.GetType());
     ASSERT_TRUE(h.GetSubType() == "x-c" || h.GetSubType() == "html");
+    if (h.GetSubType() == "x-c")
+    {
+      ASSERT_EQ(0u, h.GetParameters().size());
+    }
+    else
+    {
+      ASSERT_EQ(1u, h.GetParameters().size());
+      ASSERT_EQ("world", h.GetParameters() ["hello"]);
+    }
   }
   
   {
@@ -485,6 +513,8 @@
     ASSERT_TRUE(d.Apply(T1));
     ASSERT_EQ("text", h.GetType());
     ASSERT_EQ("x-dvi", h.GetSubType());
+    ASSERT_EQ(1u, h.GetParameters().size());
+    ASSERT_EQ("0.8", h.GetParameters() ["q"]);
   }
   
   {
@@ -493,6 +523,51 @@
     ASSERT_TRUE(d.Apply(T1));
     ASSERT_EQ("text", h.GetType());
     ASSERT_EQ("plain", h.GetSubType());
+    ASSERT_EQ(1u, h.GetParameters().size());
+    ASSERT_EQ("0.5", h.GetParameters() ["q"]);
+  }
+
+  // Below are the tests from issue 216:
+  // https://bugs.orthanc-server.com/show_bug.cgi?id=216
+
+  {
+    HttpContentNegociation d;
+    d.Register("application/dicom+json", h);
+    ASSERT_TRUE(d.Apply("image/webp, */*;q=0.8, text/html, application/xhtml+xml, application/xml;q=0.9"));
+    ASSERT_EQ("application", h.GetType());
+    ASSERT_EQ("dicom+json", h.GetSubType());
+    ASSERT_EQ(1u, h.GetParameters().size());
+    ASSERT_EQ("0.8", h.GetParameters() ["q"]);
+  }
+
+  {
+    HttpContentNegociation d;
+    d.Register("application/dicom+json", h);
+    ASSERT_TRUE(d.Apply("image/webp, */*; q  = \"0.8\"  , text/html, application/xhtml+xml, application/xml;q=0.9"));
+    ASSERT_EQ("application", h.GetType());
+    ASSERT_EQ("dicom+json", h.GetSubType());
+    ASSERT_EQ(1u, h.GetParameters().size());
+    ASSERT_EQ("0.8", h.GetParameters() ["q"]);
+  }
+
+  {
+    HttpContentNegociation d;
+    d.Register("application/dicom+json", h);
+    ASSERT_TRUE(d.Apply("text/html, application/xhtml+xml, application/xml, image/webp, */*;q=0.8"));
+    ASSERT_EQ("application", h.GetType());
+    ASSERT_EQ("dicom+json", h.GetSubType());
+    ASSERT_EQ(1u, h.GetParameters().size());
+    ASSERT_EQ("0.8", h.GetParameters() ["q"]);
+  }
+
+  {
+    HttpContentNegociation d;
+    d.Register("application/dicom+json", h);
+    ASSERT_TRUE(d.Apply("text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2"));
+    ASSERT_EQ("application", h.GetType());
+    ASSERT_EQ("dicom+json", h.GetSubType());
+    ASSERT_EQ(1u, h.GetParameters().size());
+    ASSERT_EQ(".2", h.GetParameters() ["q"]);
   }
 }
 
--- a/OrthancServer/OrthancExplorer/explorer.html	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancServer/OrthancExplorer/explorer.html	Tue Jul 04 12:20:41 2023 +0200
@@ -536,6 +536,7 @@
               <input type="checkbox" name="DR" id="qr-dr" class="custom" /> <label for="qr-dr">DR</label>
               <input type="checkbox" name="DX" id="qr-dx" class="custom" /> <label for="qr-dx">DX</label>
               <input type="checkbox" name="MG" id="qr-mg" class="custom" /> <label for="qr-mg">MG</label>
+              <input type="checkbox" name="XC" id="qr-xc" class="custom" /> <label for="qr-xc">XC</label>
             </fieldset>
           </div>
         </div>
--- a/OrthancServer/OrthancExplorer/explorer.js	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancServer/OrthancExplorer/explorer.js	Tue Jul 04 12:20:41 2023 +0200
@@ -1363,7 +1363,7 @@
                 href: '#',
                 rel: 'close',
                 text: name
-              })
+              });
               liElement.append(aElement);
     
               items.append(liElement);
--- a/OrthancServer/Plugins/Engine/OrthancPlugins.cpp	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancServer/Plugins/Engine/OrthancPlugins.cpp	Tue Jul 04 12:20:41 2023 +0200
@@ -67,6 +67,7 @@
 #include "PluginsEnumerations.h"
 #include "PluginsJob.h"
 
+#include <boost/math/special_functions/round.hpp>
 #include <boost/regex.hpp>
 #include <dcmtk/dcmdata/dcdict.h>
 #include <dcmtk/dcmdata/dcdicent.h>
@@ -2060,6 +2061,7 @@
         sizeof(int32_t) != sizeof(OrthancPluginDicomWebBinaryMode) ||
         sizeof(int32_t) != sizeof(OrthancPluginStorageCommitmentFailureReason) ||
         sizeof(int32_t) != sizeof(OrthancPluginReceivedInstanceAction) ||
+        sizeof(int32_t) != sizeof(OrthancPluginLoadDicomInstanceMode) ||
         static_cast<int>(OrthancPluginDicomToJsonFlags_IncludeBinary) != static_cast<int>(DicomToJsonFlags_IncludeBinary) ||
         static_cast<int>(OrthancPluginDicomToJsonFlags_IncludePrivateTags) != static_cast<int>(DicomToJsonFlags_IncludePrivateTags) ||
         static_cast<int>(OrthancPluginDicomToJsonFlags_IncludeUnknownTags) != static_cast<int>(DicomToJsonFlags_IncludeUnknownTags) ||
@@ -2502,9 +2504,8 @@
     std::string                            buffer_;
     std::unique_ptr<DicomInstanceToStore>  instance_;
 
-  public:
-    DicomInstanceFromBuffer(const void* buffer,
-                            size_t size)
+    void Setup(const void* buffer,
+               size_t size)
     {
       buffer_.assign(reinterpret_cast<const char*>(buffer), size);
 
@@ -2512,6 +2513,18 @@
       instance_->SetOrigin(DicomInstanceOrigin::FromPlugins());
     }
 
+  public:
+    DicomInstanceFromBuffer(const void* buffer,
+                            size_t size)
+    {
+      Setup(buffer, size);
+    }
+
+    explicit DicomInstanceFromBuffer(const std::string& buffer)
+    {
+      Setup(buffer.empty() ? NULL : buffer.c_str(), buffer.size());
+    }
+
     virtual bool CanBeFreed() const ORTHANC_OVERRIDE
     {
       return true;
@@ -2524,23 +2537,36 @@
   };
 
 
-  class OrthancPlugins::DicomInstanceFromTranscoded : public IDicomInstance
+  class OrthancPlugins::DicomInstanceFromParsed : public IDicomInstance
   {
   private:
     std::unique_ptr<ParsedDicomFile>       parsed_;
     std::unique_ptr<DicomInstanceToStore>  instance_;
 
-  public:
-    explicit DicomInstanceFromTranscoded(IDicomTranscoder::DicomImage& transcoded) :
-      parsed_(transcoded.ReleaseAsParsedDicomFile())
-    {
+    void Setup(ParsedDicomFile* parsed)
+    {
+      parsed_.reset(parsed);
+      
       if (parsed_.get() == NULL)
       {
-        throw OrthancException(ErrorCode_InternalError);
-      }
-      
-      instance_.reset(DicomInstanceToStore::CreateFromParsedDicomFile(*parsed_));
-      instance_->SetOrigin(DicomInstanceOrigin::FromPlugins());
+        throw OrthancException(ErrorCode_NullPointer);
+      }
+      else
+      {
+        instance_.reset(DicomInstanceToStore::CreateFromParsedDicomFile(*parsed_));
+        instance_->SetOrigin(DicomInstanceOrigin::FromPlugins());
+      }
+    }
+
+  public:
+    explicit DicomInstanceFromParsed(IDicomTranscoder::DicomImage& transcoded)
+    {
+      Setup(transcoded.ReleaseAsParsedDicomFile());
+    }
+
+    explicit DicomInstanceFromParsed(ParsedDicomFile* parsed /* takes ownership */)
+    {
+      Setup(parsed);
     }
 
     virtual bool CanBeFreed() const ORTHANC_OVERRIDE
@@ -4386,7 +4412,115 @@
     
     reinterpret_cast<PImpl::PluginHttpOutput*>(p.output)->SendMultipartItem(p.answer, p.answerSize, headers);
   }
-      
+
+
+  void OrthancPlugins::ApplyLoadDicomInstance(const _OrthancPluginLoadDicomInstance& params)
+  {
+    std::unique_ptr<IDicomInstance> target;
+    
+    switch (params.mode)
+    {
+      case OrthancPluginLoadDicomInstanceMode_WholeDicom:
+      {
+        std::string buffer;
+
+        {
+          PImpl::ServerContextLock lock(*pimpl_);
+          lock.GetContext().ReadDicom(buffer, params.instanceId);
+        }
+
+        target.reset(new DicomInstanceFromBuffer(buffer));
+        break;
+      }
+        
+      case OrthancPluginLoadDicomInstanceMode_UntilPixelData:
+      case OrthancPluginLoadDicomInstanceMode_EmptyPixelData:
+      {
+        std::unique_ptr<ParsedDicomFile> parsed;
+
+        {
+          std::string buffer;
+        
+          {
+            PImpl::ServerContextLock lock(*pimpl_);
+            if (!lock.GetContext().ReadDicomUntilPixelData(buffer, params.instanceId))
+            {
+              lock.GetContext().ReadDicom(buffer, params.instanceId);
+            }
+          }
+
+          parsed.reset(new ParsedDicomFile(buffer));
+        }
+
+        parsed->RemoveFromPixelData();
+
+        if (params.mode == OrthancPluginLoadDicomInstanceMode_EmptyPixelData)
+        {
+          bool hasPixelData = false;
+          ValueRepresentation pixelDataVR = parsed->GuessPixelDataValueRepresentation();
+
+          {
+            PImpl::ServerContextLock lock(*pimpl_);
+
+            std::string s;
+            int64_t revision;  // unused
+            if (lock.GetContext().GetIndex().LookupMetadata(
+                  s, revision, params.instanceId,
+                  ResourceType_Instance, MetadataType_Instance_PixelDataVR))
+            {
+              hasPixelData = true;
+              if (s == "OB")
+              {
+                pixelDataVR = ValueRepresentation_OtherByte;
+              }
+              else if (s == "OW")
+              {
+                pixelDataVR = ValueRepresentation_OtherWord;
+              }
+              else
+              {
+                LOG(WARNING) << "Corrupted PixelDataVR metadata associated with instance "
+                             << params.instanceId << ": " << s;
+              }
+            }
+            else if (lock.GetContext().GetIndex().LookupMetadata(
+                       s, revision, params.instanceId,
+                       ResourceType_Instance, MetadataType_Instance_PixelDataOffset))
+            {
+              // This file was stored by an older version of Orthanc,
+              // "PixelDataVR" is not available, so use the guess
+              hasPixelData = true;
+            }
+            else
+            {
+              hasPixelData = false;
+            }
+          }
+
+          if (hasPixelData)
+          {
+            parsed->InjectEmptyPixelData(pixelDataVR);
+          }
+        }
+
+        target.reset(new DicomInstanceFromParsed(parsed.release()));
+        break;
+      }
+        
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    if (target.get() == NULL)
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+    else
+    {
+      *params.target = reinterpret_cast<OrthancPluginDicomInstance*>(target.release());
+    } 
+  }
+  
 
   void OrthancPlugins::DatabaseAnswer(const void* parameters)
   {
@@ -5163,24 +5297,22 @@
         const _OrthancPluginSetMetricsValue& p =
           *reinterpret_cast<const _OrthancPluginSetMetricsValue*>(parameters);
 
-        MetricsType type;
-        switch (p.type)
-        {
-          case OrthancPluginMetricsType_Default:
-            type = MetricsType_Default;
-            break;
-
-          case OrthancPluginMetricsType_Timer:
-            type = MetricsType_MaxOver10Seconds;
-            break;
-
-          default:
-            throw OrthancException(ErrorCode_ParameterOutOfRange);
-        }
-        
         {
           PImpl::ServerContextLock lock(*pimpl_);
-          lock.GetContext().GetMetricsRegistry().SetValue(p.name, p.value, type);
+          lock.GetContext().GetMetricsRegistry().SetFloatValue(p.name, p.value, Plugins::Convert(p.type));
+        }
+
+        return true;
+      }
+
+      case _OrthancPluginService_SetMetricsIntegerValue:
+      {
+        const _OrthancPluginSetMetricsIntegerValue& p =
+          *reinterpret_cast<const _OrthancPluginSetMetricsIntegerValue*>(parameters);
+
+        {
+          PImpl::ServerContextLock lock(*pimpl_);
+          lock.GetContext().GetMetricsRegistry().SetIntegerValue(p.name, p.value, Plugins::Convert(p.type));
         }
 
         return true;
@@ -5279,7 +5411,7 @@
           if (success)
           {
             *(p.target) = reinterpret_cast<OrthancPluginDicomInstance*>(
-              new DicomInstanceFromTranscoded(transcoded));
+              new DicomInstanceFromParsed(transcoded));
             return true;
           }
           else
@@ -5341,6 +5473,14 @@
         RegisterIncomingHttpRequestFilter2(parameters);
         return true;
 
+      case _OrthancPluginService_LoadDicomInstance:
+      {
+        const _OrthancPluginLoadDicomInstance& p =
+          *reinterpret_cast<const _OrthancPluginLoadDicomInstance*>(parameters);
+        ApplyLoadDicomInstance(p);
+        return true;
+      }
+
       default:
         return false;
     }
--- a/OrthancServer/Plugins/Engine/OrthancPlugins.h	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancServer/Plugins/Engine/OrthancPlugins.h	Tue Jul 04 12:20:41 2023 +0200
@@ -89,7 +89,7 @@
     class IDicomInstance;
     class DicomInstanceFromCallback;
     class DicomInstanceFromBuffer;
-    class DicomInstanceFromTranscoded;
+    class DicomInstanceFromParsed;
     class WebDavCollection;
     
     void RegisterRestCallback(const void* parameters,
@@ -217,6 +217,8 @@
 
     void ApplySendMultipartItem2(const void* parameters);
 
+    void ApplyLoadDicomInstance(const _OrthancPluginLoadDicomInstance& parameters);
+
     void ComputeHash(_OrthancPluginService service,
                      const void* parameters);
 
--- a/OrthancServer/Plugins/Engine/PluginsEnumerations.cpp	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancServer/Plugins/Engine/PluginsEnumerations.cpp	Tue Jul 04 12:20:41 2023 +0200
@@ -578,5 +578,21 @@
           throw OrthancException(ErrorCode_ParameterOutOfRange);
       }
     }
+
+
+    MetricsUpdatePolicy Convert(OrthancPluginMetricsType type)
+    {
+      switch (type)
+      {
+        case OrthancPluginMetricsType_Default:
+          return MetricsUpdatePolicy_Directly;
+
+        case OrthancPluginMetricsType_Timer:
+          return MetricsUpdatePolicy_MaxOver10Seconds;
+
+        default:
+          throw OrthancException(ErrorCode_ParameterOutOfRange);
+      }
+    }
   }
 }
--- a/OrthancServer/Plugins/Engine/PluginsEnumerations.h	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancServer/Plugins/Engine/PluginsEnumerations.h	Tue Jul 04 12:20:41 2023 +0200
@@ -31,6 +31,7 @@
  * "orthanc-databases" project.
  **/
 
+#include "../../../OrthancFramework/Sources/MetricsRegistry.h"
 #include "../../Sources/Search/DatabaseConstraint.h"
 #include "../../Sources/ServerEnumerations.h"
 #include "../Include/orthanc/OrthancCPlugin.h"
@@ -71,6 +72,8 @@
     JobStepCode Convert(OrthancPluginJobStepStatus step);
 
     StorageCommitmentFailureReason Convert(OrthancPluginStorageCommitmentFailureReason reason);
+
+    MetricsUpdatePolicy Convert(OrthancPluginMetricsType type);
   }
 }
 
--- a/OrthancServer/Plugins/Engine/PluginsJob.h	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancServer/Plugins/Engine/PluginsJob.h	Tue Jul 04 12:20:41 2023 +0200
@@ -76,6 +76,12 @@
       // TODO
       return false;
     }
+
+    virtual bool DeleteOutput(const std::string& key) ORTHANC_OVERRIDE
+    {
+      // TODO
+      return false;
+    }
   };
 }
 
--- a/OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h	Tue Jul 04 12:20:41 2023 +0200
@@ -120,7 +120,7 @@
 
 #define ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER     1
 #define ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER     12
-#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER  0
+#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER  1
 
 
 #if !defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE)
@@ -447,6 +447,7 @@
     _OrthancPluginService_CreateMemoryBuffer64 = 40, /* New in Orthanc 1.9.0 */
     _OrthancPluginService_CreateDicom2 = 41,         /* New in Orthanc 1.9.0 */
     _OrthancPluginService_GetDatabaseServerIdentifier = 42,         /* New in Orthanc 1.11.1 */
+    _OrthancPluginService_SetMetricsIntegerValue = 43,              /* New in Orthanc 1.12.1 */
 
     /* Registration of callbacks */
     _OrthancPluginService_RegisterRestCallback = 1000,
@@ -526,6 +527,7 @@
     _OrthancPluginService_GetInstanceAdvancedJson = 4017,  /* New in Orthanc 1.7.0 */
     _OrthancPluginService_GetInstanceDicomWebJson = 4018,  /* New in Orthanc 1.7.0 */
     _OrthancPluginService_GetInstanceDicomWebXml = 4019,   /* New in Orthanc 1.7.0 */
+    _OrthancPluginService_LoadDicomInstance = 4020,        /* New in Orthanc 1.12.1 */
     
     /* Services for plugins implementing a database back-end */
     _OrthancPluginService_RegisterDatabaseBackend = 5000,    /* New in Orthanc 0.8.6 */
@@ -1024,6 +1026,28 @@
 
 
   /**
+   * Mode specifying how to load a DICOM instance.
+   * @see OrthancPluginLoadDicomInstance()
+   **/
+  typedef enum
+  {
+    OrthancPluginLoadDicomInstanceMode_WholeDicom = 1,
+    /*!< Load the whole DICOM file, including pixel data */
+
+    OrthancPluginLoadDicomInstanceMode_UntilPixelData = 2,
+    /*!< Load the whole DICOM file until pixel data, which will speed
+      up the loading */
+
+    OrthancPluginLoadDicomInstanceMode_EmptyPixelData = 3,
+    /*!< Load the whole DICOM file until pixel data, and replace pixel
+      data by an empty tag whose VR (value representation) is the same
+      as those of the original DICOM file */
+
+    _OrthancPluginLoadDicomInstanceMode_INTERNAL = 0x7fffffff
+  } OrthancPluginLoadDicomInstanceMode;
+
+
+  /**
    * @brief A 32-bit memory buffer allocated by the core system of Orthanc.
    *
    * A memory buffer allocated by the core system of Orthanc. When the
@@ -1723,7 +1747,8 @@
    * Signature of a callback function that is called by Orthanc
    * whenever a monitoring tool (such as Prometheus) asks the current
    * values of the metrics. This callback gives the plugin a chance to
-   * update its metrics, by calling OrthancPluginSetMetricsValue().
+   * update its metrics, by calling OrthancPluginSetMetricsValue() or
+   * OrthancPluginSetMetricsIntegerValue().
    * This is typically useful for metrics that are expensive to
    * acquire.
    * 
@@ -1874,7 +1899,7 @@
    * @param expectedRevision Expected revision.
    * @return 1 if and only if the versions are compatible. If the
    * result is 0, the initialization of the plugin should fail.
-   * @see OrthancPluginCheckVersion
+   * @see OrthancPluginCheckVersion()
    * @ingroup Callbacks
    **/
   ORTHANC_PLUGIN_INLINE int  OrthancPluginCheckVersionAdvanced(
@@ -1906,7 +1931,7 @@
         sizeof(int32_t) != sizeof(OrthancPluginMetricsType) ||
         sizeof(int32_t) != sizeof(OrthancPluginDicomWebBinaryMode) ||
         sizeof(int32_t) != sizeof(OrthancPluginStorageCommitmentFailureReason) ||
-        sizeof(int32_t) != sizeof(OrthancPluginReceivedInstanceAction))
+        sizeof(int32_t) != sizeof(OrthancPluginLoadDicomInstanceMode))
     {
       /* Mismatch in the size of the enumerations */
       return 0;
@@ -1980,7 +2005,7 @@
    * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
    * @return 1 if and only if the versions are compatible. If the
    * result is 0, the initialization of the plugin should fail.
-   * @see OrthancPluginCheckVersionAdvanced
+   * @see OrthancPluginCheckVersionAdvanced()
    * @ingroup Callbacks
    **/
   ORTHANC_PLUGIN_INLINE int  OrthancPluginCheckVersion(
@@ -2340,7 +2365,7 @@
    * @param uri The URI in the built-in Orthanc API.
    * @return 0 if success, or the error code if failure.
    * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource.
-   * @see OrthancPluginRestApiGetAfterPlugins
+   * @see OrthancPluginRestApiGetAfterPlugins()
    * @ingroup Orthanc
    **/
   ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiGet(
@@ -2370,7 +2395,7 @@
    * @param uri The URI in the built-in Orthanc API.
    * @return 0 if success, or the error code if failure.
    * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource.
-   * @see OrthancPluginRestApiGet
+   * @see OrthancPluginRestApiGet()
    * @ingroup Orthanc
    **/
   ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiGetAfterPlugins(
@@ -2407,7 +2432,7 @@
    * @param bodySize The size of the body.
    * @return 0 if success, or the error code if failure.
    * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource.
-   * @see OrthancPluginRestApiPostAfterPlugins
+   * @see OrthancPluginRestApiPostAfterPlugins()
    * @ingroup Orthanc
    **/
   ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiPost(
@@ -2442,7 +2467,7 @@
    * @param bodySize The size of the body.
    * @return 0 if success, or the error code if failure.
    * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource.
-   * @see OrthancPluginRestApiPost
+   * @see OrthancPluginRestApiPost()
    * @ingroup Orthanc
    **/
   ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiPostAfterPlugins(
@@ -2471,7 +2496,7 @@
    * @param uri The URI to delete in the built-in Orthanc API.
    * @return 0 if success, or the error code if failure.
    * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource.
-   * @see OrthancPluginRestApiDeleteAfterPlugins
+   * @see OrthancPluginRestApiDeleteAfterPlugins()
    * @ingroup Orthanc
    **/
   ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiDelete(
@@ -2494,7 +2519,7 @@
    * @param uri The URI to delete in the built-in Orthanc API.
    * @return 0 if success, or the error code if failure.
    * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource.
-   * @see OrthancPluginRestApiDelete
+   * @see OrthancPluginRestApiDelete()
    * @ingroup Orthanc
    **/
   ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiDeleteAfterPlugins(
@@ -2519,7 +2544,7 @@
    * @param bodySize The size of the body.
    * @return 0 if success, or the error code if failure.
    * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource.
-   * @see OrthancPluginRestApiPutAfterPlugins
+   * @see OrthancPluginRestApiPutAfterPlugins()
    * @ingroup Orthanc
    **/
   ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiPut(
@@ -2555,7 +2580,7 @@
    * @param bodySize The size of the body.
    * @return 0 if success, or the error code if failure.
    * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource.
-   * @see OrthancPluginRestApiPut
+   * @see OrthancPluginRestApiPut()
    * @ingroup Orthanc
    **/
   ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiPutAfterPlugins(
@@ -5033,7 +5058,7 @@
    * @return The NULL value if the case of an error, or the JSON
    * string. This string must be freed by OrthancPluginFreeString().
    * @ingroup Toolbox
-   * @see OrthancPluginDicomInstanceToJson
+   * @see OrthancPluginDicomInstanceToJson()
    **/
   ORTHANC_PLUGIN_INLINE char* OrthancPluginDicomBufferToJson(
     OrthancPluginContext*           context,
@@ -5082,7 +5107,7 @@
    * @return The NULL value if the case of an error, or the JSON
    * string. This string must be freed by OrthancPluginFreeString().
    * @ingroup Toolbox
-   * @see OrthancPluginDicomInstanceToJson
+   * @see OrthancPluginDicomInstanceToJson()
    **/
   ORTHANC_PLUGIN_INLINE char* OrthancPluginDicomInstanceToJson(
     OrthancPluginContext*           context,
@@ -5139,7 +5164,7 @@
    * @param afterPlugins If 0, the built-in API of Orthanc is used.
    * If 1, the API is tainted by the plugins.
    * @return 0 if success, or the error code if failure.
-   * @see OrthancPluginRestApiGet, OrthancPluginRestApiGetAfterPlugins
+   * @see OrthancPluginRestApiGet(), OrthancPluginRestApiGetAfterPlugins()
    * @ingroup Orthanc
    **/
   ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiGet2(
@@ -5402,8 +5427,8 @@
    * @param flags Flags governing the output.
    * @return 0 if success, other value if error.
    * @ingroup Toolbox
-   * @see OrthancPluginCreateDicom2
-   * @see OrthancPluginDicomBufferToJson
+   * @see OrthancPluginCreateDicom2()
+   * @see OrthancPluginDicomBufferToJson()
    **/
   ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCreateDicom(
     OrthancPluginContext*          context,
@@ -7055,11 +7080,12 @@
   } _OrthancPluginSetMetricsValue;
 
   /**
-   * @brief Set the value of a metrics.
-   *
-   * This function sets the value of a metrics to monitor the behavior
-   * of the plugin through tools such as Prometheus. The values of all
-   * the metrics are stored within the Orthanc context.
+   * @brief Set the value of a floating-point metrics.
+   *
+   * This function sets the value of a floating-point metrics to
+   * monitor the behavior of the plugin through tools such as
+   * Prometheus. The values of all the metrics are stored within the
+   * Orthanc context.
    * 
    * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
    * @param name The name of the metrics to be set.
@@ -7067,6 +7093,7 @@
    * @param type The type of the metrics. This parameter is only taken into consideration
    * the first time this metrics is set.
    * @ingroup Toolbox
+   * @see OrthancPluginSetMetricsIntegerValue()
    **/
   ORTHANC_PLUGIN_INLINE void OrthancPluginSetMetricsValue(
     OrthancPluginContext*     context,
@@ -7092,7 +7119,8 @@
    * @brief Register a callback to refresh the metrics.
    *
    * This function registers a callback to refresh the metrics. The
-   * callback must make calls to OrthancPluginSetMetricsValue().
+   * callback must make calls to OrthancPluginSetMetricsValue() or
+   * OrthancPluginSetMetricsIntegerValue().
    *
    * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
    * @param callback The callback function to handle the refresh.
@@ -8402,7 +8430,7 @@
    * @return The NULL value if the case of an error, or the JSON
    * string. This string must be freed by OrthancPluginFreeString().
    * @ingroup DicomInstance
-   * @see OrthancPluginDicomBufferToJson
+   * @see OrthancPluginDicomBufferToJson()
    **/
   ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceAdvancedJson(
     OrthancPluginContext*              context,
@@ -8759,8 +8787,8 @@
    * Check out the global configuration option "Dictionary" of Orthanc.
    * @return 0 if success, other value if error.
    * @ingroup Toolbox
-   * @see OrthancPluginCreateDicom
-   * @see OrthancPluginDicomBufferToJson
+   * @see OrthancPluginCreateDicom()
+   * @see OrthancPluginDicomBufferToJson()
    **/
   ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCreateDicom2(
     OrthancPluginContext*          context,
@@ -8827,7 +8855,7 @@
    * @param afterPlugins If 0, the built-in API of Orthanc is used.
    * If 1, the API is tainted by the plugins.
    * @return 0 if success, or the error code if failure.
-   * @see OrthancPluginRestApiGet2, OrthancPluginRestApiPost, OrthancPluginRestApiPut, OrthancPluginRestApiDelete
+   * @see OrthancPluginRestApiGet2(), OrthancPluginRestApiPost(), OrthancPluginRestApiPut(), OrthancPluginRestApiDelete()
    * @ingroup Orthanc
    **/
   ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginCallRestApi(
@@ -9225,6 +9253,87 @@
     return context->InvokeService(context, _OrthancPluginService_RegisterDatabaseBackendV4, &params);
   }
 
+
+  typedef struct
+  {
+    OrthancPluginDicomInstance**        target;
+    const char*                         instanceId;
+    OrthancPluginLoadDicomInstanceMode  mode;
+  } _OrthancPluginLoadDicomInstance;
+
+  /**
+   * @brief Load a DICOM instance from the Orthanc server.
+   *
+   * This function loads a DICOM instance from the content of the
+   * Orthanc database. The function returns a new pointer to a data
+   * structure that is managed by the Orthanc core.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instanceId The Orthanc identifier of the DICOM instance of interest.
+   * @param mode Flag specifying how to deal with pixel data.
+   * @return The newly allocated DICOM instance. It must be freed with OrthancPluginFreeDicomInstance().
+   * @ingroup DicomInstance
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginDicomInstance* OrthancPluginLoadDicomInstance(
+    OrthancPluginContext*               context,
+    const char*                         instanceId,
+    OrthancPluginLoadDicomInstanceMode  mode)
+  {
+    OrthancPluginDicomInstance* target = NULL;
+
+    _OrthancPluginLoadDicomInstance params;
+    params.target = &target;
+    params.instanceId = instanceId;
+    params.mode = mode;
+
+    if (context->InvokeService(context, _OrthancPluginService_LoadDicomInstance, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+
+  typedef struct
+  {
+    const char*               name;
+    int64_t                   value;
+    OrthancPluginMetricsType  type;
+  } _OrthancPluginSetMetricsIntegerValue;
+
+  /**
+   * @brief Set the value of an integer metrics.
+   *
+   * This function sets the value of an integer metrics to monitor the
+   * behavior of the plugin through tools such as Prometheus. The
+   * values of all the metrics are stored within the Orthanc context.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param name The name of the metrics to be set.
+   * @param value The value of the metrics.
+   * @param type The type of the metrics. This parameter is only taken into consideration
+   * the first time this metrics is set.
+   * @ingroup Toolbox
+   * @see OrthancPluginSetMetricsValue()
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSetMetricsIntegerValue(
+    OrthancPluginContext*     context,
+    const char*               name,
+    int64_t                   value,
+    OrthancPluginMetricsType  type)
+  {
+    _OrthancPluginSetMetricsIntegerValue params;
+    params.name = name;
+    params.value = value;
+    params.type = type;
+    context->InvokeService(context, _OrthancPluginService_SetMetricsIntegerValue, &params);
+  }
+
+
 #ifdef  __cplusplus
 }
 #endif
--- a/OrthancServer/Plugins/Samples/Basic/Plugin.c	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancServer/Plugins/Samples/Basic/Plugin.c	Tue Jul 04 12:20:41 2023 +0200
@@ -282,6 +282,63 @@
 }
 
 
+OrthancPluginErrorCode CallbackDicomWeb(OrthancPluginRestOutput* output,
+                                        const char* url,
+                                        const OrthancPluginHttpRequest* request)
+{
+  if (request->method != OrthancPluginHttpMethod_Get)
+  {
+    OrthancPluginSendMethodNotAllowed(context, output, "GET");
+  }
+  else
+  {
+    OrthancPluginLoadDicomInstanceMode mode = OrthancPluginLoadDicomInstanceMode_WholeDicom;
+    OrthancPluginDicomInstance* instance;
+    char* json;
+
+    if (request->getCount == 1)
+    {
+      if (strcmp(request->getKeys[0], "until-pixel-data") == 0)
+      {
+        mode = OrthancPluginLoadDicomInstanceMode_UntilPixelData;
+      }
+      else if (strcmp(request->getKeys[0], "empty-pixel-data") == 0)
+      {
+        mode = OrthancPluginLoadDicomInstanceMode_EmptyPixelData;
+      }
+      else
+      {
+        return OrthancPluginErrorCode_ParameterOutOfRange;
+      }
+    }
+    
+    instance = OrthancPluginLoadDicomInstance(context, request->groups[0], mode);
+    if (instance == NULL)
+    {
+      return OrthancPluginErrorCode_UnknownResource;
+    }
+
+    json = OrthancPluginEncodeDicomWebXml(context,
+                                          OrthancPluginGetInstanceData(context, instance),
+                                          OrthancPluginGetInstanceSize(context, instance),
+                                          DicomWebBinaryCallback);
+    OrthancPluginFreeDicomInstance(context, instance);
+
+    if (json != NULL)
+    {
+      OrthancPluginAnswerBuffer(context, output, json, strlen(json), "application/json");
+      OrthancPluginFreeString(context, json);
+    }
+    else
+    {
+      return OrthancPluginErrorCode_InternalError;
+    }
+  }
+
+  return OrthancPluginErrorCode_Success;
+}
+
+
 OrthancPluginErrorCode OnStoredCallback(const OrthancPluginDicomInstance* instance,
                                         const char* instanceId)
 {
@@ -511,6 +568,7 @@
   OrthancPluginRegisterRestCallback(context, "/forward/(built-in)(/.+)", Callback5);
   OrthancPluginRegisterRestCallback(context, "/forward/(plugins)(/.+)", Callback5);
   OrthancPluginRegisterRestCallback(context, "/plugin/create", CallbackCreateDicom);
+  OrthancPluginRegisterRestCallback(context, "/instances/([^/]+)/dicom-web", CallbackDicomWeb);
 
   OrthancPluginRegisterOnStoredInstanceCallback(context, OnStoredCallback);
   OrthancPluginRegisterOnChangeCallback(context, OnChangeCallback);
--- a/OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp	Tue Jul 04 12:20:41 2023 +0200
@@ -253,14 +253,15 @@
   // helper class to convert std::map of headers to the plugin SDK C structure
   class PluginHttpHeaders
   {
+  private:
     std::vector<const char*> headersKeys_;
     std::vector<const char*> headersValues_;
+
   public:
-
-    PluginHttpHeaders(const std::map<std::string, std::string>& httpHeaders)
+    explicit PluginHttpHeaders(const std::map<std::string, std::string>& httpHeaders)
     {
       for (std::map<std::string, std::string>::const_iterator
-           it = httpHeaders.begin(); it != httpHeaders.end(); it++)
+           it = httpHeaders.begin(); it != httpHeaders.end(); ++it)
       {
         headersKeys_.push_back(it->first.c_str());
         headersValues_.push_back(it->second.c_str());
@@ -3751,6 +3752,27 @@
 #endif
 
 
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 1)
+  DicomInstance* DicomInstance::Load(const std::string& instanceId,
+                                     OrthancPluginLoadDicomInstanceMode mode)
+  {
+    OrthancPluginDicomInstance* instance = OrthancPluginLoadDicomInstance(
+      GetGlobalContext(), instanceId.c_str(), mode);
+
+    if (instance == NULL)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin);
+    }
+    else
+    {
+      boost::movelib::unique_ptr<DicomInstance> result(new DicomInstance(instance));
+      result->toFree_ = true;
+      return result.release();
+    }
+  }
+#endif
+
+
 #if HAS_ORTHANC_PLUGIN_WEBDAV == 1
   static std::vector<std::string> WebDavConvertPath(uint32_t pathSize,
                                                     const char* const*  pathItems)
--- a/OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.h	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.h	Tue Jul 04 12:20:41 2023 +0200
@@ -1264,6 +1264,11 @@
 
     ~DicomInstance();
 
+    const OrthancPluginDicomInstance* GetObject() const
+    {
+      return instance_;
+    }
+
     std::string GetRemoteAet() const;
 
     const void* GetBuffer() const
@@ -1318,6 +1323,11 @@
                                     size_t size,
                                     const std::string& transferSyntax);
 #endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 1)
+    static DicomInstance* Load(const std::string& instanceId,
+                               OrthancPluginLoadDicomInstanceMode mode);
+#endif
   };
 
 // helper method to convert Http headers from the plugin SDK to a std::map
--- a/OrthancServer/Plugins/Samples/DelayedDeletion/LargeDeleteJob.cpp	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancServer/Plugins/Samples/DelayedDeletion/LargeDeleteJob.cpp	Tue Jul 04 12:20:41 2023 +0200
@@ -168,6 +168,7 @@
   levels_(levels),
   posResources_(0),
   posInstances_(0),
+  posSeries_(0),
   posDelete_(0)
 {
   if (resources.size() != levels.size())
--- a/OrthancServer/Plugins/Samples/Housekeeper/Plugin.cpp	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancServer/Plugins/Samples/Housekeeper/Plugin.cpp	Tue Jul 04 12:20:41 2023 +0200
@@ -127,7 +127,7 @@
     Json::Value::Members names = scheduleConfiguration.getMemberNames();
 
     for (Json::Value::Members::const_iterator it = names.begin();
-      it != names.end(); it++)
+      it != names.end(); ++it)
     {
       for (Json::Value::ArrayIndex i = 0; i < scheduleConfiguration[*it].size(); i++)
       {
@@ -144,7 +144,7 @@
     }
 
     for (std::list<RunningPeriod>::const_iterator it = runningPeriods_.begin();
-      it != runningPeriods_.end(); it++)
+      it != runningPeriods_.end(); ++it)
     {
       if (it->isInPeriod())
       {
--- a/OrthancServer/Plugins/Samples/ModalityWorklists/Plugin.cpp	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancServer/Plugins/Samples/ModalityWorklists/Plugin.cpp	Tue Jul 04 12:20:41 2023 +0200
@@ -151,11 +151,12 @@
 
     fs::path source(folder_);
     fs::directory_iterator end;
-    unsigned int parsedFilesCount = 0;
-    unsigned int matchedWorklistCount = 0;
 
     try
     {
+      unsigned int parsedFilesCount = 0;
+      unsigned int matchedWorklistCount = 0;
+      
       for (fs::directory_iterator it(source); it != end; ++it)
       {
         fs::file_type type(it->status().type());
--- a/OrthancServer/Plugins/Samples/MultitenantDicom/MoveRequestHandler.cpp	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancServer/Plugins/Samples/MultitenantDicom/MoveRequestHandler.cpp	Tue Jul 04 12:20:41 2023 +0200
@@ -47,12 +47,12 @@
   {
   }
 
-  unsigned int GetSubOperationCount() const
+  virtual unsigned int GetSubOperationCount() const ORTHANC_OVERRIDE
   {
     return 1;
   }
 
-  Status DoNext()
+  virtual Status DoNext() ORTHANC_OVERRIDE
   {
     Json::Value answer;
 
@@ -95,15 +95,15 @@
       if (OrthancPlugins::RestApiPost(response, "/tools/find", request, false) &&
           response.type() == Json::arrayValue)
       {
-        for (Json::Value::ArrayIndex i = 0; i < response.size(); i++)
+        for (Json::Value::ArrayIndex j = 0; j < response.size(); j++)
         {
-          if (response[i].type() != Json::stringValue)
+          if (response[j].type() != Json::stringValue)
           {
             throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
           }
           else
           {
-            publicIds.insert(response[i].asString());
+            publicIds.insert(response[j].asString());
           }
         }
       }
--- a/OrthancServer/Plugins/Samples/MultitenantDicom/MultitenantDicomServer.h	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancServer/Plugins/Samples/MultitenantDicom/MultitenantDicomServer.h	Tue Jul 04 12:20:41 2023 +0200
@@ -61,7 +61,7 @@
   std::unique_ptr<Orthanc::DicomServer>  server_;
 
 public:
-  MultitenantDicomServer(const Json::Value& serverConfig);
+  explicit MultitenantDicomServer(const Json::Value& serverConfig);
 
   void Start();
 
--- a/OrthancServer/Resources/Configuration.json	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancServer/Resources/Configuration.json	Tue Jul 04 12:20:41 2023 +0200
@@ -852,9 +852,9 @@
 
   // If "DeidentifyLogs" is true, this sets the DICOM standard to
   // follow for the deidentification/anonymization of the query
-  // contents. Possible values are "2008", "2017c" and "2021b" (new
-  // in Orthanc 1.8.2)
-  "DeidentifyLogsDicomVersion" : "2021b",
+  // contents. Possible values are "2008", "2017c", "2021b" (new
+  // in Orthanc 1.8.2), and "2023b" (new in Orthanc 1.12.1)
+  "DeidentifyLogsDicomVersion" : "2023b",
 
   // Maximum length of the PDU (Protocol Data Unit) in the DICOM
   // network protocol, expressed in bytes. This value affects both
--- a/OrthancServer/Resources/GenerateAnonymizationProfile.py	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancServer/Resources/GenerateAnonymizationProfile.py	Tue Jul 04 12:20:41 2023 +0200
@@ -26,7 +26,7 @@
 import xml.etree.ElementTree as ET
 
 # Usage:
-# ./GenerateAnonymizationProfile.py https://raw.githubusercontent.com/jodogne/dicom-specification/master/2021b/part15.xml
+# ./GenerateAnonymizationProfile.py https://raw.githubusercontent.com/jodogne/dicom-specification/master/2023b/part15.xml > ../../OrthancFramework/Sources/DicomParsing/DicomModification_Anonymization2023b.impl.h
 
 if len(sys.argv) != 2:
     raise Exception('Please provide the path or the URL to the part15.xml file from the DICOM standard')
--- a/OrthancServer/Resources/ImplementationNotes/memory_consumption.txt	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancServer/Resources/ImplementationNotes/memory_consumption.txt	Tue Jul 04 12:20:41 2023 +0200
@@ -1,6 +1,7 @@
 In Orthanc 1.11.3, we have introduced a Housekeeper thread that 
 tries to give back unused memory back to the system.  This is implemented 
-by calling malloc_trim every 100ms.
+by calling malloc_trim every 30s (note: on 1.11.3 and 1.12.0, the interval
+was 100ms which caused high idle CPU load).
 
 
 Here is how we validated the effect of this new feature:
@@ -133,3 +134,16 @@
   specifies the minimum size that is released.  So, even without
   malloc_trim, Orthanc is able to give back memory to the system.
 - free() never gives back block allocated by mmap() to the system, only malloc_trim() does !
+
+UPDATE on June 2023:
+-------------------
+
+Given this discussion: https://discourse.orthanc-server.org/t/onchange-callbacks-and-cpu-loads/3534,
+changed the interval from 100ms to 30s.
+We also added a metrics to monitor the duration: orthanc_memory_trimming_duration_ms
+
+Good reference article:
+https://www.algolia.com/blog/engineering/when-allocators-are-hoarding-your-precious-memory/
+
+
+
--- a/OrthancServer/Resources/RunCppCheck.sh	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancServer/Resources/RunCppCheck.sh	Tue Jul 04 12:20:41 2023 +0200
@@ -12,8 +12,8 @@
 constParameter:../../OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp
 knownArgument:../../OrthancFramework/UnitTestsSources/ImageTests.cpp
 knownConditionTrueFalse:../../OrthancServer/Plugins/Engine/OrthancPlugins.cpp
-nullPointer:../../OrthancFramework/UnitTestsSources/RestApiTests.cpp:322
-stlFindInsert:../../OrthancFramework/Sources/DicomFormat/DicomMap.cpp:1475
+nullPointer:../../OrthancFramework/UnitTestsSources/RestApiTests.cpp:315
+stlFindInsert:../../OrthancFramework/Sources/DicomFormat/DicomMap.cpp:1476
 stlFindInsert:../../OrthancFramework/Sources/RestApi/RestApiCallDocumentation.cpp:165
 stlFindInsert:../../OrthancFramework/Sources/RestApi/RestApiCallDocumentation.cpp:73
 stlFindInsert:../../OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp:373
@@ -22,7 +22,7 @@
 stlFindInsert:../../OrthancServer/Sources/ServerJobs/SplitStudyJob.cpp:190
 stlFindInsert:../../OrthancServer/Sources/ServerJobs/ResourceModificationJob.cpp:334
 syntaxError:../../OrthancFramework/Sources/SQLite/FunctionContext.h:52
-syntaxError:../../OrthancFramework/UnitTestsSources/DicomMapTests.cpp:72
+syntaxError:../../OrthancFramework/UnitTestsSources/DicomMapTests.cpp:73
 syntaxError:../../OrthancFramework/UnitTestsSources/ZipTests.cpp:132
 syntaxError:../../OrthancServer/UnitTestsSources/UnitTestsMain.cpp:310
 uninitMemberVar:../../OrthancServer/Sources/ServerJobs/StorageCommitmentScpJob.cpp:416
@@ -36,8 +36,9 @@
 assertWithSideEffect:../../OrthancServer/Plugins/Engine/OrthancPluginDatabase.cpp:1025
 assertWithSideEffect:../../OrthancServer/Sources/Database/Compatibility/DatabaseLookup.cpp:289
 assertWithSideEffect:../../OrthancServer/Sources/Database/Compatibility/DatabaseLookup.cpp:388
-assertWithSideEffect:../../OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp:3526
+assertWithSideEffect:../../OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp:3551
 assertWithSideEffect:../../OrthancServer/Sources/ServerJobs/ResourceModificationJob.cpp:272
+assertWithSideEffect:../../OrthancFramework/Sources/DicomNetworking/Internals/CommandDispatcher.cpp:453
 EOF
 
 ${CPPCHECK} --enable=all --quiet --std=c++11 \
@@ -88,6 +89,7 @@
             -D__cplusplus=201103 \
             -D__linux__ \
             -UNDEBUG \
+            -DHAS_ORTHANC_EXCEPTION=1 \
             \
             ../../OrthancFramework/Sources \
             ../../OrthancFramework/UnitTestsSources \
@@ -95,5 +97,11 @@
             ../../OrthancServer/Plugins/Include \
             ../../OrthancServer/Sources \
             ../../OrthancServer/UnitTestsSources \
+            ../../OrthancServer/Plugins/Samples/Common \
+            ../../OrthancServer/Plugins/Samples/ConnectivityChecks \
+            ../../OrthancServer/Plugins/Samples/DelayedDeletion \
+            ../../OrthancServer/Plugins/Samples/Housekeeper \
+            ../../OrthancServer/Plugins/Samples/ModalityWorklists \
+            ../../OrthancServer/Plugins/Samples/MultitenantDicom \
             \
             2>&1
--- a/OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp	Tue Jul 04 12:20:41 2023 +0200
@@ -2036,6 +2036,24 @@
             remainingAncestor_["RemainingAncestor"]["Path"] = GetBasePath(remainingLevel, remainingPublicId);
             remainingAncestor_["RemainingAncestor"]["Type"] = EnumerationToString(remainingLevel);
             remainingAncestor_["RemainingAncestor"]["ID"] = remainingPublicId;
+
+            { // update the LastUpdate metadata of all parents
+              std::string now = SystemToolbox::GetNowIsoString(true /* use UTC time (not local time) */);
+              ResourcesContent content(true);
+
+              int64_t parentId = 0;
+              if (transaction.LookupResource(parentId, remainingLevel, remainingPublicId))
+              {
+
+                do
+                {
+                  content.AddMetadata(parentId, MetadataType_LastUpdate, now);
+                }
+                while (transaction.LookupParent(parentId, parentId));
+    
+                transaction.SetResourcesContent(content);
+              }
+            }
           }
           else
           {
@@ -2938,6 +2956,7 @@
                                                  DicomTransferSyntax transferSyntax,
                                                  bool hasPixelDataOffset,
                                                  uint64_t pixelDataOffset,
+                                                 ValueRepresentation pixelDataVR,
                                                  MaxStorageMode maximumStorageMode,
                                                  uint64_t maximumStorageSize,
                                                  unsigned int maximumPatients,
@@ -2957,6 +2976,7 @@
       DicomTransferSyntax                  transferSyntax_;
       bool                                 hasPixelDataOffset_;
       uint64_t                             pixelDataOffset_;
+      ValueRepresentation                  pixelDataVR_;
       MaxStorageMode                       maximumStorageMode_;
       uint64_t                             maximumStorageSize_;
       unsigned int                         maximumPatientCount_;
@@ -3060,6 +3080,7 @@
                  DicomTransferSyntax transferSyntax,
                  bool hasPixelDataOffset,
                  uint64_t pixelDataOffset,
+                 ValueRepresentation pixelDataVR,
                  MaxStorageMode maximumStorageMode,
                  uint64_t maximumStorageSize,
                  unsigned int maximumPatientCount,
@@ -3075,6 +3096,7 @@
         transferSyntax_(transferSyntax),
         hasPixelDataOffset_(hasPixelDataOffset),
         pixelDataOffset_(pixelDataOffset),
+        pixelDataVR_(pixelDataVR),
         maximumStorageMode_(maximumStorageMode),
         maximumStorageSize_(maximumStorageSize),
         maximumPatientCount_(maximumPatientCount),
@@ -3098,326 +3120,329 @@
         
       virtual void Apply(ReadWriteTransaction& transaction) ORTHANC_OVERRIDE
       {
-        try
+        IDatabaseWrapper::CreateInstanceResult status;
+        int64_t instanceId;
+        
+        bool isNewInstance = transaction.CreateInstance(status, instanceId, hashPatient_,
+                                                        hashStudy_, hashSeries_, hashInstance_);
+
+        if (isReconstruct_ && isNewInstance)
         {
-          IDatabaseWrapper::CreateInstanceResult status;
-          int64_t instanceId;
-          
-          bool isNewInstance = transaction.CreateInstance(status, instanceId, hashPatient_,
-                                                          hashStudy_, hashSeries_, hashInstance_);
-
-          if (isReconstruct_ && isNewInstance)
-          {
-            // In case of reconstruct, we just want to modify the attachments and some metadata like the TransferSyntex
-            // The DicomTags and many metadata have already been updated before we get here in ReconstructInstance
-            throw OrthancException(ErrorCode_InternalError, "New instance while reconstructing; this should not happen.");
-          }
-
-          // Check whether this instance is already stored
-          if (!isNewInstance && !isReconstruct_)
+          // In case of reconstruct, we just want to modify the attachments and some metadata like the TransferSyntex
+          // The DicomTags and many metadata have already been updated before we get here in ReconstructInstance
+          throw OrthancException(ErrorCode_InternalError, "New instance while reconstructing; this should not happen.");
+        }
+
+        // Check whether this instance is already stored
+        if (!isNewInstance && !isReconstruct_)
+        {
+          // The instance already exists
+          if (overwrite_)
           {
-            // The instance already exists
-            if (overwrite_)
+            // Overwrite the old instance
+            LOG(INFO) << "Overwriting instance: " << hashInstance_;
+            transaction.DeleteResource(instanceId);
+
+            // Re-create the instance, now that the old one is removed
+            if (!transaction.CreateInstance(status, instanceId, hashPatient_,
+                                            hashStudy_, hashSeries_, hashInstance_))
             {
-              // Overwrite the old instance
-              LOG(INFO) << "Overwriting instance: " << hashInstance_;
-              transaction.DeleteResource(instanceId);
-
-              // Re-create the instance, now that the old one is removed
-              if (!transaction.CreateInstance(status, instanceId, hashPatient_,
-                                              hashStudy_, hashSeries_, hashInstance_))
-              {
-                throw OrthancException(ErrorCode_InternalError, "No new instance while overwriting; this should not happen.");
-              }
-            }
-            else
-            {
-              // Do nothing if the instance already exists and overwriting is disabled
-              transaction.GetAllMetadata(instanceMetadata_, instanceId);
-              storeStatus_ = StoreStatus_AlreadyStored;
-              return;
+              throw OrthancException(ErrorCode_InternalError, "No new instance while overwriting; this should not happen.");
             }
           }
-
-
-          if (!isReconstruct_)  // don't signal new resources if this is a reconstruction
+          else
+          {
+            // Do nothing if the instance already exists and overwriting is disabled
+            transaction.GetAllMetadata(instanceMetadata_, instanceId);
+            storeStatus_ = StoreStatus_AlreadyStored;
+            return;
+          }
+        }
+
+
+        if (!isReconstruct_)  // don't signal new resources if this is a reconstruction
+        {
+          // Warn about the creation of new resources. The order must be
+          // from instance to patient.
+
+          // NB: In theory, could be sped up by grouping the underlying
+          // calls to "transaction.LogChange()". However, this would only have an
+          // impact when new patient/study/series get created, which
+          // occurs far less often that creating new instances. The
+          // positive impact looks marginal in practice.
+          transaction.LogChange(instanceId, ChangeType_NewInstance, ResourceType_Instance, hashInstance_);
+
+          if (status.isNewSeries_)
           {
-            // Warn about the creation of new resources. The order must be
-            // from instance to patient.
-
-            // NB: In theory, could be sped up by grouping the underlying
-            // calls to "transaction.LogChange()". However, this would only have an
-            // impact when new patient/study/series get created, which
-            // occurs far less often that creating new instances. The
-            // positive impact looks marginal in practice.
-            transaction.LogChange(instanceId, ChangeType_NewInstance, ResourceType_Instance, hashInstance_);
-
-            if (status.isNewSeries_)
-            {
-              transaction.LogChange(status.seriesId_, ChangeType_NewSeries, ResourceType_Series, hashSeries_);
-            }
-        
-            if (status.isNewStudy_)
-            {
-              transaction.LogChange(status.studyId_, ChangeType_NewStudy, ResourceType_Study, hashStudy_);
-            }
-        
-            if (status.isNewPatient_)
-            {
-              transaction.LogChange(status.patientId_, ChangeType_NewPatient, ResourceType_Patient, hashPatient_);
-            }
-          }      
+            transaction.LogChange(status.seriesId_, ChangeType_NewSeries, ResourceType_Series, hashSeries_);
+          }
+      
+          if (status.isNewStudy_)
+          {
+            transaction.LogChange(status.studyId_, ChangeType_NewStudy, ResourceType_Study, hashStudy_);
+          }
       
-          // Ensure there is enough room in the storage for the new instance
-          uint64_t instanceSize = 0;
-          for (Attachments::const_iterator it = attachments_.begin();
-               it != attachments_.end(); ++it)
+          if (status.isNewPatient_)
+          {
+            transaction.LogChange(status.patientId_, ChangeType_NewPatient, ResourceType_Patient, hashPatient_);
+          }
+        }      
+    
+        // Ensure there is enough room in the storage for the new instance
+        uint64_t instanceSize = 0;
+        for (Attachments::const_iterator it = attachments_.begin();
+              it != attachments_.end(); ++it)
+        {
+          instanceSize += it->GetCompressedSize();
+        }
+
+        if (!isReconstruct_)  // reconstruction should not affect recycling
+        {
+          if (maximumStorageMode_ == MaxStorageMode_Reject)
           {
-            instanceSize += it->GetCompressedSize();
-          }
-
-          if (!isReconstruct_)  // reconstruction should not affect recycling
-          {
-            if (maximumStorageMode_ == MaxStorageMode_Reject)
+            if (transaction.HasReachedMaxStorageSize(maximumStorageSize_, instanceSize))
             {
-              if (transaction.HasReachedMaxStorageSize(maximumStorageSize_, instanceSize))
-              {
-                storeStatus_ = StoreStatus_StorageFull;
-                throw OrthancException(ErrorCode_FullStorage, HttpStatus_507_InsufficientStorage, "Maximum storage size reached"); // throw to cancel the transaction
-              }
-              if (transaction.HasReachedMaxPatientCount(maximumPatientCount_, hashPatient_))
-              {
-                storeStatus_ = StoreStatus_StorageFull;
-                throw OrthancException(ErrorCode_FullStorage, HttpStatus_507_InsufficientStorage, "Maximum patient count reached");  // throw to cancel the transaction
-              }
+              storeStatus_ = StoreStatus_StorageFull;
+              throw OrthancException(ErrorCode_FullStorage, HttpStatus_507_InsufficientStorage, "Maximum storage size reached"); // throw to cancel the transaction
+            }
+            if (transaction.HasReachedMaxPatientCount(maximumPatientCount_, hashPatient_))
+            {
+              storeStatus_ = StoreStatus_StorageFull;
+              throw OrthancException(ErrorCode_FullStorage, HttpStatus_507_InsufficientStorage, "Maximum patient count reached");  // throw to cancel the transaction
             }
-            else
+          }
+          else
+          {
+            transaction.Recycle(maximumStorageSize_, maximumPatientCount_,
+                                instanceSize, hashPatient_ /* don't consider the current patient for recycling */);
+          }
+        }  
+    
+        // Attach the files to the newly created instance
+        for (Attachments::const_iterator it = attachments_.begin();
+              it != attachments_.end(); ++it)
+        {
+          if (isReconstruct_)
+          {
+            // we are replacing attachments during a reconstruction
+            transaction.DeleteAttachment(instanceId, it->GetContentType());
+          }
+
+          transaction.AddAttachment(instanceId, *it, 0 /* this is the first revision */);
+        }
+
+        if (!isReconstruct_)
+        {
+          ResourcesContent content(true /* new resource, metadata can be set */);
+
+          // Attach the user-specified metadata (in case of reconstruction, metadata_ contains all past metadata, including the system ones we want to keep)
+          for (MetadataMap::const_iterator 
+                  it = metadata_.begin(); it != metadata_.end(); ++it)
+          {
+            switch (it->first.first)
             {
-              transaction.Recycle(maximumStorageSize_, maximumPatientCount_,
-                                  instanceSize, hashPatient_ /* don't consider the current patient for recycling */);
+              case ResourceType_Patient:
+                content.AddMetadata(status.patientId_, it->first.second, it->second);
+                break;
+
+              case ResourceType_Study:
+                content.AddMetadata(status.studyId_, it->first.second, it->second);
+                break;
+
+              case ResourceType_Series:
+                content.AddMetadata(status.seriesId_, it->first.second, it->second);
+                break;
+
+              case ResourceType_Instance:
+                SetInstanceMetadata(content, instanceMetadata_, instanceId,
+                                    it->first.second, it->second);
+                break;
+
+              default:
+                throw OrthancException(ErrorCode_ParameterOutOfRange);
             }
-          }  
-     
-          // Attach the files to the newly created instance
-          for (Attachments::const_iterator it = attachments_.begin();
-               it != attachments_.end(); ++it)
-          {
-            if (isReconstruct_)
-            {
-              // we are replacing attachments during a reconstruction
-              transaction.DeleteAttachment(instanceId, it->GetContentType());
-            }
-
-            transaction.AddAttachment(instanceId, *it, 0 /* this is the first revision */);
           }
 
           if (!isReconstruct_)
           {
-            ResourcesContent content(true /* new resource, metadata can be set */);
-
-            // Attach the user-specified metadata (in case of reconstruction, metadata_ contains all past metadata, including the system ones we want to keep)
-            for (MetadataMap::const_iterator 
-                   it = metadata_.begin(); it != metadata_.end(); ++it)
+            // Populate the tags of the newly-created resources
+            content.AddResource(instanceId, ResourceType_Instance, dicomSummary_);
+            SetInstanceMetadata(content, instanceMetadata_, instanceId, MetadataType_MainDicomTagsSignature, DicomMap::GetMainDicomTagsSignature(ResourceType_Instance));  // New in Orthanc 1.11.0
+            SetMainDicomSequenceMetadata(content, instanceId, dicomSummary_, ResourceType_Instance);   // new in Orthanc 1.11.1
+
+            if (status.isNewSeries_)
             {
-              switch (it->first.first)
-              {
-                case ResourceType_Patient:
-                  content.AddMetadata(status.patientId_, it->first.second, it->second);
-                  break;
-
-                case ResourceType_Study:
-                  content.AddMetadata(status.studyId_, it->first.second, it->second);
-                  break;
-
-                case ResourceType_Series:
-                  content.AddMetadata(status.seriesId_, it->first.second, it->second);
-                  break;
-
-                case ResourceType_Instance:
-                  SetInstanceMetadata(content, instanceMetadata_, instanceId,
-                                      it->first.second, it->second);
-                  break;
-
-                default:
-                  throw OrthancException(ErrorCode_ParameterOutOfRange);
-              }
+              content.AddResource(status.seriesId_, ResourceType_Series, dicomSummary_);
+              content.AddMetadata(status.seriesId_, MetadataType_MainDicomTagsSignature, DicomMap::GetMainDicomTagsSignature(ResourceType_Series));  // New in Orthanc 1.11.0
+              SetMainDicomSequenceMetadata(content, status.seriesId_, dicomSummary_, ResourceType_Series);   // new in Orthanc 1.11.1
             }
 
-            if (!isReconstruct_)
+            if (status.isNewStudy_)
             {
-              // Populate the tags of the newly-created resources
-              content.AddResource(instanceId, ResourceType_Instance, dicomSummary_);
-              SetInstanceMetadata(content, instanceMetadata_, instanceId, MetadataType_MainDicomTagsSignature, DicomMap::GetMainDicomTagsSignature(ResourceType_Instance));  // New in Orthanc 1.11.0
-              SetMainDicomSequenceMetadata(content, instanceId, dicomSummary_, ResourceType_Instance);   // new in Orthanc 1.11.1
-
-              if (status.isNewSeries_)
-              {
-                content.AddResource(status.seriesId_, ResourceType_Series, dicomSummary_);
-                content.AddMetadata(status.seriesId_, MetadataType_MainDicomTagsSignature, DicomMap::GetMainDicomTagsSignature(ResourceType_Series));  // New in Orthanc 1.11.0
-                SetMainDicomSequenceMetadata(content, status.seriesId_, dicomSummary_, ResourceType_Series);   // new in Orthanc 1.11.1
-              }
-
-              if (status.isNewStudy_)
-              {
-                content.AddResource(status.studyId_, ResourceType_Study, dicomSummary_);
-                content.AddMetadata(status.studyId_, MetadataType_MainDicomTagsSignature, DicomMap::GetMainDicomTagsSignature(ResourceType_Study));  // New in Orthanc 1.11.0
-                SetMainDicomSequenceMetadata(content, status.studyId_, dicomSummary_, ResourceType_Study);   // new in Orthanc 1.11.1
-              }
-
-              if (status.isNewPatient_)
-              {
-                content.AddResource(status.patientId_, ResourceType_Patient, dicomSummary_);
-                content.AddMetadata(status.patientId_, MetadataType_MainDicomTagsSignature, DicomMap::GetMainDicomTagsSignature(ResourceType_Patient));  // New in Orthanc 1.11.0
-                SetMainDicomSequenceMetadata(content, status.patientId_, dicomSummary_, ResourceType_Patient);   // new in Orthanc 1.11.1
-              }
-
-              // Attach the auto-computed metadata for the patient/study/series levels
-              std::string now = SystemToolbox::GetNowIsoString(true /* use UTC time (not local time) */);
-              content.AddMetadata(status.seriesId_, MetadataType_LastUpdate, now);
-              content.AddMetadata(status.studyId_, MetadataType_LastUpdate, now);
-              content.AddMetadata(status.patientId_, MetadataType_LastUpdate, now);
-
-              if (status.isNewSeries_)
+              content.AddResource(status.studyId_, ResourceType_Study, dicomSummary_);
+              content.AddMetadata(status.studyId_, MetadataType_MainDicomTagsSignature, DicomMap::GetMainDicomTagsSignature(ResourceType_Study));  // New in Orthanc 1.11.0
+              SetMainDicomSequenceMetadata(content, status.studyId_, dicomSummary_, ResourceType_Study);   // new in Orthanc 1.11.1
+            }
+
+            if (status.isNewPatient_)
+            {
+              content.AddResource(status.patientId_, ResourceType_Patient, dicomSummary_);
+              content.AddMetadata(status.patientId_, MetadataType_MainDicomTagsSignature, DicomMap::GetMainDicomTagsSignature(ResourceType_Patient));  // New in Orthanc 1.11.0
+              SetMainDicomSequenceMetadata(content, status.patientId_, dicomSummary_, ResourceType_Patient);   // new in Orthanc 1.11.1
+            }
+
+            // Attach the auto-computed metadata for the patient/study/series levels
+            std::string now = SystemToolbox::GetNowIsoString(true /* use UTC time (not local time) */);
+            content.AddMetadata(status.seriesId_, MetadataType_LastUpdate, now);
+            content.AddMetadata(status.studyId_, MetadataType_LastUpdate, now);
+            content.AddMetadata(status.patientId_, MetadataType_LastUpdate, now);
+
+            if (status.isNewSeries_)
+            {
+              if (hasExpectedInstances_)
               {
-                if (hasExpectedInstances_)
-                {
-                  content.AddMetadata(status.seriesId_, MetadataType_Series_ExpectedNumberOfInstances,
-                                      boost::lexical_cast<std::string>(expectedInstances_));
-                }
-
-                // New in Orthanc 1.9.0
-                content.AddMetadata(status.seriesId_, MetadataType_RemoteAet,
-                                    origin_.GetRemoteAetC());
-              }
-              // Attach the auto-computed metadata for the instance level,
-              // reflecting these additions into the input metadata map
-              SetInstanceMetadata(content, instanceMetadata_, instanceId,
-                                  MetadataType_Instance_ReceptionDate, now);
-              SetInstanceMetadata(content, instanceMetadata_, instanceId, MetadataType_RemoteAet,
-                                  origin_.GetRemoteAetC());
-              SetInstanceMetadata(content, instanceMetadata_, instanceId, MetadataType_Instance_Origin, 
-                                  EnumerationToString(origin_.GetRequestOrigin()));
-
-              std::string s;
-
-              if (origin_.LookupRemoteIp(s))
-              {
-                // New in Orthanc 1.4.0
-                SetInstanceMetadata(content, instanceMetadata_, instanceId,
-                                    MetadataType_Instance_RemoteIp, s);
+                content.AddMetadata(status.seriesId_, MetadataType_Series_ExpectedNumberOfInstances,
+                                    boost::lexical_cast<std::string>(expectedInstances_));
               }
 
-              if (origin_.LookupCalledAet(s))
-              {
-                // New in Orthanc 1.4.0
-                SetInstanceMetadata(content, instanceMetadata_, instanceId,
-                                    MetadataType_Instance_CalledAet, s);
-              }
-
-              if (origin_.LookupHttpUsername(s))
-              {
-                // New in Orthanc 1.4.0
-                SetInstanceMetadata(content, instanceMetadata_, instanceId,
-                                    MetadataType_Instance_HttpUsername, s);
-              }
+              // New in Orthanc 1.9.0
+              content.AddMetadata(status.seriesId_, MetadataType_RemoteAet,
+                                  origin_.GetRemoteAetC());
+            }
+            // Attach the auto-computed metadata for the instance level,
+            // reflecting these additions into the input metadata map
+            SetInstanceMetadata(content, instanceMetadata_, instanceId,
+                                MetadataType_Instance_ReceptionDate, now);
+            SetInstanceMetadata(content, instanceMetadata_, instanceId, MetadataType_RemoteAet,
+                                origin_.GetRemoteAetC());
+            SetInstanceMetadata(content, instanceMetadata_, instanceId, MetadataType_Instance_Origin, 
+                                EnumerationToString(origin_.GetRequestOrigin()));
+
+            std::string s;
+
+            if (origin_.LookupRemoteIp(s))
+            {
+              // New in Orthanc 1.4.0
+              SetInstanceMetadata(content, instanceMetadata_, instanceId,
+                                  MetadataType_Instance_RemoteIp, s);
+            }
+
+            if (origin_.LookupCalledAet(s))
+            {
+              // New in Orthanc 1.4.0
+              SetInstanceMetadata(content, instanceMetadata_, instanceId,
+                                  MetadataType_Instance_CalledAet, s);
+            }
+
+            if (origin_.LookupHttpUsername(s))
+            {
+              // New in Orthanc 1.4.0
+              SetInstanceMetadata(content, instanceMetadata_, instanceId,
+                                  MetadataType_Instance_HttpUsername, s);
             }
-
-            // Following metadatas are also updated if reconstructing the instance.
-            // They might be missing since they have been introduced along Orthanc versions.
-
-            if (hasTransferSyntax_)
+          }
+
+          // Following metadatas are also updated if reconstructing the instance.
+          // They might be missing since they have been introduced along Orthanc versions.
+
+          if (hasTransferSyntax_)
+          {
+            // New in Orthanc 1.2.0
+            SetInstanceMetadata(content, instanceMetadata_, instanceId,
+                                MetadataType_Instance_TransferSyntax,
+                                GetTransferSyntaxUid(transferSyntax_));
+          }
+
+          if (hasPixelDataOffset_)
+          {
+            // New in Orthanc 1.9.1
+            SetInstanceMetadata(content, instanceMetadata_, instanceId,
+                                MetadataType_Instance_PixelDataOffset,
+                                boost::lexical_cast<std::string>(pixelDataOffset_));
+
+            // New in Orthanc 1.12.1
+            if (dicomSummary_.GuessPixelDataValueRepresentation(transferSyntax_) != pixelDataVR_)
             {
-              // New in Orthanc 1.2.0
+              // Store the VR of pixel data if it doesn't comply with the standard
               SetInstanceMetadata(content, instanceMetadata_, instanceId,
-                                  MetadataType_Instance_TransferSyntax,
-                                  GetTransferSyntaxUid(transferSyntax_));
+                                  MetadataType_Instance_PixelDataVR,
+                                  EnumerationToString(pixelDataVR_));
             }
-
-            if (hasPixelDataOffset_)
-            {
-              // New in Orthanc 1.9.1
-              SetInstanceMetadata(content, instanceMetadata_, instanceId,
-                                  MetadataType_Instance_PixelDataOffset,
-                                  boost::lexical_cast<std::string>(pixelDataOffset_));
-            }
-        
-            const DicomValue* value;
-            if ((value = dicomSummary_.TestAndGetValue(DICOM_TAG_SOP_CLASS_UID)) != NULL &&
-                !value->IsNull() &&
+          }
+      
+          const DicomValue* value;
+          if ((value = dicomSummary_.TestAndGetValue(DICOM_TAG_SOP_CLASS_UID)) != NULL &&
+              !value->IsNull() &&
+              !value->IsBinary())
+          {
+            SetInstanceMetadata(content, instanceMetadata_, instanceId,
+                                MetadataType_Instance_SopClassUid, value->GetContent());
+          }
+
+
+          if ((value = dicomSummary_.TestAndGetValue(DICOM_TAG_INSTANCE_NUMBER)) != NULL ||
+              (value = dicomSummary_.TestAndGetValue(DICOM_TAG_IMAGE_INDEX)) != NULL)
+          {
+            if (!value->IsNull() && 
                 !value->IsBinary())
             {
               SetInstanceMetadata(content, instanceMetadata_, instanceId,
-                                  MetadataType_Instance_SopClassUid, value->GetContent());
-            }
-
-
-            if ((value = dicomSummary_.TestAndGetValue(DICOM_TAG_INSTANCE_NUMBER)) != NULL ||
-                (value = dicomSummary_.TestAndGetValue(DICOM_TAG_IMAGE_INDEX)) != NULL)
-            {
-              if (!value->IsNull() && 
-                  !value->IsBinary())
-              {
-                SetInstanceMetadata(content, instanceMetadata_, instanceId,
-                                    MetadataType_Instance_IndexInSeries, Toolbox::StripSpaces(value->GetContent()));
-              }
-            }
-
-        
-            transaction.SetResourcesContent(content);
-          }
-
-  
-          // Check whether the series of this new instance is now completed
-          int64_t expectedNumberOfInstances;
-          if (ComputeExpectedNumberOfInstances(expectedNumberOfInstances, dicomSummary_))
-          {
-            SeriesStatus seriesStatus = transaction.GetSeriesStatus(status.seriesId_, expectedNumberOfInstances);
-            if (seriesStatus == SeriesStatus_Complete)
-            {
-              transaction.LogChange(status.seriesId_, ChangeType_CompletedSeries, ResourceType_Series, hashSeries_);
+                                  MetadataType_Instance_IndexInSeries, Toolbox::StripSpaces(value->GetContent()));
             }
           }
-          
-          transaction.LogChange(status.seriesId_, ChangeType_NewChildInstance, ResourceType_Series, hashSeries_);
-          transaction.LogChange(status.studyId_, ChangeType_NewChildInstance, ResourceType_Study, hashStudy_);
-          transaction.LogChange(status.patientId_, ChangeType_NewChildInstance, ResourceType_Patient, hashPatient_);
-          
-          // Mark the parent resources of this instance as unstable
-          transaction.GetTransactionContext().MarkAsUnstable(status.seriesId_, ResourceType_Series, hashSeries_);
-          transaction.GetTransactionContext().MarkAsUnstable(status.studyId_, ResourceType_Study, hashStudy_);
-          transaction.GetTransactionContext().MarkAsUnstable(status.patientId_, ResourceType_Patient, hashPatient_);
-          transaction.GetTransactionContext().SignalAttachmentsAdded(instanceSize);
-
-          storeStatus_ = StoreStatus_Success;          
+
+      
+          transaction.SetResourcesContent(content);
         }
-        catch (OrthancException& e)
+
+
+        // Check whether the series of this new instance is now completed
+        int64_t expectedNumberOfInstances;
+        if (ComputeExpectedNumberOfInstances(expectedNumberOfInstances, dicomSummary_))
         {
-          if (e.GetErrorCode() == ErrorCode_DatabaseCannotSerialize)
-          {
-            throw;  // the transaction has failed -> do not commit the current transaction (and retry)
-          }
-          else
+          SeriesStatus seriesStatus = transaction.GetSeriesStatus(status.seriesId_, expectedNumberOfInstances);
+          if (seriesStatus == SeriesStatus_Complete)
           {
-            LOG(ERROR) << "EXCEPTION [" << e.What() << " - " << e.GetDetails() << "]";
-
-            if (e.GetErrorCode() == ErrorCode_FullStorage)
-            {
-              throw; // do not commit the current transaction
-            }
-
-            // this is an expected failure, exit normaly and commit the current transaction
-            storeStatus_ = StoreStatus_Failure;
+            transaction.LogChange(status.seriesId_, ChangeType_CompletedSeries, ResourceType_Series, hashSeries_);
           }
         }
+        
+        transaction.LogChange(status.seriesId_, ChangeType_NewChildInstance, ResourceType_Series, hashSeries_);
+        transaction.LogChange(status.studyId_, ChangeType_NewChildInstance, ResourceType_Study, hashStudy_);
+        transaction.LogChange(status.patientId_, ChangeType_NewChildInstance, ResourceType_Patient, hashPatient_);
+        
+        // Mark the parent resources of this instance as unstable
+        transaction.GetTransactionContext().MarkAsUnstable(status.seriesId_, ResourceType_Series, hashSeries_);
+        transaction.GetTransactionContext().MarkAsUnstable(status.studyId_, ResourceType_Study, hashStudy_);
+        transaction.GetTransactionContext().MarkAsUnstable(status.patientId_, ResourceType_Patient, hashPatient_);
+        transaction.GetTransactionContext().SignalAttachmentsAdded(instanceSize);
+
+        storeStatus_ = StoreStatus_Success;          
       }
     };
 
 
-    Operations operations(instanceMetadata, dicomSummary, attachments, metadata, origin,
-                          overwrite, hasTransferSyntax, transferSyntax, hasPixelDataOffset,
-                          pixelDataOffset, maximumStorageMode, maximumStorageSize, maximumPatients, isReconstruct);
-    Apply(operations);
-    return operations.GetStoreStatus();
+    Operations operations(instanceMetadata, dicomSummary, attachments, metadata, origin, overwrite,
+                          hasTransferSyntax, transferSyntax, hasPixelDataOffset, pixelDataOffset,
+                          pixelDataVR, maximumStorageMode, maximumStorageSize, maximumPatients, isReconstruct);
+
+    try
+    {
+      Apply(operations);
+      return operations.GetStoreStatus();
+    }
+    catch (OrthancException& e)
+    {
+      if (e.GetErrorCode() == ErrorCode_FullStorage)
+      {
+        return StoreStatus_StorageFull;
+      }
+      else
+      {
+        // the transaction has failed -> do not commit the current transaction (and retry)
+        throw;
+      }
+    }
   }
 
 
@@ -3475,7 +3500,7 @@
         int64_t resourceId;
         if (!transaction.LookupResource(resourceId, resourceType, publicId_))
         {
-          status_ = StoreStatus_Failure;  // Inexistent resource
+          throw OrthancException(ErrorCode_InexistentItem, HttpStatus_404_NotFound);
         }
         else
         {
--- a/OrthancServer/Sources/Database/StatelessDatabaseOperations.h	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancServer/Sources/Database/StatelessDatabaseOperations.h	Tue Jul 04 12:20:41 2023 +0200
@@ -759,6 +759,7 @@
                       DicomTransferSyntax transferSyntax,
                       bool hasPixelDataOffset,
                       uint64_t pixelDataOffset,
+                      ValueRepresentation pixelDataVR,
                       MaxStorageMode maximumStorageMode,
                       uint64_t maximumStorageSize,
                       unsigned int maximumPatients,
--- a/OrthancServer/Sources/OrthancInitialization.cpp	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancServer/Sources/OrthancInitialization.cpp	Tue Jul 04 12:20:41 2023 +0200
@@ -241,7 +241,7 @@
 
           if (DicomMap::IsComputedTag(tag))
           {
-            LOG(WARNING) << "  - " << tagName << " can not be added in the Extra Main Dicom Tags since the value of this tag is computed when requested";
+            LOG(WARNING) << "  - " << tagName << " cannot be added in the Extra Main Dicom Tags since the value of this tag is computed when requested";
           }
           else
           {
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestAnonymizeModify.cpp	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestAnonymizeModify.cpp	Tue Jul 04 12:20:41 2023 +0200
@@ -959,7 +959,7 @@
     InjectTags(dicom, request[TAGS], decodeBinaryTags, privateCreator, force);
 
 
-    // Inject the content (either an image, or a PDF file)
+    // Inject the content (either an image, a PDF file, or a STL/OBJ/MTL file)
     if (request.isMember(CONTENT))
     {
       const Json::Value& content = request[CONTENT];
@@ -1000,8 +1000,9 @@
         .SetRequestField(TAGS, RestApiCallDocumentation::Type_JsonObject,
                          "Associative array containing the tags of the new instance to be created", true)
         .SetRequestField(CONTENT, RestApiCallDocumentation::Type_String,
-                         "This field can be used to embed an image (pixel data) or a PDF inside the created DICOM instance. "
-                         "The PNG image, the JPEG image or the PDF file must be provided using their "
+                         "This field can be used to embed an image (pixel data encoded as PNG or JPEG), a PDF, or a "
+                         "3D manufactoring model (MTL/OBJ/STL) inside the created DICOM instance. "
+                         "The file to be encapsulated must be provided using its "
                          "[data URI scheme encoding](https://en.wikipedia.org/wiki/Data_URI_scheme). "
                          "This field can possibly contain a JSON array, in which case a DICOM series is created "
                          "containing one DICOM instance for each item in the `Content` field.", false)
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestApi.cpp	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestApi.cpp	Tue Jul 04 12:20:41 2023 +0200
@@ -259,7 +259,7 @@
     resetRequestReceived_(false),
     activeRequests_(context.GetMetricsRegistry(), 
                     "orthanc_rest_api_active_requests", 
-                    MetricsType_MaxOver10Seconds)
+                    MetricsUpdatePolicy_MaxOver10Seconds)
   {
     RegisterSystem(orthancExplorerEnabled);
 
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp	Tue Jul 04 12:20:41 2023 +0200
@@ -639,7 +639,8 @@
       }
 
       virtual void Handle(const std::string& type,
-                          const std::string& subtype) ORTHANC_OVERRIDE
+                          const std::string& subtype,
+                          const HttpContentNegociation::Dictionary& parameters) ORTHANC_OVERRIDE
       {
         assert(type == "image");
         assert(subtype == "png");
@@ -658,7 +659,8 @@
       }
 
       virtual void Handle(const std::string& type,
-                          const std::string& subtype) ORTHANC_OVERRIDE
+                          const std::string& subtype,
+                          const HttpContentNegociation::Dictionary& parameters) ORTHANC_OVERRIDE
       {
         assert(type == "image");
         assert(subtype == "x-portable-arbitrarymap");
@@ -698,7 +700,8 @@
       }
 
       virtual void Handle(const std::string& type,
-                          const std::string& subtype) ORTHANC_OVERRIDE
+                          const std::string& subtype,
+                          const HttpContentNegociation::Dictionary& parameters) ORTHANC_OVERRIDE
       {
         assert(type == "image");
         assert(subtype == "jpeg");
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestSystem.cpp	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestSystem.cpp	Tue Jul 04 12:20:41 2023 +0200
@@ -770,6 +770,36 @@
   }
 
 
+  static void DeleteJobOutput(RestApiDeleteCall& call)
+  {
+    if (call.IsDocumentation())
+    {
+      call.GetDocumentation()
+        .SetTag("Jobs")
+        .SetSummary("Delete a job output")
+        .SetDescription("Delete the output produced by a job. As of Orthanc 1.12.1, only the jobs that generate a "
+                        "DICOMDIR media or a ZIP archive provide such an output (with `key` equals to `archive`).")
+        .SetUriArgument("id", "Identifier of the job of interest")
+        .SetUriArgument("key", "Name of the output of interest");
+      return;
+    }
+
+    std::string job = call.GetUriComponent("id", "");
+    std::string key = call.GetUriComponent("key", "");
+
+    if (OrthancRestApi::GetContext(call).GetJobsEngine().
+        GetRegistry().DeleteJobOutput(job, key))
+    {
+      call.GetOutput().AnswerBuffer("", MimeType_PlainText);
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_InexistentItem,
+                             "Job has no such output: " + key);
+    }
+  }
+
+
   enum JobAction
   {
     JobAction_Cancel,
@@ -883,19 +913,19 @@
     context.GetIndex().GetLastChange(lastChange);
 
     MetricsRegistry& registry = context.GetMetricsRegistry();
-    registry.SetValue("orthanc_disk_size_mb", static_cast<float>(diskSize) / MEGA_BYTES);
-    registry.SetValue("orthanc_uncompressed_size_mb", static_cast<float>(diskSize) / MEGA_BYTES);
-    registry.SetValue("orthanc_count_patients", static_cast<unsigned int>(countPatients));
-    registry.SetValue("orthanc_count_studies", static_cast<unsigned int>(countStudies));
-    registry.SetValue("orthanc_count_series", static_cast<unsigned int>(countSeries));
-    registry.SetValue("orthanc_count_instances", static_cast<unsigned int>(countInstances));
-    registry.SetValue("orthanc_jobs_pending", jobsPending);
-    registry.SetValue("orthanc_jobs_running", jobsRunning);
-    registry.SetValue("orthanc_jobs_completed", jobsSuccess + jobsFailed);
-    registry.SetValue("orthanc_jobs_success", jobsSuccess);
-    registry.SetValue("orthanc_jobs_failed", jobsFailed);
-    registry.SetValue("orthanc_up_time_s", serverUpTime);
-    registry.SetValue("orthanc_last_change", lastChange["Last"].asInt64());
+    registry.SetFloatValue("orthanc_disk_size_mb", static_cast<float>(diskSize) / MEGA_BYTES);
+    registry.SetFloatValue("orthanc_uncompressed_size_mb", static_cast<float>(diskSize) / MEGA_BYTES);
+    registry.SetIntegerValue("orthanc_count_patients", static_cast<int64_t>(countPatients));
+    registry.SetIntegerValue("orthanc_count_studies", static_cast<int64_t>(countStudies));
+    registry.SetIntegerValue("orthanc_count_series", static_cast<int64_t>(countSeries));
+    registry.SetIntegerValue("orthanc_count_instances", static_cast<int64_t>(countInstances));
+    registry.SetIntegerValue("orthanc_jobs_pending", jobsPending);
+    registry.SetIntegerValue("orthanc_jobs_running", jobsRunning);
+    registry.SetIntegerValue("orthanc_jobs_completed", jobsSuccess + jobsFailed);
+    registry.SetIntegerValue("orthanc_jobs_success", jobsSuccess);
+    registry.SetIntegerValue("orthanc_jobs_failed", jobsFailed);
+    registry.SetIntegerValue("orthanc_up_time_s", serverUpTime);
+    registry.SetIntegerValue("orthanc_last_change", lastChange["Last"].asInt64());
 
     std::string s;
     registry.ExportPrometheusText(s);
@@ -1113,6 +1143,7 @@
     Register("/jobs/{id}/resubmit", ApplyJobAction<JobAction_Resubmit>);
     Register("/jobs/{id}/resume", ApplyJobAction<JobAction_Resume>);
     Register("/jobs/{id}/{key}", GetJobOutput);
+    Register("/jobs/{id}/{key}", DeleteJobOutput);
 
     // New in Orthanc 1.9.0
     Register("/tools/accepted-transfer-syntaxes", GetAcceptedTransferSyntaxes);
--- a/OrthancServer/Sources/Search/HierarchicalMatcher.cpp	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancServer/Sources/Search/HierarchicalMatcher.cpp	Tue Jul 04 12:20:41 2023 +0200
@@ -248,6 +248,8 @@
   {
     std::unique_ptr<DcmDataset> target(new DcmDataset);
 
+    std::string currentPrivateCreator = "";
+
     for (std::set<DicomTag>::const_iterator it = flatTags_.begin();
          it != flatTags_.end(); ++it)
     {
@@ -257,13 +259,19 @@
       if (source.findAndGetElement(tag, element).good() &&
           element != NULL)
       {
-        if (it->IsPrivate())
+        if (tag.isPrivateReservation())
         {
-          throw OrthancException(ErrorCode_NotImplemented,
-                                 "Not applicable to private tags: " + it->Format());
+          OFString privateCreator;
+          element->getOFString(privateCreator, 0, false);
+          currentPrivateCreator = privateCreator.c_str();
         }
-        
-        std::unique_ptr<DcmElement> cloned(FromDcmtkBridge::CreateElementForTag(*it, "" /* no private creator */));
+        else if (!it->IsPrivate())
+        {
+          // reset the private creator as soon as we reach the end of the current private block
+          currentPrivateCreator = "";
+        }
+
+        std::unique_ptr<DcmElement> cloned(FromDcmtkBridge::CreateElementForTag(*it, currentPrivateCreator.c_str()));
         cloned->copyFrom(*element);
         target->insert(cloned.release());
       }
--- a/OrthancServer/Sources/ServerContext.cpp	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancServer/Sources/ServerContext.cpp	Tue Jul 04 12:20:41 2023 +0200
@@ -110,16 +110,28 @@
 
 #if HAVE_MALLOC_TRIM == 1
   void ServerContext::MemoryTrimmingThread(ServerContext* that,
-                                           unsigned int sleepDelay)
+                                           unsigned int intervalInSeconds)
   {
+    boost::posix_time::ptime lastExecution = boost::posix_time::second_clock::universal_time();
+
     // This thread is started only if malloc_trim is defined
     while (!that->done_)
     {
-      boost::this_thread::sleep(boost::posix_time::milliseconds(sleepDelay));
-      
-      // If possible, gives memory back to the system 
-      // (see OrthancServer/Resources/ImplementationNotes/memory_consumption.txt)
-      malloc_trim(128*1024);
+      boost::posix_time::ptime now = boost::posix_time::second_clock::universal_time();
+      boost::posix_time::time_duration elapsed = now - lastExecution;
+
+      if (elapsed.total_seconds() > intervalInSeconds)
+      {
+        // If possible, gives memory back to the system 
+        // (see OrthancServer/Resources/ImplementationNotes/memory_consumption.txt)
+        {
+          MetricsRegistry::Timer timer(that->GetMetricsRegistry(), "orthanc_memory_trimming_duration_ms");
+          malloc_trim(128*1024);
+        }
+        lastExecution = boost::posix_time::second_clock::universal_time();
+      }
+
+      boost::this_thread::sleep(boost::posix_time::milliseconds(100));
     }
   }
 #endif
@@ -297,10 +309,9 @@
 
   void ServerContext::PublishDicomCacheMetrics()
   {
-    metricsRegistry_->SetValue("orthanc_dicom_cache_size",
-                               static_cast<float>(dicomCache_.GetCurrentSize()) / static_cast<float>(1024 * 1024));
-    metricsRegistry_->SetValue("orthanc_dicom_cache_count",
-                               static_cast<float>(dicomCache_.GetNumberOfItems()));
+    metricsRegistry_->SetFloatValue("orthanc_dicom_cache_size",
+                                    static_cast<float>(dicomCache_.GetCurrentSize()) / static_cast<float>(1024 * 1024));
+    metricsRegistry_->SetIntegerValue("orthanc_dicom_cache_count", dicomCache_.GetNumberOfItems());
   }
 
 
@@ -406,7 +417,7 @@
           CLOG(INFO, DICOM) << "Deidentification of log contents (notably for DIMSE queries) is enabled";
 
           DicomVersion version = StringToDicomVersion(
-              lock.GetConfiguration().GetStringParameter("DeidentifyLogsDicomVersion", "2021b"));
+              lock.GetConfiguration().GetStringParameter("DeidentifyLogsDicomVersion", "2023b"));
           CLOG(INFO, DICOM) << "Version of DICOM standard used for deidentification is "
                             << EnumerationToString(version);
 
@@ -440,7 +451,8 @@
       changeThread_ = boost::thread(ChangeThread, this, (unitTesting ? 20 : 100));
       
 #if HAVE_MALLOC_TRIM == 1
-      memoryTrimmingThread_ = boost::thread(MemoryTrimmingThread, this, 100);
+      LOG(INFO) << "Starting memory trimming thread at 30 seconds interval";
+      memoryTrimmingThread_ = boost::thread(MemoryTrimmingThread, this, 30);
 #else
       LOG(INFO) << "Your platform does not support malloc_trim(), not starting the memory trimming thread";
 #endif
@@ -552,8 +564,9 @@
 
     bool hasPixelDataOffset;
     uint64_t pixelDataOffset;
+    ValueRepresentation pixelDataVR;
     hasPixelDataOffset = DicomStreamReader::LookupPixelDataOffset(
-      pixelDataOffset, dicom.GetBufferData(), dicom.GetBufferSize());
+      pixelDataOffset, pixelDataVR, dicom.GetBufferData(), dicom.GetBufferSize());
 
     DicomTransferSyntax transferSyntax;
     bool hasTransferSyntax = dicom.LookupTransferSyntax(transferSyntax);
@@ -653,7 +666,7 @@
       InstanceMetadata  instanceMetadata;
       result.SetStatus(index_.Store(
         instanceMetadata, summary, attachments, dicom.GetMetadata(), dicom.GetOrigin(), overwrite,
-        hasTransferSyntax, transferSyntax, hasPixelDataOffset, pixelDataOffset, isReconstruct));
+        hasTransferSyntax, transferSyntax, hasPixelDataOffset, pixelDataOffset, pixelDataVR, isReconstruct));
 
       // Only keep the metadata for the "instance" level
       dicom.ClearMetadata();
@@ -688,8 +701,12 @@
             break;
 
           case StoreStatus_Failure:
-            LOG(ERROR) << "Store failure";
-            break;
+            LOG(ERROR) << "Unknown store failure";
+            throw OrthancException(ErrorCode_InternalError, HttpStatus_500_InternalServerError);
+
+          case StoreStatus_StorageFull:
+            LOG(ERROR) << "Storage full";
+            throw OrthancException(ErrorCode_FullStorage, HttpStatus_507_InsufficientStorage);
 
           default:
             // This should never happen
@@ -1090,7 +1107,8 @@
            * Orthanc have failed. Try again this precomputation now
            * for future calls.
            **/
-          if (DicomStreamReader::LookupPixelDataOffset(pixelDataOffset, dicom) &&
+          ValueRepresentation pixelDataVR;
+          if (DicomStreamReader::LookupPixelDataOffset(pixelDataOffset, pixelDataVR, dicom) &&
               pixelDataOffset < dicom.size())
           {
             index_.OverwriteMetadata(instancePublicId, MetadataType_Instance_PixelDataOffset,
@@ -1138,13 +1156,23 @@
   bool ServerContext::ReadDicomUntilPixelData(std::string& dicom,
                                               const std::string& instancePublicId)
   {
+    FileInfo attachment;
+    int64_t revision;  // Ignored
+    if (index_.LookupAttachment(attachment, revision, instancePublicId, FileContentType_DicomUntilPixelData))
+    {
+      StorageAccessor accessor(area_, storageCache_, GetMetricsRegistry());
+
+      accessor.Read(dicom, attachment);
+      assert(dicom.size() == attachment.GetUncompressedSize());
+
+      return true;
+    }
+
     if (!area_.HasReadRange())
     {
       return false;
     }
     
-    FileInfo attachment;
-    int64_t revision;  // Ignored
     if (!index_.LookupAttachment(attachment, revision, instancePublicId, FileContentType_Dicom))
     {
       throw OrthancException(ErrorCode_InternalError,
--- a/OrthancServer/Sources/ServerContext.h	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancServer/Sources/ServerContext.h	Tue Jul 04 12:20:41 2023 +0200
@@ -189,7 +189,7 @@
 
 #if HAVE_MALLOC_TRIM == 1
     static void MemoryTrimmingThread(ServerContext* that,
-                                     unsigned int sleepDelay);
+                                     unsigned int intervalInSeconds);
 #endif
 
     void SaveJobsEngine();
--- a/OrthancServer/Sources/ServerEnumerations.cpp	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancServer/Sources/ServerEnumerations.cpp	Tue Jul 04 12:20:41 2023 +0200
@@ -62,6 +62,7 @@
     dictMetadataType_.Add(MetadataType_Instance_PixelDataOffset, "PixelDataOffset");
     dictMetadataType_.Add(MetadataType_MainDicomTagsSignature, "MainDicomTagsSignature");
     dictMetadataType_.Add(MetadataType_MainDicomSequences, "MainDicomSequences");
+    dictMetadataType_.Add(MetadataType_Instance_PixelDataVR, "PixelDataVR");
 
     dictContentType_.Add(FileContentType_Dicom, "dicom");
     dictContentType_.Add(FileContentType_DicomAsJson, "dicom-as-json");
--- a/OrthancServer/Sources/ServerEnumerations.h	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancServer/Sources/ServerEnumerations.h	Tue Jul 04 12:20:41 2023 +0200
@@ -160,6 +160,7 @@
     MetadataType_Instance_PixelDataOffset = 14,  // New in Orthanc 1.9.0
     MetadataType_MainDicomTagsSignature = 15,    // New in Orthanc 1.11.0
     MetadataType_MainDicomSequences = 16,        // New in Orthanc 1.11.1
+    MetadataType_Instance_PixelDataVR = 17,      // New in Orthanc 1.12.1
     
     // Make sure that the value "65535" can be stored into this enumeration
     MetadataType_StartUser = 1024,
--- a/OrthancServer/Sources/ServerIndex.cpp	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancServer/Sources/ServerIndex.cpp	Tue Jul 04 12:20:41 2023 +0200
@@ -538,6 +538,7 @@
                                  DicomTransferSyntax transferSyntax,
                                  bool hasPixelDataOffset,
                                  uint64_t pixelDataOffset,
+                                 ValueRepresentation pixelDataVR,
                                  bool isReconstruct)
   {
     uint64_t maximumStorageSize;
@@ -553,7 +554,8 @@
 
     return StatelessDatabaseOperations::Store(
       instanceMetadata, dicomSummary, attachments, metadata, origin, overwrite, hasTransferSyntax,
-      transferSyntax, hasPixelDataOffset, pixelDataOffset, maximumStorageMode, maximumStorageSize, maximumPatients, isReconstruct);
+      transferSyntax, hasPixelDataOffset, pixelDataOffset, pixelDataVR, maximumStorageMode,
+      maximumStorageSize, maximumPatients, isReconstruct);
   }
 
   
--- a/OrthancServer/Sources/ServerIndex.h	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancServer/Sources/ServerIndex.h	Tue Jul 04 12:20:41 2023 +0200
@@ -88,7 +88,8 @@
                       DicomTransferSyntax transferSyntax,
                       bool hasPixelDataOffset,
                       uint64_t pixelDataOffset,
-                      bool isResonstruct);
+                      ValueRepresentation pixelDataVR,
+                      bool isReconstruct);
 
     StoreStatus AddAttachment(int64_t& newRevision /*out*/,
                               const FileInfo& attachment,
--- a/OrthancServer/Sources/ServerJobs/ArchiveJob.cpp	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancServer/Sources/ServerJobs/ArchiveJob.cpp	Tue Jul 04 12:20:41 2023 +0200
@@ -176,6 +176,7 @@
   class ArchiveJob::ThreadedInstanceLoader : public ArchiveJob::InstanceLoader
   {
     Semaphore                           availableInstancesSemaphore_;
+    Semaphore                           bufferedInstancesSemaphore_;
     std::map<std::string, boost::shared_ptr<std::string> >  availableInstances_;
     boost::mutex                        availableInstancesMutex_;
     SharedMessageQueue                  instancesToPreload_;
@@ -185,7 +186,8 @@
   public:
     ThreadedInstanceLoader(ServerContext& context, size_t threadCount, bool transcode, DicomTransferSyntax transferSyntax)
     : InstanceLoader(context, transcode, transferSyntax),
-      availableInstancesSemaphore_(0)
+      availableInstancesSemaphore_(0),
+      bufferedInstancesSemaphore_(3*threadCount)
     {
       for (size_t i = 0; i < threadCount; i++)
       {
@@ -227,6 +229,9 @@
         {
           return;
         }
+        
+        // wait for the consumers (zip writer), no need to accumulate instances in memory if loaders are faster than writers
+        that->bufferedInstancesSemaphore_.Acquire();
 
         try
         {
@@ -270,6 +275,7 @@
       {
         // wait for an instance to be available but this might not be the one we are waiting for !
         availableInstancesSemaphore_.Acquire();
+        bufferedInstancesSemaphore_.Release(); // unlock the "flow" of loaders
 
         boost::shared_ptr<std::string> dicomContent;
         {
@@ -1453,4 +1459,27 @@
       return false;
     }
   }
+
+  bool ArchiveJob::DeleteOutput(const std::string& key)
+  {   
+    if (key == "archive" &&
+        !mediaArchiveId_.empty())
+    {
+      SharedArchive::Accessor accessor(context_.GetMediaArchive(), mediaArchiveId_);
+
+      if (accessor.IsValid())
+      {
+        context_.GetMediaArchive().Remove(mediaArchiveId_);
+        return true;
+      }
+      else
+      {
+        return false;
+      }
+    }    
+    else
+    {
+      return false;
+    }
+  }
 }
--- a/OrthancServer/Sources/ServerJobs/ArchiveJob.h	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancServer/Sources/ServerJobs/ArchiveJob.h	Tue Jul 04 12:20:41 2023 +0200
@@ -120,5 +120,7 @@
                            MimeType& mime,
                            std::string& filename,
                            const std::string& key) ORTHANC_OVERRIDE;
+
+    virtual bool DeleteOutput(const std::string& key) ORTHANC_OVERRIDE;
   };
 }
--- a/OrthancServer/Sources/ServerJobs/ResourceModificationJob.cpp	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancServer/Sources/ServerJobs/ResourceModificationJob.cpp	Tue Jul 04 12:20:41 2023 +0200
@@ -222,7 +222,7 @@
     catch (OrthancException& e)
     {
       LOG(WARNING) << "An error occurred while executing a Modification job on instance " << instance << ": " << e.GetDetails();
-      return false;
+      throw;
     }
 
 
@@ -762,11 +762,15 @@
                     (!modification_->IsReplaced(*mainPatientTag) ||
                      modification_->GetReplacementAsString(*mainPatientTag) != targetPatientTags.GetStringValue(*mainPatientTag, "", false)))
                 {
-                  throw OrthancException(ErrorCode_BadRequest, std::string("Trying to change patient tags in a study.  The Patient already exists and has other studies.  All the 'Replace' tags should match the existing patient main dicom tags.  Try using /patients/../modify instead to modify the patient. Failing tag: ") + mainPatientTag->Format());
+                  throw OrthancException(ErrorCode_BadRequest, std::string("Trying to change patient tags in a study.  " 
+                    "The Patient already exists and has other studies.  All the 'Replace' tags should match the existing patient main dicom tags "
+                    "and you should specify all Patient MainDicomTags in your query.  Try using /patients/../modify instead to modify the patient. Failing tag: ") + mainPatientTag->Format());
                 }
                 else if (!targetPatientTags.HasTag(*mainPatientTag) && modification_->IsReplaced(*mainPatientTag) )
                 {
-                  throw OrthancException(ErrorCode_BadRequest, std::string("Trying to change patient tags in a study.  The Patient already exists and has other studies.  You are trying to replace a tag that is not defined yet in this patient.  Try using /patients/../modify instead to modify the patient. Failing tag: ") + mainPatientTag->Format());
+                  throw OrthancException(ErrorCode_BadRequest, std::string("Trying to change patient tags in a study.  "
+                    "The Patient already exists and has other studies.  You are trying to replace a tag that is not defined yet in this patient. " 
+                    "Try using /patients/../modify instead to modify the patient. Failing tag: ") + mainPatientTag->Format());
                 }
               }
             }
--- a/OrthancServer/Sources/ServerJobs/ThreadedSetOfInstancesJob.cpp	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancServer/Sources/ServerJobs/ThreadedSetOfInstancesJob.cpp	Tue Jul 04 12:20:41 2023 +0200
@@ -295,15 +295,6 @@
   }
 
 
-  bool ThreadedSetOfInstancesJob::GetOutput(std::string &output,
-                                            MimeType &mime,
-                                            std::string& filename,
-                                            const std::string &key)
-  {
-    return false;
-  }
-
-
   size_t ThreadedSetOfInstancesJob::GetInstancesCount() const
   {
     boost::recursive_mutex::scoped_lock lock(mutex_);
@@ -351,7 +342,7 @@
 
     if (started_)
     {
-      // We actually can not clean the instances that would have been generated during a previous run
+      // We actually cannot clean the instances that would have been generated during a previous run
       // because the generated instances may or may not have the same orthanc ids as the source
       // it is too dangerous to guess if they should be deleted or not
       currentStep_ = ThreadedJobStep_NotStarted;
--- a/OrthancServer/Sources/ServerJobs/ThreadedSetOfInstancesJob.h	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancServer/Sources/ServerJobs/ThreadedSetOfInstancesJob.h	Tue Jul 04 12:20:41 2023 +0200
@@ -152,7 +152,15 @@
     virtual bool GetOutput(std::string& output,
                            MimeType& mime,
                            std::string& filename,
-                           const std::string& key) ORTHANC_OVERRIDE;
+                           const std::string& key) ORTHANC_OVERRIDE
+    {
+      return false;
+    }
+
+    virtual bool DeleteOutput(const std::string& key) ORTHANC_OVERRIDE
+    {
+      return false;
+    }
 
     bool IsFailedInstance(const std::string& instance) const;
 
--- a/OrthancServer/Sources/ServerToolbox.cpp	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancServer/Sources/ServerToolbox.cpp	Tue Jul 04 12:20:41 2023 +0200
@@ -211,6 +211,31 @@
       std::string t;
       t.reserve(value.size());
 
+#if 0
+      // This version solves some indexing issue (https://discourse.orthanc-server.org/t/postgress-index-effectively-disabled-when-searching-for-greek-names/3371)
+      // and seems functional: I could run the integration tests with both SQLite and PG + the DicomWeb tests with PG.
+      // However, it can not go into production because NormalizeIdentifier is used both at ingest time and at search time;
+      // therefore, if we change it while we have an already populated DB, the searches won't work anymore and, on very large
+      // systems, running the Housekeeper to rebuild the indexes might take months ...
+      // We keep it here because it might be handy once we refactor the DicomIdentifier searches in the future.
+      for (size_t i = 0; i < value.size(); i++)
+      {
+        if (value[i] == '%' ||
+            value[i] == '_')
+        {
+          t.push_back(' ');  // These characters might break wildcard queries in SQL
+        }
+        else if (//isascii(value[i]) &&
+                 !iscntrl(value[i]) &&
+                 (!isspace(value[i]) || value[i] == ' '))
+        {
+          t.push_back(value[i]);
+        }
+      }
+
+      //Toolbox::ToUpperCase(t);
+      t = Toolbox::ToUpperCaseWithAccents(t);
+#else
       for (size_t i = 0; i < value.size(); i++)
       {
         if (value[i] == '%' ||
@@ -227,6 +252,7 @@
       }
 
       Toolbox::ToUpperCase(t);
+#endif
 
       return Toolbox::StripSpaces(t);
     }
--- a/OrthancServer/Sources/main.cpp	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancServer/Sources/main.cpp	Tue Jul 04 12:20:41 2023 +0200
@@ -1181,7 +1181,7 @@
       else
       {
         context.SetRestApiWriteToFileSystemEnabled(false);
-        LOG(WARNING) << "REST API can not write to the file system.";
+        LOG(WARNING) << "REST API cannot write to the file system.";
       }
 
       if (lock.GetConfiguration().GetBooleanParameter("WebDavEnabled", true))
--- a/OrthancServer/UnitTestsSources/ServerIndexTests.cpp	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancServer/UnitTestsSources/ServerIndexTests.cpp	Tue Jul 04 12:20:41 2023 +0200
@@ -742,18 +742,21 @@
       ASSERT_EQ(StoreStatus_Success, index.Store(
                   instanceMetadata, summary, attachments, toStore->GetMetadata(),
                   toStore->GetOrigin(), false /* don't overwrite */,
-                  hasTransferSyntax, transferSyntax, true /* pixel data offset */, 42, false));
+                  hasTransferSyntax, transferSyntax, true /* has pixel data */, 42 /* pixel data offset */,
+                  ValueRepresentation_PersonName /* pixel data VR */, false));
     }
     
-    ASSERT_EQ(7u, instanceMetadata.size());
+    ASSERT_EQ(8u, instanceMetadata.size());
     ASSERT_TRUE(instanceMetadata.find(MetadataType_RemoteAet) != instanceMetadata.end());
     ASSERT_TRUE(instanceMetadata.find(MetadataType_Instance_ReceptionDate) != instanceMetadata.end());
     ASSERT_TRUE(instanceMetadata.find(MetadataType_Instance_TransferSyntax) != instanceMetadata.end());
     ASSERT_TRUE(instanceMetadata.find(MetadataType_Instance_SopClassUid) != instanceMetadata.end());
     ASSERT_TRUE(instanceMetadata.find(MetadataType_Instance_PixelDataOffset) != instanceMetadata.end());
     ASSERT_TRUE(instanceMetadata.find(MetadataType_MainDicomTagsSignature) != instanceMetadata.end());
+    ASSERT_TRUE(instanceMetadata.find(MetadataType_Instance_PixelDataVR) != instanceMetadata.end());
 
     ASSERT_EQ("42", instanceMetadata[MetadataType_Instance_PixelDataOffset]);
+    ASSERT_EQ("PN", instanceMetadata[MetadataType_Instance_PixelDataVR]);
 
     // The default transfer syntax depends on the OS endianness
     std::string s = instanceMetadata[MetadataType_Instance_TransferSyntax];
--- a/OrthancServer/UnitTestsSources/ServerJobsTests.cpp	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancServer/UnitTestsSources/ServerJobsTests.cpp	Tue Jul 04 12:20:41 2023 +0200
@@ -134,6 +134,11 @@
     {
       return false;
     }
+
+    virtual bool DeleteOutput(const std::string& key) ORTHANC_OVERRIDE
+    {
+      return false;
+    }
   };
 
 
@@ -720,7 +725,7 @@
 
   {
     std::unique_ptr<DicomModification> modification(new DicomModification);
-    modification->SetupAnonymization(DicomVersion_2021b);
+    modification->SetupAnonymization(DicomVersion_2023b);
     
     ModifyInstanceOperation operation(GetContext(), RequestOrigin_Lua, modification.release());
 
--- a/OrthancServer/UnitTestsSources/VersionsTests.cpp	Tue May 16 07:09:06 2023 +0200
+++ b/OrthancServer/UnitTestsSources/VersionsTests.cpp	Tue Jul 04 12:20:41 2023 +0200
@@ -112,7 +112,7 @@
 
 TEST(Versions, BoostStatic)
 {
-  ASSERT_TRUE(std::string(BOOST_LIB_VERSION) == "1_80" ||
+  ASSERT_TRUE(std::string(BOOST_LIB_VERSION) == "1_82" ||
               std::string(BOOST_LIB_VERSION) == "1_69" /* if USE_LEGACY_BOOST */);
 }
 
--- a/TODO	Tue May 16 07:09:06 2023 +0200
+++ b/TODO	Tue Jul 04 12:20:41 2023 +0200
@@ -99,6 +99,8 @@
   https://groups.google.com/g/orthanc-users/c/y3-xa_GcdLM/m/m0Kr5G5UPAAJ
 * (1) Specify the transfer syntax in /tools/create-dicom
   https://groups.google.com/g/orthanc-users/c/o15Dekecgds/m/xmPE2y3bAwAJ
+* Support Palette PNG in /tools/create-dicom:
+  https://discourse.orthanc-server.org/t/404-on-tools-create-dicom-endpoint-with-specific-png/3562
 * (1) In the /studies/{id}/anonymize route, add an option to remove
   secondary captures.  They usually contains Patient info in the
   image. The SOPClassUID might be used to identify such secondary
@@ -118,6 +120,9 @@
 * Also implement a GET variant of /tools/create-archive + sibling routes
   in which resources & transcode options are provided as get arguments.
   https://groups.google.com/g/orthanc-users/c/PmaRZ609ztA/m/JdwXvIBKAQAJ
+* Allow skiping automatic conversion of color-space in transcoding/decoding.
+  The patch that was initialy provided was breaking the IngestTranscoding.
+  https://discourse.orthanc-server.org/t/orthanc-convert-ybr-to-rgb-but-does-not-change-metadata/3533/9
 
 
 ---------
@@ -186,6 +191,8 @@
     - ModalitiesInStudy
     - all computed counters at series/study/patient level
     - RequestAttributesSequence (sequence that must be included in all DicomWeb QIDO-RS for series)
+* Investigate increase of CPU consumption while idle after anonymize/delete
+  (https://discourse.orthanc-server.org/t/onchange-callbacks-and-cpu-loads/3534)
 
 * Long-shot & not sure it is even feasible at all: try to reduce memory usage by implementing streaming
   when receiving DICOM instances from the Rest API or from DICOM and store files directly to disk as they
@@ -322,6 +329,12 @@
 * Add more complex testing scenarios like data-migration, change of 
   configuration files, multiple orthanc interacting togethers with various 
   config.  This should probably look like the python toolbox tests ...
+  - add a test to validate Modalities and Peers stored in DB are not lost 
+    while upgrading from one version to the other (Sylvain)
+* On Ubuntu 20.04, accesses to unitialized memory are sometimes
+  reported in libgjpeg by valgrind, if running the following command
+  (this is probably unrelated to Orthanc):
+  $ ./Start.sh --force Orthanc.test_bitbucket_issue_141 Orthanc.test_create_pdf Orthanc.test_decode_brainix_as_jpeg
   
 
 ---------------------